From 240e7f36774e019b432f4ae183065e6cb7205251 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 23 Sep 2022 03:33:44 -0700 Subject: [PATCH] Initial commit --- .gitignore | 11 + LICENSE | 674 ++++++++++++ README.md | 3 + build.gradle | 39 + scripts/PKGBUILD | 37 + scripts/build-macos-app.sh | 55 + scripts/linux/curator.desktop | 11 + scripts/linux/curator.png | Bin 0 -> 1087 bytes scripts/linux/curator.svg | 76 ++ scripts/linux/curator.xpm | 104 ++ scripts/macos/Info.plist | 45 + scripts/macos/icon.icns | Bin 0 -> 39933 bytes src/main/java/zander/Start.java | 27 + src/main/java/zander/library/FileUtils.java | 35 + src/main/java/zander/library/Library.java | 241 +++++ .../java/zander/library/LibraryConfig.java | 95 ++ .../zander/library/LibraryMediaCache.java | 237 +++++ src/main/java/zander/library/LocalCache.java | 196 ++++ src/main/java/zander/library/Manifest.java | 229 ++++ src/main/java/zander/library/RemoteStore.java | 370 +++++++ .../library/RemoteStoreFileTypeHandler.java | 71 ++ .../zander/library/media/AudioMediaData.java | 90 ++ .../library/media/AudioMediaViewer.java | 26 + .../java/zander/library/media/FFmpeg.java | 123 +++ .../library/media/ImageMediaComponent.java | 42 + .../zander/library/media/ImageMediaData.java | 95 ++ .../java/zander/library/media/MediaData.java | 23 + .../zander/library/media/MediaLoader.java | 71 ++ .../media/SerializableImageContainer.java | 51 + .../library/media/UnknownMediaData.java | 98 ++ .../zander/library/media/VideoMediaData.java | 117 ++ .../library/media/VideoMediaViewer.java | 92 ++ .../library/metadata/MediaMetadata.java | 78 ++ .../library/metadata/MetadataLoader.java | 127 +++ .../zander/library/secrets/PassSecrets.java | 237 +++++ .../library/secrets/PassSecretsFactory.java | 46 + .../library/secrets/PreferencesSecrets.java | 135 +++ .../secrets/PreferencesSecretsFactory.java | 31 + .../java/zander/library/secrets/Secrets.java | 37 + .../library/secrets/SecretsFactory.java | 107 ++ .../zander/library/state/AlbumConfig.java | 232 ++++ .../java/zander/library/state/AlbumState.java | 134 +++ .../zander/library/state/DateTimeUtils.java | 91 ++ .../zander/library/state/LibraryConfig.java | 95 ++ .../zander/library/state/LibraryState.java | 165 +++ .../java/zander/library/state/MediaState.java | 173 +++ src/main/java/zander/ui/ErrorDialog.java | 123 +++ src/main/java/zander/ui/FileChooserField.java | 88 ++ src/main/java/zander/ui/LibraryFrame.java | 248 +++++ .../java/zander/ui/LibraryUploadDialog.java | 492 +++++++++ .../java/zander/ui/MediaCellRenderer.java | 0 .../zander/ui/OverlayPainterComponent.java | 55 + src/main/java/zander/ui/ProgressTracker.java | 84 ++ .../java/zander/ui/ProgressTrackerDialog.java | 120 +++ .../java/zander/ui/RememberConfirmDialog.java | 71 ++ src/main/java/zander/ui/UIUtils.java | 185 ++++ .../ui/_import/ImportAlbumSelectDialog.java | 215 ++++ .../ui/_import/ImportLibrarySelectDialog.java | 143 +++ .../zander/ui/album/AlbumCreateDialog.java | 172 +++ .../zander/ui/album/AlbumPropertyPanel.java | 501 +++++++++ .../ui/album/AlbumPropertyUpdateEvent.java | 45 + .../ui/album/AlbumPropertyUpdateListener.java | 9 + .../zander/ui/album/AlbumSelectPanel.java | 420 ++++++++ .../ui/album/AlbumThumbnailSelectDialog.java | 175 +++ .../ui/album/LibraryPropertyDialog.java | 171 +++ .../ui/cellrenderer/AlbumCellRenderer.java | 131 +++ .../ui/cellrenderer/MediaCellRenderer.java | 166 +++ .../zander/ui/docs/CuratorAboutPanel.java | 97 ++ .../zander/ui/docs/DocumentationViewer.java | 295 ++++++ .../ui/library/CuratorOptionsDialog.java | 221 ++++ .../ui/library/LibraryCreateDialog.java | 269 +++++ .../ui/library/LibraryDeleteDialog.java | 89 ++ .../zander/ui/library/LibraryEditDialog.java | 290 +++++ .../ui/library/LibraryExistsDialog.java | 82 ++ .../ui/library/LibraryImportDialog.java | 201 ++++ .../zander/ui/library/LibrarySelectFrame.java | 997 ++++++++++++++++++ .../zander/ui/library/LibrarySyncDialog.java | 133 +++ .../zander/ui/media/MediaManagePanel.java | 747 +++++++++++++ .../ui/media/MediaNameConflictDialog.java | 88 ++ .../zander/ui/media/MediaPropertyPanel.java | 528 ++++++++++ .../ui/media/NameConflictListDialog.java | 66 ++ .../java/zander/ui/media/map/ListLayout.java | 56 + .../zander/ui/media/map/LocationEncoder.java | 14 + .../map/LocationEncoderCacheCleaner.java | 70 ++ .../zander/ui/media/map/LocationEntry.java | 33 + .../ui/media/map/LocationEntryComponent.java | 103 ++ .../ui/media/map/LocationEntryList.java | 119 +++ .../ui/media/map/LocationEntryListener.java | 0 .../ui/media/map/LocationSelectDialog.java | 319 ++++++ .../ui/media/map/LocationSelectEvent.java | 22 + .../ui/media/map/LocationSelectListener.java | 7 + .../ui/media/map/LocationSelectPanel.java | 78 ++ .../ui/media/map/MapSelectionPainter.java | 37 + .../ui/media/map/OSMLocationEncoder.java | 138 +++ .../ListArrangeTransferHandler.java | 139 +++ src/main/resources/docs.xml | 21 + .../resources/docs/album-create-dialog.png | Bin 0 -> 4460 bytes .../resources/docs/album-manage-window.html | 35 + .../resources/docs/album-property-window.png | Bin 0 -> 41846 bytes .../resources/docs/album-select-window.png | Bin 0 -> 23339 bytes src/main/resources/docs/album-settings.html | 34 + .../resources/docs/global-settings-dialog.png | Bin 0 -> 14832 bytes src/main/resources/docs/global-settings.html | 37 + src/main/resources/docs/gpl3.html | 693 ++++++++++++ src/main/resources/docs/import-external.html | 24 + .../docs/import-select-album-window.png | Bin 0 -> 17261 bytes .../docs/import-select-library-window.png | Bin 0 -> 9872 bytes src/main/resources/docs/info.html | 27 + .../resources/docs/library-create-dialog.png | Bin 0 -> 7778 bytes .../resources/docs/library-edit-dialog.png | Bin 0 -> 8501 bytes .../resources/docs/library-import-dialog.png | Bin 0 -> 5429 bytes .../resources/docs/library-select-window.html | 33 + .../resources/docs/library-select-window.png | Bin 0 -> 19801 bytes src/main/resources/docs/library-settings.html | 19 + .../docs/local-library-settings.html | 45 + .../resources/docs/location-select-dialog.png | Bin 0 -> 331091 bytes src/main/resources/docs/managing-media.html | 36 + src/main/resources/docs/media-properties.html | 28 + .../resources/docs/media-property-window.png | Bin 0 -> 48532 bytes .../resources/docs/media-select-window.png | Bin 0 -> 28151 bytes src/main/resources/docs/media-types.html | 76 ++ .../docs/remote-library-settings.png | Bin 0 -> 5038 bytes .../resources/docs/selecting-locations.html | 40 + src/main/resources/eye-closed-icon.svg | 58 + src/main/resources/eye-open-icon.svg | 49 + src/main/resources/logo.svg | 76 ++ src/main/resources/menu-icon.svg | 65 ++ src/main/resources/missing-image-icon.svg | 94 ++ src/main/resources/music-icon.svg | 117 ++ src/main/resources/pause.svg | 58 + src/main/resources/play.svg | 48 + src/main/resources/reset-arrow.svg | 57 + src/main/resources/star.svg | 62 ++ src/main/resources/video-icon.png | Bin 0 -> 95229 bytes src/main/resources/video-icon.svg | 102 ++ 135 files changed, 15858 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 scripts/PKGBUILD create mode 100755 scripts/build-macos-app.sh create mode 100644 scripts/linux/curator.desktop create mode 100644 scripts/linux/curator.png create mode 100644 scripts/linux/curator.svg create mode 100644 scripts/linux/curator.xpm create mode 100644 scripts/macos/Info.plist create mode 100644 scripts/macos/icon.icns create mode 100644 src/main/java/zander/Start.java create mode 100644 src/main/java/zander/library/FileUtils.java create mode 100644 src/main/java/zander/library/Library.java create mode 100644 src/main/java/zander/library/LibraryConfig.java create mode 100644 src/main/java/zander/library/LibraryMediaCache.java create mode 100644 src/main/java/zander/library/LocalCache.java create mode 100644 src/main/java/zander/library/Manifest.java create mode 100644 src/main/java/zander/library/RemoteStore.java create mode 100644 src/main/java/zander/library/RemoteStoreFileTypeHandler.java create mode 100644 src/main/java/zander/library/media/AudioMediaData.java create mode 100644 src/main/java/zander/library/media/AudioMediaViewer.java create mode 100644 src/main/java/zander/library/media/FFmpeg.java create mode 100644 src/main/java/zander/library/media/ImageMediaComponent.java create mode 100644 src/main/java/zander/library/media/ImageMediaData.java create mode 100644 src/main/java/zander/library/media/MediaData.java create mode 100644 src/main/java/zander/library/media/MediaLoader.java create mode 100644 src/main/java/zander/library/media/SerializableImageContainer.java create mode 100644 src/main/java/zander/library/media/UnknownMediaData.java create mode 100644 src/main/java/zander/library/media/VideoMediaData.java create mode 100644 src/main/java/zander/library/media/VideoMediaViewer.java create mode 100644 src/main/java/zander/library/metadata/MediaMetadata.java create mode 100644 src/main/java/zander/library/metadata/MetadataLoader.java create mode 100644 src/main/java/zander/library/secrets/PassSecrets.java create mode 100644 src/main/java/zander/library/secrets/PassSecretsFactory.java create mode 100644 src/main/java/zander/library/secrets/PreferencesSecrets.java create mode 100644 src/main/java/zander/library/secrets/PreferencesSecretsFactory.java create mode 100644 src/main/java/zander/library/secrets/Secrets.java create mode 100644 src/main/java/zander/library/secrets/SecretsFactory.java create mode 100644 src/main/java/zander/library/state/AlbumConfig.java create mode 100644 src/main/java/zander/library/state/AlbumState.java create mode 100644 src/main/java/zander/library/state/DateTimeUtils.java create mode 100644 src/main/java/zander/library/state/LibraryConfig.java create mode 100644 src/main/java/zander/library/state/LibraryState.java create mode 100644 src/main/java/zander/library/state/MediaState.java create mode 100644 src/main/java/zander/ui/ErrorDialog.java create mode 100644 src/main/java/zander/ui/FileChooserField.java create mode 100644 src/main/java/zander/ui/LibraryFrame.java create mode 100644 src/main/java/zander/ui/LibraryUploadDialog.java create mode 100644 src/main/java/zander/ui/MediaCellRenderer.java create mode 100644 src/main/java/zander/ui/OverlayPainterComponent.java create mode 100644 src/main/java/zander/ui/ProgressTracker.java create mode 100644 src/main/java/zander/ui/ProgressTrackerDialog.java create mode 100644 src/main/java/zander/ui/RememberConfirmDialog.java create mode 100644 src/main/java/zander/ui/UIUtils.java create mode 100644 src/main/java/zander/ui/_import/ImportAlbumSelectDialog.java create mode 100644 src/main/java/zander/ui/_import/ImportLibrarySelectDialog.java create mode 100644 src/main/java/zander/ui/album/AlbumCreateDialog.java create mode 100644 src/main/java/zander/ui/album/AlbumPropertyPanel.java create mode 100644 src/main/java/zander/ui/album/AlbumPropertyUpdateEvent.java create mode 100644 src/main/java/zander/ui/album/AlbumPropertyUpdateListener.java create mode 100644 src/main/java/zander/ui/album/AlbumSelectPanel.java create mode 100644 src/main/java/zander/ui/album/AlbumThumbnailSelectDialog.java create mode 100644 src/main/java/zander/ui/album/LibraryPropertyDialog.java create mode 100644 src/main/java/zander/ui/cellrenderer/AlbumCellRenderer.java create mode 100644 src/main/java/zander/ui/cellrenderer/MediaCellRenderer.java create mode 100644 src/main/java/zander/ui/docs/CuratorAboutPanel.java create mode 100644 src/main/java/zander/ui/docs/DocumentationViewer.java create mode 100644 src/main/java/zander/ui/library/CuratorOptionsDialog.java create mode 100644 src/main/java/zander/ui/library/LibraryCreateDialog.java create mode 100644 src/main/java/zander/ui/library/LibraryDeleteDialog.java create mode 100644 src/main/java/zander/ui/library/LibraryEditDialog.java create mode 100644 src/main/java/zander/ui/library/LibraryExistsDialog.java create mode 100644 src/main/java/zander/ui/library/LibraryImportDialog.java create mode 100644 src/main/java/zander/ui/library/LibrarySelectFrame.java create mode 100644 src/main/java/zander/ui/library/LibrarySyncDialog.java create mode 100644 src/main/java/zander/ui/media/MediaManagePanel.java create mode 100644 src/main/java/zander/ui/media/MediaNameConflictDialog.java create mode 100644 src/main/java/zander/ui/media/MediaPropertyPanel.java create mode 100644 src/main/java/zander/ui/media/NameConflictListDialog.java create mode 100644 src/main/java/zander/ui/media/map/ListLayout.java create mode 100644 src/main/java/zander/ui/media/map/LocationEncoder.java create mode 100644 src/main/java/zander/ui/media/map/LocationEncoderCacheCleaner.java create mode 100644 src/main/java/zander/ui/media/map/LocationEntry.java create mode 100644 src/main/java/zander/ui/media/map/LocationEntryComponent.java create mode 100644 src/main/java/zander/ui/media/map/LocationEntryList.java create mode 100644 src/main/java/zander/ui/media/map/LocationEntryListener.java create mode 100644 src/main/java/zander/ui/media/map/LocationSelectDialog.java create mode 100644 src/main/java/zander/ui/media/map/LocationSelectEvent.java create mode 100644 src/main/java/zander/ui/media/map/LocationSelectListener.java create mode 100644 src/main/java/zander/ui/media/map/LocationSelectPanel.java create mode 100644 src/main/java/zander/ui/media/map/MapSelectionPainter.java create mode 100644 src/main/java/zander/ui/media/map/OSMLocationEncoder.java create mode 100644 src/main/java/zander/ui/transferhandlers/ListArrangeTransferHandler.java create mode 100644 src/main/resources/docs.xml create mode 100644 src/main/resources/docs/album-create-dialog.png create mode 100644 src/main/resources/docs/album-manage-window.html create mode 100644 src/main/resources/docs/album-property-window.png create mode 100644 src/main/resources/docs/album-select-window.png create mode 100644 src/main/resources/docs/album-settings.html create mode 100644 src/main/resources/docs/global-settings-dialog.png create mode 100644 src/main/resources/docs/global-settings.html create mode 100644 src/main/resources/docs/gpl3.html create mode 100644 src/main/resources/docs/import-external.html create mode 100644 src/main/resources/docs/import-select-album-window.png create mode 100644 src/main/resources/docs/import-select-library-window.png create mode 100644 src/main/resources/docs/info.html create mode 100644 src/main/resources/docs/library-create-dialog.png create mode 100644 src/main/resources/docs/library-edit-dialog.png create mode 100644 src/main/resources/docs/library-import-dialog.png create mode 100644 src/main/resources/docs/library-select-window.html create mode 100644 src/main/resources/docs/library-select-window.png create mode 100644 src/main/resources/docs/library-settings.html create mode 100644 src/main/resources/docs/local-library-settings.html create mode 100644 src/main/resources/docs/location-select-dialog.png create mode 100644 src/main/resources/docs/managing-media.html create mode 100644 src/main/resources/docs/media-properties.html create mode 100644 src/main/resources/docs/media-property-window.png create mode 100644 src/main/resources/docs/media-select-window.png create mode 100644 src/main/resources/docs/media-types.html create mode 100644 src/main/resources/docs/remote-library-settings.png create mode 100644 src/main/resources/docs/selecting-locations.html create mode 100644 src/main/resources/eye-closed-icon.svg create mode 100644 src/main/resources/eye-open-icon.svg create mode 100644 src/main/resources/logo.svg create mode 100644 src/main/resources/menu-icon.svg create mode 100644 src/main/resources/missing-image-icon.svg create mode 100644 src/main/resources/music-icon.svg create mode 100644 src/main/resources/pause.svg create mode 100644 src/main/resources/play.svg create mode 100644 src/main/resources/reset-arrow.svg create mode 100644 src/main/resources/star.svg create mode 100644 src/main/resources/video-icon.png create mode 100644 src/main/resources/video-icon.svg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6aaeeb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.idea +*.log +tmp/ +bin/ +.metadata +.classpath +.gradle/ +.project +.settings/ +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fb1e48d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Curator + Copyright (C) 2021 Alexander Rosenberg + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) 2021 Alexander Rosenberg + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f06e0bd --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Curator + +A program to manage the collections for [Art Museum](https://gitlab.com/Zander671/art-museum). diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..960dfc4 --- /dev/null +++ b/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'application' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'commons-net:commons-net:3.8.0' + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + implementation 'org.slf4j:slf4j-api:1.7.30' + implementation 'org.slf4j:slf4j-simple:1.7.30' + implementation 'com.drewnoakes:metadata-extractor:2.15.0' + implementation 'org.apache.xmlgraphics:batik-all:1.14' + implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.7.0' + implementation 'com.twelvemonkeys.imageio:imageio-batik:3.7.0' + implementation 'com.twelvemonkeys.imageio:imageio-webp:3.7.0' + implementation 'com.twelvemonkeys.imageio:imageio-tiff:3.7.0' + implementation 'com.twelvemonkeys.imageio:imageio-icns:3.7.0' + implementation 'com.twelvemonkeys.imageio:imageio-jpeg:3.7.0' + implementation 'com.github.lgooddatepicker:LGoodDatePicker:11.2.1' + implementation 'org.jxmapviewer:jxmapviewer2:2.6' + implementation 'org.apache.tika:tika-core:2.1.0' + // Needed to fix Batik interfering by having the javax.xml package + configurations { + all*.exclude group: 'xml-apis', module: 'xml-apis' + } +} + +application { + mainClass = 'zander.Start' +} + +jar { + manifest { + attributes 'Main-Class': 'zander.Start' + } +} diff --git a/scripts/PKGBUILD b/scripts/PKGBUILD new file mode 100644 index 0000000..20ea005 --- /dev/null +++ b/scripts/PKGBUILD @@ -0,0 +1,37 @@ +# Maintainer: Alexander Rosenberg + +pkgname=curator +pkgver=1 +pkgrel=1 +pkgdesc="A program for uploading images to Art Museum" +url="https://gitlab.com/zander671/curator" +arch=('i686' 'x86_64') +license=('GPL3') +depends=('ffmpeg') +makedepends=('gradle' 'git') +source=("git+https://gitlab.com/zander671/curator.git") +sha256sums=('SKIP') + +pkgver(){ + cd $pkgname + git rev-list --count HEAD +} + +build() { + cd "$srcdir/$pkgname" + gradle installDist +} + +package() { + cd "$srcdir/$pkgname" + mkdir -p "$pkgdir/usr/share" + cp -r build/install/curator "$pkgdir/usr/share/" + mkdir -p "$pkgdir/usr/bin" + ln -fs "/usr/share/curator/bin/curator" "$pkgdir/usr/bin/curator" + install -m644 -D LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -m644 -D README.md "$pkgdir/usr/share/doc/$pkgname/README" + install -m644 -D scripts/linux/curator.xpm "$pkgdir/usr/share/pixmaps/curator.xpm" + install -m644 -D scripts/linux/curator.png "$pkgdir/usr/share/icons/hicolor/48x48/apps/curator.png" + install -m644 -D scripts/linux/curator.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/curator.svg" + install -m644 -D scripts/linux/curator.desktop "$pkgdir/usr/share/applications/curator.desktop" +} diff --git a/scripts/build-macos-app.sh b/scripts/build-macos-app.sh new file mode 100755 index 0000000..3d1966b --- /dev/null +++ b/scripts/build-macos-app.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +echo 'Generating macos app bundle...' + +realpath() { + path="$(ls "$PWD/$1")" + echo "$path" +} + +# Change our working directory to the parent directory of this script +real_path="$(realpath "$0")" +parent_dir="$(dirname "$real_path")" +cd "$parent_dir" + +if [ ! -e ../build/install/curator ]; then + echo "You must run 'gradle installDist' before this script!" 1>&2 + exit 1 +fi + +# Delete old app +rm -rf ../build/macos/Curator.app + +# Copy empty application +mkdir -p ../build/macos/Curator.app/Contents/MacOS/ ../build/macos/Curator.app/Contents/Resources/ + +# Copy Info.plist and icon +cp macos/Info.plist ../build/macos/Curator.app/Contents/Info.plist +cp macos/icon.icns ../build/macos/Curator.app/Contents/Resources/icon.icns + +# Copy compiled app +cp -r ../build/install/curator ../build/macos/Curator.app/Contents/MacOS/ +rm ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator.bat + +echo "Compiling start script..." +# Compile start script (required to make JFileChooser work) +shc -r -f ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator -o ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator.x +rm ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator.x.c +mv ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator.x ../build/macos/Curator.app/Contents/MacOS/curator/bin/curator + +# Build install image (if we are on a mac) +echo 'Building intsall image...' +if which hdiutil >/dev/null 2>/dev/null; then + # Set up temp dir + rm -rf ../build/tmp/macos + mkdir -p ../build/tmp/macos + cp -r ../build/macos/Curator.app ../build/tmp/macos/ + ln -s /Applications ../build/tmp/macos/Applications + + # Create image + hdiutil create -ov -volname "Curator" -srcfolder ../build/tmp/macos ../build/macos/Curator.dmg >/dev/null +else + echo "hdiutil(1) not found! Can't build DMG image." +fi + +echo "App bundle generated in 'build/macos'!" diff --git a/scripts/linux/curator.desktop b/scripts/linux/curator.desktop new file mode 100644 index 0000000..f776d19 --- /dev/null +++ b/scripts/linux/curator.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Curator +GenericName=Media Manager +Comment=Manage photos uploaded to Art Museums instances +Exec=curator +Icon=curator +Type=Application +Terminal=false +Categories=Graphics; +StartupWMClass=Curator +Keywords=Curator;Art Museum;Media; diff --git a/scripts/linux/curator.png b/scripts/linux/curator.png new file mode 100644 index 0000000000000000000000000000000000000000..3df2c513cab1347a7644e38516c331e0abb09abe GIT binary patch literal 1087 zcmV-F1i<@=P)+NsYEj6&Df&CA74-PRcV-1FY;Fov5 zW3SjCzGNB>Z}9TsA1goBE=p+wl@YJGHuW{#_UlKOYky>uQ-aT*E^++lTdEjp1Lp6Y z`0j_FXv4I@nW#gwTtfeAUJX>57cr1ZrEq_LUzXg48^~lbSYKa<&*y{F>4YE%uvjdx zTCL!D9<8md7#JA9*47p>nT+mwhAs`(*47XR1aN+S{^ak35F`=_BoYa%uCAi7u@O^K zQy3f^gvmtz1Of~)kj-W>Ha3RE#l?c-O-)Vc>gs~sZim@yhAhiCJw3(2!2$C5Jg%;; zFfuX%p64+%G*meQ!^6V}hr?>$<#Hhy45Gcg{mJo)q97CsVSaudsZ4*+(%9f3dqXJ=<{xm>#H>pBDJbQvEb1ejYbU-M2oJOxWB(&ZAT&z+}zyg7DS6K1Af0B_4V}t9%ts! z(NXFgxm*qx7Z=PLbQ!3rslnXboZ62>BAA?<)cjbzzP^SeN!Z@rX4as4Kj8QK;q&>_ z{_N~5BuT>2(b2OpilSh8dK%5m&Dh&3`tG(QhV00emzN=mB0`}M03a5NAr^}jY$*jn zfGo>6IXOW*9!EZ(SL3UztC?#XVxX$33QJ2%=;a^+?L4e2O zfyd)fkEK#65JF(FSd1?BGM|j<>gvjt`}N&urTOL!P~QWe|G5AN` + + + + + + + image/svg+xml + + + + + + + + C + + diff --git a/scripts/linux/curator.xpm b/scripts/linux/curator.xpm new file mode 100644 index 0000000..f1a3ea2 --- /dev/null +++ b/scripts/linux/curator.xpm @@ -0,0 +1,104 @@ +/* XPM */ +static char *logo[] = { +/* columns rows colors chars-per-pixel */ +"32 32 66 1 ", +" c None", +". c #000000", +"X c #020202", +"o c #030303", +"O c #060606", +"+ c #090909", +"@ c #0A0A0A", +"# c #0D0D0D", +"$ c #101010", +"% c #131313", +"& c #161616", +"* c #191919", +"= c #1B1B1B", +"- c #222222", +"; c #232323", +": c #242424", +"> c #272727", +", c #2D2D2D", +"< c #323232", +"1 c #333333", +"2 c #373737", +"3 c #383838", +"4 c #434343", +"5 c #4E4E4E", +"6 c #4F4F4F", +"7 c #505050", +"8 c #5E5E5E", +"9 c #6A6A6A", +"0 c #747474", +"q c #757575", +"w c #787878", +"e c #797979", +"r c #C18F00", +"t c #878787", +"y c #8A8A8A", +"u c #909090", +"i c #9F9F9F", +"p c #A0A0A0", +"a c #A2A2A2", +"s c #A3A3A3", +"d c #B6B6B6", +"f c #B7B7B7", +"g c #BABABA", +"h c #BBBBBB", +"j c #C0C0C0", +"k c #C3C3C3", +"l c #C4C4C4", +"z c #CDCDCD", +"x c #D0D0D0", +"c c #D1D1D1", +"v c #D2D2D2", +"b c #D3D3D3", +"n c #D5D5D5", +"m c #DBDBDB", +"M c #F5EDD5", +"N c #FAF5E8", +"B c #FAF6EA", +"V c #F0F0F0", +"C c #F1F1F1", +"Z c #F4F4F4", +"A c #F5F5F5", +"S c #FBFBFB", +"D c #FCFCFC", +"F c #FDFDFD", +"G c #FEFEFE", +"H c #FFFFFF", +/* pixels */ +" ", +" ", +" rrrrrrrrrrrrrrrrrrrr ", +" rrrrrrrrrrrrrrrrrrrr ", +" rrMNNNNNNNNNNNNNNMrr ", +" rrNHHHHHHHHHHHHHHNrr ", +" rrNHHHHHHHHHHHHHHNrr ", +" rrNHHHHHHHHZHHHHHNrr ", +" rrNHHHZj8-..@1tzHNrr ", +" rrNHHHe*.$473#.>vNrr ", +" rrNHZq.*aZHHHZu,hNrr ", +" rrNHh&$iHHHHHHHvmNrr ", +" rrNH5.9SHHHHHHHHHNrr ", +" rrNS$.fHHHHHHHHHHNrr ", +" rrNn..CHHHHHHHHHHNrr ", +" rrNj.OHHHHHHHHHHHNrr ", +" rrNl.OHHHHHHHHHHHNrr ", +" rrNn..CHHHHHHHHHHNrr ", +" rrNS$.fHHHHHHHHHHNrr ", +" rrNH5.9SHHHHHHHHHNrr ", +" rrNHh&$aHHHHHHHvmNrr ", +" rrNHZq.*aZHHHZu,hNrr ", +" rrNHHHe*.$462#.>vNrr ", +" rrNHHHZj8-..@1yvHNrr ", +" rrNHHHHHHHHZHHHHHNrr ", +" rrNHHHHHHHHHHHHHHNrr ", +" rrNHHHHHHHHHHHHHHNrr ", +" rrMNNNNNNNNNNNNNNMrr ", +" rrrrrrrrrrrrrrrrrrrr ", +" rrrrrrrrrrrrrrrrrrrr ", +" ", +" " +}; diff --git a/scripts/macos/Info.plist b/scripts/macos/Info.plist new file mode 100644 index 0000000..9deb6b0 --- /dev/null +++ b/scripts/macos/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + English + + CFBundleExecutable + curator/bin/curator + + CFBundleIconFile + icon.icns + + CFBundleIdentifier + zander.curator + + CFBundleDisplayName + Curator + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundleName + Curator + + CFBundlePackageType + APPL + + CFBundleVersion + 1.0 + + CFBundleShortVersionString + 1.0 + + CFBundleSignature + ???? + + CFBundleVersion + 1.0 + + NSHumanReadableCopyright + (c) 2021 Alexander Rosenberg + + + diff --git a/scripts/macos/icon.icns b/scripts/macos/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..25715f6defe9c0070edd09ea1de61acd4b616186 GIT binary patch literal 39933 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 new LibrarySelectFrame(args)); + } + + private static void registerSecretsFactories() { + SecretsFactory.registerFactory(new PassSecretsFactory()); + SecretsFactory.registerFactory(new PreferencesSecretsFactory()); + } + +} diff --git a/src/main/java/zander/library/FileUtils.java b/src/main/java/zander/library/FileUtils.java new file mode 100644 index 0000000..05a9398 --- /dev/null +++ b/src/main/java/zander/library/FileUtils.java @@ -0,0 +1,35 @@ +package zander.library; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.tika.utils.SystemUtils; + +public class FileUtils { + + public static File createTempFile(boolean deleteOnExit) throws IOException { + return createTempFile(null, deleteOnExit); + } + + public static File createTempFile(byte[] data, boolean deleteOnExit) throws IOException { + File file; + if (SystemUtils.IS_OS_MAC) { + final File dir = new File(System.getProperty("user.home") + "/.cache/curator"); + dir.mkdirs(); + file = File.createTempFile("curator-temp-file-", null, dir); + } else { + file = File.createTempFile("curator-temp-file-", null); + } + if (deleteOnExit) { + file.deleteOnExit(); + } + if (data != null) { + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(data); + } + } + return file; + } + +} diff --git a/src/main/java/zander/library/Library.java b/src/main/java/zander/library/Library.java new file mode 100644 index 0000000..a4ad2af --- /dev/null +++ b/src/main/java/zander/library/Library.java @@ -0,0 +1,241 @@ +package zander.library; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.function.Consumer; + +import javax.swing.SwingUtilities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.Manifest.ManifestDifference; +import zander.library.Manifest.ManifestParseException; +import zander.library.secrets.Secrets; + +public class Library { + private static final Logger LOGGER = LoggerFactory.getLogger(Library.class); + + public static final class SyncStatus { + private int total = -1; + private int current; + private boolean started; + private Throwable error; + private boolean canceled; + private String note; + private boolean done; + + private Consumer updateAction; + + public SyncStatus(Consumer updateAction) { + this.updateAction = updateAction; + } + + private synchronized void finish() { + done = true; + runUpdateAction(); + } + + public synchronized boolean isDone() { + return done; + } + + public synchronized String getNote() { + return note; + } + + private synchronized void setNote(String note) { + LOGGER.info(note); + this.note = note; + runUpdateAction(); + } + + private synchronized void setTotal(int total) { + this.total = total; + runUpdateAction(); + } + + public synchronized boolean isStarted() { + return started; + } + + public synchronized Consumer getUpdateAction() { + return updateAction; + } + + public synchronized Throwable getError() { + return error; + } + + public synchronized boolean isFailed() { + return error != null; + } + + public synchronized void setUpdateAction(Consumer updateAction) { + this.updateAction = updateAction; + } + + public synchronized int getTotal() { + return total; + } + + public synchronized int getCurrent() { + return current; + } + + public synchronized float getPrecent() { + return (float) current / total; + } + + public synchronized void cancel() { + canceled = true; + } + + public synchronized boolean isCanceled() { + return canceled; + } + + private synchronized void incrementCurrent() { + ++this.current; + runUpdateAction(); + } + + private synchronized void runUpdateAction() { + if (updateAction != null) { + SwingUtilities.invokeLater(() -> updateAction.accept(this)); + } + } + + } + + public static final String CONFIG_FILE_PATH = "config.json"; + public static final String ALBUMS_FILE_PATH = "albums"; + public static final String THUMBNAILS_FILE_PATH = "thumbnails"; + public static final String ALBUM_CONFIG_FILE_NAME = "data.json"; + + private LocalCache localCache; + private RemoteStore remoteStore; + + public Library(File local, URI remote, Secrets secrets) throws ManifestParseException, IOException { + remoteStore = new RemoteStore(remote, secrets); + localCache = new LocalCache(local); + } + + private void syncLocalToRemote(SyncStatus status) throws IOException, ManifestParseException { + if (status != null && !status.isStarted()) { + Thread t = new Thread(() -> { + status.started = true; + status.setNote("Fetching manifest..."); + try { + syncLocalToRemote(status); + } catch (Throwable e) { + LOGGER.error("Failed to sync local to remote!", e); + status.error = e; + status.runUpdateAction(); + return; + } + }); + t.start(); + return; + } + LOGGER.info("Syncing local cahce to remote store..."); + remoteStore.doFtpActions(() -> { + status.setNote("Retreving remote manifest"); + remoteStore.fetchManifest(); + Manifest rm = remoteStore.getManifest(); + status.setNote("Reading remote manifest"); + Manifest lm = localCache.getManifest(); + status.setNote("Calculating differences..."); + ManifestDifference diff = lm.compareTo(rm); + status.setTotal(diff.newFiles.size() + diff.removedFiles.size()); + if (status != null) { + if (status.isCanceled()) { + LOGGER.info("Sync canceled!"); + return; + } + } + status.setNote("Deleting old files..."); + for (String f : diff.removedFiles) { + localCache.deleteFile(f); + status.incrementCurrent(); + status.setNote("Deleted: " + f); + if (status != null) { + if (status.isCanceled()) { + LOGGER.info("Sync canceled!"); + return; + } + } + } + status.setNote("Retrieving new and updated files..."); + for (String f : diff.newFiles) { + status.setNote("Retrieving '" + f + "'.."); + byte[] data = remoteStore.retrieveFile(f); + localCache.addFile(f, data); + status.setNote("Retrived: '" + f + "' (" + data.length + " B)"); + status.incrementCurrent(); + if (status != null) { + if (status.isCanceled()) { + LOGGER.info("Sync canceled!"); + return; + } + } + } + }); + if (!status.isCanceled()) { + status.setNote("Done!"); + status.finish(); + } + } + + public void syncLocalToRemote() throws IOException, ManifestParseException { + syncLocalToRemote(null); + } + + public SyncStatus syncLocalToRemoteAsync(Consumer updateAction) { + SyncStatus s = new SyncStatus(updateAction); + try { + syncLocalToRemote(s); + } catch (ManifestParseException | IOException e) { + // ignore + } + return s; + } + + public void clearLocalCache() throws IOException { + localCache.clear(); + } + + public LocalCache getLocalCache() { + return localCache; + } + + public RemoteStore getRemoteStore() { + return remoteStore; + } + + public void addFile(String name, byte[] data) throws IOException { + localCache.addFile(name, data); + remoteStore.addFile(name, data); + } + + public void addBinaryFile(String name, byte[] data) throws IOException { + localCache.addFile(name, data); + remoteStore.addBinaryFile(name, data); + } + + public void addAsciiFile(String name, String data) throws IOException { + localCache.addFile(name, data); + remoteStore.addAsciiFile(name, data); + } + + public void deleteFile(String name) throws IOException { + localCache.deleteFile(name); + remoteStore.deleteFile(name); + } + + public void moveFile(String name, String target) throws IOException { + localCache.moveFile(name, target); + remoteStore.moveFile(name, target); + } +} diff --git a/src/main/java/zander/library/LibraryConfig.java b/src/main/java/zander/library/LibraryConfig.java new file mode 100644 index 0000000..83b72b4 --- /dev/null +++ b/src/main/java/zander/library/LibraryConfig.java @@ -0,0 +1,95 @@ +package zander.library; + +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class LibraryConfig { + + private String name; + private String license; + private List albums; + + public LibraryConfig() { + this("", "", new ArrayList()); + } + + public LibraryConfig(String name, String license, List albums) { + this.name = name; + this.license = license; + this.albums = albums; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public List getAlbums() { + return albums; + } + + public void setAlbums(List albums) { + this.albums = albums; + } + + public static LibraryConfig parseJson(String json) throws ParseException { + JSONParser p = new JSONParser(); + Object o = p.parse(json); + if (!(o instanceof JSONObject)) { + throw new ClassCastException("Json root was not an object"); + } + return parseJson((JSONObject) o); + } + + public static LibraryConfig parseJson(JSONObject json) { + Object nameObj = json.get("library_name"); + Object licenseObj = json.get("license"); + Object albumsObj = json.get("albums"); + if (!(nameObj instanceof String) || !(licenseObj instanceof String)) { + throw new ClassCastException("Name and license must be strings"); + } else if (!(albumsObj instanceof JSONArray)) { + throw new ClassCastException("Albums must be an array"); + } + JSONArray albumsArr = (JSONArray) albumsObj; + final ArrayList albums = new ArrayList(albumsArr.size()); + for (Object o : albumsArr) { + if (!(o instanceof String)) { + throw new ClassCastException("All members of albums must be a string"); + } + albums.add((String) o); + } + return new LibraryConfig((String) nameObj, (String) licenseObj, albums); + } + + @SuppressWarnings("unchecked") + public static JSONObject toJson(LibraryConfig lc) { + JSONObject root = new JSONObject(); + root.put("library_name", lc.getName()); + root.put("license", lc.getLicense()); + JSONArray albums = new JSONArray(); + albums.addAll(lc.getAlbums()); + root.put("albums", albums); + return root; + } + + public static String toJsonString(LibraryConfig lc) { + return toJson(lc).toJSONString(); + } + +} diff --git a/src/main/java/zander/library/LibraryMediaCache.java b/src/main/java/zander/library/LibraryMediaCache.java new file mode 100644 index 0000000..ce4167f --- /dev/null +++ b/src/main/java/zander/library/LibraryMediaCache.java @@ -0,0 +1,237 @@ +package zander.library; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import zander.library.media.MediaData; +import zander.library.media.MediaLoader; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.ui.ProgressTracker.ProgressManager; + +public class LibraryMediaCache { + + public static class AlbumMediaCache { + + private final HashMap cache = new HashMap(); + private final HashMap swapFiles = new HashMap(); + private final LibraryState library; + private final AlbumState album; + + public AlbumMediaCache(LibraryState library, String name) { + this.library = library; + album = library.getAlbumForName(name); + } + + public Map getAllMedia() { + return cache; + } + + public MediaData clearMedia(String name) { + return removeMedia(name); + } + + public void clear() { + cache.clear(); + swapFiles.clear(); + } + + public void swapOutMedia(String name) { + MediaData data = cache.remove(name); + if (data != null) { + swapFiles.put(name, data.getSourceFile()); + } + } + + public void swapOutAll(boolean doThumbnail) { + for (var i = cache.entrySet().iterator(); i.hasNext();) { + Entry entry = i.next(); + if (doThumbnail || !entry.getKey().equals(album.getThumbnailName())) { + i.remove(); + swapFiles.put(entry.getKey(), entry.getValue().getSourceFile()); + } + } + } + + public MediaData getMedia(String name, ProgressManager t) { + if (cache.containsKey(name)) { + return cache.get(name); + } + try { + if (swapFiles.containsKey(name)) { + File file = swapFiles.get(name); + MediaData data = MediaLoader.loadBytes(file, readFileBytes(file), false, t); + cache.put(name, data); + swapFiles.remove(name); + return data; + } + LocalCache lc = library.getLibrary().getLocalCache(); + String path = getPathForImage(name); + if (!lc.fileExists(path)) { + return null; + } + MediaData data = MediaLoader.loadBytes(lc.forPath(path), lc.retrieveFile(path), false, t); + cache.put(name, data); + return data; + } catch (IOException e) { + return null; + } + } + + public MediaData getCheckedMedia(String name, ProgressManager t) { + if (album.getMediaForName(name) != null) { + return getMedia(name, t); + } + return null; + } + + public void moveMedia(String oldName, String newName) { + if (cache.containsKey(oldName)) { + MediaData d = cache.remove(oldName); + cache.put(newName, d); + swapFiles.remove(oldName); + swapFiles.put(newName, d.getSourceFile()); + } + } + + public MediaData removeMedia(String name) { + swapFiles.remove(name); + return cache.remove(name); + } + + public void putMedia(String name, MediaData data) { + cache.put(name, data); + } + + private String getPathForImage(String name) { + return Library.ALBUMS_FILE_PATH + "/" + album.getNameOnDisk() + "/" + name; + } + + private byte[] readFileBytes(File file) throws IOException { + try (FileInputStream fin = new FileInputStream(file)) { + return fin.readAllBytes(); + } + } + } + + private final HashMap cache = new HashMap(); + private final LibraryState library; + + public LibraryMediaCache(LibraryState library) { + this.library = library; + } + + public LibraryState getLibrary() { + return library; + } + + public Map getAlbums() { + return cache; + } + + public AlbumMediaCache getAlbum(String name) { + if (cache.containsKey(name)) { + return cache.get(name); + } else if (!albumExists(name)) { + return null; + } + AlbumMediaCache album = new AlbumMediaCache(library, name); + cache.put(name, album); + return album; + } + + public void moveAlbum(String oldName, String newName) { + if (cache.containsKey(oldName)) { + AlbumMediaCache ac = cache.remove(oldName); + cache.put(newName, ac); + } + } + + public AlbumMediaCache removeAlbum(String album) { + return cache.remove(album); + } + + public void moveMedia(String album, String oldMedia, String newMedia) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + a.moveMedia(oldMedia, newMedia); + } + } + + public MediaData removeMedia(String album, String media) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + return a.removeMedia(media); + } + return null; + } + + public MediaData getMedia(String album, String media, ProgressManager t) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + return a.getMedia(media, t); + } + return null; + } + + public MediaData getCheckedMedia(String album, String media, ProgressManager t) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + return a.getCheckedMedia(media, t); + } + return null; + } + + public void putMedia(String album, String media, MediaData data) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + a.putMedia(media, data); + } + } + + public MediaData clearMedia(String album, String media) { + return removeMedia(album, media); + } + + public void swapOutAlbum(String album, boolean doThumbnail) { + AlbumMediaCache a = getAlbum(album); + if (a != null) { + a.swapOutAll(doThumbnail); + } + } + + public void swapOutAll(boolean doThumbnails) { + swapOutAllBut(null, doThumbnails); + } + + public void swapOutAllBut(String album, boolean doThumbnails) { + for (Entry entry : cache.entrySet()) { + AlbumMediaCache a = entry.getValue(); + if (!a.album.getName().equals(album)) { + a.swapOutAll(doThumbnails); + } + } + } + + public void clearAlbum(String name) { + cache.remove(name); + } + + public void clear() { + cache.clear(); + } + + private boolean albumExists(String name) { + for (AlbumState album : library.getAlbums()) { + if (name.equals(album.getName())) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/zander/library/LocalCache.java b/src/main/java/zander/library/LocalCache.java new file mode 100644 index 0000000..82afc15 --- /dev/null +++ b/src/main/java/zander/library/LocalCache.java @@ -0,0 +1,196 @@ +package zander.library; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.Manifest.ManifestParseException; + +public class LocalCache { + private static final Logger LOGGER = LoggerFactory.getLogger(LocalCache.class); + + public static final String MANIFEST_PATH = ".manifest"; + + /** Will always be canonical */ + private File root; + private Manifest manifest; + + /** @param uid the uid of the remote manifest */ + public LocalCache(File root) throws IOException, ManifestParseException { + this.root = root.getCanonicalFile(); + this.root.mkdirs(); + reloadManifest(); + } + + public File getRoot() { + return root; + } + + public Manifest getManifest() { + return manifest; + } + + private void reloadManifest() throws IOException, ManifestParseException { + File mf = getAbsoluteFile(MANIFEST_PATH); + if (!mf.exists()) { + manifest = new Manifest(); + writeManifest(); + } else { + manifest = new Manifest(mf); + } + } + + public void addFile(String name, String data) throws IOException { + addFile(name, data.getBytes()); + } + + public void addFile(String name, byte[] data) throws IOException { + createParentDirectories(name); + try (FileOutputStream out = new FileOutputStream(getAbsoluteFile(name))) { + out.write(data); + } + manifest.add(name, data); + LOGGER.info("Wrote file: {} ({} B)", name, data.length); + writeManifest(); + } + + public void deleteFile(String name) throws IOException { + File file = getAbsoluteFile(name); + recursiveDelete(file); + deleteEmptyParents(file, root); + manifest.delete(name); + LOGGER.info("Deleted file: {}", name); + writeManifest(); + } + + public void moveFile(String source, String target) throws IOException { + File sf = getAbsoluteFile(source); + File tf = getAbsoluteFile(target); + Path sp = sf.toPath(); + Path tp = tf.toPath(); + createParentDirectories(target); + Files.move(sp, tp, StandardCopyOption.REPLACE_EXISTING); + deleteEmptyParents(sf, tf); + manifest.move(source, target); + LOGGER.info("Moved file '{}' to '{}'", source, target); + writeManifest(); + } + + public byte[] retrieveFile(String path) throws IOException { + FileInputStream in = new FileInputStream(getAbsoluteFile(path)); + byte[] d = in.readAllBytes(); + in.close(); + return d; + } + + public boolean fileExists(String path) throws IOException { + File f = getAbsoluteFile(path); + return f.exists(); + } + + public boolean isFileTracked(String path) { + return manifest.contains(path); + } + + public void clear() throws IOException { + recursiveDelete(root); + manifest.clear(); + writeManifest(); + LOGGER.info("Cleared local cache"); + } + + public File forPath(String path) throws IOException { + return getAbsoluteFile(path); + } + + private String getRootPath() { + return root.getPath() + File.separator; + } + + private File getAbsoluteFile(String path) throws IOException { + return new File(getRootPath() + cleanPath(path)).getCanonicalFile(); + } + + private void writeManifest() throws IOException { + if (!root.exists()) { + root.mkdirs(); + } + try (FileOutputStream out = new FileOutputStream(getAbsoluteFile(MANIFEST_PATH))) { + byte[] data = manifest.getData().getBytes(); + out.write(data); + LOGGER.info("Wrote local manifest ({} B)", data.length); + } + } + + private void createParentDirectories(String path) throws IOException { + String pp = getParentPath(path); + if (pp == null) { + return; + } + File parent = getAbsoluteFile(pp); + if (parent.exists()) { + if (parent.isDirectory()) { + return; + } else { + throw new IOException("Not a directory: " + pp); + } + } + try { + if (!getAbsoluteFile(pp).mkdirs()) { + throw new IOException("Could not create directoy: '" + pp + "'"); + } + LOGGER.info("Created directory: {}", path); + } catch (SecurityException e) { + throw new IOException("No perms for directory: '" + pp + "'", e); + } + } + + private static void deleteEmptyParents(File file, File top) throws IOException { + if (!file.equals(top)) { + if (file.isDirectory() && file.list().length == 0) { + File parent = file.getParentFile(); + file.delete(); + deleteEmptyParents(parent, top); + } + } + } + + private static void recursiveDelete(File file) throws IOException { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + recursiveDelete(child); + } + } + try { + file.delete(); + } catch (SecurityException e) { + throw new IOException("No perms for file: " + file.getPath(), e); + } + } + + private static String cleanPath(String p) { + if (!p.isEmpty() && p.charAt(0) == '/') { + p = p.substring(1); + } + if (p.isEmpty() && p.charAt(p.length() - 1) == '/') { + p = p.substring(0, p.length() - 1); + } + return p; + } + + private static String getParentPath(String path) { + String cp = cleanPath(path); + int i = cp.lastIndexOf("/"); + if (i == -1) { + return null; + } + return cp.substring(0, i); + } +} diff --git a/src/main/java/zander/library/Manifest.java b/src/main/java/zander/library/Manifest.java new file mode 100644 index 0000000..6d88b2b --- /dev/null +++ b/src/main/java/zander/library/Manifest.java @@ -0,0 +1,229 @@ +package zander.library; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class Manifest implements Cloneable { + private static MessageDigest HASHER; + static { + try { + HASHER = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new Error("Could not create SHA-256 MessageDigest!", e); + } + } + + public static class ManifestParseException extends Exception { + public ManifestParseException(String what, String line) { + this(what + ": HERE -> '" + line + "'"); + } + + public ManifestParseException(String message) { + super(message); + } + + public ManifestParseException(String message, Throwable cause) { + super(message, cause); + } + } + + private final Map entries; + + public Manifest() { + entries = new HashMap(); + } + + public Manifest(String source) throws ManifestParseException { + this(); + addEntriesFromString(source); + } + + public Manifest(File source) throws IOException, ManifestParseException { + this(); + addEntriesFromFile(source); + } + + public Map getEntries() { + return entries; + } + + public void add(String path, String data) { + add(path, data.getBytes()); + } + + public void add(String path, byte[] data) { + byte[] rh = HASHER.digest(data); + String b64eh = Base64.getEncoder().encodeToString(rh); + entries.put(cleanPath(path), b64eh); + } + + public boolean contains(String path) { + return entries.containsKey(cleanPath(path)); + } + + public boolean delete(String path) { + if (path.isEmpty()) { + return false; + } + String cp = cleanPath(path); + String hash = entries.remove(cleanPath(path)); + if (hash != null) { + return true; + } + // path is a directory + cp += "/"; + boolean deleted = false; + for (var i = entries.entrySet().iterator(); i.hasNext();) { + Entry e = i.next(); + if (e.getKey().startsWith(cp)) { + i.remove(); + deleted = true; + } + } + return deleted; + } + + public void move(String source, String target) { + String cs = cleanPath(source); + String ct = cleanPath(target); + String hash = entries.remove(cs); + if (hash != null) { + entries.put(ct, hash); + return; + } + final HashMap moved = new HashMap(); + // source & target are directory + cs += "/"; + ct += "/"; + for (var i = entries.entrySet().iterator(); i.hasNext();) { + Entry e = i.next(); + if (e.getKey().startsWith(cs)) { + i.remove(); + String np = replaceDirectoryPart(e.getKey(), cs, ct); + moved.put(np, e.getValue()); + } + } + entries.putAll(moved); + } + + /** path must be clean, od and nd can be clean or end with "/" (but not start with) */ + private String replaceDirectoryPart(String path, String od, String nd) { + String np = path.substring(od.length()); + np = nd + np; + return np; + } + + public String get(String file) { + return entries.get(file); + } + + public void clear() { + entries.clear(); + } + + public Set getFiles() { + return new HashSet(entries.values()); + } + + public String getData() { + final StringBuilder sb = new StringBuilder(); + for (Entry e : entries.entrySet()) { + sb.append(e.getKey()); + sb.append(" "); + sb.append(e.getValue()); + sb.append("\n"); + } + return sb.toString(); + } + + public static class ManifestDifference { + public final Set newFiles; + public final Set removedFiles; + + public ManifestDifference(Set nf, Set rf) { + newFiles = Collections.unmodifiableSet(new HashSet(nf)); + removedFiles = Collections.unmodifiableSet(new HashSet(rf)); + } + } + + public ManifestDifference compareTo(Manifest om) { + final HashSet nf = new HashSet(); + final HashSet rf = new HashSet(); + for (Entry e : om.entries.entrySet()) { + String oh = e.getValue(); + String th = entries.get(e.getKey()); + if (th == null || !th.equals(oh)) { + nf.add(e.getKey()); + } + } + for (Entry e : entries.entrySet()) { + String oh = om.entries.get(e.getKey()); + if (oh == null) { + rf.add(e.getKey()); + } + } + return new ManifestDifference(nf, rf); + } + + @Override + public Manifest clone() { + Manifest n = new Manifest(); + n.entries.putAll(entries); + return n; + } + + @Override + public String toString() { + return getData(); + } + + private void addEntriesFromString(String source) throws ManifestParseException { + String[] lines = source.split("\\r?\\n"); + for (String line : lines) { + addLineEntry(line); + } + } + + private void addLineEntry(String line) throws ManifestParseException { + if (line.isEmpty()) { + return; + } + int di = line.lastIndexOf(" "); + if (di == -1) { + throw new ManifestParseException("No divider", line); + } else if (di == line.length() - 1) { + throw new ManifestParseException("No hash", line); + } else if (di == 0) { + throw new ManifestParseException("No path", line); + } + String path = line.substring(0, di); + String hash = line.substring(di + 1); + entries.put(path, hash); + } + + private void addEntriesFromFile(File file) throws IOException, ManifestParseException { + try (FileInputStream fin = new FileInputStream(file)) { + addEntriesFromString(new String(fin.readAllBytes())); + } + } + + private static String cleanPath(String p) { + if (!p.isEmpty() && p.charAt(0) == '/') { + p = p.substring(1); + } + if (!p.isEmpty() && p.charAt(p.length() - 1) == '/') { + p = p.substring(0, p.length() - 1); + } + return p; + } +} diff --git a/src/main/java/zander/library/RemoteStore.java b/src/main/java/zander/library/RemoteStore.java new file mode 100644 index 0000000..508ffb1 --- /dev/null +++ b/src/main/java/zander/library/RemoteStore.java @@ -0,0 +1,370 @@ +package zander.library; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.ProtocolException; +import java.net.URI; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; +import org.apache.commons.net.ftp.FTPSClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.Manifest.ManifestParseException; +import zander.library.secrets.Secrets; + +public class RemoteStore { + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteStore.class); + private static final String MANIFEST_PATH = ".manifest"; + + public static interface FTPAction { + public void run() throws IOException; + } + + private URI url; + private Secrets secrets; + private Manifest manifestCahce; + + /** may be connected or disconnected */ + private final FTPClient ftp; + private int connectionLevel = 0; + private boolean ignoreManifest = false; + + public RemoteStoreFileTypeHandler fileTypeHandler = new RemoteStoreFileTypeHandler(); + + public RemoteStore(URI url, Secrets secrets) throws IOException, ManifestParseException { + this.url = url; + if (!url.isAbsolute() || url.getScheme().equals("ftp")) { + ftp = new FTPClient(); + } else if (url.getScheme().equals("ftps")) { + ftp = new FTPSClient(false); + } else { + throw new ProtocolException("unknown protocol: '" + url.getScheme() + "'"); + } + this.secrets = secrets; + } + + public URI getURL() { + return url; + } + + public boolean isIgnoringManifest() { + return ignoreManifest; + } + + public void setIgnoreManifest(boolean ignoreManifest) { + this.ignoreManifest = ignoreManifest; + } + + public void fetchManifest() throws IOException { + doFtpActions(() -> { + FTPFile info = fileInformation(MANIFEST_PATH); + if (info == null) { + manifestCahce = new Manifest(); + uploadManifest(); + } else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.setFileType(FTP.ASCII_FILE_TYPE); + ftp.retrieveFile(resolvePath(MANIFEST_PATH), out); + LOGGER.info("Retrieved manifest ({} B)", out.size()); + try { + manifestCahce = new Manifest(new String(out.toByteArray())); + } catch (ManifestParseException e) { + throw new IOException("Could not parse remote manifest"); + } + } + }); + } + + public Manifest getManifest() throws IOException { + if (manifestCahce == null) { + fetchManifest(); + } + return manifestCahce; + } + + public void addBinaryFile(String path, byte[] data) throws IOException { + doFtpActions(() -> { + Manifest mf = getManifest(); + createParentDirectories(path); + ftp.setFileType(FTP.BINARY_FILE_TYPE); + final ByteArrayInputStream in = new ByteArrayInputStream(data); + ftp.storeFile(resolvePath(path), in); + checkFtpResponse("Could not upload file: " + path); + LOGGER.info("Uploaded ascii file: {} ({} B)", path, data.length); + if (!ignoreManifest) { + mf.add(cleanPath(path), data); + uploadManifest(); + } + }); + } + + public void addAsciiFile(String path, String data) throws IOException { + doFtpActions(() -> { + final Manifest mf = getManifest(); + createParentDirectories(path); + ftp.setFileType(FTP.ASCII_FILE_TYPE); + final byte[] bytes = data.getBytes(); + final ByteArrayInputStream in = new ByteArrayInputStream(bytes); + ftp.storeFile(resolvePath(path), in); + checkFtpResponse("Could not upload file: " + path); + LOGGER.info("Uploaded file: {} ({} B)", path, bytes.length); + if (!ignoreManifest) { + mf.add(cleanPath(path), data); + uploadManifest(); + } + }); + } + + public void addFile(String path, byte[] data) throws IOException { + if (fileTypeHandler.isBinaryFile(path)) { + addBinaryFile(path, data); + } else { + addAsciiFile(path, new String(data)); + } + } + + public void deleteFile(String path) throws IOException { + doFtpActions(() -> { + Manifest mf = getManifest(); + String rp = resolvePath(path); + recursiveDelete(rp); + LOGGER.info("Deleted file: {}", path); + deleteEmptyUpward(rp); + if (!ignoreManifest) { + mf.delete(path); + uploadManifest(); + } + }); + } + + public void moveFile(String source, String target) throws IOException { + doFtpActions(() -> { + Manifest mf = getManifest(); + String rs = resolvePath(source); + String rt = resolvePath(target); + FTPFile info = fileInformation(target); + if (info != null) { + recursiveDelete(rt); + } + ftp.rename(rs, rt); + LOGGER.info("Moved remote file '{}' to '{}'", source, target); + if (!ignoreManifest) { + mf.move(cleanPath(source), cleanPath(target)); + uploadManifest(); + } + }); + } + + public byte[] retrieveBinaryFile(String path) throws IOException { + AtomicReference data = new AtomicReference(); + doFtpActions(() -> { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.setFileType(FTP.BINARY_FILE_TYPE); + ftp.retrieveFile(resolvePath(path), out); + checkFtpResponse("Could not retrieve binary file: " + path); + LOGGER.info("Retrieved binary file: {} ({} B)", path, out.size()); + data.set(out.toByteArray()); + }); + return data.get(); + } + + public String retrieveAsciiFile(String path) throws IOException { + AtomicReference data = new AtomicReference(); + doFtpActions(() -> { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.setFileType(FTP.ASCII_FILE_TYPE); + ftp.retrieveFile(resolvePath(path), out); + checkFtpResponse("Could not retrieve ascii file: " + path); + LOGGER.info("Retrieved ascii file: {} ({} B)", path, out.size()); + data.set(new String(out.toByteArray())); + }); + return data.get(); + } + + public byte[] retrieveFile(String path) throws IOException { + if (fileTypeHandler.isBinaryFile(path)) { + return retrieveBinaryFile(path); + } else { + return retrieveAsciiFile(path).getBytes(); + } + } + + public boolean fileExists(String path) throws IOException { + return fileInformation(path) != null; + } + + public FTPFile fileInformation(String path) throws IOException { + AtomicReference file = new AtomicReference(); + doFtpActions(() -> { + file.set(ftp.mlistFile(resolvePath(path))); + }); + return file.get(); + } + + public void openConnection() throws IOException { + if (connectionLevel++ == 0) { + int port = url.getPort(); + String host = url.getHost(); + ftp.connect(host == null ? "" : host, port == -1 ? FTPClient.DEFAULT_PORT : port); + if (!ftp.login(secrets.getUsername(), secrets.getPassword())) { + throw new IOException("Invalid ftp secrets! Username: '" + secrets.getUsername() + "'"); + } + LOGGER.info("Logged info ftp as user '{}'", secrets.getUsername()); + } + } + + public void closeConnection() throws IOException { + if (connectionLevel == 0) { + return; + } + if (--connectionLevel == 0) { + try { + ftp.logout(); + ftp.disconnect(); + } catch (IOException e) { + ftp.disconnect(); + throw e; + } finally { + LOGGER.info("Disconnected from ftp"); + } + } + } + + public void closeNow() throws IOException { + connectionLevel = 1; + closeConnection(); + } + + public void doFtpActions(FTPAction action) throws IOException { + try { + openConnection(); + action.run(); + } finally { + closeConnection(); + } + } + + /** path must be resolved */ + private void deleteEmptyUpward(String path) throws IOException { + doFtpActions(() -> { + if ((path).equals(cleanPath(getBasePath()))) { + return; + } + FTPFile info = fileInformation(path); + if (info == null) { + return; + } else if (!info.isDirectory()) { + throw new IOException("Not a directory: " + path); + } + FTPFile[] children = ftp.mlistDir(path); + checkFtpResponse("Could not list directory: " + path); + if (children.length == 0) { + ftp.deleteFile(path); + checkFtpResponse("Could not delete directory: " + path); + LOGGER.info("Deleted empty directory: {}", path); + deleteEmptyUpward(getParentPath(path)); + } + }); + } + + /** path must be resolved */ + private void recursiveDelete(String path) throws IOException { + doFtpActions(() -> { + FTPFile info = fileInformation(path); + if (info == null) { + return; + } else if (info.isDirectory()) { + FTPFile[] children = ftp.mlistDir(path); + checkFtpResponse("Could not list directory: " + path); + for (FTPFile child : children) { + recursiveDelete(path + "/" + child.getName()); + } + } + ftp.deleteFile(path); + checkFtpResponse("Could not delete file: " + path); + LOGGER.info("Deleted remote file: {}", path); + }); + } + + private void createParentDirectories(String path) throws IOException { + doFtpActions(() -> { + String pp = getParentPath(path); + if (pp == null) { + return; + } + FTPFile info = fileInformation(pp); + if (info == null) { + final StringBuilder pb = new StringBuilder(getBasePath()); + String[] parts = pp.split("/"); + for (String part : parts) { + pb.append(part); + pb.append("/"); + ftp.makeDirectory(pb.toString()); + } + checkFtpResponse("Could not create directory: " + pp); + LOGGER.info("Created directory: {}", pp); + } else if (!info.isDirectory()) { + throw new IOException("Not a directory: " + pp); + } + }); + } + + private void checkFtpResponse(String error) throws IOException { + if (ftp.isConnected()) { + int r = ftp.getReplyCode(); + if (!FTPReply.isPositiveCompletion(r)) { + LOGGER.info(error); + throw new IOException(error); + } + } + } + + private void uploadManifest() throws IOException { + doFtpActions(() -> { + Manifest mf = getManifest(); + byte[] bytes = mf.getData().getBytes(); + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + ftp.setFileType(FTP.ASCII_FILE_TYPE); + ftp.storeFile(resolvePath(MANIFEST_PATH), in); + checkFtpResponse("Could not upload manifest"); + LOGGER.info("Uploaded manifest ({} B)", bytes.length); + }); + } + + private String getBasePath() { + String base = url.getPath(); + if (!base.endsWith("/")) { + base += "/"; + } + return base; + } + + private String resolvePath(String p) { + return getBasePath() + cleanPath(p); + } + + private static String cleanPath(String p) { + if (!p.isEmpty() && p.charAt(0) == '/') { + p = p.substring(1); + } + if (p.isEmpty() && p.charAt(p.length() - 1) == '/') { + p = p.substring(0, p.length() - 1); + } + return p; + } + + private static String getParentPath(String path) { + String cp = cleanPath(path); + int i = cp.lastIndexOf("/"); + if (i == -1) { + return null; + } + return cp.substring(0, i); + } +} diff --git a/src/main/java/zander/library/RemoteStoreFileTypeHandler.java b/src/main/java/zander/library/RemoteStoreFileTypeHandler.java new file mode 100644 index 0000000..6f79209 --- /dev/null +++ b/src/main/java/zander/library/RemoteStoreFileTypeHandler.java @@ -0,0 +1,71 @@ +package zander.library; + +import java.util.HashSet; +import java.util.Set; + +public class RemoteStoreFileTypeHandler { + + public static enum HandlerMode { + BINARY, + ASCII + }; + + public static final String[] INITIAL_FILTER = { "png", "jpg", "jpeg", "mp4", "mp3", "wav", "ogg", "mov", "bmp" }; + public static final HandlerMode INITIAL_MODE = HandlerMode.BINARY; + public static final boolean INITIAL_IS_EMPTY_BINARY = true; + + /** must be lower case */ + public Set filters = arrayToSet(INITIAL_FILTER); + public HandlerMode mode = INITIAL_MODE; + public boolean isEmptyBinary = INITIAL_IS_EMPTY_BINARY; + + public boolean isBinaryFile(String path) { + String e = getExtension(path).toLowerCase(); + if (e.length() == 0) { + return isEmptyBinary; + } + return filters.contains(e) && mode == HandlerMode.BINARY; + } + + public boolean isAsciiFile(String path) { + return !isBinaryFile(path); + } + + /** returns "" if file has no extension */ + private String getExtension(String path) { + String name = getName(path); + int i = name.lastIndexOf("."); + if (i == -1) { + return ""; + } + return name.substring(i + 1); + } + + private String getName(String path) { + String cp = cleanPath(path); + int i = cp.lastIndexOf("/"); + if (i == -1) { + return cp; + } + return cp.substring(i + 1); + } + + private String cleanPath(String path) { + if (path.startsWith("/")) { + path = path.substring(1); + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + private static Set arrayToSet(T[] arr) { + final Set set = new HashSet(); + for (T e : arr) { + set.add(e); + } + return set; + } + +} diff --git a/src/main/java/zander/library/media/AudioMediaData.java b/src/main/java/zander/library/media/AudioMediaData.java new file mode 100644 index 0000000..b543675 --- /dev/null +++ b/src/main/java/zander/library/media/AudioMediaData.java @@ -0,0 +1,90 @@ +package zander.library.media; + +import java.awt.Component; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.tika.Tika; + +import zander.library.FileUtils; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class AudioMediaData implements MediaData { + private static final SerializableImageContainer MUSIC_ICON_IMAGE = getMusicIcon(); + private static final Tika TIKA = new Tika(); + + private final AudioMediaViewer viewer; + private final String type; + + public AudioMediaData(File file) throws IOException { + this(file, readFileBytes(file)); + } + + public AudioMediaData(byte[] data) throws IOException { + this(FileUtils.createTempFile(data, true), data); + } + + public AudioMediaData(File file, byte[] data) { + this.viewer = new AudioMediaViewer(file); + type = TIKA.detect(data); + } + + @Override + public byte[] loadRawBytes() throws IOException { + return readFileBytes(getSourceFile()); + } + + @Override + public String getType() { + return type; + } + + @Override + public Component createThumbnailComponent() { + ImageMediaComponent thumbnail = new ImageMediaComponent(MUSIC_ICON_IMAGE); + thumbnail.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + return thumbnail; + } + + @Override + public Component getViewerComponent() { + return viewer; + } + + @Override + public File getSourceFile() { + return viewer.getSourceFile(); + } + + @Override + public void setSourceFile(File file) { + viewer.setSourceFile(file); + } + + @Override + public boolean hasSourceFile() { + return viewer.getSourceFile() != null; + } + + private static byte[] readFileBytes(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + byte[] buffer = in.readAllBytes(); + in.close(); + return buffer; + } + + private static SerializableImageContainer getMusicIcon() { + try { + BufferedImage img = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("music-icon.svg"), 1000, 1000); + return new SerializableImageContainer(img); + } catch (IOException e) { + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(1); + return null; + } + } +} diff --git a/src/main/java/zander/library/media/AudioMediaViewer.java b/src/main/java/zander/library/media/AudioMediaViewer.java new file mode 100644 index 0000000..b02ed20 --- /dev/null +++ b/src/main/java/zander/library/media/AudioMediaViewer.java @@ -0,0 +1,26 @@ +package zander.library.media; + +import java.awt.image.BufferedImage; +import java.io.File; + +import zander.ui.UIUtils; + +public class AudioMediaViewer extends VideoMediaViewer { + private static final long serialVersionUID = -9143966700980397199L; + private static final SerializableImageContainer COVER_IMAGE; + static { + BufferedImage cover = null; + try { + cover = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("music-icon.svg"), 1000, 1000); + } catch (Throwable e) { + System.err.println("Could not load internal resource."); + System.exit(0); + } + COVER_IMAGE = new SerializableImageContainer(cover); + } + + public AudioMediaViewer(File sourceFile) { + super(sourceFile, COVER_IMAGE); + } + +} diff --git a/src/main/java/zander/library/media/FFmpeg.java b/src/main/java/zander/library/media/FFmpeg.java new file mode 100644 index 0000000..46e9995 --- /dev/null +++ b/src/main/java/zander/library/media/FFmpeg.java @@ -0,0 +1,123 @@ +package zander.library.media; + +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.FileUtils; +import zander.ui.ProgressTracker.ProgressManager; + +public class FFmpeg { + private static final Logger LOGGER = LoggerFactory.getLogger(FFmpeg.class); + private static final String FFMPEG_PATH = "ffmpeg"; + + public static BufferedImage createThumbnail(byte[] indata, ProgressManager t) throws IOException { + File tmp = FileUtils.createTempFile(indata, true); + File to = FileUtils.createTempFile(true); + final String[] COMMAND = { FFMPEG_PATH, "-ss", "0:0:0", "-i", tmp.getPath(), "-frames:v", "1", "-y", + "-nostdin", "-f", "image2", to.getPath() }; + final String STRCMD = String.join(" ", COMMAND); + final ProcessBuilder pb = new ProcessBuilder(COMMAND); + LOGGER.info("Starting FFmpeg: '" + STRCMD + "'"); + if (t != null) { + t.setIndeterminate(true); + } + final Process p = pb.start(); + try { + if (t == null) { + p.waitFor(2L, TimeUnit.SECONDS); + } else { + while (!p.waitFor(10L, TimeUnit.MILLISECONDS)) { + if (t.isCanceled()) { + dumpError(p); + p.destroyForcibly(); + return null; + } + } + } + if (p.exitValue() != 0) { + dumpError(p); + return null; + } + } catch (InterruptedException e) { + p.destroyForcibly(); + dumpError(p); + throw new IOException("Could not wait on FFmpeg: '" + STRCMD + "'"); + }catch (IllegalThreadStateException e) { + p.destroyForcibly(); + dumpError(p); + throw new IOException("FFmpeg not exited: '" + STRCMD + "'"); + } + BufferedImage img = ImageIO.read(to); + to.delete(); + tmp.delete(); + // dumpError(p); + LOGGER.info("FFmpeg done!"); + if (t != null) { + t.setIndeterminate(false); + } + return img; + } + + public static byte[] convertMedia(byte[] indata, String outfmt, ProgressManager t) throws IOException { + File tmp = FileUtils.createTempFile(indata, true); + File to = FileUtils.createTempFile(true); + final String[] COMMAND = { FFMPEG_PATH, "-i", tmp.getPath(), "-vcodec", "copy", "-y", + "-nostdin", "-f", outfmt, to.getPath() }; + final String STRCMD = String.join(" ", COMMAND); + final ProcessBuilder pb = new ProcessBuilder(COMMAND); + LOGGER.info("Starting FFmpeg: '" + STRCMD + "'"); + t.setIndeterminate(true); + final Process p = pb.start(); + try { + while (!p.waitFor(10L, TimeUnit.MILLISECONDS)) { + if (t.isCanceled()) { + dumpError(p); + p.destroyForcibly(); + return null; + } + } + if (p.exitValue() != 0) { + dumpError(p); + return null; + } + } catch (InterruptedException e) { + dumpError(p); + p.destroyForcibly(); + throw new IOException("Could not wait on FFmpeg: '" + STRCMD + "'"); + }catch (IllegalThreadStateException e) { + p.destroyForcibly(); + dumpError(p); + throw new IOException("FFmpeg not exited: '" + STRCMD + "'"); + } + final FileInputStream in = new FileInputStream(to); + final byte[] outdata = in.readAllBytes(); + in.close(); + tmp.delete(); + to.delete(); + // dumpError(p); + LOGGER.info("FFmpeg done!"); + t.setIndeterminate(false); + return outdata; + } + + private static void dumpError(Process p) throws IOException { + InputStream s = p.getErrorStream(); + BufferedReader err = new BufferedReader(new InputStreamReader(s)); + String line; + while ((line = err.readLine()) != null) { + System.err.println("[ffmpeg - stderr] " + line); + } + err.close(); + } +} diff --git a/src/main/java/zander/library/media/ImageMediaComponent.java b/src/main/java/zander/library/media/ImageMediaComponent.java new file mode 100644 index 0000000..f874247 --- /dev/null +++ b/src/main/java/zander/library/media/ImageMediaComponent.java @@ -0,0 +1,42 @@ +package zander.library.media; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; + +public class ImageMediaComponent extends JComponent { + private static final long serialVersionUID = -471866227150575822L; + + private transient SerializableImageContainer image; + + public ImageMediaComponent(BufferedImage image) { + setOpaque(false); + this.image = new SerializableImageContainer(image); + } + + public ImageMediaComponent(SerializableImageContainer image) { + setOpaque(false); + this.image = image; + } + + public BufferedImage getImage() { + return image.getImage(); + } + + @Override + protected void paintComponent(Graphics g) { + BufferedImage image = this.image.getImage(); + int cw = getWidth(); + int ch = getHeight(); + int iw = image.getWidth(); + int ih = image.getHeight(); + float scale = Math.min((float) cw / iw, (float) ch / ih); + int fw = (int) Math.floor(scale * iw); + int fh = (int) Math.floor(scale * ih); + int fx = (cw / 2) - (fw / 2); + int fy = (ch / 2) - (fh / 2); + g.drawImage(image, fx, fy, fw, fh, null); + } + +} diff --git a/src/main/java/zander/library/media/ImageMediaData.java b/src/main/java/zander/library/media/ImageMediaData.java new file mode 100644 index 0000000..7d98fc8 --- /dev/null +++ b/src/main/java/zander/library/media/ImageMediaData.java @@ -0,0 +1,95 @@ +package zander.library.media; + +import java.awt.Component; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOError; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.apache.tika.Tika; + +import zander.library.FileUtils; +import zander.ui.UIUtils; + +public class ImageMediaData implements MediaData { + private static final Tika TIKA = new Tika(); + + private File sourceFile; + + private final SerializableImageContainer data; + private final ImageMediaComponent viewer; + private final String type; + + public ImageMediaData(File file) throws IOException { + this(file, readFileBytes(file)); + } + + public ImageMediaData(byte[] data) throws IOException { + this(FileUtils.createTempFile(data, true), data); + } + + public ImageMediaData(File file, byte[] data) { + try { + ByteArrayInputStream in = new ByteArrayInputStream(data); + BufferedImage img = UIUtils.fitImageToBox(ImageIO.read(in), UIUtils.IMAGE_PREVEW_SIZE); + this.data = new SerializableImageContainer(img); + if (!this.data.hasImage()) { + throw new IllegalArgumentException("Image data was invalid"); + } + viewer = new ImageMediaComponent(this.data.getImage()); + type = TIKA.detect(data); + this.sourceFile = file; + } catch (IOException e) { + // Should not happen + throw new IOError(e); + } + } + + @Override + public byte[] loadRawBytes() throws IOException { + return readFileBytes(sourceFile); + } + + @Override + public String getType() { + return type; + } + + @Override + public Component createThumbnailComponent() { + ImageMediaComponent thumbnail = new ImageMediaComponent(this.data.getImage()); + thumbnail.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + return thumbnail; + } + + @Override + public Component getViewerComponent() { + return viewer; + } + + @Override + public File getSourceFile() { + return sourceFile; + } + + @Override + public void setSourceFile(File file) { + this.sourceFile = file; + } + + @Override + public boolean hasSourceFile() { + return sourceFile != null; + } + + private static byte[] readFileBytes(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + byte[] buffer = in.readAllBytes(); + in.close(); + return buffer; + } +} diff --git a/src/main/java/zander/library/media/MediaData.java b/src/main/java/zander/library/media/MediaData.java new file mode 100644 index 0000000..5168508 --- /dev/null +++ b/src/main/java/zander/library/media/MediaData.java @@ -0,0 +1,23 @@ +package zander.library.media; + +import java.awt.Component; +import java.io.File; +import java.io.IOException; + +public interface MediaData { + + public byte[] loadRawBytes() throws IOException; + + public String getType(); + + public Component createThumbnailComponent(); + + public Component getViewerComponent(); + + public File getSourceFile(); + + public void setSourceFile(File file); + + public boolean hasSourceFile(); + +} diff --git a/src/main/java/zander/library/media/MediaLoader.java b/src/main/java/zander/library/media/MediaLoader.java new file mode 100644 index 0000000..7b917a3 --- /dev/null +++ b/src/main/java/zander/library/media/MediaLoader.java @@ -0,0 +1,71 @@ +package zander.library.media; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.tika.Tika; + +import zander.library.FileUtils; +import zander.ui.ProgressTracker.ProgressManager; + +public class MediaLoader { + private static final Tika TIKA = new Tika(); + + private static enum ContentType { + VIDEO, + AUDIO, + IMAGE, + UNKNOWN + } + + public static MediaData loadFile(File file, boolean moveToTemp, ProgressManager t) throws IOException { + FileInputStream in = new FileInputStream(file); + byte[] data = in.readAllBytes(); + in.close(); + return loadBytes(file.getCanonicalFile(), data, moveToTemp, t); + } + + public static MediaData loadBytes(File file, byte[] data, boolean moveToTemp, ProgressManager t) throws IOException { + MediaData md = null; + if (moveToTemp) { + file = FileUtils.createTempFile(data, true); + } + switch (getFileType(data)) { + case IMAGE: + md = new ImageMediaData(file, data); + break; + case VIDEO: + md = new VideoMediaData(file, data, t); + break; + case AUDIO: + md = new AudioMediaData(file, data); + break; + default: + md = new UnknownMediaData(file, data); + break; + } + return md; + } + + private static ContentType getFileType(byte[] data) { + try { + ByteArrayInputStream in = new ByteArrayInputStream(data); + String type = TIKA.detect(in); + if (type == null) { + return ContentType.UNKNOWN; + } else if (type.startsWith("image/")) { + return ContentType.IMAGE; + } else if (type.startsWith("video/")) { + return ContentType.VIDEO; + } else if (type.startsWith("audio/")) { + return ContentType.AUDIO; + } else { + return ContentType.UNKNOWN; + } + } catch (IOException e) { + return ContentType.UNKNOWN; + } + } +} diff --git a/src/main/java/zander/library/media/SerializableImageContainer.java b/src/main/java/zander/library/media/SerializableImageContainer.java new file mode 100644 index 0000000..f13a9c2 --- /dev/null +++ b/src/main/java/zander/library/media/SerializableImageContainer.java @@ -0,0 +1,51 @@ +package zander.library.media; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import javax.imageio.ImageIO; + +public class SerializableImageContainer implements Serializable { + private static final long serialVersionUID = 2606473417847123614L; + + private transient BufferedImage image; + + public SerializableImageContainer(BufferedImage image) { + this.image = image; + } + + public BufferedImage getImage() { + return image; + } + + public void setImage(BufferedImage image) { + this.image = image; + } + + public boolean hasImage() { + return image != null; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (image == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + ImageIO.write(image, "png", out); + } + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + if (in.readBoolean()) { + image = ImageIO.read(in); + } else { + image = null; + } + } + +} diff --git a/src/main/java/zander/library/media/UnknownMediaData.java b/src/main/java/zander/library/media/UnknownMediaData.java new file mode 100644 index 0000000..f56feee --- /dev/null +++ b/src/main/java/zander/library/media/UnknownMediaData.java @@ -0,0 +1,98 @@ +package zander.library.media; + +import java.awt.Component; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import javax.swing.SwingUtilities; + +import org.apache.tika.Tika; + +import zander.library.FileUtils; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class UnknownMediaData implements MediaData { + private static final Tika TIKA = new Tika(); + + private final SerializableImageContainer errorImage; + private final ImageMediaComponent viewer; + private final String type; + + private File sourceFile; + + public UnknownMediaData(File file) throws IOException { + this(file, readFileBytes(file)); + } + + public UnknownMediaData(byte[] data) throws IOException { + this(FileUtils.createTempFile(data, true), data); + } + + public UnknownMediaData(File file, byte[] data) { + errorImage = new SerializableImageContainer(getErrorIcon()); + viewer = new ImageMediaComponent(errorImage.getImage()); + type = TIKA.detect(data); + sourceFile = file; + } + + @Override + public byte[] loadRawBytes() throws IOException { + return readFileBytes(sourceFile); + } + + @Override + public String getType() { + return type; + } + + @Override + public Component createThumbnailComponent() { + ImageMediaComponent c = new ImageMediaComponent(errorImage.getImage()); + c.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + return c; + } + + @Override + public Component getViewerComponent() { + return viewer; + } + + @Override + public File getSourceFile() { + return sourceFile; + } + + @Override + public void setSourceFile(File file) { + this.sourceFile = file; + } + + @Override + public boolean hasSourceFile() { + return sourceFile != null; + } + + private static byte[] readFileBytes(File file) throws IOException{ + try (FileInputStream in = new FileInputStream(file)) { + return in.readAllBytes(); + } + } + + private static BufferedImage getErrorIcon() { + try { + BufferedImage i = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("missing-image-icon.svg"), -1, -1); + return i; + } catch (IOException e) { + SwingUtilities.invokeLater(() -> { + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal internal error has occured!", e); + ed.setVisible(true); + }); + System.err.println("Could not load missing image icon!"); + System.exit(1); + return null; + } + } +} diff --git a/src/main/java/zander/library/media/VideoMediaData.java b/src/main/java/zander/library/media/VideoMediaData.java new file mode 100644 index 0000000..337a37d --- /dev/null +++ b/src/main/java/zander/library/media/VideoMediaData.java @@ -0,0 +1,117 @@ +package zander.library.media; + +import java.awt.Component; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import javax.swing.SwingUtilities; + +import org.apache.tika.Tika; + +import zander.library.FileUtils; +import zander.ui.ErrorDialog; +import zander.ui.ProgressTracker.ProgressManager; +import zander.ui.UIUtils; + +public class VideoMediaData implements MediaData { + private static final Tika TIKA = new Tika(); + private static final SerializableImageContainer ERROR_ICON = getErrorIcon(); + + private SerializableImageContainer thumbnail; + private final VideoMediaViewer viewer; + private final String type; + + public VideoMediaData(File file, ProgressManager t) throws IOException { + this(readAllBytes(file), t); + } + + public VideoMediaData(byte[] data, ProgressManager t) throws IOException { + this(FileUtils.createTempFile(data, true), data, t); + } + + public VideoMediaData(File file, byte[] data, ProgressManager t) { + try { + BufferedImage i = getThumbnailFrame(data, t); + if (i != null) { + this.thumbnail = new SerializableImageContainer(i); + } else { + this.thumbnail = ERROR_ICON; + } + } catch (Throwable e) { + e.printStackTrace(); + this.thumbnail = ERROR_ICON; + } + viewer = new VideoMediaViewer(file, this.thumbnail); + type = TIKA.detect(data); + } + + public SerializableImageContainer getThumbnailFrame() { + return thumbnail; + } + + @Override + public byte[] loadRawBytes() throws IOException { + return readAllBytes(getSourceFile()); + } + + @Override + public String getType() { + return type; + } + + @Override + public Component createThumbnailComponent() { + ImageMediaComponent thumbnail = new ImageMediaComponent(this.thumbnail); + thumbnail.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + return thumbnail; + } + + @Override + public Component getViewerComponent() { + return viewer; + } + + @Override + public File getSourceFile() { + return viewer.getSourceFile(); + } + + @Override + public void setSourceFile(File file) { + viewer.setSourceFile(file); + } + + @Override + public boolean hasSourceFile() { + return viewer.getSourceFile() != null; + } + + private static byte[] readAllBytes(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + byte[] b = in.readAllBytes(); + in.close(); + return b; + } + + private static BufferedImage getThumbnailFrame(byte[] data, ProgressManager t) throws IOException { + BufferedImage img = FFmpeg.createThumbnail(data, t); + return UIUtils.fitImageToBox(img, UIUtils.IMAGE_PREVEW_SIZE); + } + + private static SerializableImageContainer getErrorIcon() { + try { + BufferedImage i = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("video-icon.png"), -1, -1); + return new SerializableImageContainer(i); + } catch (IOException e) { + SwingUtilities.invokeLater(() -> { + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal internal error has occured!", e); + ed.setVisible(true); + }); + System.err.println("Could not load missing image icon!"); + System.exit(1); + return null; + } + } +} diff --git a/src/main/java/zander/library/media/VideoMediaViewer.java b/src/main/java/zander/library/media/VideoMediaViewer.java new file mode 100644 index 0000000..d25cadc --- /dev/null +++ b/src/main/java/zander/library/media/VideoMediaViewer.java @@ -0,0 +1,92 @@ +package zander.library.media; + +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Graphics; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; + +import javax.swing.SwingUtilities; + +import zander.ui.ErrorDialog; + +public class VideoMediaViewer extends ImageMediaComponent { + private static final long serialVersionUID = 2913980500748591535L; + + private final MouseAdapter action = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Thread t = new Thread(() -> open()); + t.start(); + } + }; + private final Desktop desktop; + + private File sourceFile; + + public VideoMediaViewer(File sourceFile, SerializableImageContainer thumb) { + super(thumb); + if (Desktop.isDesktopSupported()) { + desktop = Desktop.getDesktop(); + addMouseListener(action); + } else { + desktop = null; + } + this.sourceFile = sourceFile; + } + + @Override + public void setEnabled(boolean enabled) { + boolean old = isEnabled(); + super.setEnabled(enabled); + if (!old && enabled) { + addMouseListener(action); + } else if (!enabled) { + removeMouseListener(action); + } + } + + public void setSourceFile(File file) { + this.sourceFile = file; + } + + public File getSourceFile() { + return sourceFile; + } + + public void open() { + try { + desktop.open(sourceFile); + } catch (IOException e) { + SwingUtilities.invokeLater(() -> { + ErrorDialog diag = new ErrorDialog("Open Error", "Could not open video!", e); + diag.setVisible(true); + }); + } + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (isEnabled()) { + int cw = getWidth(); + int ch = getHeight(); + int size = Math.round(Math.max(cw, ch) * 0.1f); + int cx = cw / 2; + int cy = ch / 2; + int x1 = cx - (size / 2); + int y1 = cy - (size / 2); + int x2 = cx - (size / 2); + int y2 = cy + (size / 2); + int x3 = cx + (size / 2); + int y3 = cy; + int[] xv = { x1, x2, x3 }; + int[] yv = { y1, y2, y3 }; + g.setColor(new Color(255, 255, 255, (2 * 255 / 3) /* 75% */)); + g.fillPolygon(xv, yv, 3); + } + } + +} diff --git a/src/main/java/zander/library/metadata/MediaMetadata.java b/src/main/java/zander/library/metadata/MediaMetadata.java new file mode 100644 index 0000000..9f59d62 --- /dev/null +++ b/src/main/java/zander/library/metadata/MediaMetadata.java @@ -0,0 +1,78 @@ +package zander.library.metadata; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalTime; + +public class MediaMetadata implements Serializable { + private static final long serialVersionUID = 7970810201152417800L; + + private String description; + private LocalDate date; + private LocalTime time; + private Double latitude; + private Double longitude; + private long size; + + public MediaMetadata() { + this(null, null, null, null, null, 0); + } + + public MediaMetadata(String description, LocalDate date, LocalTime time, Double latitude, Double longitude, long size) { + this.description = description; + this.date = date; + this.time = time; + this.latitude = latitude; + this.longitude = longitude; + this.size = size; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + + public Double getLatitude() { + return latitude; + } + + public void setLatitude(Double latitude) { + this.latitude = latitude; + } + + public Double getLongitude() { + return longitude; + } + + public void setLongitude(Double longitude) { + this.longitude = longitude; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + +} diff --git a/src/main/java/zander/library/metadata/MetadataLoader.java b/src/main/java/zander/library/metadata/MetadataLoader.java new file mode 100644 index 0000000..1427592 --- /dev/null +++ b/src/main/java/zander/library/metadata/MetadataLoader.java @@ -0,0 +1,127 @@ +package zander.library.metadata; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Collection; +import java.util.Date; + +import com.drew.imaging.FileType; +import com.drew.imaging.FileTypeDetector; +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.lang.GeoLocation; +import com.drew.metadata.Directory; +import com.drew.metadata.Metadata; +import com.drew.metadata.exif.ExifIFD0Directory; +import com.drew.metadata.exif.ExifSubIFDDirectory; +import com.drew.metadata.exif.GpsDirectory; +import com.drew.metadata.iptc.IptcDirectory; +import com.drew.metadata.mov.metadata.QuickTimeMetadataDirectory; + +import zander.library.state.DateTimeUtils; + +public class MetadataLoader { + + public static MediaMetadata fromBytes(byte[] rawData) { + MediaMetadata md = new MediaMetadata(null, null, null, null, null, rawData.length); + FileType ft = null; + Metadata rm = null; + try { + ByteArrayInputStream bin = new ByteArrayInputStream(rawData); + BufferedInputStream bs = new BufferedInputStream(bin); + ft = FileTypeDetector.detectFileType(bs); + bin.reset(); + rm = ImageMetadataReader.readMetadata(bin); + } catch (IOException | ImageProcessingException e) { + return md; + } + switch (ft) { + case Png: + fillPngMetadata(md, rm); + break; + case Jpeg: + fillJpegMetadata(md, rm); + break; + case Tiff: + fillTiffMetadata(md, rm); + break; + case Mp4: + fillMp4Metadata(md, rm); + break; + case QuickTime: + fillQuickTimeMetadata(md, rm); + break; + default: + // Just return the empty object + break; + } + return md; + } + + private static void fillPngMetadata(MediaMetadata md, Metadata rm) { + fillDateTime(md, rm, ExifIFD0Directory.class, ExifIFD0Directory.TAG_DATETIME); + fillGeoLocation(md, rm); + } + + private static void fillJpegMetadata(MediaMetadata md, Metadata rm) { + fillDescription(md, rm, IptcDirectory.class, IptcDirectory.TAG_CAPTION); + fillDateTime(md, rm, ExifIFD0Directory.class, ExifIFD0Directory.TAG_DATETIME); + fillGeoLocation(md, rm); + } + + private static void fillTiffMetadata(MediaMetadata md, Metadata rm) { + fillDescription(md, rm, IptcDirectory.class, IptcDirectory.TAG_CAPTION); + fillDateTime(md, rm, ExifSubIFDDirectory.class, ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL); + fillGeoLocation(md, rm); + } + + private static void fillMp4Metadata(MediaMetadata md, Metadata rm) { + fillGeoLocation(md, rm); + } + + private static void fillQuickTimeMetadata(MediaMetadata md, Metadata rm) { + fillDescription(md, rm, QuickTimeMetadataDirectory.class, QuickTimeMetadataDirectory.TAG_DESCRIPTION); + fillDateTime(md, rm, QuickTimeMetadataDirectory.class, QuickTimeMetadataDirectory.TAG_CREATION_DATE); + fillGeoLocation(md, rm); + } + + private static void fillDescription(MediaMetadata md, Metadata rm, Class dc, int tag) { + final Collection dirs = rm.getDirectoriesOfType(dc); + for (Directory d : dirs) { + String desc = d.getString(tag); + if (desc != null) { + md.setDescription(desc); + break; + } + } + } + + private static void fillDateTime(MediaMetadata md, Metadata rm, Class dc, int tag) { + final Collection dirs = rm.getDirectoriesOfType(dc); + for (Directory d : dirs) { + Date date = d.getDate(tag); + if (date != null) { + LocalDate ld = DateTimeUtils.dateToLocalDate(date); + LocalTime lt = DateTimeUtils.dateToLocalTime(date); + md.setDate(ld); + md.setTime(lt); + break; + } + } + } + + private static void fillGeoLocation(MediaMetadata md, Metadata rm) { + final Collection gpsDirs = rm.getDirectoriesOfType(GpsDirectory.class); + for (GpsDirectory d : gpsDirs) { + GeoLocation loc = d.getGeoLocation(); + if (loc != null && !loc.isZero()) { + md.setLatitude(loc.getLatitude()); + md.setLongitude(loc.getLongitude()); + break; + } + } + } +} diff --git a/src/main/java/zander/library/secrets/PassSecrets.java b/src/main/java/zander/library/secrets/PassSecrets.java new file mode 100644 index 0000000..bdc43d4 --- /dev/null +++ b/src/main/java/zander/library/secrets/PassSecrets.java @@ -0,0 +1,237 @@ +package zander.library.secrets; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PassSecrets implements Secrets { + + private String name; + private HashMap keys; + + public PassSecrets(String name) { + this.name = name; + checkPassInstalledAndConfigured(); + addEntryIfAbsent(); + } + + private Process runPass(String ...args) { + String[] cmd = new String[args.length + 1]; + cmd[0] = "pass"; + System.arraycopy(args, 0, cmd, 1, args.length); + try { + return Runtime.getRuntime().exec(cmd); + } catch (IOException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + private void checkPassInstalledAndConfigured() { + try { + Process p = runPass(); + if (p.waitFor() != 0) { + String err = new String(p.getErrorStream().readAllBytes()); + throw new RuntimeException("Pass exited with code '" + p.exitValue() + "': " + err); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + private void addEntry() { + try { + Process p = runPass("insert", name); + OutputStream out = p.getOutputStream(); + out.write('\n'); + out.flush(); + out.write('\n'); + out.close(); + p.waitFor(); + try { + if (p.waitFor() != 0) { + throw new RuntimeException("Pass failed!"); + } + } catch (InterruptedException e) { + throw new RuntimeException("Could not wait for pass", e); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + private void addEntryIfAbsent() { + try { + Process p = runPass(name); + if (p.waitFor() != 0) { + addEntry(); + } + } catch (InterruptedException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + private void writeKeys() { + try { + Process p = runPass("insert", "-fm", name); + OutputStream out = p.getOutputStream(); + for (Map.Entry e : keys.entrySet()) { + if (e.getKey().length() == 0) { + out.write(e.getValue().getBytes()); + } else { + out.write((e.getKey() + ":" + e.getValue()).getBytes()); + } + out.write('\n'); + } + out.close(); + try { + if (p.waitFor() != 0) { + throw new RuntimeException("Pass failed!"); + } + } catch (InterruptedException e) { + throw new RuntimeException("Could not wait for pass", e); + } + } catch (IOException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + public String getPassEntryName() { + return name; + } + + @Override + public void refresh() { + try { + Process p = runPass(name); + if (p.waitFor() != 0) { + addEntry(); + } + InputStream in = p.getInputStream(); + String data = new String(in.readAllBytes()); + String[] lines = data.split("\n"); + HashMap map = new HashMap(); + if (lines.length == 0) { + map.put("", ""); + } else { + map.put("", lines[0]); + } + for (int i = 1; i < lines.length; ++i) { + Matcher m = Pattern.compile(":").matcher(lines[i]); + if (!m.find()) { + continue; + } + String key = lines[i].substring(0, m.start()); + if (m.start() + 1 == lines[i].length()) { + continue; + } + String value = lines[i].substring(m.start() + 1); + map.put(key, value); + } + keys = map; + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + private void refreshIfNeeded() { + if (keys == null) { + refresh(); + } + } + + @Override + public Map getKeys() { + refreshIfNeeded(); + return keys; + } + + @Override + public void setAll(Map keys) { + for (Map.Entry key : keys.entrySet()) { + set(key.getKey(), key.getValue()); + } + } + + @Override + public void clear() { + Map keys = getKeys(); + for (Map.Entry key : keys.entrySet()) { + remove(key.getKey()); + } + } + + @Override + public String getPassword() { + return get(""); + } + + @Override + public void setPassword(String password) { + set("", password); + } + + @Override + public String getUsername() { + return get("username"); + } + + @Override + public void setUsername(String username) { + set("username", username); + } + + @Override + public String get(String key) { + Map keys = getKeys(); + return keys.get(key); + } + + @Override + public void set(String key, String value) { + refreshIfNeeded(); + keys.put(key, value); + writeKeys(); + } + + @Override + public String remove(String key) { + refreshIfNeeded(); + String value = keys.remove(key); + writeKeys(); + return value; + } + + @Override + public boolean contains(String key) { + refreshIfNeeded(); + return keys.containsKey(key); + } + + @Override + public void delete() { + Process p = runPass("rm", "-f", name); + try { + if (p.waitFor() != 0) { + String err = new String(p.getErrorStream().readAllBytes()); + throw new RuntimeException("Pass exited with code '" + p.exitValue() + "': " + err); + } + } catch (InterruptedException | IOException e) { + throw new RuntimeException("Could not execute pass", e); + } + } + + @Override + public String getBackend() { + return "Pass"; + } + + @Override + public boolean isEncrypted() { + return true; + } + +} diff --git a/src/main/java/zander/library/secrets/PassSecretsFactory.java b/src/main/java/zander/library/secrets/PassSecretsFactory.java new file mode 100644 index 0000000..39180c2 --- /dev/null +++ b/src/main/java/zander/library/secrets/PassSecretsFactory.java @@ -0,0 +1,46 @@ +package zander.library.secrets; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class PassSecretsFactory extends SecretsFactory { + private static final long serialVersionUID = -2401450268365168782L; + + @Override + public boolean isEncrypted() { + return true; + } + + @Override + public boolean isRecommended() { + return true; + } + + @Override + public String getBackendName() { + return "Pass"; + } + + @Override + public Secrets createSecrets(String key) { + return new PassSecrets(key); + } + + @Override + public boolean isSupported() { + return isBackendSupported(); + } + + public static boolean isBackendSupported() { + try { + Process p = Runtime.getRuntime().exec("which pass"); + if (!p.waitFor(1000, TimeUnit.MILLISECONDS)) { + return false; + } + return p.waitFor() == 0; + } catch (IOException | InterruptedException e) { + return false; + } + } + +} diff --git a/src/main/java/zander/library/secrets/PreferencesSecrets.java b/src/main/java/zander/library/secrets/PreferencesSecrets.java new file mode 100644 index 0000000..6da1da0 --- /dev/null +++ b/src/main/java/zander/library/secrets/PreferencesSecrets.java @@ -0,0 +1,135 @@ +package zander.library.secrets; + +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import zander.ui.UIUtils; + +public class PreferencesSecrets implements Secrets { + + private String path; + private Preferences prefs; + + public PreferencesSecrets(String path) { + this.path = path; + this.prefs = UIUtils.PREFS_ROOT.node(path); + } + + public String getPath() { + return path; + } + + @Override + public void refresh() { + // Do nothing + } + + @Override + public Map getKeys() { + checkState(); + final Map keys = new HashMap(); + try { + for (String key : prefs.keys()) { + keys.put(key, prefs.get(key, null)); + } + } catch (BackingStoreException e) { + throw new RuntimeException("Could not access preferences", e); + } + return keys; + } + + @Override + public void setAll(Map keys) { + checkState(); + for (Map.Entry key : keys.entrySet()) { + prefs.put(key.getKey(), key.getValue()); + } + } + + @Override + public void clear() { + checkState(); + try { + prefs.clear();; + } catch(BackingStoreException e) { + throw new RuntimeException("Could not access preferences", e); + } + } + + private void checkState() { + try { + if (!prefs.nodeExists("")) { + prefs = Preferences.userRoot().node(path); + } + } catch (BackingStoreException e) { + throw new RuntimeException("Could not acces preferences", e); + } + } + + @Override + public String getPassword() { + return get(""); + } + + @Override + public void setPassword(String password) { + set("", password); + } + + @Override + public String getUsername() { + return get("username"); + } + + @Override + public void setUsername(String username) { + set("username", username); + } + + @Override + public String get(String key) { + checkState(); + return prefs.get(key, null); + } + + @Override + public void set(String key, String value) { + checkState(); + prefs.put(key, value); + } + + @Override + public String remove(String key) { + checkState(); + String val = prefs.get(key, null); + prefs.remove(key); + return val; + } + + @Override + public boolean contains(String key) { + return get(key) != null; + } + + @Override + public void delete() { + try { + prefs.removeNode(); + } catch (BackingStoreException e) { + throw new RuntimeException("Could not access preferences", e); + } + } + + @Override + public String getBackend() { + return "Java Preferences API"; + } + + @Override + public boolean isEncrypted() { + return false; + } + +} diff --git a/src/main/java/zander/library/secrets/PreferencesSecretsFactory.java b/src/main/java/zander/library/secrets/PreferencesSecretsFactory.java new file mode 100644 index 0000000..bf3478f --- /dev/null +++ b/src/main/java/zander/library/secrets/PreferencesSecretsFactory.java @@ -0,0 +1,31 @@ +package zander.library.secrets; + +public class PreferencesSecretsFactory extends SecretsFactory { + private static final long serialVersionUID = 1L; + + @Override + public boolean isEncrypted() { + return false; + } + + @Override + public boolean isRecommended() { + return false; + } + + @Override + public String getBackendName() { + return "Java Preferences API"; + } + + @Override + public Secrets createSecrets(String key) { + return new PreferencesSecrets(key); + } + + @Override + public boolean isSupported() { + return true; + } + +} diff --git a/src/main/java/zander/library/secrets/Secrets.java b/src/main/java/zander/library/secrets/Secrets.java new file mode 100644 index 0000000..0eba28c --- /dev/null +++ b/src/main/java/zander/library/secrets/Secrets.java @@ -0,0 +1,37 @@ +package zander.library.secrets; + +import java.util.Map; + +public interface Secrets { + + public void refresh(); + + public Map getKeys(); + + public void setAll(Map keys); + + public void clear(); + + public String getPassword(); + + public void setPassword(String password); + + public String getUsername(); + + public void setUsername(String username); + + public String get(String key); + + public void set(String key, String value); + + public String remove(String key); + + public boolean contains(String key); + + public void delete(); + + public String getBackend(); + + public boolean isEncrypted(); + +} diff --git a/src/main/java/zander/library/secrets/SecretsFactory.java b/src/main/java/zander/library/secrets/SecretsFactory.java new file mode 100644 index 0000000..36a8651 --- /dev/null +++ b/src/main/java/zander/library/secrets/SecretsFactory.java @@ -0,0 +1,107 @@ +package zander.library.secrets; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public abstract class SecretsFactory implements Serializable { + private static final long serialVersionUID = 3438101849590929517L; + + public abstract boolean isEncrypted(); + + public abstract boolean isRecommended(); + + public abstract String getBackendName(); + + public abstract Secrets createSecrets(String key); + + public abstract boolean isSupported(); + + @Override + public boolean equals(Object o) { + if (o instanceof SecretsFactory) { + return ((SecretsFactory) o).getBackendName().equals(getBackendName()); + } + return false; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getBackendName()); + if (isRecommended()) { + sb.append(" (Recommended)"); + } else if (!isEncrypted()) { + sb.append(" (Unencrypted, Not Recommended)"); + } + return sb.toString(); + } + + private static final SecretsFactory DEFAULT_FACTORY = new DefaultSecretsFactory(); + private static Set FACTORIES = Collections.unmodifiableSet(new HashSet()); + + public static void registerFactory(SecretsFactory c) { + final HashSet sf = new HashSet(FACTORIES); + sf.add(c); + FACTORIES = Collections.unmodifiableSet(sf); + } + + public static Set getFactories() { + return FACTORIES; + } + + + public static SecretsFactory getDefaultFactory() { + return DEFAULT_FACTORY; + } + + private static final class DefaultSecretsFactory extends SecretsFactory { + private static final long serialVersionUID = 8071227730925085343L; + + private SecretsFactory factory; + + public DefaultSecretsFactory() { + factory = getFactory(); + } + + private SecretsFactory getFactory() { + if (PassSecretsFactory.isBackendSupported()) { + return new PassSecretsFactory(); + } else { + return new PreferencesSecretsFactory(); + } + } + + @Override + public boolean isEncrypted() { + return factory.isEncrypted(); + } + + @Override + public boolean isRecommended() { + return factory.isRecommended(); + } + + @Override + public String getBackendName() { + return "Default"; + } + + @Override + public Secrets createSecrets(String key) { + return factory.createSecrets(key); + } + + @Override + public boolean isSupported() { + return factory != null && factory.isSupported(); + } + + @Override + public String toString() { + return "Auto (" + factory.getBackendName() + ")"; + } + + } + +} diff --git a/src/main/java/zander/library/state/AlbumConfig.java b/src/main/java/zander/library/state/AlbumConfig.java new file mode 100644 index 0000000..47bcf8e --- /dev/null +++ b/src/main/java/zander/library/state/AlbumConfig.java @@ -0,0 +1,232 @@ +package zander.library.state; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class AlbumConfig implements Serializable { + private static final long serialVersionUID = -5741698724083700184L; + + public static class AlbumImage implements Serializable { + private static final long serialVersionUID = 2580196202496621048L; + + public String name; + public String description; + public String date; + public String time; + public Number latitude; + public Number longitude; + + @SuppressWarnings("unchecked") + public JSONObject toJsonObject() { + JSONObject r = new JSONObject(); + if (description != null) { + r.put("description", description); + } + if (date != null) { + r.put("date", date); + } + if (time != null) { + r.put("time", time); + } + if (latitude != null) { + r.put("latitude", String.valueOf(latitude)); + } + if (longitude != null) { + r.put("longitude", String.valueOf(longitude)); + } + return r; + } + + public boolean hasDescription() { + return description != null; + } + + public boolean hasDate() { + return date != null; + } + + public boolean hasTime() { + return time != null; + } + + public boolean hasLatitude() { + return latitude != null; + } + + public boolean hasLongitude() { + return longitude != null; + } + + public boolean hasLocation() { + return hasLatitude() && hasLongitude(); + } + + public boolean isEmpty() { + return description == null && date == null && time == null && latitude == null && longitude == null; + } + } + + private String title; + private boolean nameFollowsTitle; + private String thumbnail; + private List images; + + public AlbumConfig() { + this("", false, "", new ArrayList()); + } + + public AlbumConfig(String title, boolean nameFollowsTitle, String thumbnail, List images) { + this.title = title; + this.nameFollowsTitle = nameFollowsTitle; + this.thumbnail = thumbnail; + this.images = images; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean doesNameFollowTitle() { + return nameFollowsTitle; + } + + public void setNameFollowsTitle(boolean nameFollowsTitle) { + this.nameFollowsTitle = nameFollowsTitle; + } + + public String getThumnail() { + return thumbnail; + } + + public void setThumbnail(String thumbnail) { + this.thumbnail = thumbnail; + } + + public List getImages() { + return images; + } + + public void setImages(List images) { + this.images = images; + } + + public static AlbumConfig parseJson(String json) throws ParseException { + JSONParser p = new JSONParser(); + Object o = p.parse(json); + if (!(o instanceof JSONObject)) { + throw new ClassCastException("Json root was not an object"); + } + return parseJson((JSONObject) o); + } + + public static AlbumConfig parseJson(JSONObject json) { + Object titleObj = json.get("title"); + Object nameFollowsObj = json.get("nameFollowsTitle"); + Object thumbnailObj = json.get("thumbnail"); + Object imagesObj = json.get("images"); + if (!(titleObj instanceof String) || !(thumbnailObj instanceof String)) { + throw new ClassCastException("Title and thumbnail must be strings"); + } else if (!(imagesObj instanceof JSONArray)) { + throw new ClassCastException("Images must be an array"); + } else if (!(nameFollowsObj instanceof Boolean)) { + throw new ClassCastException("nameFollowsTitle must be a boolean"); + } + JSONArray imagesArr = (JSONArray) imagesObj; + final ArrayList images = new ArrayList(imagesArr.size()); + for (Object o : imagesArr) { + if (!(o instanceof String)) { + throw new ClassCastException("All members of images must be a string"); + } + images.add(getImage(json, (String) o)); + } + return new AlbumConfig((String) titleObj, (Boolean) nameFollowsObj, (String) thumbnailObj, images); + } + + private static AlbumImage getImage(JSONObject root, String name) { + AlbumImage i = new AlbumImage(); + i.name = name; + Object mtdaObj = root.get("metadata"); + if (mtdaObj instanceof JSONObject) { + JSONObject mtda = (JSONObject) mtdaObj; + Object imgObj = mtda.get(name); + if (imgObj instanceof JSONObject) { + JSONObject img = (JSONObject) imgObj; + Object descObj = img.get("description"); + if (descObj instanceof String) { + i.description = (String) descObj; + } else { + i.description = null; + } + Object dateObj = img.get("date"); + if (dateObj instanceof String) { + i.date = (String) dateObj; + } else { + i.date = null; + } + Object timeObj = img.get("time"); + if (timeObj instanceof String) { + i.time = (String) timeObj; + } else { + i.time = null; + } + Object latObj = img.get("latitude"); + if (latObj instanceof String) { + i.latitude = parseNumber((String) latObj); + } else { + i.latitude = null; + } + Object lonObj = img.get("longitude"); + if (lonObj instanceof String) { + i.longitude = parseNumber((String) lonObj); + } else { + i.longitude = null; + } + } + } + return i; + } + + private static Number parseNumber(String str) { + try { + return Double.parseDouble(str); + } catch (NumberFormatException | NullPointerException e) { + return null; + } + } + + @SuppressWarnings("unchecked") + public static JSONObject toJson(AlbumConfig lc) { + JSONObject root = new JSONObject(); + root.put("title", lc.getTitle()); + root.put("nameFollowsTitle", lc.doesNameFollowTitle()); + root.put("thumbnail", lc.getThumnail()); + JSONArray images = new JSONArray(); + JSONObject metadata = new JSONObject(); + for (AlbumImage i : lc.images) { + images.add(i.name); + if (!i.isEmpty()) { + metadata.put(i.name, i.toJsonObject()); + } + } + root.put("images", images); + if (!metadata.isEmpty()) { + root.put("metadata", metadata); + } + return root; + } + + public static String toJsonString(AlbumConfig lc) { + return toJson(lc).toJSONString(); + } + +} diff --git a/src/main/java/zander/library/state/AlbumState.java b/src/main/java/zander/library/state/AlbumState.java new file mode 100644 index 0000000..99048eb --- /dev/null +++ b/src/main/java/zander/library/state/AlbumState.java @@ -0,0 +1,134 @@ +package zander.library.state; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONObject; + +import zander.library.state.AlbumConfig.AlbumImage; + +public class AlbumState implements Serializable { + private static final long serialVersionUID = -5291546798543244207L; + + private String nameOnDisk; + private String name; + private String title; + private boolean nameFollowsTitle; + private String thumbnail; + private List media; + + private final String originalName; + private final String originalTitle; + private final String originalThumbnail; + private final List originalMedia; + + public AlbumState(String name, String title, boolean nameFollowsTitle, String thumbnail, List media) { + this.nameOnDisk = name; + this.name = name; + this.title = title; + this.nameFollowsTitle = nameFollowsTitle; + this.thumbnail = thumbnail; + this.media = new ArrayList(media); + + originalName = name; + originalTitle = title; + originalThumbnail = thumbnail; + originalMedia = new ArrayList(); + for (MediaState s : media) { + if (s.getNameOnDisk() != null) { + originalMedia.add(s.getNameOnDisk()); + } + } + } + + public List getMedia() { + return media; + } + + public void setMedia(List media) { + this.media = media; + } + + public String getThumbnailName() { + return thumbnail; + } + + public void setThumbnailName(String thumbnail) { + this.thumbnail = thumbnail; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean doesNameFollowTitle() { + return nameFollowsTitle; + } + + public void setNameFollowsTitle(boolean nameFollowsTitle) { + this.nameFollowsTitle = nameFollowsTitle; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNameOnDisk() { + return nameOnDisk; + } + + public void setNameOnDisk(String nameOnDisk) { + this.nameOnDisk = nameOnDisk; + } + + public MediaState getMediaForName(String name) { + for (MediaState m : media) { + if (m.getName().equals(name)) { + return m; + } + } + return null; + } + + public String getOriginalName() { + return originalName; + } + + public String getOriginalTitle() { + return originalTitle; + } + + public String getOriginalThumbnail() { + return originalThumbnail; + } + + public List getOriginalMediaNames() { + return originalMedia; + } + + public boolean isThumbnail(MediaState state) { + return thumbnail.equals(state.getName()); + } + + public static JSONObject toJson(AlbumState state) { + final ArrayList images = new ArrayList(); + for (MediaState ms : state.media) { + images.add(MediaState.toAlbumImage(ms)); + } + AlbumConfig ac = new AlbumConfig(state.title, state.nameFollowsTitle, state.thumbnail, images); + return AlbumConfig.toJson(ac); + } + + public static String toJsonString(AlbumState state) { + return toJson(state).toJSONString(); + } +} diff --git a/src/main/java/zander/library/state/DateTimeUtils.java b/src/main/java/zander/library/state/DateTimeUtils.java new file mode 100644 index 0000000..67827f6 --- /dev/null +++ b/src/main/java/zander/library/state/DateTimeUtils.java @@ -0,0 +1,91 @@ +package zander.library.state; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Date; + +public class DateTimeUtils { + + private static final ArrayList DATE_PARSERS = new ArrayList(); + private static final ArrayList TIME_PARSERS = new ArrayList(); + static { + DATE_PARSERS.add(DateTimeFormatter.ofPattern("MMM d[','] u")); + DATE_PARSERS.add(DateTimeFormatter.ofPattern("MMMM d[','] u")); + DATE_PARSERS.add(DateTimeFormatter.ofPattern("MMMMM d[','] u")); + DATE_PARSERS.add(DateTimeFormatter.ofPattern("M'/'d'/'u")); + DATE_PARSERS.add(DateTimeFormatter.ofPattern("M'-'d'-'u")); + + TIME_PARSERS.add(DateTimeFormatter.ofPattern("k':'m[':'s]")); + TIME_PARSERS.add(DateTimeFormatter.ofPattern("h':'m[':'s] a")); + } + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, uuuu"); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("hh':'mm[':'ss] a"); + + public static LocalDate parseDate(String date) { + if (date == null) { + return null; + } + for (DateTimeFormatter f : DATE_PARSERS) { + try { + return LocalDate.parse(date, f); + } catch (DateTimeParseException e) { + continue; + } + } + return null; + } + + public static LocalTime parseTime(String time) { + if (time == null) { + return null; + } + for (DateTimeFormatter f : TIME_PARSERS) { + try { + return LocalTime.parse(time, f); + } catch (DateTimeParseException e) { + continue; + } + } + return null; + } + + public static String formatDate(LocalDate date) { + if (date == null) { + return null; + } + return date.format(DATE_FORMATTER); + } + + public static String formatTime(LocalTime time) { + if (time == null) { + return null; + } + return time.format(TIME_FORMATTER); + } + + public static LocalDate dateToLocalDate(Date date) { + if (date == null) { + return null; + } + return date.toInstant().atZone(ZoneId.of("GMT")).toLocalDate(); + } + + public static LocalTime dateToLocalTime(Date date) { + if (date == null) { + return null; + } + return date.toInstant().atZone(ZoneId.of("GMT")).toLocalTime().withSecond(0); + } + + public static LocalTime cleanLocalTime(LocalTime time) { + if (time == null) { + return null; + } + return LocalTime.of(time.getHour(), time.getMinute(), 0); + } + +} diff --git a/src/main/java/zander/library/state/LibraryConfig.java b/src/main/java/zander/library/state/LibraryConfig.java new file mode 100644 index 0000000..fbe046a --- /dev/null +++ b/src/main/java/zander/library/state/LibraryConfig.java @@ -0,0 +1,95 @@ +package zander.library.state; + +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class LibraryConfig { + + private String name; + private String license; + private List albums; + + public LibraryConfig() { + this("", "", new ArrayList()); + } + + public LibraryConfig(String name, String license, List albums) { + this.name = name; + this.license = license; + this.albums = albums; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public List getAlbums() { + return albums; + } + + public void setAlbums(List albums) { + this.albums = albums; + } + + public static LibraryConfig parseJson(String json) throws ParseException { + JSONParser p = new JSONParser(); + Object o = p.parse(json); + if (!(o instanceof JSONObject)) { + throw new ClassCastException("Json root was not an object"); + } + return parseJson((JSONObject) o); + } + + public static LibraryConfig parseJson(JSONObject json) { + Object nameObj = json.get("library_name"); + Object licenseObj = json.get("license"); + Object albumsObj = json.get("albums"); + if (!(nameObj instanceof String) || !(licenseObj instanceof String)) { + throw new ClassCastException("Name and license must be strings"); + } else if (!(albumsObj instanceof JSONArray)) { + throw new ClassCastException("Albums must be an array"); + } + JSONArray albumsArr = (JSONArray) albumsObj; + final ArrayList albums = new ArrayList(albumsArr.size()); + for (Object o : albumsArr) { + if (!(o instanceof String)) { + throw new ClassCastException("All members of albums must be a string"); + } + albums.add((String) o); + } + return new LibraryConfig((String) nameObj, (String) licenseObj, albums); + } + + @SuppressWarnings("unchecked") + public static JSONObject toJson(LibraryConfig lc) { + JSONObject root = new JSONObject(); + root.put("library_name", lc.getName()); + root.put("license", lc.getLicense()); + JSONArray albums = new JSONArray(); + albums.addAll(lc.getAlbums()); + root.put("albums", albums); + return root; + } + + public static String toJsonString(LibraryConfig lc) { + return toJson(lc).toJSONString(); + } + +} diff --git a/src/main/java/zander/library/state/LibraryState.java b/src/main/java/zander/library/state/LibraryState.java new file mode 100644 index 0000000..728d48d --- /dev/null +++ b/src/main/java/zander/library/state/LibraryState.java @@ -0,0 +1,165 @@ +package zander.library.state; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; + +import zander.library.Library; +import zander.library.LocalCache; +import zander.library.state.AlbumConfig.AlbumImage; + +public class LibraryState { + + private final Library library; + + private String name; + private String license; + private List albums; + + private String originalName; + private String originalLicense; + private List originalAlbums; + + public LibraryState(Library library) { + this.library = library; + } + + public Library getLibrary() { + return library; + } + + public List getAlbums() { + return albums; + } + + public void setAlbums(List albums) { + this.albums = albums; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AlbumState getAlbumForName(String name) { + for (AlbumState a : albums) { + if (a.getName().equals(name)) { + return a; + } + } + return null; + } + + public AlbumState removeAlbumForName(String name) { + for (int i = 0; i < albums.size(); ++i) { + AlbumState a = albums.get(i); + if (a.getName().equals(name)) { + return albums.remove(i); + } + } + return null; + } + + public boolean hasAlbum(String name) { + return getAlbumForName(name) != null; + } + + public String getOriginalName() { + return originalName; + } + + public String getOriginalLicense() { + return originalLicense; + } + + public List getOriginalAlbumNames() { + return originalAlbums; + } + + private static Double numToDouble(Number num) { + if (num == null) { + return null; + } + return num.doubleValue(); + } + + private static MediaState getMediaFromLibrary(LocalCache cache, String albumPath, AlbumImage ai) throws IOException { + File imageFile = cache.forPath(albumPath + ai.name); + long size = imageFile.length(); + MediaState state = new MediaState(ai.name, ai.description, DateTimeUtils.parseDate(ai.date), DateTimeUtils.parseTime(ai.time), + numToDouble(ai.latitude), numToDouble(ai.longitude), size); + return state; + } + + private static AlbumState getAlbumFromLibrary(LibraryState parent, LibraryConfig libConf, String name) throws IOException, ParseException { + LocalCache cache = parent.library.getLocalCache(); + String albumPath = Library.ALBUMS_FILE_PATH + "/" + name + "/"; + AlbumConfig config; + if (cache.fileExists(albumPath + Library.ALBUM_CONFIG_FILE_NAME)) { + byte[] data = cache.retrieveFile(albumPath + Library.ALBUM_CONFIG_FILE_NAME); + config = AlbumConfig.parseJson(new String(data)); + } else { + config = new AlbumConfig(); + } + ArrayList media = new ArrayList(); + for (AlbumImage mn : config.getImages()) { + media.add(getMediaFromLibrary(cache, albumPath, mn)); + } + AlbumState state = new AlbumState(name, config.getTitle(), config.doesNameFollowTitle(), config.getThumnail(), media); + return state; + } + + public static LibraryState fromLibrary(Library library) throws IOException, ParseException { + LibraryState state = new LibraryState(library); + LocalCache cache = library.getLocalCache(); + LibraryConfig config; + if (cache.fileExists(Library.CONFIG_FILE_PATH)) { + byte[] data = cache.retrieveFile(Library.CONFIG_FILE_PATH); + config = LibraryConfig.parseJson(new String(data)); + } else { + config = new LibraryConfig(); + } + state.name = config.getName(); + state.originalName = state.name; + state.license = config.getLicense(); + state.originalLicense = state.license; + state.albums = new ArrayList(); + state.originalAlbums = new ArrayList(); + for (String album : config.getAlbums()) { + AlbumState as = getAlbumFromLibrary(state, config, album); + state.albums.add(as); + state.originalAlbums.add(as.getNameOnDisk()); + } + return state; + } + + public static JSONObject toJson(LibraryState state) { + final ArrayList albums = new ArrayList(); + for (AlbumState m : state.albums) { + albums.add(m.getName()); + } + LibraryConfig lc = new LibraryConfig(state.name, state.license, albums); + return LibraryConfig.toJson(lc); + } + + public static String toJsonString(LibraryState state) { + JSONObject jo = toJson(state); + return jo.toJSONString(); + } + +} diff --git a/src/main/java/zander/library/state/MediaState.java b/src/main/java/zander/library/state/MediaState.java new file mode 100644 index 0000000..c8b7915 --- /dev/null +++ b/src/main/java/zander/library/state/MediaState.java @@ -0,0 +1,173 @@ +package zander.library.state; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalTime; + +import zander.library.state.AlbumConfig.AlbumImage; + +public class MediaState implements Serializable { + private static final long serialVersionUID = 3132312462137542610L; + + private String name; + private String nameOnDisk; + private String description; + private LocalDate date; + private LocalTime time; + private Double latitude; + private Double longitude; + private long size; + + private final String originalName; + private final String originalDescription; + private final LocalDate originalDate; + private final LocalTime originalTime; + private final Double originalLatitude; + private final Double originalLongitude; + private final long originalSize; + + public MediaState(String name, String description, LocalDate date, LocalTime time, + Double latitude, Double longitude, long size) { + LocalTime cleanTime = DateTimeUtils.cleanLocalTime(time); + + this.name = name; + this.nameOnDisk = name; + this.description = description; + this.date = date; + this.time = cleanTime; + this.latitude = latitude; + this.longitude = longitude; + this.size = size; + + this.originalName = name; + this.originalDescription = description; + this.originalDate = date; + this.originalTime = cleanTime; + this.originalLatitude = latitude; + this.originalLongitude = longitude; + this.originalSize = size; + } + + public boolean hasDescription() { + return description != null; + } + + public boolean hasDate() { + return date != null; + } + + public boolean hasTime() { + return time != null; + } + + public boolean hasLatitude() { + return latitude != null; + } + + public boolean hasLongitude() { + return longitude != null; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = DateTimeUtils.cleanLocalTime(time); + } + + public Double getLatitude() { + return latitude; + } + + public void setLatitude(Double latitude) { + this.latitude = latitude; + } + + public Double getLongitude() { + return longitude; + } + + public void setLongitude(Double longitude) { + this.longitude = longitude; + } + + public long getFileSize() { + return size; + } + + public void setFileSize(long size) { + this.size = size; + } + + public String getNameOnDisk() { + return nameOnDisk; + } + + public void setNameOnDisk(String nameOnDisk) { + this.nameOnDisk = nameOnDisk; + } + + public String getOriginalName() { + return originalName; + } + + public String getOriginalDescription() { + return originalDescription; + } + + public LocalDate getOriginalDate() { + return originalDate; + } + + public LocalTime getOriginalTime() { + return originalTime; + } + + public Double getOriginalLatitude() { + return originalLatitude; + } + + public Double getOriginalLongitude() { + return originalLongitude; + } + + public long getOriginalSize() { + return originalSize; + } + + public static AlbumImage toAlbumImage(MediaState state) { + AlbumImage ai = new AlbumImage(); + ai.name = state.name; + ai.description = state.description; + ai.date = DateTimeUtils.formatDate(state.date); + ai.time = DateTimeUtils.formatTime(state.time); + ai.latitude = state.latitude; + ai.longitude = state.longitude; + return ai; + } +} diff --git a/src/main/java/zander/ui/ErrorDialog.java b/src/main/java/zander/ui/ErrorDialog.java new file mode 100644 index 0000000..a2f2fc9 --- /dev/null +++ b/src/main/java/zander/ui/ErrorDialog.java @@ -0,0 +1,123 @@ +package zander.ui; + +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingConstants; +import javax.swing.UIManager; + +public class ErrorDialog extends JDialog { + + private static final long serialVersionUID = 7389430189757589219L; + + private Throwable error; + + private final JLabel iconLabel; + private final JLabel messageLabel; + private final JPanel messagePanel; + private final JTextArea errorBox; + private final JScrollPane errorScroll; + private final JButton advancedButton; + private final JButton closeButton; + private final JPanel buttonPanel; + private final JPanel content; + + public ErrorDialog(String title, String message, Throwable error) { + this(null, title, message, error); + } + + public ErrorDialog(Window parent, String title, String message, Throwable error) { + super(parent, title); + this.error = error; + iconLabel = new JLabel(UIManager.getIcon("OptionPane.errorIcon")); + messageLabel = new JLabel(message, SwingConstants.CENTER); + messagePanel = new JPanel(); + messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.X_AXIS)); + messagePanel.add(iconLabel); + messagePanel.add(messageLabel); + errorBox = new JTextArea(getErrorMessage()); + errorBox.setRows(getErrorBoxRows()); + errorBox.setColumns(getErrorBoxColumns()); + errorBox.setEditable(false); + errorScroll = new JScrollPane(errorBox); + errorScroll.setVisible(false); + advancedButton = new JButton("Show More..."); + advancedButton.setMnemonic(KeyEvent.VK_S); + advancedButton.addActionListener((e) -> { + errorScroll.setVisible(!errorScroll.isVisible()); + pack(); + }); + closeButton = new JButton("Close"); + advancedButton.setMnemonic(KeyEvent.VK_C); + closeButton.addActionListener((e) -> { + dispose(); + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(advancedButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(closeButton); + content = new JPanel(new BorderLayout()); + content.setLayout(new BorderLayout()); + content.add(messagePanel, BorderLayout.PAGE_START); + content.add(errorScroll, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setContentPane(content); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + pack(); + UIUtils.centerWindow(this, parent); + } + + private int getErrorBoxRows() { + int nlc = 0; + for (char c : errorBox.getText().toCharArray()) { + if (c == '\n') { + ++nlc; + } + } + return Math.min(nlc, 15); + } + + private int getErrorBoxColumns() { + String[] lines = errorBox.getText().split("\\n"); + int longest = 0; + for (String line : lines) { + if (line.length() > longest) { + longest = line.length(); + } + } + return Math.min(longest, 35); + } + + public String getMessage() { + return messageLabel.getText(); + } + + public void setMessage(String message) { + messageLabel.setText(message); + } + + private String getErrorMessage() { + if (error == null) { + return "No error information available"; + } + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + error.printStackTrace(pw); + return sw.toString(); + } + +} diff --git a/src/main/java/zander/ui/FileChooserField.java b/src/main/java/zander/ui/FileChooserField.java new file mode 100644 index 0000000..7037e81 --- /dev/null +++ b/src/main/java/zander/ui/FileChooserField.java @@ -0,0 +1,88 @@ +package zander.ui; + +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; + +public class FileChooserField extends JPanel { + private static final long serialVersionUID = -6880481322623583547L; + + private final JFileChooser chooser; + private final JTextField pathField; + private final JButton chooserButton; + private Runnable action; + + public FileChooserField() { + this(0); + } + + public FileChooserField(int cols) { + this(cols, "", null); + } + + public FileChooserField(int cols, String path) { + this(cols, path, null); + } + + public FileChooserField(int cols, String path, File dir) { + super(new GridBagLayout()); + chooser = new JFileChooser(dir); + chooser.setMultiSelectionEnabled(false); + chooser.setDialogType(JFileChooser.OPEN_DIALOG); + pathField = new JTextField(cols); + pathField.setText(path); + chooserButton = new JButton("..."); + chooserButton.addActionListener((e) -> { + if (action == null) { + int c = chooser.showDialog(SwingUtilities.windowForComponent(this), "Select"); + if (c == JFileChooser.APPROVE_OPTION) { + pathField.setText(chooser.getSelectedFile().getPath()); + } + } else { + action.run(); + } + }); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.weightx = 1.0f; + gbc.fill = GridBagConstraints.HORIZONTAL; + add(pathField, gbc); + gbc.weightx = 0.0f; + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 1; + add(chooserButton, gbc); + } + + public JFileChooser getChooser() { + return chooser; + } + + public JTextField getPathField() { + return pathField; + } + + public JButton getChooserBUtton() { + return chooserButton; + } + + public Runnable getAction() { + return action; + } + + public void setAction(Runnable action) { + this.action = action; + } + + @Override + public void setEnabled(boolean e) { + super.setEnabled(e); + pathField.setEnabled(e); + chooserButton.setEnabled(e); + } + +} diff --git a/src/main/java/zander/ui/LibraryFrame.java b/src/main/java/zander/ui/LibraryFrame.java new file mode 100644 index 0000000..0144008 --- /dev/null +++ b/src/main/java/zander/ui/LibraryFrame.java @@ -0,0 +1,248 @@ +package zander.ui; + +import java.awt.Component; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.prefs.Preferences; + +import javax.swing.JComponent; +import javax.swing.JFrame; + +import org.json.simple.parser.ParseException; + +import zander.library.Library; +import zander.library.LibraryMediaCache; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.ui._import.ImportAlbumSelectDialog; +import zander.ui.album.AlbumSelectPanel; +import zander.ui.library.LibrarySelectFrame; +import zander.ui.media.MediaManagePanel; + +public class LibraryFrame extends JFrame { + private static final long serialVersionUID = 3016763184972515087L; + private static final Preferences LF_CACHE = UIUtils.PREFS_ROOT.node("lf-cache"); + + private final Library realLibrary; + private LibraryState library; + private final Window oldWindow; + private final LibraryMediaCache cache; + private final AlbumSelectPanel albumPanel; + private final ArrayList openHistory = new ArrayList(); + + private boolean canceled = false; + + public LibraryFrame(Library library, Window oldWindow, String localName) { + this.realLibrary = library; + this.oldWindow = oldWindow; + try { + this.library = LibraryState.fromLibrary(library); + } catch (IOException | ParseException e) { + e.printStackTrace(); + ErrorDialog diag = new ErrorDialog("Internal Error", "An internal error has occured! Please try again.", e); + diag.setVisible(true); + dispose(); + oldWindow.setVisible(true); + albumPanel = null; + cache = null; + return; + } catch (ClassCastException e) { + e.printStackTrace(); + ErrorDialog diag = new ErrorDialog("Internal Error", "Library config invalid!", e); + diag.setVisible(true); + dispose(); + oldWindow.setVisible(true); + albumPanel = null; + cache = null; + return; + } + cache = new LibraryMediaCache(this.library); + setTitle(this.library.getName()); + albumPanel = new AlbumSelectPanel(this, openHistory, localName); + canceled = albumPanel.wasCancled(); + addComponentListener(new ComponentAdapter() { + @Override + public void componentMoved(ComponentEvent e) { + updateWindowCache(); + } + + @Override + public void componentResized(ComponentEvent e) { + updateWindowCache(); + } + }); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + updateWindowCache(); + if (LibrarySelectFrame.ASK_TO_RETURN_TO_LIBRARY_SELECT) { + RememberConfirmDialog bcd = new RememberConfirmDialog(LibraryFrame.this, "Confirm Action", + "Are you sure you would like to exit? " + + "Any un-uploaded changes will be lost!"); + + bcd.setVisible(true); + if (bcd.getResponse() == RememberConfirmDialog.RESPONSE_NO) { + return; + } else if (bcd.rememberAwnser()) { + LibrarySelectFrame.updateAskToBack(false); + } + } + System.exit(0); + } + }); + readWindowCache(); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + setContentPane(albumPanel); + } + + public boolean wasCanceled() { + return canceled; + } + + public Library getRealLibrary() { + return realLibrary; + } + + public LibraryState getLibraryState() { + return library; + } + + public void backButtonHandler() { + Component cp = getContentPane(); + if (cp instanceof AlbumSelectPanel) { + if (LibrarySelectFrame.ASK_TO_RETURN_TO_LIBRARY_SELECT) { + RememberConfirmDialog bcd = new RememberConfirmDialog(this, "Confirm Action", + "Are you sure you would like " + + "to return to library select? " + + "Any un-uploaded changes will be lost!"); + bcd.setVisible(true); + if (bcd.getResponse() == RememberConfirmDialog.RESPONSE_NO) { + return; + } else if (bcd.rememberAwnser()) { + LibrarySelectFrame.updateAskToBack(false); + } + } + updateWindowCache(); + dispose(); + oldWindow.setVisible(true); + } else { + setCurrentPanel(albumPanel); + albumPanel.rereadAlbum(); + } + } + + public void setCurrentPanel(JComponent c) { + setContentPane(c); + revalidate(); + repaint(); + String name; + if (library.getName().isBlank()) { + name = "Unamed Library"; + } else { + name = library.getName(); + } + if (c instanceof AlbumSelectPanel) { + setTitle(name); + } else if (c instanceof MediaManagePanel) { + MediaManagePanel p = (MediaManagePanel) c; + String albumName; + if (p.getAlbum().getName().isBlank()) { + albumName = "Unamed Album"; + } else { + albumName = p.getAlbum().getTitle(); + } + setTitle(name + " - " + albumName); + } + } + + public LibraryMediaCache getMediaCache() { + return cache; + } + + public boolean handleImports(String[] files, boolean reorder) { + final ArrayList fal = new ArrayList(); + MediaManagePanel mp = null; + while (mp == null || mp.wasCanceled()) { + ImportAlbumSelectDialog iasd = new ImportAlbumSelectDialog(cache, library); + if (iasd.wasCanceled()) { + canceled = true; + return false; + } + iasd.setVisible(true); + AlbumState na = iasd.getSelectedAlbum(); + if (na == null) { + return false; + } + if (!library.getAlbums().contains(na)) { + library.getAlbums().add(na); + albumPanel.reloadAlbumsFromLibrary(); + } + fal.clear(); + for (String p : files) { + fal.add(new File(p)); + } + mp = new MediaManagePanel(this, na); + openHistory.remove(na); + openHistory.add(na); + while (openHistory.size() > AlbumSelectPanel.ALBUMS_TO_HOLD_IN_MEMORY) { + AlbumState toSwap = openHistory.remove(0); + cache.swapOutAlbum(toSwap.getName(), false); + } + } + setCurrentPanel(mp); + setVisible(true); + mp.importMediaFromFiles(fal, reorder); + return true; + } + + public void handleUploadDone(Component source) { + // updateWindowCache(); + // dispose(); + // oldWindow.setVisible(true); + if (oldWindow instanceof LibrarySelectFrame) { + ((LibrarySelectFrame) oldWindow).promptWebOpen(source); + } + } + + private void readWindowCache() { + int cw = LF_CACHE.getInt("width", -1); + int ch = LF_CACHE.getInt("height", -1); + if (cw < 0 || ch < 0) { + int w = UIUtils.SCREEN_SIZE.width / 2; + int h = UIUtils.SCREEN_SIZE.height / 2; + setSize(w, h); + LF_CACHE.putInt("width", w); + LF_CACHE.putInt("height", h); + } else { + setSize(cw, ch); + } + int cx = LF_CACHE.getInt("x", -1); + int cy = LF_CACHE.getInt("y", -1); + if (cx < 0 || cy < 0) { + UIUtils.centerWindow(this); + LF_CACHE.putInt("x", getX()); + LF_CACHE.putInt("y", getY()); + } else { + setLocation(cx, cy); + } + setExtendedState(LF_CACHE.getInt("extended", NORMAL)); + } + + private void updateWindowCache() { + LF_CACHE.putInt("x", Math.max(0, getX())); + LF_CACHE.putInt("y", Math.max(0, getY())); + LF_CACHE.putInt("width", getWidth()); + LF_CACHE.putInt("height", getHeight()); + int es = getExtendedState(); + if (es != ICONIFIED) { + LF_CACHE.putInt("extended", es); + } + } + +} diff --git a/src/main/java/zander/ui/LibraryUploadDialog.java b/src/main/java/zander/ui/LibraryUploadDialog.java new file mode 100644 index 0000000..cf9fe17 --- /dev/null +++ b/src/main/java/zander/ui/LibraryUploadDialog.java @@ -0,0 +1,492 @@ +package zander.ui; + +import java.awt.Dimension; +import java.awt.Window; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Objects; + +import javax.imageio.ImageIO; +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.Library; +import zander.library.LibraryMediaCache; +import zander.library.RemoteStore; +import zander.library.media.ImageMediaData; +import zander.library.media.MediaData; +import zander.library.media.SerializableImageContainer; +import zander.library.media.VideoMediaData; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; + +public class LibraryUploadDialog extends JDialog { + private static final long serialVersionUID = -2859125891849291L; + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryUploadDialog.class); + private static final SecureRandom RANDOM = new SecureRandom(); + private static final Dimension SMALL_THUMBNAIL_SIZE = new Dimension(250, 250); + private static final Dimension MEDIUM_THUMBNAIL_SIZE = new Dimension(500, 500); + private static final Dimension LARGE_THUMBNAIL_SIZE = new Dimension(1000, 1000); + + private final JLabel headerLabel; + private final JProgressBar bar; + private final JTextArea messageArea; + private final JScrollPane messageScroll; + private final JPanel content; + + private int progress = 0; + + public LibraryUploadDialog(Window parent) { + super(parent, "Upload Changes"); + headerLabel = new JLabel("Uploading changes...", SwingConstants.CENTER); + headerLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); + bar = new JProgressBar(); + bar.setAlignmentX(JComponent.CENTER_ALIGNMENT); + messageArea = new JTextArea(); + messageArea.setEditable(false); + messageScroll = new JScrollPane(messageArea); + messageScroll.setAlignmentX(JComponent.CENTER_ALIGNMENT); + content = new JPanel(); + content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); + content.add(headerLabel); + content.add(bar); + content.add(messageScroll); + setContentPane(content); + setMinimumSize(new Dimension(UIUtils.SCREEN_SIZE.width / 3, UIUtils.SCREEN_SIZE.height / 3)); + setSize(getMinimumSize()); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + setResizable(true); + UIUtils.centerWindow(this, parent); + } + + private void addMessage(String message) { + SwingUtilities.invokeLater(() -> { + String ct = messageArea.getText(); + if (ct.isBlank()) { + messageArea.setText(message); + } else { + messageArea.setText(ct + "\n" + message); + } + LOGGER.info(message); + }); + } + + public boolean upload(LibraryState state, LibraryMediaCache mc) { + int resp = JOptionPane.showConfirmDialog(getOwner(), "Are you sure you would like to upload changes?" + + "\nThis process may take a while and cannot be canceled.", + "Confirm Upload", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (resp != JOptionPane.YES_OPTION) { + return false; + } + bar.setMinimum(0); + bar.setMaximum(getUploadLength(state)); + Thread ut = new Thread(() -> { + doUpload(state, mc); + }); + ut.start(); + setVisible(true); + return true; + } + + private void doUpload(LibraryState state, LibraryMediaCache mc) { + addMessage("Starting upload..."); + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + try { + rs.doFtpActions(() -> { + addMessage("Moving albums..."); + addMessage("Resolving album movement, 1st pass."); + for (int i = 0; moveAlbums(state); ++i) { + if (i == state.getAlbums().size()) { + throw new IOException("Could not resolve album name conflicts"); + } + addMessage("Resolving album movement, " + getNumberSuffixString(i + 2) + " pass."); + } + addMessage("Done moving albums."); + incrementProgress(); + addMessage("Moving media..."); + moveMedia(state); + addMessage("Done moving media."); + incrementProgress(); + addMessage("Uploading library config..."); + rl.addAsciiFile(Library.CONFIG_FILE_PATH, LibraryState.toJsonString(state)); + incrementProgress(); + addMessage("Uploading album configs..."); + uploadAlbumConfigs(state); + addMessage("Uploading media..."); + uploadMedia(state, mc); + addMessage("Done uploading meadia."); + addMessage("Deleting old files..."); + deleteOldFiles(state); + incrementProgress(); + addMessage("Done deleteing old files."); + }); + } catch (Throwable e) { + e.printStackTrace(); + SwingUtilities.invokeLater(() -> { + setVisible(false); + ErrorDialog ed = new ErrorDialog("Upload Error", "Could not complete upload!", e); + ed.setVisible(true); + }); + } + addMessage("Upload complete. Have a nice day! ^_^"); + SwingUtilities.invokeLater(() -> setVisible(false)); + } + + private void deleteOldFiles(LibraryState state) throws IOException { + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + for (String oan : state.getOriginalAlbumNames()) { + if (state.getAlbumForName(oan) == null) { + String ap = getPathForAlbum(oan); + if (rs.fileExists(ap)) { + rl.deleteFile(ap); + } + } + } + deleteOldMedia(state); + } + + private void deleteOldMedia(LibraryState state) throws IOException { + for (AlbumState as : state.getAlbums()) { + deleteOldMediaForAlbum(state, as); + } + } + + private void deleteOldMediaForAlbum(LibraryState state, AlbumState as) throws IOException { + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + for (String omn : as.getOriginalMediaNames()) { + if (as.getMediaForName(omn) == null) { + String mp = getPathForMedia(as, omn); + if (rs.fileExists(mp)) { + rl.deleteFile(mp); + rs.setIgnoreManifest(true); + rs.deleteFile(getSmallThumbnailPathForMedia(as, omn)); + rs.deleteFile(getLargeThumbnailPathForMedia(as, omn)); + rs.setIgnoreManifest(false); + } + } + } + } + + private void moveMedia(LibraryState state) throws IOException { + for (AlbumState as : state.getAlbums()) { + addMessage("Resolving media movement for album '" + as.getName() + "', 1st pass."); + for (int i = 0; moveAlbumMedia(state, as); ++i) { + if (i == as.getMedia().size()) { + throw new IOException("Could not resolve media name conflicts for album '" + + as.getName() + "'"); + } + addMessage("Resolving media movement for album '" + as.getName() + "', " + + getNumberSuffixString(i + 2) + " pass."); + } + addMessage("Movement resolved for album '" + as.getName() + "'"); + } + } + + private String getNumberSuffixString(int n) { + int l = n % 10; + switch (l) { + case 1: + return n + "st"; + case 2: + return n + "nd"; + case 3: + return n + "rd"; + default: + return n + "th"; + } + } + + private boolean moveAlbumMedia(LibraryState state, AlbumState as) throws IOException { + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + boolean tempFiles = false; + for (MediaState ms : as.getMedia()) { + if (ms.getNameOnDisk() == null) { + continue; + } + String nod = ms.getNameOnDisk(); + String nn = ms.getName(); + if (!nod.equals(nn)) { + if (rs.fileExists(getPathForMedia(as, nn)) && moveMediaToTempFile(state, as, nn)) { + tempFiles = true; + } + rl.moveFile(getPathForMedia(as, nod), getPathForMedia(as, nn)); + rs.setIgnoreManifest(true); + rs.moveFile(getSmallThumbnailPathForMedia(as, nod), getSmallThumbnailPathForMedia(as, nn)); + rs.moveFile(getLargeThumbnailPathForMedia(as, nod), getLargeThumbnailPathForMedia(as, nn)); + rs.setIgnoreManifest(false); + ms.setNameOnDisk(nn); + } + } + return tempFiles; + } + + private boolean moveMediaToTempFile(LibraryState state, AlbumState as, String nn) throws IOException { + MediaState ms = null; + for (MediaState tm : as.getMedia()) { + if (Objects.equals(nn, tm.getNameOnDisk())) { + ms = tm; + break; + } + } + if (ms == null) { + return false; + } + Library rl = state.getLibrary(); + String tn = getTempMediaName(rl, as, nn); + String tp = getPathForMedia(as, tn); + String stp = getSmallThumbnailPathForMedia(as, tn); + String ltp = getLargeThumbnailPathForMedia(as, tn); + rl.moveFile(getPathForMedia(as, ms.getNameOnDisk()), tp); + RemoteStore rs = rl.getRemoteStore(); + rs.setIgnoreManifest(true); + rs.moveFile(getSmallThumbnailPathForMedia(as, ms.getNameOnDisk()), stp); + rs.moveFile(getLargeThumbnailPathForMedia(as, ms.getNameOnDisk()), ltp); + rs.setIgnoreManifest(false); + ms.setNameOnDisk(tn); + return true; + } + + private String getSmallThumbnailPathForMedia(AlbumState as, String media) { + return getSmallThumbnailPathForMedia(as.getNameOnDisk(), media); + } + + private String getSmallThumbnailPathForMedia(String anod, String media) { + return getSmallThumbnailPathForAlbum(anod) + media; + } + + private String getLargeThumbnailPathForMedia(AlbumState as, String media) { + return getLargeThumbnailPathForMedia(as.getNameOnDisk(), media); + } + + private String getLargeThumbnailPathForMedia(String anod, String media) { + return getLargeThumbnailPathForAlbum(anod) + media; + } + + private String getPathForMedia(AlbumState as, String media) { + return getPathForMedia(as.getNameOnDisk(), media); + } + + private String getPathForMedia(String anod, String media) { + return getPathForAlbum(anod) + media; + } + + private void uploadMedia(LibraryState state, LibraryMediaCache mc) throws IOException { + for (AlbumState as : state.getAlbums()) { + uploadAlbumMedia(state, as, mc); + } + } + + private void uploadAlbumMedia(LibraryState state, AlbumState as, LibraryMediaCache mc) throws IOException { + Library rl = state.getLibrary(); + for (MediaState ms : as.getMedia()) { + if (ms.getNameOnDisk() == null) { + MediaData md = mc.getMedia(as.getName(), ms.getName(), null); + if (md == null) { + rl.addBinaryFile(getPathForAlbum(as.getNameOnDisk()) + ms.getName(), new byte[0]); + } else { + byte[] bytes = md.loadRawBytes(); + rl.addBinaryFile(getPathForAlbum(as.getNameOnDisk()) + ms.getName(), bytes); + if (md instanceof ImageMediaData) { + uploadImageThumbnail(state, as, mc, ms, bytes); + } else if (md instanceof VideoMediaData) { + uploadVideoThumbnail(state, as, mc, ms, (VideoMediaData) md); + } + } + incrementProgress(); + } + } + } + + private void uploadVideoThumbnail(LibraryState state, AlbumState as, + LibraryMediaCache mc, MediaState ms, VideoMediaData md) throws IOException { + SerializableImageContainer bigFrame = md.getThumbnailFrame(); + BufferedImage smallFrame; + if (as.getThumbnailName().equals(ms.getName())) { + smallFrame = UIUtils.fitImageToBox(bigFrame.getImage(), MEDIUM_THUMBNAIL_SIZE); + } else { + smallFrame = UIUtils.fitImageToBox(bigFrame.getImage(), SMALL_THUMBNAIL_SIZE); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(smallFrame, "png", out); + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + rs.setIgnoreManifest(true); + rs.addBinaryFile(getSmallThumbnailPathForMedia(as, ms.getName()), out.toByteArray()); + rs.setIgnoreManifest(false); + } + + private byte[] getScaledImageData(byte[] rawBytes, Dimension d) { + try { + ByteArrayInputStream in = new ByteArrayInputStream(rawBytes); + BufferedImage origImage = ImageIO.read(in); + BufferedImage scaledImage = UIUtils.fitImageToBox(origImage, d); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(scaledImage, "png", out); + return out.toByteArray(); + } catch (IOException e) { + // Should not happen + throw new RuntimeException(e); + } + } + + private void uploadImageThumbnail(LibraryState state, AlbumState as, LibraryMediaCache mc, + MediaState ms, byte[] rawBytes) throws IOException { + Library rl = state.getLibrary(); + RemoteStore rs = rl.getRemoteStore(); + byte[] smallBytes; + if (as.getThumbnailName().equals(ms.getName())) { + smallBytes = getScaledImageData(rawBytes, MEDIUM_THUMBNAIL_SIZE); + } else { + smallBytes = getScaledImageData(rawBytes, SMALL_THUMBNAIL_SIZE); + } + byte[] largeBytes = getScaledImageData(rawBytes, LARGE_THUMBNAIL_SIZE); + rs.setIgnoreManifest(true); + rs.addBinaryFile(getSmallThumbnailPathForMedia(as, ms.getName()), smallBytes); + rs.addBinaryFile(getLargeThumbnailPathForMedia(as, ms.getName()), largeBytes); + rs.setIgnoreManifest(false); + } + + private void uploadAlbumConfigs(LibraryState state) throws IOException { + Library rl = state.getLibrary(); + for (AlbumState a : state.getAlbums()) { + rl.addAsciiFile(getPathForAlbum(a.getName()) + Library.ALBUM_CONFIG_FILE_NAME, + AlbumState.toJsonString(a)); + incrementProgress(); + } + } + + private boolean moveAlbums(LibraryState state) throws IOException { + boolean tempsExist = false; + Library rl = state.getLibrary(); + for (AlbumState as : state.getAlbums()) { + RemoteStore rs = rl.getRemoteStore(); + String nod = as.getNameOnDisk(); + String nn = as.getName(); + if (nod == null) { + as.setNameOnDisk(nn); + } else if (!nod.equals(nn)) { + if (rs.fileExists(getPathForAlbum(nn)) && moveAlbumToTempFile(state, nn)) { + tempsExist = true; + } + rl.moveFile(getPathForAlbum(nod), getPathForAlbum(nn)); + as.setNameOnDisk(nn); + } + } + return tempsExist; + } + + private boolean moveAlbumToTempFile(LibraryState state, String nod) throws IOException { + AlbumState as = null; + for (AlbumState ta : state.getAlbums()) { + if (Objects.equals(nod, ta.getNameOnDisk())) { + as = ta; + break; + } + } + if (as == null) { + // un untracked file + return false; + } + Library rl = state.getLibrary(); + String tn = getTempAlbumName(rl, as.getName()); + String tp = getPathForAlbum(tn); + rl.moveFile(getPathForAlbum(as.getNameOnDisk()), tp); + as.setNameOnDisk(tn); + return true; + } + + private String getTempMediaName(Library l, AlbumState as, String cn) throws IOException { + String tn = getTempName(l, cn + "-", ".tmp"); + RemoteStore rs = l.getRemoteStore(); + if (rs.fileExists(getPathForMedia(as, tn))) { + try { + return getTempMediaName(l, as, cn); + } catch (StackOverflowError e) { + throw new IOException("Could not generate random file name!"); + } + } + return tn; + } + + private String getTempAlbumName(Library l, String cn) throws IOException { + String tn = getTempName(l, cn + "-", ".tmp"); + RemoteStore rs = l.getRemoteStore(); + if (rs.fileExists(getPathForAlbum(tn))) { + try { + return getTempAlbumName(l, cn); + } catch (StackOverflowError e) { + throw new IOException("Could not generate random file name!"); + } + } + return tn; + } + + private String getTempName(Library l, String prefix, String suffix) { + final StringBuilder sb = new StringBuilder(); + if (prefix != null) { + sb.append(prefix); + } + sb.append(Long.toUnsignedString(RANDOM.nextLong())); + sb.append(Long.toUnsignedString(RANDOM.nextLong())); + if (suffix != null) { + sb.append(suffix); + } + return sb.toString(); + } + + private String getPathForAlbum(String name) { + return Library.ALBUMS_FILE_PATH + "/" + name + "/"; + } + + private String getSmallThumbnailPathForAlbum(String name) { + return Library.THUMBNAILS_FILE_PATH + "/small/" + name + "/"; + } + + private String getLargeThumbnailPathForAlbum(String name) { + return Library.THUMBNAILS_FILE_PATH + "/large/" + name + "/"; + } + + private void incrementProgress() { + SwingUtilities.invokeLater(() -> { + bar.setValue(++progress); + }); + } + + private int getUploadLength(LibraryState state) { + int length = 1; // library config file + ++length; // move albums + ++length; // move media + ++length; // delete files + length += state.getAlbums().size(); // album config count + for (AlbumState as : state.getAlbums()) { + for (MediaState ms : as.getMedia()) { + if (ms.getNameOnDisk() == null) { + ++length; // media that needs to be written to disk + } + } + } + return length; + } +} diff --git a/src/main/java/zander/ui/MediaCellRenderer.java b/src/main/java/zander/ui/MediaCellRenderer.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/zander/ui/OverlayPainterComponent.java b/src/main/java/zander/ui/OverlayPainterComponent.java new file mode 100644 index 0000000..ccf7b40 --- /dev/null +++ b/src/main/java/zander/ui/OverlayPainterComponent.java @@ -0,0 +1,55 @@ +package zander.ui; + +import java.awt.Graphics; + +import javax.swing.JComponent; + +import zander.library.media.SerializableImageContainer; + +public class OverlayPainterComponent extends JComponent { + private static final long serialVersionUID = -91235129812512L; + + private final SerializableImageContainer img; + private int x; + private int y; + private int w; + private int h; + + public OverlayPainterComponent(SerializableImageContainer img, int x, int y, int w, int h) { + this.img = img; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + setOpaque(false); + } + + @Override + protected void paintComponent(Graphics g) { + g.drawImage(img.getImage(), x, y, w, h, null); + } + + public int getOverlayX() { + return x; + } + + public int getOverlayY() { + return y; + } + + public int getOverlayWidth() { + return w; + } + + public int getOverlayHeight() { + return h; + } + + public void setOverlayBounds(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + +} diff --git a/src/main/java/zander/ui/ProgressTracker.java b/src/main/java/zander/ui/ProgressTracker.java new file mode 100644 index 0000000..5a8672e --- /dev/null +++ b/src/main/java/zander/ui/ProgressTracker.java @@ -0,0 +1,84 @@ +package zander.ui; + +import java.awt.Window; +import java.util.function.Consumer; + +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; + +public class ProgressTracker { + + public final class ProgressManager { + public void setProgress(int value) { + SwingUtilities.invokeLater(() -> dialog.setProgress(value)); + } + + public int getProgress() { + return dialog.getProgress(); + } + + public boolean isCanceled() { + return dialog.isCanceled(); + } + + public void setIndeterminate(boolean indeterminate) { + SwingUtilities.invokeLater(() -> dialog.setIndeterminate(indeterminate)); + } + + public boolean isIndeterminate() { + return dialog.isIndeterminate(); + } + + public int getMaximum() { + return dialog.getMaximum(); + } + + public void setMaximum(int max) { + SwingUtilities.invokeLater(() -> { + dialog.setMaximum(max); + }); + } + + public int getMinimum() { + return dialog.getMinimum(); + } + + public void setMinimum(int min) { + SwingUtilities.invokeLater(() -> { + dialog.setMinimum(min); + }); + } + + public void setRange(int min, int max) { + SwingUtilities.invokeLater(() -> { + dialog.setRange(min, max); + }); + } + } + + private final ProgressTrackerDialog dialog; + private final SwingWorker worker; + + public ProgressTracker(Window parent, String title, String message, Consumer action) { + dialog = new ProgressTrackerDialog(parent, title, message); + worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + action.accept(new ProgressManager()); + return null; + } + }; + worker.addPropertyChangeListener((e) -> { + if ("state".equals(e.getPropertyName())) { + if (SwingWorker.StateValue.DONE == e.getNewValue()) { + dialog.dispose(); + } + } + }); + } + + public void start() { + worker.execute(); + dialog.setVisible(true); + } +} diff --git a/src/main/java/zander/ui/ProgressTrackerDialog.java b/src/main/java/zander/ui/ProgressTrackerDialog.java new file mode 100644 index 0000000..df41b51 --- /dev/null +++ b/src/main/java/zander/ui/ProgressTrackerDialog.java @@ -0,0 +1,120 @@ +package zander.ui; + +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +public class ProgressTrackerDialog extends JDialog { + private static final long serialVersionUID = 171605381923750912L; + + private final JLabel label; + private final JProgressBar bar; + private final JButton button; + private final JPanel content; + + private boolean indeterminate = false; + private boolean canceled = false; + private boolean cancelable = true; + + public ProgressTrackerDialog(Window parent, String title, String message) { + this(parent, title, message, 0, 0); + setIndeterminate(true); + } + + public ProgressTrackerDialog(Window parent, String title, String message, int min, int max) { + super(parent, title); + label = new JLabel(message == null ? "" : message); + label.setAlignmentX(JComponent.CENTER_ALIGNMENT); + bar = new JProgressBar(min, max); + bar.setAlignmentX(JComponent.CENTER_ALIGNMENT); + bar.setIndeterminate(false); + button = new JButton("Cancel"); + button.setAlignmentX(JComponent.CENTER_ALIGNMENT); + button.addActionListener((e) -> { + cancel(); + }); + content = new JPanel(); + content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); + content.add(label); + content.add(bar); + content.add(button); + setContentPane(content); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + cancel(); + } + }); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + setResizable(false); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + pack(); + UIUtils.centerWindow(this, parent); + } + + public int getProgress() { + return bar.getValue(); + } + + public void setProgress(int progress) { + bar.setValue(progress); + } + + public boolean isIndeterminate() { + return indeterminate; + } + + public void setIndeterminate(boolean indeterminate) { + this.indeterminate = indeterminate; + bar.setIndeterminate(indeterminate); + } + + public void setMaximum(int max) { + bar.setMaximum(max); + } + + public int getMaximum() { + return bar.getMaximum(); + } + + public void setMinimum(int min) { + bar.setMinimum(min); + } + + public int getMinimum() { + return bar.getMinimum(); + } + + public void setRange(int min, int max) { + setMinimum(min); + setMaximum(max); + } + + public boolean isCanceled() { + return canceled; + } + + public boolean isCancelable() { + return cancelable; + } + + public void setCancelable(boolean cancelable) { + this.cancelable = cancelable; + button.setVisible(cancelable); + } + + public void cancel() { + canceled = true; + setIndeterminate(true); + label.setText("Canceling..."); + } +} diff --git a/src/main/java/zander/ui/RememberConfirmDialog.java b/src/main/java/zander/ui/RememberConfirmDialog.java new file mode 100644 index 0000000..195f82d --- /dev/null +++ b/src/main/java/zander/ui/RememberConfirmDialog.java @@ -0,0 +1,71 @@ +package zander.ui; + +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; + +public class RememberConfirmDialog extends JDialog { + private static final long serialVersionUID = 92109258152L; + + public static final int RESPONSE_NO = 0; + public static final int RESPONSE_YES = 1; + + private final JLabel messageLabel; + private final JCheckBox showCheck; + private final JButton noButton; + private final JButton yesButton; + private final JPanel buttonPanel; + private final JPanel content; + + private int response = RESPONSE_NO; + + public RememberConfirmDialog(Window parent, String title, String message) { + super(parent, title); + messageLabel = new JLabel(message); + showCheck = new JCheckBox("Remember my response"); + showCheck.setMnemonic(KeyEvent.VK_R); + showCheck.setHorizontalAlignment(SwingConstants.CENTER); + noButton = new JButton("No"); + noButton.setMnemonic(KeyEvent.VK_N); + noButton.addActionListener((e) -> dispose()); + yesButton = new JButton("Yes"); + yesButton.addActionListener((e) -> { + response = RESPONSE_YES; + dispose(); + }); + yesButton.setMnemonic(KeyEvent.VK_Y); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(noButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(yesButton); + content = new JPanel(new BorderLayout()); + content.add(messageLabel, BorderLayout.PAGE_START); + content.add(showCheck, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setResizable(false); + pack(); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + } + + public int getResponse() { + return response; + } + + public boolean rememberAwnser() { + return showCheck.isSelected(); + } +} diff --git a/src/main/java/zander/ui/UIUtils.java b/src/main/java/zander/ui/UIUtils.java new file mode 100644 index 0000000..b0c7d52 --- /dev/null +++ b/src/main/java/zander/ui/UIUtils.java @@ -0,0 +1,185 @@ +package zander.ui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.prefs.Preferences; + +import javax.imageio.ImageIO; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.media.SerializableImageContainer; + +public class UIUtils { + + public static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize(); + public static final Preferences PREFS_ROOT = Preferences.userRoot().node("curator-store"); + public static final String SYSTEM_LAF_NAME = getSystemLafName(); + public static final Dimension THUMBNAIL_SIZE = new Dimension(SCREEN_SIZE.width / 7, SCREEN_SIZE.height / 7); + public static final Dimension IMAGE_PREVEW_SIZE = new Dimension(1280, 720); + + private static final Logger LOGGER = LoggerFactory.getLogger(UIUtils.class); + + private static String getSystemLafName() { + String cp = UIManager.getSystemLookAndFeelClassName(); + for (LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) { + if (laf.getClassName().equals(cp)) { + return laf.getName(); + } + } + throw new Error("Could not get system LAF"); + } + + public static BufferedImage loadBufferedImage(URL resource, int w, int h) throws IOException { + try { + BufferedImage i = ImageIO.read(resource); + if (w < 0 && h < 0) { + return i; + } + int nw = w; + int nh = h; + float aspect = (float) i.getWidth() / i.getHeight(); + if (w < 0) { + nw = Math.round(h * aspect); + nh = h; + } else if (h < 0) { + nh = Math.round(w * aspect); + nw = w; + } + BufferedImage fi = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = fi.createGraphics(); + g.drawImage(i.getScaledInstance(nw, nh, BufferedImage.SCALE_SMOOTH), 0, 0, null); + g.dispose(); + return fi; + } catch (IOException e) { + LOGGER.error("Could not load image", e); + throw e; + } + } + + public static SerializableImageContainer loadSerializableImage(URL resource, int w, int h) { + try { + BufferedImage bi = loadBufferedImage(resource, w, h); + return new SerializableImageContainer(bi); + } catch (IOException e) { + LOGGER.error("Could not load image! URL: " + resource.toExternalForm(), e); + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + public static int getLargerScreenDimension() { + return Math.max(SCREEN_SIZE.width, SCREEN_SIZE.height); + } + + public static int getSmallerScreenDimension() { + return Math.max(SCREEN_SIZE.width, SCREEN_SIZE.height); + } + + public static Font scaleFont(Font orig, float scale) { + return orig.deriveFont(orig.getSize() * scale); + } + + public static Dimension getInnerWindowSize(Window win) { + Insets i = win.getInsets(); + return new Dimension(win.getWidth() - i.left - i.right, win.getHeight() - i.top - i.bottom); + } + + public static void centerWindow(Window w) { + centerWindow(w, 0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height); + } + + public static void centerWindow(Window w, Window parent) { + if (parent == null) { + centerWindow(w); + } else { + centerWindow(w, parent.getX(), parent.getY(), parent.getWidth(), parent.getHeight()); + } + } + + public static void centerWindow(Window win, int px, int py, int pw, int ph) { + Dimension ws = getInnerWindowSize(win); + win.setLocation(px + ((pw / 2) - (ws.width / 2)), py + ((ph / 2) - (ws.height / 2))); + } + + // From: https://stackoverflow.com/questions/37758061/rotate-a-buffered-image-in-java + public static BufferedImage rotateImage(BufferedImage buffImage, double angle) { + double radian = Math.toRadians(angle); + double sin = Math.abs(Math.sin(radian)); + double cos = Math.abs(Math.cos(radian)); + + int width = buffImage.getWidth(); + int height = buffImage.getHeight(); + + int nWidth = (int) Math.floor((double) width * cos + (double) height * sin); + int nHeight = (int) Math.floor((double) height * cos + (double) width * sin); + + BufferedImage rotatedImage = new BufferedImage( + nWidth, nHeight, BufferedImage.TYPE_INT_ARGB); + + Graphics2D graphics = rotatedImage.createGraphics(); + + graphics.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + + graphics.translate((nWidth - width) / 2, (nHeight - height) / 2); + // rotation around the center point + graphics.rotate(radian, (double) (width / 2), (double) (height / 2)); + graphics.drawImage(buffImage, 0, 0, null); + graphics.dispose(); + + return rotatedImage; + } + + public static Color scaleColor(Color c, float sr, float sg, float sb) { + int nr = clamp(Math.round(c.getRed() * sr), 0, 255); + int ng = clamp(Math.round(c.getGreen() * sg), 0, 255); + int nb = clamp(Math.round(c.getBlue() * sb), 0, 255); + return new Color(nr, ng, nb); + } + + private static int clamp(int n, int min, int max) { + return n < min ? min : n > max ? max : n; + } + + public static BufferedImage fitImageToBox(BufferedImage source, Dimension dim) { + return fitImageToBox(source, (int) dim.getWidth(), (int) dim.getHeight()); + } + + public static BufferedImage fitImageToBox(BufferedImage source, int mw, int mh) { + int fw; + int fh; + int ow = source.getWidth(); + int oh = source.getHeight(); + if (ow <= mw && oh <= mh) { + fw = ow; + fh = oh; + } else { + float scale = Math.min((float) mw / ow, (float) mh / oh); + fw = (int) Math.floor(scale * ow); + fh = (int) Math.floor(scale * oh); + } + BufferedImage n = new BufferedImage(fw, fh, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = n.createGraphics(); + Image scaled = source.getScaledInstance(fw, fh, BufferedImage.SCALE_SMOOTH); + g.drawImage(scaled, 0, 0, null); + g.dispose(); + return n; + } +} diff --git a/src/main/java/zander/ui/_import/ImportAlbumSelectDialog.java b/src/main/java/zander/ui/_import/ImportAlbumSelectDialog.java new file mode 100644 index 0000000..3622d64 --- /dev/null +++ b/src/main/java/zander/ui/_import/ImportAlbumSelectDialog.java @@ -0,0 +1,215 @@ +package zander.ui._import; + +import java.awt.BorderLayout; +import java.awt.Rectangle; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.library.LibraryMediaCache; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; +import zander.ui.UIUtils; +import zander.ui.album.AlbumCreateDialog; +import zander.ui.cellrenderer.AlbumCellRenderer; +import zander.ui.docs.DocumentationViewer; + +public class ImportAlbumSelectDialog extends JDialog { + private static final long serialVersionUID = 1285137618912358123L; + + private final LibraryState library; + + private final JPopupMenu popupMenu; + private final JMenuItem newItem; + private final JMenuItem selectItem; + private final JLabel searchLabel; + private final JTextField searchField; + private final JPanel searchPanel; + private final DefaultListModel albumModel; + private final JList albumList; + private final JScrollPane albumScroll; + private final JButton cancelButton; + private final JButton backButton; + private final JButton helpButton; + private final JButton newButton; + private final JButton selectButton; + private final JPanel buttonPanel; + private final JPanel content; + + private AlbumState selectedAlbum; + private boolean canceled = false; + + public ImportAlbumSelectDialog(LibraryMediaCache cache, LibraryState library) { + super((JDialog) null, "Select Album"); + this.library = library; + popupMenu = new JPopupMenu(); + newItem = new JMenuItem("New"); + selectItem = new JMenuItem("Select"); + selectItem.setEnabled(false); + popupMenu.add(newItem); + popupMenu.addSeparator(); + popupMenu.add(selectItem); + searchLabel = new JLabel("Search:"); + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { onSearch(); } + @Override + public void removeUpdate(DocumentEvent e) { onSearch(); } + @Override + public void changedUpdate(DocumentEvent e) { onSearch(); } + }); + searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.add(searchLabel); + searchPanel.add(searchField); + albumModel = new DefaultListModel(); + albumModel.addAll(library.getAlbums()); + albumList = new JList(albumModel); + albumList.setDragEnabled(false); + albumList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + albumList.setLayoutOrientation(JList.HORIZONTAL_WRAP); + albumList.setVisibleRowCount(-1); + albumList.setCellRenderer(new AlbumCellRenderer(cache)); + albumList.addListSelectionListener((e) -> onHighlight()); + albumList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { onListClick(e); } + }); + albumScroll = new JScrollPane(albumList); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> System.exit(0)); + backButton = new JButton("Select Library"); + backButton.setMnemonic(KeyEvent.VK_L); + backButton.addActionListener((e) -> dispose()); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Media/Direct Import")); + newButton = new JButton("New"); + newButton.setMnemonic(KeyEvent.VK_N); + newButton.addActionListener((e) -> onNew()); + selectButton = new JButton("Select"); + selectButton.setMnemonic(KeyEvent.VK_S); + selectButton.setEnabled(false); + selectButton.addActionListener((e) -> onSelect()); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(backButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(newButton); + buttonPanel.add(selectButton); + content = new JPanel(new BorderLayout()); + content.add(searchPanel, BorderLayout.PAGE_START); + content.add(albumScroll, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setSize((2 * UIUtils.SCREEN_SIZE.width) / 3, (2 * UIUtils.SCREEN_SIZE.height) / 3); + setMinimumSize(getSize()); + setResizable(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + UIUtils.centerWindow(this); + } + + public boolean wasCanceled() { + return canceled; + } + + private void onNew() { + AlbumCreateDialog diag = new AlbumCreateDialog(null); + UIUtils.centerWindow(diag, this); + diag.setVisible(true); + if (diag.getResponse() == AlbumCreateDialog.RESPONSE_CREATE) { + String name = diag.getAlbumName(); + if (library.getAlbumForName(name) != null) { + JOptionPane.showMessageDialog(this, "An album with the name '" + name + "' already exists!", + "Name Conflict", JOptionPane.ERROR_MESSAGE); + } else { + String title = diag.getAlbumTitle(); + selectedAlbum = new AlbumState(name, title, diag.doesNameFollowTitle(), "", new ArrayList()); + dispose(); + } + } + } + + private void onSelect() { + int i = albumList.getSelectedIndex(); + if (i != -1) { + selectAlbum(i); + } + } + + private void selectAlbum(int i) { + selectedAlbum = albumModel.get(i); + dispose(); + } + + private void onHighlight() { + boolean enabled = albumList.getSelectedIndex() != -1; + selectItem.setEnabled(enabled); + selectButton.setEnabled(enabled); + } + + private void onListClick(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Rectangle r = albumList.getCellBounds(0, albumList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint()) && e.getClickCount() == 2) { + int i = albumList.locationToIndex(e.getPoint()); + selectAlbum(i); + } + } else if (e.getButton() == MouseEvent.BUTTON3) { + Rectangle r = albumList.getCellBounds(0, albumList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = albumList.locationToIndex(e.getPoint()); + if (!albumList.isSelectedIndex(i)) { + albumList.setSelectedIndex(i); + } + } + popupMenu.show(albumList, e.getX(), e.getY()); + } + } + + private void onSearch() { + String search = searchField.getText(); + search = search.trim(); + albumModel.clear(); + for (AlbumState a : library.getAlbums()) { + if (a.getTitle().trim().startsWith(search)) { + albumModel.addElement(a); + } + } + } + + public AlbumState getSelectedAlbum() { + return selectedAlbum; + } +} diff --git a/src/main/java/zander/ui/_import/ImportLibrarySelectDialog.java b/src/main/java/zander/ui/_import/ImportLibrarySelectDialog.java new file mode 100644 index 0000000..02bc76e --- /dev/null +++ b/src/main/java/zander/ui/_import/ImportLibrarySelectDialog.java @@ -0,0 +1,143 @@ +package zander.ui._import; + +import java.awt.BorderLayout; +import java.awt.Rectangle; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.UIManager; + +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; +import zander.ui.library.LibrarySelectFrame.LibraryEntry; + +public class ImportLibrarySelectDialog extends JDialog { + private static final long serialVersionUID = -281751309123958181L; + + private final JPopupMenu popupMenu; + private final JMenuItem selectItem; + private final DefaultListModel libraryModel; + private final JList libraryList; + private final JScrollPane libraryScroll; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton selectButton; + private final JPanel buttonPanel; + private final JPanel content; + + private LibraryEntry selectedLibrary; + + public ImportLibrarySelectDialog(DefaultListModel entries) { + super((JDialog) null, "Select Library"); + popupMenu = new JPopupMenu(); + selectItem = new JMenuItem("Select"); + selectItem.setEnabled(false); + selectItem.addActionListener((e) -> onSelect()); + popupMenu.add(selectItem); + libraryModel = new DefaultListModel(); + for (Object oe : entries.toArray()) { + libraryModel.addElement((LibraryEntry) oe); + } + libraryList = new JList(libraryModel); + libraryList.setFont(UIUtils.scaleFont(UIManager.getFont("List.font"), 2.0f)); + libraryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + libraryList.setDragEnabled(false); + libraryList.addListSelectionListener((e) -> onHighlight()); + libraryList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { onListClick(e); } + }); + libraryScroll = new JScrollPane(libraryList); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> onCancel()); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Media/Direct Import")); + selectButton = new JButton("Select"); + selectButton.setMnemonic(KeyEvent.VK_S); + selectButton.setEnabled(false); + selectButton.addActionListener((e) -> onSelect()); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(selectButton); + content = new JPanel(new BorderLayout()); + content.add(libraryScroll, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + pack(); + setMinimumSize(getSize()); + setSize((2 * UIUtils.SCREEN_SIZE.width) / 3, (2 * UIUtils.SCREEN_SIZE.height) / 3); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + setResizable(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + UIUtils.centerWindow(this); + } + + private void onCancel() { + System.exit(0); + } + + private void onSelect() { + int i = libraryList.getSelectedIndex(); + if (i != -1) { + selectLibrary(i); + } + } + + public LibraryEntry getSelectedLibrary() { + return selectedLibrary; + } + + private void selectLibrary(int i) { + selectedLibrary = libraryModel.get(i); + dispose(); + } + + private void onHighlight() { + boolean enabled = libraryList.getSelectedIndex() != -1; + selectItem.setEnabled(enabled); + selectButton.setEnabled(enabled); + } + + private void onListClick(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Rectangle r = libraryList.getCellBounds(0, libraryList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint()) && e.getClickCount() == 2) { + int i = libraryList.locationToIndex(e.getPoint()); + selectLibrary(i); + } + } else if (e.getButton() == MouseEvent.BUTTON3) { + Rectangle r = libraryList.getCellBounds(0, libraryList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = libraryList.locationToIndex(e.getPoint()); + if (!libraryList.isSelectedIndex(i)) { + libraryList.setSelectedIndex(i); + } + } + popupMenu.show(libraryList, e.getX(), e.getY()); + } + } +} diff --git a/src/main/java/zander/ui/album/AlbumCreateDialog.java b/src/main/java/zander/ui/album/AlbumCreateDialog.java new file mode 100644 index 0000000..1b0f284 --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumCreateDialog.java @@ -0,0 +1,172 @@ +package zander.ui.album; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.apache.batik.ext.swing.GridBagConstants; + +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class AlbumCreateDialog extends JDialog { + private static final long serialVersionUID = 4903014837170670083L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_CREATE = 1; + + private final JLabel titleLabel; + private final JTextField titleField; + private final JLabel nameLabel; + private final JTextField nameField; + private final JCheckBox nameCheck; + private final JPanel fieldPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton createButton; + private final JPanel buttonPanel; + private final JPanel content; + + private int response = RESPONSE_CANCEL; + + public AlbumCreateDialog(Window parent) { + super(parent, "Create Album"); + titleLabel = new JLabel("Album Title:"); + titleField = new JTextField(20); + titleField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + + @Override + public void removeUpdate(DocumentEvent e) { update(); } + + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + if (!nameCheck.isSelected()) { + nameField.setText(titleField.getText()); + } + } + }); + nameLabel = new JLabel("Custom URL:"); + nameField = new JTextField(20); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + + @Override + public void removeUpdate(DocumentEvent e) { update(); } + + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + createButton.setEnabled(!nameField.getText().isBlank()); + } + }); + nameLabel.setEnabled(false); + nameField.setEnabled(false); + nameCheck = new JCheckBox("Different Title and URL"); + nameCheck.addActionListener((e) -> { + boolean dtu = nameCheck.isSelected(); + if (!dtu) { + nameField.setText(titleField.getText()); + } + nameLabel.setEnabled(dtu); + nameField.setEnabled(dtu); + }); + fieldPanel = new JPanel(new GridBagLayout()); + buildFieldUI(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> dispose()); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Albums/Album Settings")); + createButton = new JButton("Create"); + createButton.setMnemonic(KeyEvent.VK_R); + createButton.addActionListener((e) -> { + response = RESPONSE_CREATE; + dispose(); + }); + createButton.setEnabled(false); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(createButton); + content = new JPanel(new BorderLayout()); + content.add(fieldPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + pack(); + setResizable(false); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + } + + public void buildFieldUI() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.insets = new Insets(1, 1, 1, 1); + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.LINE_END; + fieldPanel.add(titleLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + fieldPanel.add(titleField, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + fieldPanel.add(nameLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + fieldPanel.add(nameField, gbc); + gbc.fill = GridBagConstants.NONE; + gbc.anchor = GridBagConstraints.CENTER; + gbc.gridx = 0; + gbc.gridy = 2; + gbc.gridwidth = 2; + fieldPanel.add(nameCheck, gbc); + } + + public int getResponse() { + return response; + } + + public String getAlbumName() { + return nameField.getText(); + } + + public String getAlbumTitle() { + return titleField.getText(); + } + + public boolean doesNameFollowTitle() { + return !nameCheck.isSelected(); + } + +} diff --git a/src/main/java/zander/ui/album/AlbumPropertyPanel.java b/src/main/java/zander/ui/album/AlbumPropertyPanel.java new file mode 100644 index 0000000..cbbd212 --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumPropertyPanel.java @@ -0,0 +1,501 @@ +package zander.ui.album; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.library.LibraryMediaCache; +import zander.library.media.ImageMediaComponent; +import zander.library.media.MediaData; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; +import zander.ui.album.AlbumPropertyUpdateEvent.AlbumEventProperty; + +public class AlbumPropertyPanel extends JPanel { + private static final long serialVersionUID = 2338201447544771877L; + + private final LibraryMediaCache cache; + private final LibraryState library; + private final List openHistory; + + private final JLabel thumbLabel; + private final JPanel thumbPanel; + private final JLabel imageCountLabel; + private final JLabel imageCountNumber; + private final JLabel thumbNameLabel; + private final JTextField thumbNameField; + private final JButton thumbNameReset; + private final JLabel titleLabel; + private final JTextField titleField; + private final JButton titleReset; + private final JLabel nameLabel; + private final JTextField nameField; + private final JButton nameReset; + private final JCheckBox nameCheck; + + private final ImageMediaComponent missingImageIcon; + + private AlbumState currentAlbum; + private boolean disableUpdates = false; + + private final ArrayList propertyListeners = new ArrayList(); + + public AlbumPropertyPanel(LibraryMediaCache cache, List openHistory) { + super(new GridBagLayout()); + this.cache = cache; + this.openHistory = openHistory; + missingImageIcon = getMissingImageComponent(); + library = cache.getLibrary(); + thumbLabel = new JLabel("Thumbnail:"); + thumbLabel.setHorizontalAlignment(SwingConstants.CENTER); + thumbPanel = new JPanel(new BorderLayout()); + thumbPanel.setPreferredSize(new Dimension(UIUtils.SCREEN_SIZE.width / 15, UIUtils.SCREEN_SIZE.height / 15)); + thumbPanel.setMinimumSize(new Dimension(UIUtils.SCREEN_SIZE.width / 15, UIUtils.SCREEN_SIZE.height / 15)); + thumbPanel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { selectThumbnail(); } + }); + thumbNameLabel = new JLabel("Thumbnail Name:"); + thumbNameField = new JTextField(); + thumbNameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { doUpdate(); } + @Override + public void removeUpdate(DocumentEvent e) { doUpdate(); } + @Override + public void changedUpdate(DocumentEvent e) { doUpdate(); } + private void doUpdate() { + if (!disableUpdates) { + albumThumbnailUpdated(thumbNameField.getText()); + } + } + }); + thumbNameReset = new JButton("↺"); + thumbNameReset.addActionListener((e) -> { + thumbNameField.setText(currentAlbum.getOriginalThumbnail()); + }); + imageCountLabel = new JLabel("Images:"); + imageCountNumber = new JLabel("0"); + titleLabel = new JLabel("Album Title:"); + titleField = new JTextField(); + titleField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + titleReset.setEnabled(!resetStringEquals(titleField.getText(), currentAlbum.getOriginalTitle())); + if (!nameCheck.isSelected()) { + disableUpdates = true; + nameField.setText(titleField.getText()); + disableUpdates = false; + } + } + }); + titleField.addActionListener((e) -> { + if (!disableUpdates) { + albumTitleUpdated(titleField.getText()); + } + }); + titleField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (!disableUpdates) { + albumTitleUpdated(titleField.getText()); + } + } + }); + titleReset = new JButton("↺"); + titleReset.addActionListener((e) -> { + titleField.setText(currentAlbum.getOriginalTitle()); + albumTitleUpdated(titleField.getText()); + }); + nameLabel = new JLabel("Custom URL:"); + nameField = new JTextField(); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + nameReset.setEnabled(!resetStringEquals(nameField.getText(), currentAlbum.getOriginalName())); + } + }); + nameField.addActionListener((e) -> { + if (!disableUpdates) { + albumNameUpdated(nameField.getText()); + } + }); + nameField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (!disableUpdates) { + albumNameUpdated(nameField.getText()); + } + } + }); + nameReset = new JButton("↺"); + nameReset.addActionListener((e) -> { + nameField.setText(currentAlbum.getOriginalName()); + albumNameUpdated(nameField.getText()); + }); + nameCheck = new JCheckBox("Different Title and URL"); + nameCheck.addActionListener((e) -> { + if (!disableUpdates) { + boolean en = nameCheck.isSelected(); + if (!en) { + String name = titleField.getText(); + if (checkNameErrorAndShowMessage(name)) { + disableUpdates = true; + nameCheck.setSelected(true); + disableUpdates = false; + return; + } else { + String oldName = currentAlbum.getName(); + currentAlbum.setName(name); + cache.moveAlbum(oldName, name); + disableUpdates = true; + nameField.setText(name); + disableUpdates = false; + } + } + currentAlbum.setNameFollowsTitle(!en); + nameReset.setEnabled(en); + nameField.setEnabled(en); + nameLabel.setEnabled(en); + } + }); + buildUI(); + } + + private ImageMediaComponent getMissingImageComponent() { + try { + BufferedImage bi = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("missing-image-icon.svg"), -1, -1); + return new ImageMediaComponent(bi); + } catch (IOException e) { + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + private void buildUI() { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(1, 1, 1, 1); + c.gridwidth = 1; + c.gridheight = 1; + c.gridx = 0; + c.gridy = 0; + c.weighty = 0.1; + c.weightx = 0.0; + c.anchor = GridBagConstraints.LINE_END; + c.fill = GridBagConstraints.NONE; + add(thumbLabel, c); + c.gridx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1.0; + c.weighty = 1.0; + add(thumbPanel, c); + c.weighty = 0.1; + c.gridx = 0; + c.gridy = 1; + c.anchor = GridBagConstraints.LINE_END; + c.fill = GridBagConstraints.NONE; + c.weightx = 0.0; + add(imageCountLabel, c); + c.gridx = 1; + c.anchor = GridBagConstraints.LINE_START; + add(imageCountNumber, c); + c.gridwidth = 1; + c.gridx = 0; + c.gridy = 2; + c.anchor = GridBagConstraints.LINE_END; + add(thumbNameLabel, c); + c.gridx = 1; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.HORIZONTAL; + add(thumbNameField, c); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.gridx = 2; + add(thumbNameReset, c); + c.gridwidth = 1; + c.gridx = 0; + c.gridy = 3; + c.anchor = GridBagConstraints.LINE_END; + add(titleLabel, c); + c.gridx = 1; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.HORIZONTAL; + add(titleField, c); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.gridx = 2; + add(titleReset, c); + c.gridx = 0; + c.gridy = 4; + c.anchor = GridBagConstraints.LINE_END; + add(nameLabel, c); + c.gridx = 1; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.HORIZONTAL; + add(nameField, c); + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.NONE; + c.gridx = 2; + add(nameReset, c); + c.gridx = 0; + c.gridy = 5; + c.gridwidth = 3; + c.anchor = GridBagConstraints.CENTER; + add(nameCheck, c); + } + + public void setAlbum(AlbumState album) { + setAlbum(album, false); + } + + public void setAlbum(AlbumState album, boolean clearOld) { + disableUpdates = true; + if (currentAlbum != null && !clearOld) { + if (!titleField.getText().equals(currentAlbum.getTitle())) { + currentAlbum.setTitle(titleField.getText()); + } + if (!nameField.getText().equals(currentAlbum.getName())) { + String name = nameField.getText(); + if (!checkNameErrorAndShowMessage(name)) { + String oldName = currentAlbum.getName(); + currentAlbum.setName(name); + cache.moveAlbum(oldName, name); + } else { + currentAlbum.setTitle(currentAlbum.getName()); + } + } + } + currentAlbum = album; + refreshThumbnail(); + thumbNameField.setText(album.getThumbnailName()); + titleField.setText(album.getTitle()); + nameField.setText(album.getName()); + boolean nft = album.doesNameFollowTitle(); + nameCheck.setSelected(!nft); + nameField.setEnabled(!nft); + nameLabel.setEnabled(!nft); + nameReset.setEnabled(!nft); + imageCountNumber.setText(Integer.toString(album.getMedia().size())); + updateResetButtons(); + disableUpdates = false; + } + + public AlbumState getAlbum() { + return currentAlbum; + } + + public String getThumbnailName() { + return thumbNameField.getText(); + } + + public void setThumbnailName(String name) { + thumbNameField.setText(name); + } + + public String getAlbumName() { + return nameField.getText(); + } + + public void setAlbumName(String name) { + nameField.setText(name); + } + + public String getAlbumTitle() { + return titleField.getText(); + } + + public void setAlbumTitle(String title) { + titleField.setText(title); + } + + public LibraryMediaCache getMediaCache() { + return cache; + } + + public LibraryState getLibrary() { + return library; + } + + public void addAlbumPropertyUpdateListener(AlbumPropertyUpdateListener l) { + propertyListeners.add(l); + } + + public void removeAlbumPropertyUpdateListener(AlbumPropertyUpdateListener l) { + propertyListeners.remove(l); + } + + private void fireUpdatePropertyEvent(String oldVal, String newVal, AlbumEventProperty prop) { + AlbumPropertyUpdateEvent e = new AlbumPropertyUpdateEvent(currentAlbum, oldVal, newVal, prop); + for (AlbumPropertyUpdateListener l : propertyListeners) { + l.propertyUpdated(e); + } + } + + private void selectThumbnail() { + AlbumThumbnailSelectDialog diag = + new AlbumThumbnailSelectDialog(SwingUtilities.getWindowAncestor(this), cache, currentAlbum, openHistory); + diag.setVisible(true); + if (diag.getResponse() == AlbumThumbnailSelectDialog.RESPONSE_SELECT && diag.getSelectedThumbnail() != null) { + thumbNameField.setText(diag.getSelectedThumbnail()); + } + } + + private void albumThumbnailUpdated(String thumbnail) { + String oldName = currentAlbum.getThumbnailName(); + currentAlbum.setThumbnailName(thumbnail); + refreshThumbnail(); + thumbNameReset.setEnabled(!resetStringEquals(thumbnail, currentAlbum.getOriginalThumbnail())); + fireUpdatePropertyEvent(oldName, thumbnail, AlbumEventProperty.THUMBNAIL); + } + + private void albumTitleUpdated(String title) { + if (!nameCheck.isSelected()) { + if (!title.equals(currentAlbum.getName()) && library.hasAlbum(title)) { + disableUpdates = true; + titleField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "An album with the title \"" + title + "\" is invalid becuase it " + + "result in a URL that is the same as one that exists!", + "Name Conflict", JOptionPane.ERROR_MESSAGE); + return; + } else if (title.isBlank()) { + disableUpdates = true; + titleField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The title \"" + title + "\" is invalid becaue it would result in an " + + " invalid URL!", + "Invalid Name", JOptionPane.ERROR_MESSAGE); + return; + } else if (title.contains("/")) { + disableUpdates = true; + titleField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The title \"" + title + + "\" is invalid because it contains the character '/' which is not " + + "allowed in URLs!", + "Invalid Name", JOptionPane.ERROR_MESSAGE); + return; + } + String oldName = currentAlbum.getName(); + currentAlbum.setName(title); + cache.moveAlbum(oldName, title); + } + String oldTitle = currentAlbum.getTitle(); + currentAlbum.setTitle(title); + titleReset.setEnabled(!resetStringEquals(title, currentAlbum.getOriginalTitle())); + fireUpdatePropertyEvent(oldTitle, title, AlbumEventProperty.TITLE); + } + + /** @return true if an error occured, false otherwise */ + private boolean checkNameErrorAndShowMessage(String name) { + if (name.equals(currentAlbum.getName())) { + return false; + } else if (library.hasAlbum(name)) { + disableUpdates = true; + nameField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "An album with the URL \"" + name + "\" already exists!", + "Name Conflict", JOptionPane.ERROR_MESSAGE); + return true; + } else if (name.isBlank()) { + disableUpdates = true; + nameField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The URL \"" + name + "\" is invalid!", + "Invalid Name", JOptionPane.ERROR_MESSAGE); + return true; + } else if (name.contains("/")) { + disableUpdates = true; + nameField.setText(currentAlbum.getName()); + disableUpdates = false; + JOptionPane.showMessageDialog(this, + "The URL \"" + name + "\" is invalid because it contains the character '/'!", "Invalid Name", + JOptionPane.ERROR_MESSAGE); + return true; + } + return false; + } + + private void albumNameUpdated(String name) { + if (!currentAlbum.getName().equals(name)) { + if (!checkNameErrorAndShowMessage(name)) { + String oldName = currentAlbum.getName(); + currentAlbum.setName(name); + cache.moveAlbum(oldName, name); + } + } + nameReset.setEnabled(!resetStringEquals(nameField.getText(), currentAlbum.getOriginalName())); + } + + private void refreshThumbnail() { + MediaData md = cache.getCheckedMedia(currentAlbum.getName(), currentAlbum.getThumbnailName(), null); + thumbPanel.removeAll(); + if (md != null) { + Component thumbComp = md.createThumbnailComponent(); + thumbPanel.add(thumbComp, BorderLayout.CENTER); + } else { + thumbPanel.add(missingImageIcon, BorderLayout.CENTER); + } + } + + private void updateResetButtons() { + thumbNameReset.setEnabled(!resetStringEquals(thumbNameField.getText(), currentAlbum.getOriginalThumbnail())); + nameReset.setEnabled(!resetStringEquals(nameField.getText(), currentAlbum.getOriginalName()) && + nameCheck.isSelected()); + titleReset.setEnabled(!resetStringEquals(titleField.getText(), currentAlbum.getOriginalTitle())); + } + + private static boolean resetStringEquals(String s1, String s2) { + if (s1 != null && s1.isEmpty()) { + s1 = null; + } + if (s2 != null && s2.isEmpty()) { + s2 = null; + } + return Objects.equals(s1, s2); + } + + public void updateName() { + albumNameUpdated(nameField.getText()); + } +} diff --git a/src/main/java/zander/ui/album/AlbumPropertyUpdateEvent.java b/src/main/java/zander/ui/album/AlbumPropertyUpdateEvent.java new file mode 100644 index 0000000..c20e56f --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumPropertyUpdateEvent.java @@ -0,0 +1,45 @@ +package zander.ui.album; + +import java.util.EventObject; + +import zander.library.state.AlbumState; + +public class AlbumPropertyUpdateEvent extends EventObject { + private static final long serialVersionUID = 1836669249042617105L; + + public static enum AlbumEventProperty { + NAME, + TITLE, + THUMBNAIL + } + + private final AlbumState source; + private final String oldValue; + private final String newValue; + private final AlbumEventProperty property; + + public AlbumPropertyUpdateEvent(AlbumState source, String oldValue, String newValue, AlbumEventProperty property) { + super(source); + this.source = source; + this.oldValue = oldValue; + this.newValue = newValue; + this.property = property; + } + + public AlbumEventProperty getProperty() { + return property; + } + + public String getNewValue() { + return newValue; + } + + public String getOldValue() { + return oldValue; + } + + @Override + public AlbumState getSource() { + return source; + } +} diff --git a/src/main/java/zander/ui/album/AlbumPropertyUpdateListener.java b/src/main/java/zander/ui/album/AlbumPropertyUpdateListener.java new file mode 100644 index 0000000..9f92471 --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumPropertyUpdateListener.java @@ -0,0 +1,9 @@ +package zander.ui.album; + +import java.util.EventListener; + +public interface AlbumPropertyUpdateListener extends EventListener { + + public void propertyUpdated(AlbumPropertyUpdateEvent e); + +} diff --git a/src/main/java/zander/ui/album/AlbumSelectPanel.java b/src/main/java/zander/ui/album/AlbumSelectPanel.java new file mode 100644 index 0000000..83042ad --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumSelectPanel.java @@ -0,0 +1,420 @@ +package zander.ui.album; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.DropMode; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.library.LibraryMediaCache; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; +import zander.ui.LibraryFrame; +import zander.ui.LibraryUploadDialog; +import zander.ui.ProgressTracker; +import zander.ui.UIUtils; +import zander.ui.cellrenderer.AlbumCellRenderer; +import zander.ui.docs.DocumentationViewer; +import zander.ui.media.MediaManagePanel; +import zander.ui.transferhandlers.ListArrangeTransferHandler; + +public class AlbumSelectPanel extends JPanel { + private static final long serialVersionUID = -3560698858822813839L; + + public static final int ALBUMS_TO_HOLD_IN_MEMORY = 5; + + private final LibraryFrame parent; + private final LibraryState library; + private final LibraryMediaCache cache; + private final JPopupMenu popupMenu; + private final JMenuItem openItem; + private final JMenuItem deleteItem; + private final JMenuItem newItem; + private final JMenuItem editItem; + private final JButton backButton; + private final JButton libraryPropButton; + private final JLabel searchLabel; + private final JTextField searchField; + private final JButton uploadButton; + private final JButton helpButton; + private final JButton newButton; + private final JButton deleteButton; + private final JButton openButton; + private final JButton editButton; + private final JPanel buttonPanel; + private final JLabel screenLabel; + private final JPanel topPanel; + private final DefaultListModel albumModel; + private final TransferHandler albumTransferHandler; + private final JList albumList; + private final JScrollPane albumScroll; + private final AlbumPropertyPanel propsPanel; + private final JLabel manyPropsLabel; + private final JLabel noPropsLabel; + private final JSplitPane splitPane; + + private int lastClicked = -1; + private Component currentComp; + private boolean firstToggle = true; + private boolean canceled = false; + private final List openHistory; + + public AlbumSelectPanel(LibraryFrame parent, List openHistory, String localName) { + super(new BorderLayout()); + this.parent = parent; + this.openHistory = openHistory; + this.library = parent.getLibraryState(); + cache = parent.getMediaCache(); + popupMenu = new JPopupMenu(); + openItem = new JMenuItem("Open"); + openItem.setEnabled(false); + openItem.addActionListener(this::openAlbumHandler); + deleteItem = new JMenuItem("Delete"); + deleteItem.setEnabled(false); + deleteItem.addActionListener(this::deleteAlbumHandler); + newItem = new JMenuItem("New"); + newItem.addActionListener((e) -> newAlbum()); + editItem = new JMenuItem("Toggle Property Panel"); + editItem.addActionListener(this::toggleEditPanelHandler); + popupMenu.add(openItem); + popupMenu.add(deleteItem); + popupMenu.addSeparator(); + popupMenu.add(newItem); + popupMenu.add(editItem); + backButton = new JButton("Back to Libraries"); + backButton.setMnemonic(KeyEvent.VK_B); + backButton.addActionListener((e) -> parent.backButtonHandler()); + libraryPropButton = new JButton("Library Properties"); + libraryPropButton.setMnemonic(KeyEvent.VK_L); + libraryPropButton.addActionListener((e) -> { + LibraryPropertyDialog diag = new LibraryPropertyDialog(SwingUtilities.getWindowAncestor(this), library); + diag.setVisible(true); + if (diag.getResponse() == LibraryPropertyDialog.RESPONSE_SAVE) { + library.setName(diag.getLibraryName()); + library.setLicense(diag.getLibraryLicense()); + } + }); + searchLabel = new JLabel("Search:"); + searchField = new JTextField(10); + searchField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { update(); } + + @Override + public void removeUpdate(DocumentEvent e) { update(); } + + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + public void update() { + doSearch(searchField.getText()); + } + }); + uploadButton = new JButton("Upload"); + uploadButton.setMnemonic(KeyEvent.VK_U); + uploadButton.addActionListener((e) -> { + LibraryUploadDialog diag = new LibraryUploadDialog(SwingUtilities.getWindowAncestor(this)); + if (diag.upload(library, cache)) { + parent.handleUploadDone(this); + } + }); + helpButton = new JButton("?"); + helpButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (currentComp == splitPane) { + DocumentationViewer.show(AlbumSelectPanel.this, "Albums/Album Settings"); + } else { + DocumentationViewer.show(AlbumSelectPanel.this, "Albums/Managing Albums"); + } + } + }); + newButton = new JButton("New"); + newButton.setMnemonic(KeyEvent.VK_N); + newButton.addActionListener((e) -> newAlbum()); + deleteButton = new JButton("Delete"); + deleteButton.setMnemonic(KeyEvent.VK_D); + deleteButton.setEnabled(false); + deleteButton.addActionListener(this::deleteAlbumHandler); + openButton = new JButton("Open"); + openButton.setMnemonic(KeyEvent.VK_O); + openButton.setEnabled(false); + openButton.addActionListener(this::openAlbumHandler); + editButton = new JButton("Toggle Property Panel"); + editButton.setMnemonic(KeyEvent.VK_T); + editButton.addActionListener(this::toggleEditPanelHandler); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(backButton); + buttonPanel.add(libraryPropButton); + buttonPanel.add(searchLabel); + buttonPanel.add(searchField); + buttonPanel.add(uploadButton); + buttonPanel.add(helpButton); + buttonPanel.add(newButton); + buttonPanel.add(deleteButton); + buttonPanel.add(openButton); + buttonPanel.add(editButton); + screenLabel = new JLabel(localName, SwingConstants.CENTER); + screenLabel.setFont(UIUtils.scaleFont(UIManager.getFont("Label.font"), 2.0f)); + topPanel = new JPanel(new BorderLayout()); + topPanel.add(buttonPanel, BorderLayout.CENTER); + topPanel.add(screenLabel, BorderLayout.PAGE_END); + albumModel = new DefaultListModel(); + albumTransferHandler = new ListArrangeTransferHandler(new Runnable() { + @Override + public void run() { + propsPanel.updateName(); + }; + }, () -> { + library.getAlbums().clear(); + for (int i = 0; i < albumModel.size(); ++i) { + library.getAlbums().add(albumModel.get(i)); + } + }); + albumList = new JList(albumModel); + albumList.setDropMode(DropMode.INSERT); + albumList.setDragEnabled(true); + albumList.setLayoutOrientation(JList.HORIZONTAL_WRAP); + albumList.setVisibleRowCount(-1); + albumList.setCellRenderer(new AlbumCellRenderer(cache)); + albumList.setTransferHandler(albumTransferHandler); + albumList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Rectangle r = albumList.getCellBounds(0, albumList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + if (e.getClickCount() == 1) { + int i = albumList.locationToIndex(e.getPoint()); + lastClicked = i; + } else if (e.getClickCount() == 2) { + int i = albumList.locationToIndex(e.getPoint()); + openAlbum(i); + } + } + } else if (e.getButton() == MouseEvent.BUTTON3) { + Rectangle r = albumList.getCellBounds(0, albumList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = albumList.locationToIndex(e.getPoint()); + if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) { + albumList.addSelectionInterval(i, i); + } else if (lastClicked != -1 + && (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) { + albumList.setSelectionInterval(lastClicked, i); + } else if (!albumList.isSelectedIndex(i)) { + albumList.setSelectedIndex(i); + } + } + int numSel = albumList.getSelectedIndices().length; + openItem.setEnabled(numSel == 1); + deleteItem.setEnabled(numSel > 0); + popupMenu.show(albumList, e.getX(), e.getY()); + } + } + }); + albumScroll = new JScrollPane(albumList); + propsPanel = new AlbumPropertyPanel(cache, openHistory); + propsPanel.addAlbumPropertyUpdateListener((e) -> { + revalidate(); + repaint(); + }); + manyPropsLabel = new JLabel("Multiple items selected.", JLabel.CENTER); + noPropsLabel = new JLabel("No items selected.", JLabel.CENTER); + splitPane = new JSplitPane(); + splitPane.setRightComponent(noPropsLabel); + splitPane.setOneTouchExpandable(false); + add(topPanel, BorderLayout.PAGE_START); + add(albumScroll, BorderLayout.CENTER); + currentComp = albumScroll; + albumList.addListSelectionListener((e) -> refreshAlbums()); + loadMedia(); + } + + private void loadMedia() { + ProgressTracker pt = new ProgressTracker(null, "Loading Thumbnails", "Loading thumbnails...", (t) -> { + t.setMinimum(0); + t.setMaximum(library.getAlbums().size()); + t.setIndeterminate(false); + for (AlbumState album : library.getAlbums()) { + cache.getMedia(album.getName(), album.getThumbnailName(), t); + albumModel.addElement(album); + t.setProgress(t.getProgress() + 1); + if (t.isCanceled()) { + canceled = true; + break; + } + } + }); + pt.start(); + } + + public boolean wasCancled() { + return canceled; + } + + private void toggleEditPanelHandler(ActionEvent e) { + if (firstToggle) { + splitPane.setDividerLocation((int) (getWidth() / 2)); + firstToggle = false; + } + if (currentComp == albumScroll) { + int div = splitPane.getDividerLocation(); + remove(albumScroll); + add(splitPane, BorderLayout.CENTER); + currentComp = splitPane; + splitPane.setLeftComponent(albumScroll); + splitPane.setDividerLocation(div); + } else { + remove(splitPane); + add(albumScroll, BorderLayout.CENTER); + currentComp = albumScroll; + } + revalidate(); + } + + private void refreshAlbums() { + int numSel = albumList.getSelectedIndices().length; + if (numSel == 0) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(noPropsLabel); + splitPane.setDividerLocation(div); + } else if (numSel == 1) { + if (splitPane.getRightComponent() != propsPanel) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(propsPanel); + splitPane.setDividerLocation(div); + } + propsPanel.setAlbum(albumList.getSelectedValue()); + } else if (splitPane.getRightComponent() != manyPropsLabel) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(manyPropsLabel); + splitPane.setDividerLocation(div); + } + revalidate(); + openButton.setEnabled(numSel == 1); + deleteButton.setEnabled(numSel > 0); + } + + public void rereadAlbum() { + AlbumState as = albumList.getSelectedValue(); + if (as != null) { + propsPanel.setAlbum(albumList.getSelectedValue(), true); + repaint(); + revalidate(); + } + } + + public void reloadAlbumsFromLibrary() { + doSearch(searchField.getText()); + } + + private void openAlbumHandler(ActionEvent e) { + int i = albumList.getSelectedIndex(); + if (i != -1) { + openAlbum(i); + } + } + + private void openAlbum(int i) { + albumList.setSelectedIndex(i); + AlbumState a = albumModel.get(i); + MediaManagePanel p = new MediaManagePanel(parent, a); + openHistory.remove(a); + openHistory.add(a); + while (openHistory.size() > ALBUMS_TO_HOLD_IN_MEMORY) { + AlbumState toSwap = openHistory.remove(0); + cache.swapOutAlbum(toSwap.getName(), false); + } + if (!p.wasCanceled()) { + parent.setCurrentPanel(p); + } + } + + private void deleteAlbumHandler(ActionEvent e) { + List as = albumList.getSelectedValuesList(); + int response; + if (as.size() == 1) { + response = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the album '" + as.get(0).getName() + "'?", + "Confirm Delete", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + } else { + response = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete " + as.size() + " albums?", + "Confirm Delete", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + } + if (response == JOptionPane.YES_OPTION) { + for (AlbumState a : as) { + deleteAlbum(a); + } + } + } + + private void deleteAlbum(AlbumState a) { + albumModel.removeElement(a); + library.removeAlbumForName(a.getName()); + cache.removeAlbum(a.getName()); + openHistory.remove(a); + } + + private void newAlbum() { + AlbumCreateDialog diag = new AlbumCreateDialog(SwingUtilities.getWindowAncestor(this)); + diag.setVisible(true); + if (diag.getResponse() == AlbumCreateDialog.RESPONSE_CREATE) { + String name = diag.getAlbumName(); + if (library.getAlbumForName(name) != null) { + JOptionPane.showMessageDialog(this, "An album with the name '" + name + "' already exists!", + "Name Conflict", JOptionPane.ERROR_MESSAGE); + } else { + AlbumState na = new AlbumState(name, diag.getAlbumTitle(), diag.doesNameFollowTitle(), + "", new ArrayList()); + na.setNameOnDisk(null); + library.getAlbums().add(na); + albumModel.addElement(na); + openAlbum(albumModel.getSize() - 1); + } + } + } + + private void doSearch(String search) { + if (!search.isBlank()) { + albumList.setDragEnabled(false); + albumList.setTransferHandler(null); + } else { + albumList.setDragEnabled(true); + albumList.setTransferHandler(albumTransferHandler); + } + search = search.trim(); + albumModel.clear(); + for (AlbumState a : library.getAlbums()) { + if (a.getTitle().trim().startsWith(search)) { + albumModel.addElement(a); + } + } + } +} diff --git a/src/main/java/zander/ui/album/AlbumThumbnailSelectDialog.java b/src/main/java/zander/ui/album/AlbumThumbnailSelectDialog.java new file mode 100644 index 0000000..92f5c40 --- /dev/null +++ b/src/main/java/zander/ui/album/AlbumThumbnailSelectDialog.java @@ -0,0 +1,175 @@ +package zander.ui.album; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; + +import zander.library.LibraryMediaCache; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; +import zander.ui.ProgressTracker; +import zander.ui.UIUtils; +import zander.ui.cellrenderer.MediaCellRenderer; + +public class AlbumThumbnailSelectDialog extends JDialog { + private static final long serialVersionUID = -9035406003694248528L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_SELECT = 1; + + private int response = RESPONSE_CANCEL; + + private final LibraryMediaCache cache; + private final LibraryState library; + private final AlbumState album; + private final List openHistory; + + private final DefaultListModel listModel; + private final JList thumbList; + private final JScrollPane scroll; + private final JButton cancelButton; + private final JButton selectButton; + private final JPanel buttonPanel; + private final JPanel content; + + private boolean valid = true; + + public AlbumThumbnailSelectDialog(Window parent, LibraryMediaCache cache, AlbumState album, List openHistory) { + super(parent, "Select Thumbnail"); + this.cache = cache; + this.library = cache.getLibrary(); + this.album = album; + this.openHistory = openHistory; + listModel = new DefaultListModel(); + thumbList = new JList(listModel); + thumbList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + thumbList.setLayoutOrientation(JList.HORIZONTAL_WRAP); + thumbList.setVisibleRowCount(-1); + thumbList.setCellRenderer(new MediaCellRenderer(cache, album)); + thumbList.setSelectedValue(album.getThumbnailName(), true); + thumbList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Rectangle r = thumbList.getCellBounds(0, thumbList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + if (e.getClickCount() == 2) { + int i = thumbList.locationToIndex(e.getPoint()); + thumbList.setSelectedIndex(i); + response = RESPONSE_SELECT; + dispose(); + } + } + } + } + }); + scroll = new JScrollPane(thumbList); + cancelButton = new JButton("Canel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + selectButton = new JButton("Select"); + selectButton.setMnemonic(KeyEvent.VK_S); + selectButton.addActionListener((e) -> { + response = RESPONSE_SELECT; + dispose(); + }); + thumbList.addListSelectionListener((e) -> { + if (thumbList.getSelectedIndex() == -1) { + selectButton.setEnabled(false); + } else { + selectButton.setEnabled(true); + } + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(selectButton); + content = new JPanel(new BorderLayout()); + content.add(scroll, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + if (parent != null) { + setSize((2 * parent.getWidth()) / 3, (2 * parent.getHeight()) / 3); + } else { + pack(); + } + setMinimumSize(new Dimension(content.getPreferredSize().width, 200)); + UIUtils.centerWindow(this, parent); + setResizable(true); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + loadMedia(); + } + + private void loadMedia() { + ProgressTracker pt = new ProgressTracker(null, "Loading Media", "Loading media...", (t) -> { + t.setIndeterminate(false); + t.setMinimum(0); + t.setMaximum(album.getMedia().size()); + for (MediaState media : album.getMedia()) { + cache.getMedia(album.getName(), media.getName(), t); + t.setProgress(t.getProgress() + 1); + listModel.addElement(media); + if (t.isCanceled()) { + valid = false; + break; + } + } + openHistory.remove(album); + openHistory.add(album); + while (openHistory.size() > AlbumSelectPanel.ALBUMS_TO_HOLD_IN_MEMORY) { + AlbumState toSwap = openHistory.remove(0); + cache.swapOutAlbum(toSwap.getName(), false); + } + }); + pt.start(); + } + + @Override + public void setVisible(boolean visible) { + if (valid) { + super.setVisible(visible); + } + } + + public LibraryMediaCache getMediaCahce() { + return cache; + } + + public LibraryState getLibrary() { + return library; + } + + public AlbumState getAlbum() { + return album; + } + + public String getSelectedThumbnail() { + return thumbList.getSelectedValue().getName(); + } + + public int getResponse () { + return response; + } + +} diff --git a/src/main/java/zander/ui/album/LibraryPropertyDialog.java b/src/main/java/zander/ui/album/LibraryPropertyDialog.java new file mode 100644 index 0000000..f13a5ea --- /dev/null +++ b/src/main/java/zander/ui/album/LibraryPropertyDialog.java @@ -0,0 +1,171 @@ +package zander.ui.album; + +import java.awt.Insets; +import java.awt.BorderLayout; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.event.KeyEvent; +import java.util.Objects; +import java.awt.Window; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.library.state.LibraryState; +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class LibraryPropertyDialog extends JDialog { + private static final long serialVersionUID = -4761044964061547045L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_SAVE = 1; + + private final LibraryState library; + + private final JLabel nameLabel; + private final JTextField nameField; + private final JButton nameReset; + private final JLabel licenseLabel; + private final JTextField licenseField; + private final JButton licenseReset; + private final JPanel fieldPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton saveButton; + private final JPanel buttonPanel; + private final JPanel content; + + private int response = RESPONSE_CANCEL; + + public LibraryPropertyDialog(Window parent, LibraryState library) { + super(parent, "Library Properties"); + this.library = library; + nameLabel = new JLabel("Name:"); + nameField = new JTextField(20); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + private void update() { + nameReset.setEnabled(!resetStringEquals(nameField.getText(), library.getOriginalName())); + } + }); + nameReset = new JButton("↺"); + nameField.setText(library.getName()); + nameReset.addActionListener((e) -> nameField.setText(library.getOriginalName())); + licenseLabel = new JLabel("License:"); + licenseField = new JTextField(20); + licenseField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + private void update() { + licenseReset.setEnabled(!resetStringEquals(licenseField.getText(), library.getOriginalLicense())); + } + }); + licenseReset = new JButton("↺"); + licenseField.setText(library.getLicense()); + licenseReset.addActionListener((e) -> licenseField.setText(library.getOriginalLicense())); + fieldPanel = new JPanel(new GridBagLayout()); + buildFieldUI(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> dispose()); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Albums/Library Settings")); + saveButton = new JButton("Save"); + saveButton.setMnemonic(KeyEvent.VK_S); + saveButton.addActionListener((e) -> { + response = RESPONSE_SAVE; + dispose(); + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(saveButton); + content = new JPanel(new BorderLayout()); + content.add(fieldPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + pack(); + setAlwaysOnTop(true); + setResizable(false); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + } + + private void buildFieldUI() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.insets = new Insets(1, 1, 1, 1); + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.LINE_END; + fieldPanel.add(nameLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + fieldPanel.add(nameField, gbc); + gbc.gridx = 2; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_START; + fieldPanel.add(nameReset, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + fieldPanel.add(licenseLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + fieldPanel.add(licenseField, gbc); + gbc.gridx = 2; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_START; + fieldPanel.add(licenseReset, gbc); + } + + public LibraryState getLibrary() { + return library; + } + + public int getResponse() { + return response; + } + + public String getLibraryName() { + return nameField.getText(); + } + + public String getLibraryLicense() { + return licenseField.getText(); + } + + private static boolean resetStringEquals(String s1, String s2) { + if (s1 != null && s1.isEmpty()) { + s1 = null; + } + if (s2 != null && s2.isEmpty()) { + s2 = null; + } + return Objects.equals(s1, s2); + } +} diff --git a/src/main/java/zander/ui/cellrenderer/AlbumCellRenderer.java b/src/main/java/zander/ui/cellrenderer/AlbumCellRenderer.java new file mode 100644 index 0000000..8398131 --- /dev/null +++ b/src/main/java/zander/ui/cellrenderer/AlbumCellRenderer.java @@ -0,0 +1,131 @@ +package zander.ui.cellrenderer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTextPane; +import javax.swing.ListCellRenderer; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.LibraryMediaCache; +import zander.library.media.ImageMediaComponent; +import zander.library.media.MediaData; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class AlbumCellRenderer extends JPanel implements ListCellRenderer { + private static final long serialVersionUID = 4031761489389255796L; + private static final Logger LOGGER = LoggerFactory.getLogger(AlbumCellRenderer.class); + + private final LibraryMediaCache cache; + private final LibraryState library; + private final ImageMediaComponent missingImageIcon; + private final JTextPane name; + private final SimpleAttributeSet attribs; + + public AlbumCellRenderer(LibraryMediaCache cache) { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + this.cache = cache; + this.library = cache.getLibrary(); + missingImageIcon = getMissingImageComponent(); + name = new JTextPane(); + name.setOpaque(false); + attribs = new SimpleAttributeSet(); + StyleConstants.setAlignment(attribs, StyleConstants.ALIGN_CENTER); + } + + private ImageMediaComponent getMissingImageComponent() { + try { + BufferedImage bi = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("missing-image-icon.svg"), -1, -1); + ImageMediaComponent c = new ImageMediaComponent(bi); + c.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + return c; + } catch (IOException e) { + LOGGER.error("Could not load missing image icon!", e); + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + public LibraryState getLibrary() { + return library; + } + + @Override + public void setFont(Font font) { + super.setFont(font); + if (name != null) { + name.setFont(font); + } + } + + @Override + public void setForeground(Color fg) { + super.setForeground(fg); + if (name != null) { + name.setForeground(fg); + } + } + + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + if (name != null) { + name.setBackground(bg); + } + } + + private void setAlbumName(String text) { + this.name.setText(text); + this.name.getStyledDocument().setParagraphAttributes(0, text.length(), attribs, false); + } + + @Override + public Component getListCellRendererComponent(JList list, AlbumState value, int index, + boolean isSelected, boolean cellHasFocus) { + Color bg; + Color fg; + if (isSelected) { + bg = list.getSelectionBackground(); + fg = list.getSelectionForeground(); + } else { + bg = list.getBackground(); + fg = list.getForeground(); + } + setBackground(bg); + setForeground(fg); + setFont(list.getFont()); + removeAll(); + setAlbumName(value.getTitle() + " (" + value.getMedia().size() + ")"); + MediaData m = cache.getCheckedMedia(value.getName(), value.getThumbnailName(), null); + Component c; + if (m != null) { + c = m.createThumbnailComponent(); + } else { + c = missingImageIcon; + } + name.setPreferredSize(new Dimension(Math.round(c.getPreferredSize().width * 1.5f), c.getPreferredSize().height / 3)); + c.setMinimumSize(UIUtils.THUMBNAIL_SIZE); + add(c); + add(name); + return this; + } + +} diff --git a/src/main/java/zander/ui/cellrenderer/MediaCellRenderer.java b/src/main/java/zander/ui/cellrenderer/MediaCellRenderer.java new file mode 100644 index 0000000..958914e --- /dev/null +++ b/src/main/java/zander/ui/cellrenderer/MediaCellRenderer.java @@ -0,0 +1,166 @@ +package zander.ui.cellrenderer; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JLayeredPane; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTextPane; +import javax.swing.ListCellRenderer; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.LibraryMediaCache; +import zander.library.media.ImageMediaComponent; +import zander.library.media.MediaData; +import zander.library.media.SerializableImageContainer; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; +import zander.ui.ErrorDialog; +import zander.ui.OverlayPainterComponent; +import zander.ui.UIUtils; + +public class MediaCellRenderer extends JPanel implements ListCellRenderer { + private static final long serialVersionUID = 8224231026387257322L; + private static final Logger LOGGER = LoggerFactory.getLogger(MediaCellRenderer.class); + private static final SerializableImageContainer STAR_IMAGE = UIUtils + .loadSerializableImage(ClassLoader.getSystemResource("star.svg"), -1, -1); + + private final LibraryMediaCache cache; + private final AlbumState album; + private final LibraryState library; + private final JLayeredPane layeredPane; + private final JPanel compPanel; + private final OverlayPainterComponent overlay; + private final ImageMediaComponent missingImageIcon; + private final JTextPane name; + private final SimpleAttributeSet attribs; + + public MediaCellRenderer(LibraryMediaCache cache, AlbumState album) { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + this.cache = cache; + this.album = album; + this.library = cache.getLibrary(); + layeredPane = new JLayeredPane(); + add(layeredPane, BorderLayout.CENTER); + compPanel = new JPanel(); + compPanel.setLayout(new BoxLayout(compPanel, BoxLayout.Y_AXIS)); + overlay = new OverlayPainterComponent(STAR_IMAGE, 0, 0, 0, 0); + missingImageIcon = getMissingImageComponent(); + name = new JTextPane(); + name.setOpaque(false); + attribs = new SimpleAttributeSet(); + StyleConstants.setAlignment(attribs, StyleConstants.ALIGN_CENTER); + layeredPane.add(compPanel, Integer.valueOf(0)); + layeredPane.add(overlay, Integer.valueOf(1)); + } + + private ImageMediaComponent getMissingImageComponent() { + try { + BufferedImage bi = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("missing-image-icon.svg"), -1, -1); + return new ImageMediaComponent(bi); + } catch (IOException e) { + LOGGER.error("Could not load missing image icon!", e); + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + public LibraryState getLibrary() { + return library; + } + + @Override + public void setFont(Font font) { + super.setFont(font); + if (name != null) { + name.setFont(font); + } + } + + @Override + public void setForeground(Color fg) { + super.setForeground(fg); + if (name != null) { + name.setForeground(fg); + } + } + + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + if (name != null) { + name.setBackground(bg); + } + } + + private void setNameLabel(MediaState state) { + String text = state.getName(); + this.name.setText(text); + this.name.getStyledDocument().setParagraphAttributes(0, text.length(), attribs, false); + } + + @Override + public Component getListCellRendererComponent(JList list, MediaState value, int index, + boolean isSelected, boolean cellHasFocus) { + Color bg; + Color fg; + if (isSelected) { + bg = list.getSelectionBackground(); + fg = list.getSelectionForeground(); + } else { + bg = list.getBackground(); + fg = list.getForeground(); + } + setBackground(bg); + setForeground(fg); + compPanel.setBackground(bg); + compPanel.setForeground(fg); + setFont(list.getFont()); + setNameLabel(value); + MediaData m = cache.getMedia(album.getName(), value.getName(), null); + Component c; + if (m != null) { + c = m.createThumbnailComponent(); + } else { + c = missingImageIcon; + } + c.setPreferredSize(UIUtils.THUMBNAIL_SIZE); + name.setPreferredSize(new Dimension(Math.round(c.getPreferredSize().width * 1.5f), c.getPreferredSize().height / 3)); + compPanel.removeAll(); + compPanel.add(c); + compPanel.add(name); + Dimension ns = compPanel.getLayout().preferredLayoutSize(compPanel); + setSize(ns); + setPreferredSize(ns); + compPanel.setLocation(0, 0); + compPanel.setSize(getPreferredSize()); + if (album.getThumbnailName().equals(value.getName())) { + overlay.setBounds(compPanel.getBounds()); + int scd = Math.min(getWidth(), getHeight()); + int op = (int) Math.floor(scd * 0.05f); + int os = (int) Math.floor(scd * 0.15f); + overlay.setOverlayBounds(op, op, os, os); + overlay.setVisible(true); + } else { + overlay.setVisible(false); + } + return this; + } + +} diff --git a/src/main/java/zander/ui/docs/CuratorAboutPanel.java b/src/main/java/zander/ui/docs/CuratorAboutPanel.java new file mode 100644 index 0000000..c3e9c30 --- /dev/null +++ b/src/main/java/zander/ui/docs/CuratorAboutPanel.java @@ -0,0 +1,97 @@ +package zander.ui.docs; + +import java.awt.BorderLayout; +import java.awt.Desktop; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.event.HyperlinkEvent; + +import zander.Start; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class CuratorAboutPanel extends JPanel { + private static final long serialVersionUID = 2835012513857102851L; + + private ImageIcon logoIcon; + + private final JLabel logoLabel; + private final JLabel nameLabel; + private final JPanel namePanel; + private final JLabel versionLabel; + private final JPanel headerPanel; + private final JEditorPane infoPane; + private final JScrollPane infoScroll; + + public CuratorAboutPanel() { + loadResources(); + logoLabel = new JLabel(logoIcon); + nameLabel = new JLabel("Curator"); + nameLabel.setFont(UIUtils.scaleFont(UIManager.getFont("Label.font"), 4.0f)); + namePanel = new JPanel(); + namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.X_AXIS)); + namePanel.add(Box.createHorizontalGlue()); + namePanel.add(logoLabel); + namePanel.add(nameLabel); + namePanel.add(Box.createHorizontalGlue()); + versionLabel = new JLabel("Version " + getVersionString(), SwingConstants.CENTER); + versionLabel.setFont(UIUtils.scaleFont(UIManager.getFont("Label.font"), 2.0f)); + headerPanel = new JPanel(new BorderLayout()); + headerPanel.add(namePanel, BorderLayout.CENTER); + headerPanel.add(versionLabel, BorderLayout.PAGE_END); + infoPane = new JEditorPane(); + infoPane.addHyperlinkListener((e) -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + URL u = e.getURL(); + try { + Desktop.getDesktop().browse(u.toURI()); + } catch (URISyntaxException | IOException ex) { + ex.printStackTrace(); + ErrorDialog ed = new ErrorDialog("Link Error", "Could not open link: " + u.toExternalForm(), ex); + ed.setVisible(true); + } + } + }); + infoPane.setEditable(false); + infoPane.setContentType("text/html"); + try { + infoPane.setPage(ClassLoader.getSystemResource("docs/info.html")); + } catch (IOException e) { + // do nothing + } + infoScroll = new JScrollPane(infoPane); + setLayout(new BorderLayout()); + setBackground(UIManager.getColor("EditorPane.background")); + add(headerPanel, BorderLayout.PAGE_START); + add(infoScroll, BorderLayout.CENTER); + } + + private String getVersionString() { + return Start.CURATOR_VERSION; + } + + private void loadResources() { + try { + int ld = UIUtils.getLargerScreenDimension(); + logoIcon = new ImageIcon(UIUtils.loadBufferedImage(ClassLoader.getSystemResource("logo.svg"), -1, + Math.round(ld * 0.07f))); + } catch (IOException e) { + e.printStackTrace(); + ErrorDialog ed = new ErrorDialog("Internal Error", "An internal error has occurend, please try again.", e); + ed.setVisible(true); + System.exit(1); + } + } + +} diff --git a/src/main/java/zander/ui/docs/DocumentationViewer.java b/src/main/java/zander/ui/docs/DocumentationViewer.java new file mode 100644 index 0000000..12cbc1a --- /dev/null +++ b/src/main/java/zander/ui/docs/DocumentationViewer.java @@ -0,0 +1,295 @@ +package zander.ui.docs; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Dialog; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.NoSuchElementException; + +import javax.swing.JCheckBox; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class DocumentationViewer extends JFrame { + private static final long serialVersionUID = -129157128310235L; + private static final Logger LOGGER = LoggerFactory.getLogger(DocumentationViewer.class); + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + private static final DocumentBuilder DOCUMENT_BUILDER; + static { + try { + DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new Error("Could not create XML parser!", e); + } + } + + private static class DocumentationEntry { + public String name; + public URL url; + + public DocumentationEntry(String name, URL url) { + this.name = name; + this.url = url; + } + + @Override + public String toString() { + return name; + } + } + + private final DefaultMutableTreeNode treeRoot; + private final DefaultMutableTreeNode aboutNode; + private final CuratorAboutPanel aboutPanel; + private final DefaultTreeModel treeModel; + private final JTree tree; + private final JScrollPane treeScroll; + private final JCheckBox onTopCheck; + private final JPanel treePanel; + private final JEditorPane viewer; + private final JScrollPane viewerScroll; + private final JSplitPane content; + + public DocumentationViewer(String structureResource) { + super("Curator Help"); + treeRoot = getDocumentFromString(structureResource); + aboutNode = new DefaultMutableTreeNode("About"); + treeRoot.add(aboutNode); + aboutPanel = new CuratorAboutPanel(); + treeModel = new DefaultTreeModel(treeRoot); + tree = new JTree(treeModel); + tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + tree.setRootVisible(false); + tree.setExpandsSelectedPaths(true); + tree.addTreeSelectionListener((e) -> { + if (e.getNewLeadSelectionPath() != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getNewLeadSelectionPath().getLastPathComponent(); + if (node == aboutNode) { + openAbout(); + } else { + openHelp((DefaultMutableTreeNode) e.getNewLeadSelectionPath().getLastPathComponent()); + } + } + }); + treeScroll = new JScrollPane(tree); + onTopCheck = new JCheckBox("Stay above other windows"); + onTopCheck.setHorizontalAlignment(SwingConstants.CENTER); + onTopCheck.setMnemonic(KeyEvent.VK_S); + onTopCheck.addActionListener((e) -> { + setAlwaysOnTop(onTopCheck.isSelected()); + }); + treePanel = new JPanel(new BorderLayout()); + treePanel.add(treeScroll, BorderLayout.CENTER); + treePanel.add(onTopCheck, BorderLayout.PAGE_END); + viewer = new JEditorPane(); + viewer.setContentType("text/html"); + viewer.setEditable(false); + viewer.addHyperlinkListener((e) -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + String tu = e.getDescription(); + if (tu.startsWith("help:")) { + if (tu.length() <= 6) { + return; + } + String path = tu.substring(5); + openHelp(path); + } else if (tu.startsWith("http://") || tu.startsWith("https://")) { + URL u = e.getURL(); + try { + Desktop.getDesktop().browse(u.toURI()); + } catch (URISyntaxException | IOException ex) { + LOGGER.error("Could not open url: {}", u.toExternalForm(), ex); + ErrorDialog ed = new ErrorDialog("Link Error", "Could not open link: " + + u.toExternalForm(), ex); + ed.setVisible(true); + } + } + } + }); + viewerScroll = new JScrollPane(viewer); + content = new JSplitPane(); + content.setOneTouchExpandable(true); + content.setLeftComponent(treePanel); + content.setRightComponent(viewerScroll); + setContentPane(content); + setModalExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDE); + setResizable(true); + setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + setSize((2 * UIUtils.SCREEN_SIZE.width) / 3, (2 * UIUtils.SCREEN_SIZE.height) / 3); + expandFirstLevelNodes(); + } + + public void openHelp(String page) { + String[] path = page.split("/"); + if (path.length != 0) { + openHelp(path, treeRoot); + } + setVisible(true); + } + + private void openHelp(String[] path, DefaultMutableTreeNode node) { + int ns = node.getChildCount(); + for (int i = 0; i < ns; ++i) { + DefaultMutableTreeNode c = (DefaultMutableTreeNode) node.getChildAt(i); + if (path[0].equals(String.valueOf(c.getUserObject()))) { + openHelp(Arrays.copyOfRange(path, 1, path.length), c); + return; + } + } + tree.setSelectionPath(new TreePath(node.getPath())); + openHelp(node); + } + + private void openHelp(DefaultMutableTreeNode node) { + setTitle("Curator Help"); + Object uo = node.getUserObject(); + if (uo instanceof DocumentationEntry) { + DocumentationEntry e = (DocumentationEntry) uo; + try { + if (content.getRightComponent() != viewerScroll) { + int div = content.getDividerLocation(); + content.setRightComponent(viewerScroll); + content.setDividerLocation(div); + revalidate(); + } + viewer.setPage(e.url); + return; + } catch (IOException ex) { + // ignore + } + } + } + + private void openAbout() { + tree.setSelectionPath(new TreePath(aboutNode.getPath())); + if (content.getRightComponent() != aboutPanel) { + int div = content.getDividerLocation(); + content.setRightComponent(aboutPanel); + content.setDividerLocation(div); + revalidate(); + } + setTitle("About Curator"); + LOGGER.info("Oppened about"); + setVisible(true); + } + + private DefaultMutableTreeNode getDocumentFromString(String resource) { + try { + try (InputStream in = ClassLoader.getSystemResourceAsStream(resource)) { + Document doc = DOCUMENT_BUILDER.parse(in); + Element root = doc.getDocumentElement(); + return getDocumentNode(root); + } + } catch (Throwable e) { + LOGGER.error("Error parsing json while initializing DocumentationFrame", e); + System.exit(1); + return null; + } + } + + private DefaultMutableTreeNode getDocumentNode(Node rn) { + if (rn.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) rn; + String name = element.getAttribute("name"); + DefaultMutableTreeNode tn = new DefaultMutableTreeNode(); + if (element.getTagName().equalsIgnoreCase("dir")) { + tn.setUserObject(name); + NodeList nl = element.getChildNodes(); + for (int i = 0; i < nl.getLength(); ++i) { + DefaultMutableTreeNode child = getDocumentNode(nl.item(i)); + if (child != null) { + tn.add(child); + } + } + } else if (element.getTagName().equalsIgnoreCase("file")) { + String value = element.getTextContent(); + URL url = ClassLoader.getSystemResource(value); + DocumentationEntry entry = new DocumentationEntry(name, url); + tn.setUserObject(entry); + } + return tn; + } + return null; + } + + private void expandFirstLevelNodes() { + try { + DefaultMutableTreeNode fn = (DefaultMutableTreeNode) treeRoot.getFirstChild(); + tree.setSelectionPath(new TreePath(fn.getPath())); + } catch (NoSuchElementException e) { + // do nothing + } + } + + private static final String DOCS_PATH = "docs.xml"; + private static DocumentationViewer FRAME; + static { + if (!SwingUtilities.isEventDispatchThread()) { + try { + SwingUtilities.invokeAndWait(() -> FRAME = new DocumentationViewer(DOCS_PATH)); + } catch (Throwable e) { + LOGGER.error("Could not create documentation viewer!", e); + System.exit(1); + } + } else { + FRAME = new DocumentationViewer(DOCS_PATH); + } + } + + public static void show(String path) { + show((Window) null, path); + } + + public static void show(Component comp, String path) { + show(SwingUtilities.getWindowAncestor(comp), path); + } + + public static void show(Window parent, String path) { + if (!FRAME.isVisible()) { + UIUtils.centerWindow(FRAME, parent); + } + FRAME.openHelp(path); + } + + public static void showAbout() { + FRAME.openAbout(); + } + + public static void showAbout(Window parent) { + if (!FRAME.isVisible()) { + UIUtils.centerWindow(FRAME, parent); + } + showAbout(); + } +} diff --git a/src/main/java/zander/ui/library/CuratorOptionsDialog.java b/src/main/java/zander/ui/library/CuratorOptionsDialog.java new file mode 100644 index 0000000..afb0f27 --- /dev/null +++ b/src/main/java/zander/ui/library/CuratorOptionsDialog.java @@ -0,0 +1,221 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.ArrayList; +import java.util.Set; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +import zander.library.secrets.SecretsFactory; +import zander.ui.FileChooserField; +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class CuratorOptionsDialog extends JDialog { + private static final long serialVersionUID = -1839450524327574007L; + + public static final String NO_DEFAULT_LIBRARY = "Do not automatically open any libraries"; + public static final String LAST_DEFAULT_LIBRARY = "Automatically open the last oppened library"; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_SAVE = 1; + + private int response = RESPONSE_CANCEL; + + private final JLabel defaultPathLabel; + private final FileChooserField defaultPathField; + private final JLabel passStoreLabel; + private final JComboBox passStoreBox; + private final JLabel lafLabel; + private final JComboBox lafBox; + private final JLabel defaultLibraryLabel; + private final DefaultComboBoxModel defaultLibraryModel; + private final JComboBox defaultLibraryBox; + private final JLabel openWebLabel; + private final JComboBox openWebBox; + private final JLabel askToBackLabel; + private final JCheckBox askToBackCheck; + private final JPanel settingsPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton saveButton; + private final JPanel buttonPanel; + private final JPanel content; + + public CuratorOptionsDialog(Window parent) { + super(parent, "Curator Settings"); + defaultPathLabel = new JLabel("Default Library Location:"); + defaultPathField = new FileChooserField(20, LibrarySelectFrame.DEFAULT_LIBRARY_PATH, new File(LibrarySelectFrame.DEFAULT_LIBRARY_PATH)); + defaultPathField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + defaultPathField.getChooser().setDialogTitle("Select Default Library Location"); + passStoreLabel = new JLabel("Password Store Backend:"); + passStoreBox = new JComboBox(getPasswordBackends()); + passStoreBox.setSelectedItem(LibrarySelectFrame.SECRETS_FACTORY); + lafLabel = new JLabel("Theme:"); + lafBox = new JComboBox(getLookAndFeels()); + setInitialLookAndFeel(); + defaultLibraryLabel = new JLabel("Default Library:"); + defaultLibraryModel = new DefaultComboBoxModel(LibrarySelectFrame.LIBRARY_ENTRIES.toArray()); + defaultLibraryModel.insertElementAt(NO_DEFAULT_LIBRARY, 0); + defaultLibraryModel.insertElementAt(LAST_DEFAULT_LIBRARY, 1); + defaultLibraryBox = new JComboBox(defaultLibraryModel); + defaultLibraryBox.setSelectedItem(LibrarySelectFrame.DEFAULT_LIBRARY); + openWebLabel = new JLabel("Default Upload Action:"); + openWebBox = new JComboBox(new String[] {"Always ask", "Never open library", "Always open library"}); + openWebBox.setSelectedIndex(LibrarySelectFrame.LIBRARY_WEB_HANDLER); + askToBackLabel = new JLabel("Ask Before Exiting Album:"); + askToBackCheck = new JCheckBox(); + askToBackCheck.setSelected(LibrarySelectFrame.ASK_TO_RETURN_TO_LIBRARY_SELECT); + settingsPanel = new JPanel(new GridBagLayout()); + buildSettingsPanel(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Global Settings")); + saveButton = new JButton("Save"); + saveButton.setMnemonic(KeyEvent.VK_S); + saveButton.addActionListener((e) -> { + dispose(); + response = RESPONSE_SAVE; + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(saveButton); + content = new JPanel(new BorderLayout()); + content.add(settingsPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + pack(); + UIUtils.centerWindow(this, parent); + } + + private void setInitialLookAndFeel() { + if (LibrarySelectFrame.LOOK_AND_FEEL.equals(UIUtils.SYSTEM_LAF_NAME)) { + lafBox.setSelectedIndex(0); + } else { + lafBox.setSelectedItem(LibrarySelectFrame.LOOK_AND_FEEL); + } + } + + private String[] getLookAndFeels() { + final ArrayList lafs = new ArrayList(); + for (LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) { + if (laf.getName().equals(UIUtils.SYSTEM_LAF_NAME)) { + lafs.add(0, "Default (" + laf.getName() + ")"); + } else { + lafs.add(laf.getName()); + } + } + return lafs.toArray(new String[0]); + } + + private SecretsFactory[] getPasswordBackends() { + Set sfs = SecretsFactory.getFactories(); + ArrayList backends = new ArrayList(); + backends.add(SecretsFactory.getDefaultFactory()); + for (SecretsFactory sf : sfs) { + if (sf.isSupported()) { + backends.add(sf); + } + } + return backends.toArray(new SecretsFactory[0]); + } + + private void buildSettingsPanel() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(1, 1, 1, 1); + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.gridy = 0; + gbc.gridx = 0; + settingsPanel.add(defaultPathLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(defaultPathField, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + settingsPanel.add(passStoreLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(passStoreBox, gbc); + gbc.gridy = 2; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + settingsPanel.add(lafLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(lafBox, gbc); + gbc.gridy = 3; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + settingsPanel.add(defaultLibraryLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(defaultLibraryBox, gbc); + gbc.gridy = 4; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + settingsPanel.add(openWebLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(openWebBox, gbc); + gbc.gridy = 5; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + settingsPanel.add(askToBackLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + settingsPanel.add(askToBackCheck, gbc); + } + + public int getResponse() { + return response; + } + + private String getSelectedLafString() { + if (lafBox.getSelectedIndex() == 0) { + return UIUtils.SYSTEM_LAF_NAME; + } + return lafBox.getItemAt(lafBox.getSelectedIndex()); + } + + public void updateSettigs() { + LibrarySelectFrame.updateLibraryPath(defaultPathField.getPathField().getText()); + if (!passStoreBox.getSelectedItem().equals(LibrarySelectFrame.SECRETS_FACTORY)) { + LibrarySelectFrame.updateSecretsFactory(passStoreBox.getItemAt(passStoreBox.getSelectedIndex())); + } + LibrarySelectFrame.updateLookAndFeel(getSelectedLafString()); + LibrarySelectFrame.updateDefaultLibrarySelection(defaultLibraryBox.getSelectedItem()); + LibrarySelectFrame.updateWebOpenHandler(openWebBox.getSelectedIndex()); + LibrarySelectFrame.updateAskToBack(askToBackCheck.isSelected()); + } + +} diff --git a/src/main/java/zander/ui/library/LibraryCreateDialog.java b/src/main/java/zander/ui/library/LibraryCreateDialog.java new file mode 100644 index 0000000..b38239f --- /dev/null +++ b/src/main/java/zander/ui/library/LibraryCreateDialog.java @@ -0,0 +1,269 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.io.File; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.ui.FileChooserField; +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class LibraryCreateDialog extends JDialog { + private static final long serialVersionUID = 5562277185250462433L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_CREATE = 1; + + private int response = RESPONSE_CANCEL; + + private final JLabel nameLabel; + private final JTextField nameField; + private final JLabel urlLabel; + private final JTextField urlField; + private final JLabel ftpLabel; + private final JTextField ftpField; + private final JLabel usernameLabel; + private final JTextField usernameField; + private final JLabel passwordLabel; + private final JPasswordField passwordField; + private final JCheckBox defaultPathCheck; + private final JLabel pathLabel; + private final FileChooserField pathField; + private final JPanel entryPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton createButton; + private final JPanel buttonPanel; + private final JPanel panel; + + public LibraryCreateDialog(Window parent) { + super(parent, "Create New Library"); + nameLabel = new JLabel("Name:"); + nameField = new JTextField(10); + urlLabel = new JLabel("(Optional) URL:"); + urlField = new JTextField(10); + ftpLabel = new JLabel("FTP:"); + ftpField = new JTextField(10); + usernameLabel = new JLabel("Username:"); + usernameField = new JTextField(10); + passwordLabel = new JLabel("Password:"); + passwordField = new JPasswordField(10); + defaultPathCheck = new JCheckBox("Use default library location"); + defaultPathCheck.setSelected(true); + pathLabel = new JLabel("Library Path:"); + pathField = new FileChooserField(10, LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/", new File(LibrarySelectFrame.DEFAULT_LIBRARY_PATH)); + pathField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + pathField.getChooser().setDialogTitle("Select Library Location"); + pathField.setEnabled(false); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (defaultPathCheck.isSelected()) { + pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); + } + } + }); + defaultPathCheck.addItemListener((e) -> { + if (defaultPathCheck.isSelected()) { + pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); + pathField.setEnabled(false); + } else { + pathField.setEnabled(true); + } + }); + entryPanel = new JPanel(new GridBagLayout()); + buildEntryPanel(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); + createButton = new JButton("Create"); + createButton.setMnemonic(KeyEvent.VK_R); + createButton.setEnabled(false); + createButton.addActionListener((e) -> { + if (nameField.getText().contains("/")) { + JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE); + } else { + dispose(); + response = RESPONSE_CREATE; + } + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(createButton); + final DocumentListener formFillListener = new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (!nameField.getText().isBlank() && !ftpField.getText().isBlank() + && !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) { + createButton.setEnabled(true); + } else { + createButton.setEnabled(false); + } + } + }; + nameField.getDocument().addDocumentListener(formFillListener); + ftpField.getDocument().addDocumentListener(formFillListener); + usernameField.getDocument().addDocumentListener(formFillListener); + pathField.getPathField().getDocument().addDocumentListener(formFillListener); + panel = new JPanel(new BorderLayout()); + panel.add(entryPanel, BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(panel); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setModalityType(DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + setAlwaysOnTop(true); + pack(); + UIUtils.centerWindow(this, parent); + } + + private void buildEntryPanel() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(1, 1, 1, 1); + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.gridy = 0; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(nameLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(nameField, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(urlLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(urlField, gbc); + gbc.gridy = 2; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(ftpLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(ftpField, gbc); + gbc.gridy = 3; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(usernameLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(usernameField, gbc); + gbc.gridy = 4; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(passwordLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(passwordField, gbc); + gbc.gridy = 5; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = 2; + entryPanel.add(defaultPathCheck, gbc); + gbc.gridwidth = 1; + gbc.gridy = 6; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(pathLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(pathField, gbc); + } + + public int getResponse() { + return response; + } + + public String getLibraryName() { + return nameField.getText(); + } + + public String getURL() { + return urlField.getText(); + } + + public String getFTP() { + return ftpField.getText(); + } + + public String getUsername() { + return usernameField.getText(); + } + + public String getPassword() { + return new String(passwordField.getPassword()); + } + + public String getLibraryFile() { + return pathField.getPathField().getText(); + } + +} diff --git a/src/main/java/zander/ui/library/LibraryDeleteDialog.java b/src/main/java/zander/ui/library/LibraryDeleteDialog.java new file mode 100644 index 0000000..d89b323 --- /dev/null +++ b/src/main/java/zander/ui/library/LibraryDeleteDialog.java @@ -0,0 +1,89 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import zander.ui.UIUtils; + +public class LibraryDeleteDialog extends JDialog { + private static final long serialVersionUID = 1L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_DELETE = 1; + + private int response = RESPONSE_CANCEL; + + private final JLabel iconLabel; + private final JLabel messageLabel; + private final JCheckBox diskBox; + private final JPanel dialogPanel; + private final JButton cancelButton; + private final JButton deleteButton; + private final JPanel buttonPanel; + private final JPanel content; + + public LibraryDeleteDialog(Window parent, String name) { + super(parent, "Delete Library"); + iconLabel = new JLabel(UIManager.getIcon("OptionPane.questionIcon")); + messageLabel = new JLabel("Are you sure you would like to delete the library '" + name + "'?"); + diskBox = new JCheckBox("Delete From Disk (Cannot Be Undone)"); + dialogPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridy = 0; + gbc.gridx = 0; + dialogPanel.add(iconLabel, gbc); + gbc.gridx = 1; + dialogPanel.add(messageLabel, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.gridwidth = 2; + dialogPanel.add(diskBox, gbc); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + deleteButton = new JButton("Delete"); + cancelButton.setMnemonic(KeyEvent.VK_D); + deleteButton.addActionListener((e) -> { + dispose(); + response = RESPONSE_DELETE; + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(deleteButton); + content = new JPanel(new BorderLayout()); + content.add(dialogPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + pack(); + UIUtils.centerWindow(this, parent); + } + + public int getResponse() { + return response; + } + + public boolean isDeleteFromDisk() { + return diskBox.isSelected(); + } + +} diff --git a/src/main/java/zander/ui/library/LibraryEditDialog.java b/src/main/java/zander/ui/library/LibraryEditDialog.java new file mode 100644 index 0000000..92e9a58 --- /dev/null +++ b/src/main/java/zander/ui/library/LibraryEditDialog.java @@ -0,0 +1,290 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.io.File; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.ui.FileChooserField; +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class LibraryEditDialog extends JDialog { + private static final long serialVersionUID = -6438014653555033365L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_SAVE = 1; + + private int response = RESPONSE_CANCEL; + + private final JLabel nameLabel; + private final JTextField nameField; + private final JLabel urlLabel; + private final JTextField urlField; + private final JLabel ftpLabel; + private final JTextField ftpField; + private final JLabel usernameLabel; + private final JTextField usernameField; + private final JLabel passwordLabel; + private final JPasswordField passwordField; + private final JCheckBox defaultPathCheck; + private final JLabel pathLabel; + private final FileChooserField pathField; + private final JPanel entryPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton clearCacheButton; + private final JButton saveButton; + private final JPanel buttonPanel; + private final JPanel panel; + + public LibraryEditDialog(Window parent, String name, String url, String ftp, String username, String password, String file, Runnable cacheClearCallback) { + super(parent, "Edit Library"); + nameLabel = new JLabel("Name:"); + nameField = new JTextField(10); + nameField.setText(name); + urlLabel = new JLabel("(Optional) URL:"); + urlField = new JTextField(10); + urlField.setText(url); + ftpLabel = new JLabel("FTP:"); + ftpField = new JTextField(10); + ftpField.setText(ftp); + usernameLabel = new JLabel("Username:"); + usernameField = new JTextField(10); + usernameField.setText(username); + passwordLabel = new JLabel("Password:"); + passwordField = new JPasswordField(10); + passwordField.setText(password); + defaultPathCheck = new JCheckBox("Use default library location"); + pathLabel = new JLabel("Library Path:"); + pathField = new FileChooserField(10, file, new File(LibrarySelectFrame.DEFAULT_LIBRARY_PATH)); + if (file.equals(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + name)) { + defaultPathCheck.setSelected(true); + pathField.setEnabled(false); + } else { + defaultPathCheck.setSelected(false); + } + pathField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + pathField.getChooser().setDialogTitle("Select Library Location"); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (defaultPathCheck.isSelected()) { + pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); + } + } + }); + defaultPathCheck.addItemListener((e) -> { + if (defaultPathCheck.isSelected()) { + pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); + pathField.setEnabled(false); + } else { + pathField.setEnabled(true); + } + }); + entryPanel = new JPanel(new GridBagLayout()); + buildEntryPanel(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + clearCacheButton = new JButton("Clear Cache"); + clearCacheButton.setMnemonic(KeyEvent.VK_L); + clearCacheButton.addActionListener((e) -> { + int c = JOptionPane.showConfirmDialog(this, "Are you sure you would like to clear this library's cache?", + "Clear Cahce", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (cacheClearCallback != null && c == JOptionPane.YES_OPTION) { + cacheClearCallback.run(); + } + }); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); + saveButton = new JButton("Save"); + saveButton.setMnemonic(KeyEvent.VK_S); + saveButton.setEnabled(false); + saveButton.addActionListener((e) -> { + if (nameField.getText().contains("/")) { + JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE); + } else { + dispose(); + response = RESPONSE_SAVE; + } + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(clearCacheButton); + buttonPanel.add(saveButton); + final DocumentListener formFillListener = new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (!nameField.getText().isBlank() && !ftpField.getText().isBlank() + && !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) { + saveButton.setEnabled(true); + } else { + saveButton.setEnabled(false); + } + } + }; + nameField.getDocument().addDocumentListener(formFillListener); + ftpField.getDocument().addDocumentListener(formFillListener); + usernameField.getDocument().addDocumentListener(formFillListener); + pathField.getPathField().getDocument().addDocumentListener(formFillListener); + panel = new JPanel(new BorderLayout()); + panel.add(entryPanel, BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(panel); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setModalityType(DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + setAlwaysOnTop(true); + pack(); + UIUtils.centerWindow(this, parent); + formFillListener.changedUpdate(null); + } + + private void buildEntryPanel() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(1, 1, 1, 1); + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.gridy = 0; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(nameLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(nameField, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(urlLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(urlField, gbc); + gbc.gridy = 2; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(ftpLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(ftpField, gbc); + gbc.gridy = 3; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(usernameLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.fill = GridBagConstraints.HORIZONTAL; + entryPanel.add(usernameField, gbc); + gbc.gridy = 4; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(passwordLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(passwordField, gbc); + gbc.gridy = 5; + gbc.gridx = 0; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.CENTER; + gbc.gridwidth = 2; + entryPanel.add(defaultPathCheck, gbc); + gbc.gridwidth = 1; + gbc.gridy = 6; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(pathLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(pathField, gbc); + } + + public int getResponse() { + return response; + } + + public String getLibraryName() { + return nameField.getText(); + } + + public String getURL() { + return urlField.getText(); + } + + public String getFTP() { + return ftpField.getText(); + } + + public String getUsername() { + return usernameField.getText(); + } + + public String getPassword() { + return new String(passwordField.getPassword()); + } + + public String getLibraryFile() { + return pathField.getPathField().getText(); + } + +} diff --git a/src/main/java/zander/ui/library/LibraryExistsDialog.java b/src/main/java/zander/ui/library/LibraryExistsDialog.java new file mode 100644 index 0000000..48cc795 --- /dev/null +++ b/src/main/java/zander/ui/library/LibraryExistsDialog.java @@ -0,0 +1,82 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import zander.ui.UIUtils; + +public class LibraryExistsDialog extends JDialog { + private static final long serialVersionUID = -7358868972104777634L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_DELETE = 1; + public static final int RESPONSE_IMPORT = 2; + + private int response = RESPONSE_CANCEL; + + private final JLabel iconLabel; + private final JLabel messageLabel; + private final JPanel messagePanel; + private final JButton cancelButton; + private final JButton deleteButton; + private final JButton importButton; + private final JPanel buttonPanel; + private final JPanel content; + + public LibraryExistsDialog(Window window) { + super(window, "Library Exists"); + iconLabel = new JLabel(UIManager.getIcon("OptionPane.warningIcon")); + messageLabel = new JLabel("A library already exists in this location. You can import it, delete it, or cancel the opperation."); + messagePanel = new JPanel(); + messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.X_AXIS)); + messagePanel.add(iconLabel); + messagePanel.add(messageLabel); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + deleteButton = new JButton("Delete"); + deleteButton.setMnemonic(KeyEvent.VK_D); + deleteButton.addActionListener((e) -> { + dispose(); + response = RESPONSE_DELETE; + }); + importButton = new JButton("Import"); + importButton.setMnemonic(KeyEvent.VK_I); + importButton.addActionListener((e) -> { + dispose(); + response = RESPONSE_IMPORT; + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(deleteButton); + buttonPanel.add(importButton); + content = new JPanel(new BorderLayout()); + content.add(messagePanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + pack(); + UIUtils.centerWindow(this, window); + } + + public int getResponse() { + return response; + } + +} diff --git a/src/main/java/zander/ui/library/LibraryImportDialog.java b/src/main/java/zander/ui/library/LibraryImportDialog.java new file mode 100644 index 0000000..70dc3a4 --- /dev/null +++ b/src/main/java/zander/ui/library/LibraryImportDialog.java @@ -0,0 +1,201 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import zander.ui.FileChooserField; +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class LibraryImportDialog extends JDialog { + private static final long serialVersionUID = 8184071881411029915L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_IMPORT = 1; + + private int response = RESPONSE_CANCEL; + + private final JLabel urlLabel; + private final JTextField urlField; + private final JLabel ftpLabel; + private final JTextField ftpField; + private final JLabel usernameLabel; + private final JTextField usernameField; + private final JLabel passwordLabel; + private final JPasswordField passwordField; + private final JLabel fileLabel; + private final FileChooserField fileField; + private final JPanel entryPanel; + private final JButton cancelButton; + private final JButton helpButton; + private final JButton importButton; + private final JPanel buttonPanel; + private final JPanel content; + + public LibraryImportDialog(Window parent) { + super(parent, "Import Library"); + urlLabel = new JLabel("(Optional) URL:"); + urlField = new JTextField(10); + ftpLabel = new JLabel("FTP:"); + ftpField = new JTextField(10); + usernameLabel = new JLabel("Username:"); + usernameField = new JTextField(10); + passwordLabel = new JLabel("Password:"); + passwordField = new JPasswordField(10); + fileLabel = new JLabel("Source:"); + fileField = new FileChooserField(10); + fileField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fileField.getChooser().setMultiSelectionEnabled(false); + fileField.getChooser().setDialogType(JFileChooser.OPEN_DIALOG); + fileField.getChooser().setDialogTitle("Select Library Location"); + entryPanel = new JPanel(new GridBagLayout()); + buildEntryPanel(); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> { + dispose(); + }); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); + importButton = new JButton("Import"); + importButton.setMnemonic(KeyEvent.VK_I); + importButton.setEnabled(false); + importButton.addActionListener((e) -> { + dispose(); + response = RESPONSE_IMPORT; + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(importButton); + final DocumentListener formFillListener = new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + importButton.setEnabled(!ftpField.getText().isBlank() && !usernameField.getText().isBlank() && !fileField.getPathField().getText().isBlank()); + } + }; + ftpField.getDocument().addDocumentListener(formFillListener); + usernameField.getDocument().addDocumentListener(formFillListener); + fileField.getPathField().getDocument().addDocumentListener(formFillListener); + content = new JPanel(new BorderLayout()); + content.add(entryPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setResizable(false); + pack(); + UIUtils.centerWindow(this, parent); + } + + private void buildEntryPanel() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.insets = new Insets(1, 1, 1, 1); + gbc.fill = GridBagConstraints.NONE; + gbc.gridy = 0; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + entryPanel.add(urlLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(urlField, gbc); + gbc.gridy = 1; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(ftpLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(ftpField, gbc); + gbc.gridy = 2; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(usernameLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(usernameField, gbc); + gbc.gridy = 3; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(passwordLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(passwordField, gbc); + gbc.gridy = 4; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + entryPanel.add(fileLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + entryPanel.add(fileField, gbc); + } + + public int getResponse() { + return response; + } + + public String getURL() { + return urlField.getText(); + } + + public String getFTP() { + return ftpField.getText(); + } + + public String getUsername() { + return usernameField.getText(); + } + + public String getPassword() { + return new String(passwordField.getPassword()); + } + + public String getFile() { + return fileField.getPathField().getText(); + } + +} diff --git a/src/main/java/zander/ui/library/LibrarySelectFrame.java b/src/main/java/zander/ui/library/LibrarySelectFrame.java new file mode 100644 index 0000000..cd122cc --- /dev/null +++ b/src/main/java/zander/ui/library/LibrarySelectFrame.java @@ -0,0 +1,997 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.prefs.Preferences; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.DropMode; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.Library; +import zander.library.Manifest.ManifestParseException; +import zander.library.secrets.Secrets; +import zander.library.secrets.SecretsFactory; +import zander.ui.ErrorDialog; +import zander.ui.LibraryFrame; +import zander.ui.RememberConfirmDialog; +import zander.ui.UIUtils; +import zander.ui._import.ImportLibrarySelectDialog; +import zander.ui.docs.DocumentationViewer; +import zander.ui.transferhandlers.ListArrangeTransferHandler; + +public class LibrarySelectFrame extends JFrame { + private static final long serialVersionUID = 5170663213012571338L; + private static final Logger LOGGER = LoggerFactory.getLogger(LibrarySelectFrame.class); + private static final Preferences LSF_CACHE = UIUtils.PREFS_ROOT.node("lsf-cache"); + private static final Preferences CURATOR_SETTINGS = UIUtils.PREFS_ROOT.node("settings"); + + public static final String SECRETS_KEY = "curator-secrets/"; + + public static class LibraryEntry implements Serializable { + private static final long serialVersionUID = 2910805274300568269L; + + public String name; + public String url; + public String ftp; + public File file; + + public LibraryEntry(String name, String url, String ftp, File file) { + this.name = name; + this.url = url; + this.ftp = ftp; + this.file = file; + } + + @Override + public String toString() { + if (url == null || url.length() == 0) { + return name; + } + return name + " (" + url + ")"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof LibraryEntry) { + LibraryEntry e = (LibraryEntry) o; + return e.name.equals(name) && e.url.equals(url) && e.file.getPath().equals(file.getPath()); + } + return false; + } + + @Override + public int hashCode() { + return (name + "/" + url).hashCode(); + } + } + + public static String DEFAULT_LIBRARY_PATH = getDefaultLibraryPath(); + public static SecretsFactory SECRETS_FACTORY = loadSecretsFactory(); + public static Object DEFAULT_LIBRARY = loadDefaultLibrarySelection(); + public static LibraryEntry LAST_LIBRARY = settingsGetObject("last-library", null); + public static final DefaultListModel LIBRARY_ENTRIES = new DefaultListModel(); + public static String LOOK_AND_FEEL; + public static int LIBRARY_WEB_HANDLER = loadLibraryWebHandler(); + public static boolean ASK_TO_RETURN_TO_LIBRARY_SELECT = loadAskToBack(); + + public static final int LIBRARY_WEB_ASK = 0; + public static final int LIBRARY_WEB_DONT_OPEN = 1; + public static final int LIBRARY_WEB_OPEN = 2; + + private ImageIcon logoIcon; + private final JLabel logoLabel; + private final JLabel titleLabel; + private final JPanel titlePanel; + private final JLabel screenLabel; + private final JPanel topPanel; + private final JList list; + private final JScrollPane listScroll; + private final JMenuItem openItem; + private final JMenuItem editItem; + private final JMenuItem deleteItem; + private final JMenuItem newItem; + private final JMenuItem importItem; + private final JPopupMenu listPopup; + private final JButton quitButton; + private final JButton helpButton; + private final JButton optionsButton; + private final JButton importButton; + private final JButton newButton; + private final JButton deleteButton; + private final JButton editButton; + private final JButton openButton; + private final JPanel buttonPanel; + private final JPanel content; + + public LibrarySelectFrame(String[] args) { + super("Curator"); + loadInitialLookAndFeel(); + loadResources(); + readWindowCache(); + logoLabel = new JLabel(logoIcon); + logoLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, Math.round(UIUtils.SCREEN_SIZE.width * 0.01f))); + titleLabel = new JLabel("Curator"); + titleLabel.setFont(getTitleFont()); + titlePanel = new JPanel(); + titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.X_AXIS)); + titlePanel.add(logoLabel); + titlePanel.add(titleLabel); + screenLabel = new JLabel("Libraries:"); + screenLabel.setFont(getScreenLabelFont()); + topPanel = new JPanel(new BorderLayout()); + topPanel.add(titlePanel, BorderLayout.CENTER); + topPanel.add(screenLabel, BorderLayout.PAGE_END); + list = new JList(LIBRARY_ENTRIES); + list.setFont(getListFont()); + list.setDropMode(DropMode.INSERT); + list.setDragEnabled(true); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setTransferHandler(new ListArrangeTransferHandler()); + openItem = new JMenuItem("Open"); + openItem.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + openLibrary(i, false); + } + }); + editItem = new JMenuItem("Edit"); + editItem.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + editLibrary(i); + } + }); + deleteItem = new JMenuItem("Delete"); + deleteItem.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + askDeleteLibrary(i); + } + }); + newItem = new JMenuItem("New"); + newItem.addActionListener((e) -> { + LibraryCreateDialog nld = new LibraryCreateDialog(this); + nld.setVisible(true); + if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { + createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(), + nld.getUsername(), nld.getPassword(), new File(nld.getLibraryFile())); + } + }); + importItem = new JMenuItem("Import"); + importItem.addActionListener((e) -> { + LibraryImportDialog lid = new LibraryImportDialog(this); + lid.setVisible(true); + if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) { + importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(), lid.getPassword(), new File(lid.getFile())); + } + }); + listPopup = new JPopupMenu(); + listPopup.add(openItem); + listPopup.add(editItem); + listPopup.add(deleteItem); + listPopup.addSeparator(); + listPopup.add(newItem); + listPopup.add(importItem); + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { + Rectangle r = list.getCellBounds(0, list.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = list.locationToIndex(e.getPoint()); + openLibrary(i, false); + } + } else if (e.getButton() == MouseEvent.BUTTON3) { + Rectangle r = list.getCellBounds(0, list.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = list.locationToIndex(e.getPoint()); + list.setSelectedIndex(i); + } + boolean enabled = list.getSelectedIndex() >= 0; + openItem.setEnabled(enabled); + deleteItem.setEnabled(enabled); + editItem.setEnabled(enabled); + listPopup.show(list, e.getX(), e.getY()); + } + } + }); + loadLibraryList(); + LIBRARY_ENTRIES.addListDataListener(new ListDataListener(){ + @Override + public void intervalAdded(ListDataEvent e) { + saveLibraryList(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + saveLibraryList(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + saveLibraryList(); + } + }); + listScroll = new JScrollPane(list); + quitButton = new JButton("Quit"); + quitButton.setMnemonic(KeyEvent.VK_Q); + quitButton.addActionListener((e) -> { + System.exit(0); + }); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Select Window")); + optionsButton = new JButton("Settings"); + optionsButton.setMnemonic(KeyEvent.VK_S); + optionsButton.addActionListener((e) -> { + CuratorOptionsDialog cod = new CuratorOptionsDialog(this); + cod.setVisible(true); + if (cod.getResponse() == CuratorOptionsDialog.RESPONSE_SAVE) { + cod.updateSettigs(); + SwingUtilities.updateComponentTreeUI(this); + SwingUtilities.updateComponentTreeUI(listPopup); + screenLabel.setFont(getScreenLabelFont()); + titleLabel.setFont(getTitleFont()); + list.setFont(getListFont()); + } + }); + importButton = new JButton("Import"); + importButton.setMnemonic(KeyEvent.VK_I); + importButton.addActionListener((e) -> { + LibraryImportDialog lid = new LibraryImportDialog(this); + lid.setVisible(true); + if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) { + importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(), lid.getPassword(), new File(lid.getFile())); + } + }); + newButton = new JButton("New"); + newButton.setMnemonic(KeyEvent.VK_N); + newButton.addActionListener((e) -> { + LibraryCreateDialog nld = new LibraryCreateDialog(this); + nld.setVisible(true); + if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { + createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(), + nld.getUsername(), nld.getPassword(), new File(nld.getLibraryFile())); + } + }); + deleteButton = new JButton("Delete"); + deleteButton.setMnemonic(KeyEvent.VK_D); + deleteButton.setEnabled(false); + deleteButton.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + askDeleteLibrary(i); + } + }); + editButton = new JButton("Edit"); + editButton.setMnemonic(KeyEvent.VK_E); + editButton.setEnabled(false); + editButton.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + editLibrary(i); + } + }); + openButton = new JButton("Open"); + openButton.setMnemonic(KeyEvent.VK_O); + openButton.setEnabled(false); + openButton.addActionListener((e) -> { + int i = list.getSelectedIndex(); + if (i >= 0) { + openLibrary(i, false); + } + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(quitButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(optionsButton); + buttonPanel.add(importButton); + buttonPanel.add(newButton); + buttonPanel.add(deleteButton); + buttonPanel.add(editButton); + buttonPanel.add(openButton); + list.addListSelectionListener((e) -> { + boolean enabled = list.getSelectedIndex() >= 0; + openButton.setEnabled(enabled); + deleteButton.setEnabled(enabled); + editButton.setEnabled(enabled); + }); + content = new JPanel(new BorderLayout()); + content.add(topPanel, BorderLayout.PAGE_START); + content.add(listScroll, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + addComponentListener(new ComponentAdapter() { + @Override + public void componentMoved(ComponentEvent e) { + updateWindowCache(); + } + + @Override + public void componentResized(ComponentEvent e) { + updateWindowCache(); + } + }); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + updateWindowCache(); + System.exit(0); + } + }); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + handleAutoOpenOrShow(args); + } + + private Font getScreenLabelFont() { + return UIUtils.scaleFont(UIManager.getFont("Label.font"), 2.0f); + } + + private Font getListFont() { + return UIUtils.scaleFont(UIManager.getFont("List.font"), 2.0f); + } + + private Font getTitleFont() { + return UIUtils.scaleFont(UIManager.getFont("Label.font"), 5.0f); + } + + private void handleAutoOpenOrShow(String[] args) { + if (args.length != 0 && args[0].equals("-i")) { + handleImport(args, false, false); + } else if (args.length != 0 && args[0].equals("-s")) { + handleImport(args, true, false); + } else if (DEFAULT_LIBRARY.equals(CuratorOptionsDialog.LAST_DEFAULT_LIBRARY) && LAST_LIBRARY != null) { + openLibrary(LAST_LIBRARY, true); + } else if (DEFAULT_LIBRARY instanceof LibraryEntry) { + openLibrary((LibraryEntry) DEFAULT_LIBRARY, true); + } else { + setVisible(true); + } + } + + private void handleImport(String[] args, boolean reorder, boolean forceAsk) { + if (LIBRARY_ENTRIES.size() == 0) { + JOptionPane.showMessageDialog(null, "No libraries found! Please open Curator normally and add a library.", + "Import Error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } else if (args.length == 1) { + JOptionPane.showMessageDialog(null, "No files for importing provided!", "Import Error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + String[] files = Arrays.copyOfRange(args, 1, args.length); + for (String file : files) { + LOGGER.info("File to import: '{}'", file); + } + LibraryEntry en; + if (forceAsk) { + ImportLibrarySelectDialog ilsd = new ImportLibrarySelectDialog(LIBRARY_ENTRIES); + ilsd.setVisible(true); + en = ilsd.getSelectedLibrary(); + } else if (DEFAULT_LIBRARY.equals(CuratorOptionsDialog.LAST_DEFAULT_LIBRARY) && LAST_LIBRARY != null) { + en = LAST_LIBRARY; + } else if (DEFAULT_LIBRARY instanceof LibraryEntry) { + en = (LibraryEntry) DEFAULT_LIBRARY; + } else { + ImportLibrarySelectDialog ilsd = new ImportLibrarySelectDialog(LIBRARY_ENTRIES); + ilsd.setVisible(true); + en = ilsd.getSelectedLibrary(); + } + Library lib = syncLibrary(en); + if (lib == null) { + SwingUtilities.invokeLater(() -> handleImport(args, reorder, true)); + return; + } + LibraryFrame lf = new LibraryFrame(lib, this, en.name); + if (lf.wasCanceled()) { + SwingUtilities.invokeLater(() -> handleImport(args, reorder, true)); + return; + } + while (!lf.handleImports(files, reorder)) { + ImportLibrarySelectDialog ilsd = new ImportLibrarySelectDialog(LIBRARY_ENTRIES); + ilsd.setVisible(true); + en = ilsd.getSelectedLibrary(); + lib = syncLibrary(en); + if (lib == null) { + SwingUtilities.invokeLater(() -> handleImport(args, reorder, true)); + return; + } + lf = new LibraryFrame(lib, this, en.name); + if (lf.wasCanceled()) { + SwingUtilities.invokeLater(() -> handleImport(args, reorder, true)); + return; + } + } + } + + private void clearLibraryCache(LibraryEntry en) { + try { + if (en.file.exists()) { + recursiveDelete(en.file); + } + } catch (IOException e) { + LOGGER.error("Could not clear library cache!", e); + ErrorDialog ed = new ErrorDialog("Library Error", "Could not clear library cache!", e); + ed.setVisible(true); + } + } + + private void editLibrary(int index) { + editLibrary(LIBRARY_ENTRIES.get(index)); + } + + private String formatLibraryUrl(String url) { + if (!url.matches("^https?\\:\\/\\/.*")) { + return "https://" + url; + } + return url; + } + + private void editLibrary(LibraryEntry en) { + Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); + LibraryEditDialog led = new LibraryEditDialog(this, en.name, en.url, en.ftp, + s.getUsername(), s.getPassword(), en.file.getPath(), + () -> clearLibraryCache(en)); + led.setVisible(true); + if (led.getResponse() == LibraryEditDialog.RESPONSE_SAVE) { + String testName = ""; + String testPath = ""; + if (!led.getLibraryName().equals(en.name)) { + testName = led.getName(); + } + if (!led.getLibraryFile().equals(en.file.getPath())) { + testPath = led.getLibraryFile(); + } + if (canCreateLibrary(testName, testPath)) { + try { + if (!led.getLibraryName().equals(en.name)) { + Secrets ns = SECRETS_FACTORY.createSecrets(SECRETS_KEY + led.getLibraryName()); + ns.setAll(s.getKeys()); + s.delete(); + } else { + s.setUsername(led.getUsername()); + s.setPassword(led.getPassword()); + } + File nf = new File(led.getLibraryFile()); + if (en.file.exists() && !en.file.renameTo(nf)) { + throw new IOException("Could not move library to: '" + nf.getPath() + "'"); + } + en.name = led.getLibraryName(); + en.url = formatLibraryUrl(led.getURL()); + en.ftp = led.getFTP(); + en.file = nf; + saveLibraryList(); + list.repaint(); + } catch (IOException e) { + LOGGER.error("Could not move library", e); + ErrorDialog ed = new ErrorDialog("Library Error", "Could not move library", e); + ed.setVisible(true); + } + } else { + Exception e = new Exception("A library with that name or path already exists!"); + LOGGER.error("Library had a conflict!", e ); + ErrorDialog ed = new ErrorDialog("Library Error", "A library with that name or path already exists!", e); + ed.setVisible(true); + } + } + } + + private void openLibrary(int index, boolean isAuto) { + openLibrary(LIBRARY_ENTRIES.get(index), isAuto); + } + + private void openLibrary(LibraryEntry en, boolean isAuto) { + Library lib = syncLibrary(en); + if (lib != null) { + updateWindowCache(); + setVisible(false); + LibraryFrame lf = new LibraryFrame(lib, this, en.name); + if (lf.wasCanceled()) { + setVisible(true); + } else { + lf.setVisible(true); + } + } else if (isAuto) { + setVisible(true); + } + } + + private Library syncLibrary(LibraryEntry en) { + LAST_LIBRARY = en; + settingsPutObject("last-library", en); + Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); + try { + Library lib = new Library(en.file, new URI(en.ftp), s); + LibrarySyncDialog lsd = new LibrarySyncDialog(null, lib); + if (lsd.sync() == LibrarySyncDialog.STATUS_OK) { + return lib; + } + } catch (URISyntaxException e) { + LOGGER.error("Bad URL: '{}'", en.ftp, e); + ErrorDialog ed = new ErrorDialog("Library Error", "The url '" + en.ftp + "' is malformed!", e); + ed.setVisible(true); + } catch (IOException | ManifestParseException e) { + LOGGER.error("Could not open library!", e); + ErrorDialog ed = new ErrorDialog("Library Error", "An internal error occurend! Could not open library.", e); + ed.setVisible(true); + } + return null; + } + + public String getNameFromLibraryConfigFile(String path) { + JSONParser p = new JSONParser(); + try { + FileReader in = new FileReader(path); + JSONObject root = (JSONObject) p.parse(in); + in.close(); + return (String) root.get("library_name"); + } catch (IOException e) { + LOGGER.error("Could not read library config: '{}'", e); + ErrorDialog ed = new ErrorDialog("Import Error", "Could not read library config file!", e); + ed.setVisible(true); + } catch (ParseException | ClassCastException e) { + LOGGER.error("Library config invalid: '{}'", e); + ErrorDialog ed = new ErrorDialog("Import Error", "Library config file was invalid!", e); + ed.setVisible(true); + } + return null; + } + + public void promptWebOpen(Component source) { + if (LAST_LIBRARY != null && !LAST_LIBRARY.url.isBlank()) { + int opt = LIBRARY_WEB_HANDLER; + if (opt == LIBRARY_WEB_ASK) { + Window sw; + if (source == null) { + sw = null; + } else { + sw = SwingUtilities.getWindowAncestor(source); + } + RememberConfirmDialog diag = new RememberConfirmDialog(sw, "Confirm Open", + "Upload complete! Would you like " + + "to open this library's website: \"" + + LAST_LIBRARY.url + "\"?"); + diag.setVisible(true); + if (diag.getResponse() == RememberConfirmDialog.RESPONSE_NO) { + opt = LIBRARY_WEB_DONT_OPEN; + } else { + opt = LIBRARY_WEB_OPEN; + } + if (diag.rememberAwnser()) { + updateWebOpenHandler(opt); + } + } + if (opt == LIBRARY_WEB_OPEN) { + Desktop d = Desktop.getDesktop(); + try { + d.browse(new URI(LAST_LIBRARY.url)); + } catch (Exception e) { + LOGGER.warn("Could not open URL: \"{}\"", LAST_LIBRARY.url, e); + ErrorDialog ed = new ErrorDialog("Could not open URL", "Could not open library with URL: " + + "\"" + LAST_LIBRARY.url + "\"!", e); + ed.setVisible(true); + } + } + } + } + + private void importLibrary(String url, String ftp, String username, String password, File dir) { + File cf = null; + try { + cf = dir.getCanonicalFile(); + } catch (IOException e) { + LOGGER.error("Could not resolve file path: '{}'", dir.getPath(), e); + ErrorDialog ed = new ErrorDialog("Import Error", "An internal error has occured, please try again.", e); + ed.setVisible(true); + return; + } + String name = getNameFromLibraryConfigFile(cf.getPath() + "/config.json"); + if (!canCreateLibrary(name, cf.getPath())) { + JOptionPane.showMessageDialog(this, "A library with that path already exists.", "Library Error", JOptionPane.ERROR_MESSAGE); + return; + } + Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); + s.setUsername(username); + s.setPassword(password); + LibraryEntry en = new LibraryEntry(name, url, ftp, cf); + LIBRARY_ENTRIES.add(0, en); + openLibrary(en, false); + } + + private void askDeleteLibrary(int index) { + LibraryEntry en = LIBRARY_ENTRIES.get(index); + LibraryDeleteDialog ldd = new LibraryDeleteDialog(this, en.name); + ldd.setVisible(true); + if (ldd.getResponse() == LibraryDeleteDialog.RESPONSE_DELETE) { + deleteLibrary(index, ldd.isDeleteFromDisk()); + } + } + + private void recursiveDelete(File f) throws IOException { + if (f.isDirectory()) { + for (File c : f.listFiles()) { + recursiveDelete(c); + } + } + if (!f.delete()) { + throw new IOException("Could not delete file: '" + f.getPath() + "'"); + } + } + + private void deleteLibrary(int index, boolean disk) { + LibraryEntry en = LIBRARY_ENTRIES.get(index); + if (disk) { + try { + if (en.file.exists()) { + recursiveDelete(en.file); + } + } catch (IOException e) { + LOGGER.error("Could not delete file: '{}'", en.file.getPath(), e); + ErrorDialog ed = new ErrorDialog("Library Deletion Error", "Could not delete library '" + en.name + "'!", e); + ed.setVisible(true); + return; + } + } + Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); + s.delete(); + if (LAST_LIBRARY.equals(en)) { + settingsPutObject("last-library", null); + LAST_LIBRARY = null; + } + if (DEFAULT_LIBRARY.equals(en)) { + updateDefaultLibrarySelection(CuratorOptionsDialog.NO_DEFAULT_LIBRARY); + } + LIBRARY_ENTRIES.remove(index); + } + + private boolean canCreateLibrary(String name, String path) { + int max = LIBRARY_ENTRIES.getSize(); + for (int i = 0; i < max; ++i) { + LibraryEntry e = LIBRARY_ENTRIES.get(i); + if (e.name.equals(name) || e.file.getPath().equals(path)) { + return false; + } + } + return true; + } + + private void createLibrary(String name, String url, String ftp, String username, String password, File file) { + File cf = null; + try { + cf = file.getCanonicalFile(); + } catch (IOException e) { + LOGGER.error("Could not query pathname for file: '{}'", file.getPath()); + ErrorDialog ed = new ErrorDialog("Library Error", "An internal error occured. Please try again.", e); + ed.setVisible(true); + return; + } + if (!canCreateLibrary(name, cf.getPath())) { + JOptionPane.showMessageDialog(this, "A library with that name or path already exists.", "Library Error", JOptionPane.ERROR_MESSAGE); + return; + } + File configFile = new File(cf.getPath() + "/config.json"); + if (configFile.exists()) { + LibraryExistsDialog led = new LibraryExistsDialog(this); + led.setVisible(true); + try { + switch (led.getResponse()) { + case LibraryExistsDialog.RESPONSE_DELETE: + recursiveDelete(cf); + break; + case LibraryExistsDialog.RESPONSE_IMPORT: + importLibrary(url, ftp, username, password, file); + return; + default: + return; + } + } catch (IOException e) { + LOGGER.error("Could not delete or import library!", e); + ErrorDialog ed = new ErrorDialog("Internal Error", "An internal error has occured! Please try again.", e); + ed.setVisible(true); + return; + } + } else if (cf.exists()) { + int c = JOptionPane.showConfirmDialog(this, "A non-library file already exists here. Delete it?", "File Exists", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (c == JOptionPane.YES_OPTION) { + try { + if (!cf.delete()) { + throw new IOException("Could not delete file: '" + cf.getPath() + "'"); + } + } catch (IOException e) { + LOGGER.error("Could not delete library!", e); + ErrorDialog ed = new ErrorDialog("Internal Error", "An internal error has occured! Please try again.", e); + ed.setVisible(true); + return; + } + } + } + Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); + s.setUsername(username); + s.setPassword(password); + LibraryEntry en = new LibraryEntry(name, url, ftp, cf); + LIBRARY_ENTRIES.add(0, en); + openLibrary(en, false); + } + + private static Object loadDefaultLibrarySelection() { + return settingsGetObject("default-library", CuratorOptionsDialog.NO_DEFAULT_LIBRARY); + } + + public static void updateDefaultLibrarySelection(Object def) { + DEFAULT_LIBRARY = def; + settingsPutObject("default-library", def); + } + + private static int loadLibraryWebHandler() { + return CURATOR_SETTINGS.getInt("web-open", LIBRARY_WEB_ASK); + } + + public static void updateWebOpenHandler(int def) { + LIBRARY_WEB_HANDLER = def; + CURATOR_SETTINGS.putInt("web-open", def); + } + + private static boolean loadAskToBack() { + return CURATOR_SETTINGS.getBoolean("ask-to-back", true); + } + + public static void updateAskToBack(boolean def) { + ASK_TO_RETURN_TO_LIBRARY_SELECT = def; + CURATOR_SETTINGS.putBoolean("ask-to-back", def); + } + + private void loadLibraryList() { + LibraryEntry[] ents = settingsGetObject("entries", new LibraryEntry[0]); + LIBRARY_ENTRIES.addAll(Arrays.asList(ents)); + } + + private void saveLibraryList() { + LibraryEntry[] ents = new LibraryEntry[LIBRARY_ENTRIES.getSize()]; + for (int i = 0; i < ents.length; ++i) { + ents[i] = LIBRARY_ENTRIES.get(i); + } + settingsPutObject("entries", ents); + } + + private void readWindowCache() { + int cw = LSF_CACHE.getInt("width", -1); + int ch = LSF_CACHE.getInt("height", -1); + if (cw < 0 || ch < 0) { + int w = UIUtils.SCREEN_SIZE.width / 2; + int h = UIUtils.SCREEN_SIZE.height / 2; + setSize(w, h); + LSF_CACHE.putInt("width", w); + LSF_CACHE.putInt("height", h); + } else { + setSize(cw, ch); + } + int cx = LSF_CACHE.getInt("x", -1); + int cy = LSF_CACHE.getInt("y", -1); + if (cx < 0 || cy < 0) { + UIUtils.centerWindow(this); + LSF_CACHE.putInt("x", getX()); + LSF_CACHE.putInt("y", getY()); + } else { + setLocation(cx, cy); + } + setExtendedState(LSF_CACHE.getInt("extended", NORMAL)); + } + + private void updateWindowCache() { + LSF_CACHE.putInt("x", Math.max(0, getX())); + LSF_CACHE.putInt("y", Math.max(0, getY())); + LSF_CACHE.putInt("width", getWidth()); + LSF_CACHE.putInt("height", getHeight()); + int es = getExtendedState(); + if (es != ICONIFIED) { + LSF_CACHE.putInt("extended", es); + } + } + + private void loadResources() { + try { + int ld = UIUtils.getLargerScreenDimension(); + logoIcon = new ImageIcon(UIUtils.loadBufferedImage(ClassLoader.getSystemResource("logo.svg"), -1, + Math.round(ld * 0.07f))); + } catch (IOException e) { + LOGGER.error("Could not load logo from resource", e); + ErrorDialog ed = new ErrorDialog("Internal Error", "An internal error has occurend, please try again.", e); + ed.setVisible(true); + } + } + + private static String getDefaultLibraryPath() { + String xdg = System.getenv("XDG_CONFIG_HOME"); + String back; + if (xdg == null) { + back = System.getProperty("user.home") + "/.config/curator"; + } else { + back = xdg + "/curator"; + } + String path = CURATOR_SETTINGS.get("library-dir", back); + File file = new File(path); + try { + File cf = file.getCanonicalFile(); + if (!file.exists()) { + File pf = cf.getParentFile(); + pf.mkdirs(); + if (!cf.mkdir()) { + throw new IOException("Could not create library store: '" + cf.getPath() + "'"); + } + } + return cf.getPath(); + } catch (IOException e) { + LOGGER.error("Could not create library store", e); + ErrorDialog ed = new ErrorDialog("Fatal Error", "Could not create library store!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + public static boolean updateLibraryPath(String path) { + File file = new File(path); + File cf = null; + try { + if (!file.exists()) { + cf = file.getCanonicalFile(); + File pf = cf.getParentFile(); + pf.mkdirs(); + if (!cf.mkdir()) { + throw new IOException("Could not create library store: '" + cf.getPath() + "'"); + } + } + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + DEFAULT_LIBRARY_PATH = path; + CURATOR_SETTINGS.put("library-dir", DEFAULT_LIBRARY_PATH); + return true; + } catch (IOException e) { + LOGGER.error("Could not create library store", e); + ErrorDialog ed = new ErrorDialog("Fatal Error", "Could not create library store!", e); + ed.setVisible(true); + return false; + } + } + + private static SecretsFactory loadSecretsFactory() { + return settingsGetObject("secrets-backend", SecretsFactory.getDefaultFactory()); + } + + public static boolean updateSecretsFactory(SecretsFactory sf) { + SecretsFactory osf = SECRETS_FACTORY; + SECRETS_FACTORY = sf; + settingsPutObject("secrets-backend", SECRETS_FACTORY); + int max = LIBRARY_ENTRIES.size(); + for (int i = 0; i < max; ++i) { + LibraryEntry e = LIBRARY_ENTRIES.get(i); + Secrets o = osf.createSecrets(SECRETS_KEY + e.name); + Secrets n = sf.createSecrets(SECRETS_KEY + e.name); + n.setAll(o.getKeys()); + o.delete(); + } + return true; + } + + public static boolean updateLookAndFeel(String name) { + if (loadLookAndFeel(name, false)) { + CURATOR_SETTINGS.put("laf", name); + return true; + } + return false; + } + + private static void loadInitialLookAndFeel() { + if (!loadLookAndFeel(CURATOR_SETTINGS.get("laf", UIUtils.SYSTEM_LAF_NAME), true)) { + System.exit(0); + } + } + + private static boolean loadLookAndFeel(String laf, boolean fallback) { + if (laf.length() != 0) { + try { + UIManager.setLookAndFeel(UIManager.createLookAndFeel(laf)); + LOOK_AND_FEEL = laf; + return true; + } catch (UnsupportedLookAndFeelException e) { + LOGGER.error("Could not load look and feel '{}'", laf); + ErrorDialog ed = new ErrorDialog("Fatal Error", "Could not load theme '" + laf + "'.", e); + ed.setVisible(true); + if (!fallback) { + return false; + } + } + } + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + LOOK_AND_FEEL = UIUtils.SYSTEM_LAF_NAME; + return true; + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | + IllegalAccessException e) { + LOGGER.error("Could not load system look and feel"); + ErrorDialog ed = new ErrorDialog("Fatal Error", "Could not load system default theme.", e); + ed.setVisible(true); + return false; + } + } + + public static void settingsPutObject(String key, Object o) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(o); + out.close(); + } catch (IOException e) { + // ignore + } + CURATOR_SETTINGS.putByteArray(key, baos.toByteArray()); + } + + @SuppressWarnings("unchecked") + public static T settingsGetObject(String key, T def) { + byte[] baDef = new byte[0]; + byte[] baVal = CURATOR_SETTINGS.getByteArray(key, baDef); + if (baVal == baDef) { + return def; + } + ByteArrayInputStream bais = new ByteArrayInputStream(baVal); + try { + ObjectInputStream in = new ObjectInputStream(bais); + Object o = in.readObject(); + in.close(); + return (T) o; + } catch (IOException | ClassCastException | ClassNotFoundException e) { + return def; + } + } + +} diff --git a/src/main/java/zander/ui/library/LibrarySyncDialog.java b/src/main/java/zander/ui/library/LibrarySyncDialog.java new file mode 100644 index 0000000..53b28c5 --- /dev/null +++ b/src/main/java/zander/ui/library/LibrarySyncDialog.java @@ -0,0 +1,133 @@ +package zander.ui.library; + +import java.awt.BorderLayout; +import java.awt.Window; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +import zander.library.Library; +import zander.library.Library.SyncStatus; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class LibrarySyncDialog extends JDialog { + private static final long serialVersionUID = -7042632100445849219L; + + public static final int STATUS_OK = 0; + public static final int STATUS_CANCELED = 1; + public static final int STATUS_FAILED = 2; + + private final Library library; + + private JLabel messageLabel; + private JProgressBar progressBar; + private JTextArea statusArea; + private JScrollPane statusScroll; + private JPanel statusPanel; + private JButton cancelButton; + private JPanel buttonPanel; + private final JPanel content; + + private SyncStatus status; + + private String lastNote = ""; + + public LibrarySyncDialog(Window parent, Library library) { + super(parent, "Sync Library"); + this.library = library; + messageLabel = new JLabel("Syncing Library..."); + progressBar = new JProgressBar(0, 1); + progressBar.setValue(0); + progressBar.setStringPainted(true); + statusArea = new JTextArea(10, 15); + statusArea.setEditable(false); + statusScroll = new JScrollPane(statusArea); + statusPanel = new JPanel(); + statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.Y_AXIS)); + statusPanel.add(progressBar); + statusPanel.add(statusScroll); + cancelButton = new JButton("Cancel"); + cancelButton.addActionListener((e) -> { + if (status != null && status.isStarted() && !status.isDone()) { + status.cancel(); + } + dispose(); + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(cancelButton); + content = new JPanel(new BorderLayout()); + content.add(messageLabel, BorderLayout.PAGE_START); + content.add(statusPanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setMinimumSize(getPreferredSize()); + UIUtils.centerWindow(this, parent); + } + + public Library getLibrary() { + return library; + } + + private void addMessage(String m) { + String t = statusArea.getText(); + if (t.isBlank()) { + statusArea.setText(m); + } else { + statusArea.setText(t + "\n" + m); + } + } + + private void syncUpdateAction(SyncStatus status) { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> syncUpdateAction(status)); + } else { + if (status.isFailed()) { + dispose(); + ErrorDialog ed = new ErrorDialog("Sync Failed", "Failed to sync local cahce to remote store!", status.getError()); + ed.setVisible(true); + } else if (status.isDone()) { + dispose(); + } else { + if (status.getTotal() < 0) { + progressBar.setIndeterminate(true); + } else { + progressBar.setIndeterminate(false); + progressBar.setMaximum(status.getTotal()); + } + progressBar.setValue(status.getCurrent()); + String note = status.getNote(); + if (note != null && !note.isBlank() && !note.equals(lastNote)) { + addMessage(note); + lastNote = note; + } + } + } + } + + public int sync() { + status = library.syncLocalToRemoteAsync(this::syncUpdateAction); + setVisible(true); + dispose(); + if (status.isFailed()) { + return STATUS_FAILED; + } else if (status.isCanceled()) { + return STATUS_CANCELED; + } + return STATUS_OK; + } + +} diff --git a/src/main/java/zander/ui/media/MediaManagePanel.java b/src/main/java/zander/ui/media/MediaManagePanel.java new file mode 100644 index 0000000..22f919a --- /dev/null +++ b/src/main/java/zander/ui/media/MediaManagePanel.java @@ -0,0 +1,747 @@ +package zander.ui.media; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.DropMode; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileNameExtensionFilter; + +import com.drew.imaging.ImageProcessingException; + +import org.apache.tika.Tika; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import zander.library.LibraryMediaCache; +import zander.library.media.FFmpeg; +import zander.library.media.MediaData; +import zander.library.media.MediaLoader; +import zander.library.metadata.MediaMetadata; +import zander.library.metadata.MetadataLoader; +import zander.library.state.AlbumState; +import zander.library.state.LibraryState; +import zander.library.state.MediaState; +import zander.ui.ErrorDialog; +import zander.ui.LibraryFrame; +import zander.ui.LibraryUploadDialog; +import zander.ui.ProgressTracker; +import zander.ui.ProgressTracker.ProgressManager; +import zander.ui.UIUtils; +import zander.ui.cellrenderer.MediaCellRenderer; +import zander.ui.docs.DocumentationViewer; +import zander.ui.transferhandlers.ListArrangeTransferHandler; + +public class MediaManagePanel extends JPanel { + private static final long serialVersionUID = 2125609797237528074L; + private static final Logger LOGGER = LoggerFactory.getLogger(MediaManagePanel.class); + private static final Tika TIKA = new Tika(); + + private final LibraryState library; + private final AlbumState album; + private final LibraryMediaCache cache; + + private final JPopupMenu popupMenu; + private final JMenuItem deleteItem; + private final JMenuItem exportItem; + private final JMenuItem addItem; + private final JMenuItem editItem; + private final JButton backButton; + private final JLabel searchLabel; + private final JTextField searchField; + private final JButton uploadButton; + private final JButton helpButton; + private final JButton setThumbnailButton; + private final JButton deleteButton; + private final JButton exportButton; + private final JButton addButton; + private final JButton editButton; + private final JPanel buttonPanel; + private final JLabel screenLabel; + private final JPanel topPanel; + private final DefaultListModel mediaModel; + private final TransferHandler mediaTransferHandler; + private final JList mediaList; + private final JScrollPane mediaScroll; + private final MediaPropertyPanel mediaPanel; + private final JLabel multipleMediaLabel; + private final JLabel noMediaLabel; + private final JSplitPane splitPane; + + private int lastClicked = -1; + private Component currentComp; + private boolean canceled = false; + private boolean firstToggle = true; + + public MediaManagePanel(LibraryFrame parent, AlbumState album) { + super(new BorderLayout()); + library = parent.getLibraryState(); + this.album = album; + this.cache = parent.getMediaCache(); + popupMenu = new JPopupMenu(); + deleteItem = new JMenuItem("Delete"); + deleteItem.setEnabled(false); + deleteItem.addActionListener((e) -> deleteMediaHandler()); + exportItem = new JMenuItem("Export"); + exportItem.setEnabled(false); + exportItem.addActionListener((e) -> exportMediaHandler()); + addItem = new JMenuItem("Add"); + addItem.addActionListener((e) -> addMedia()); + editItem = new JMenuItem("Toggle Property Panel"); + editItem.addActionListener((e) -> togglePropertyPanel()); + popupMenu.add(deleteItem); + popupMenu.addSeparator(); + popupMenu.add(exportItem); + popupMenu.add(addItem); + popupMenu.add(editItem); + backButton = new JButton("Back to Albums"); + backButton.setMnemonic(KeyEvent.VK_B); + backButton.addActionListener((e) -> parent.backButtonHandler()); + searchLabel = new JLabel("Search:"); + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + doSearch(searchField.getText()); + } + }); + uploadButton = new JButton("Upload"); + uploadButton.setMnemonic(KeyEvent.VK_U); + uploadButton.addActionListener((e) -> { + LibraryUploadDialog diag = new LibraryUploadDialog(SwingUtilities.getWindowAncestor(this)); + if (diag.upload(library, cache)) { + parent.handleUploadDone(this); + } + }); + helpButton = new JButton("?"); + helpButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (currentComp == splitPane) { + DocumentationViewer.show(MediaManagePanel.this, "Media/Media Properties"); + } else { + DocumentationViewer.show(MediaManagePanel.this, "Media/Managing Media"); + } + } + }); + setThumbnailButton = new JButton("Set As Thumbnail"); + setThumbnailButton.setMnemonic(KeyEvent.VK_T); + setThumbnailButton.setEnabled(false); + setThumbnailButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MediaState tm = mediaList.getSelectedValue(); + if (tm != null) { + album.setThumbnailName(tm.getName()); + mediaPanel.updateIsThumbnail(); + } + repaint(); + } + }); + deleteButton = new JButton("Delete"); + deleteButton.addActionListener((e) -> deleteMediaHandler()); + deleteButton.setEnabled(false); + deleteButton.setMnemonic(KeyEvent.VK_D); + exportButton = new JButton("Export"); + exportButton.addActionListener((e) -> exportMediaHandler()); + exportButton.setEnabled(false); + exportButton.setMnemonic(KeyEvent.VK_E); + addButton = new JButton("Add"); + addButton.addActionListener((e) -> addMedia()); + addButton.setMnemonic(KeyEvent.VK_A); + editButton = new JButton("Toggle Property Panel"); + editButton.setMnemonic(KeyEvent.VK_O); + editButton.addActionListener((e) -> togglePropertyPanel()); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(backButton); + buttonPanel.add(searchLabel); + buttonPanel.add(searchField); + buttonPanel.add(uploadButton); + buttonPanel.add(helpButton); + buttonPanel.add(setThumbnailButton); + buttonPanel.add(deleteButton); + buttonPanel.add(exportButton); + buttonPanel.add(addButton); + buttonPanel.add(editButton); + screenLabel = new JLabel(album.getName(), SwingConstants.CENTER); + screenLabel.setFont(UIUtils.scaleFont(UIManager.getFont("Label.font"), 2.0f)); + topPanel = new JPanel(new BorderLayout()); + topPanel.add(buttonPanel, BorderLayout.CENTER); + topPanel.add(screenLabel, BorderLayout.PAGE_END); + mediaModel = new DefaultListModel(); + mediaTransferHandler = new ListArrangeTransferHandler(new Runnable() { + @Override + public void run() { + mediaPanel.updateName(); + } + }, () -> { + album.getMedia().clear(); + for (int i = 0; i< mediaModel.size(); ++i) { + album.getMedia().add(mediaModel.get(i)); + } + }); + mediaList = new JList(mediaModel); + mediaList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + Rectangle r = mediaList.getCellBounds(0, mediaList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = mediaList.locationToIndex(e.getPoint()); + lastClicked = i; + } + } else if (e.getButton() == MouseEvent.BUTTON3) { + Rectangle r = mediaList.getCellBounds(0, mediaList.getLastVisibleIndex()); + if (r != null && r.contains(e.getPoint())) { + int i = mediaList.locationToIndex(e.getPoint()); + if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) { + mediaList.addSelectionInterval(i, i); + } else if (lastClicked != -1 + && (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) { + mediaList.setSelectionInterval(lastClicked, i); + } else if (!mediaList.isSelectedIndex(i)) { + mediaList.setSelectedIndex(i); + } + } + int numSel = mediaList.getSelectedIndices().length; + boolean enabled = numSel > 0; + deleteItem.setEnabled(enabled); + exportItem.setEnabled(enabled); + popupMenu.show(mediaList, e.getX(), e.getY()); + } + } + }); + mediaList.addListSelectionListener((e) -> refreshMedia()); + mediaList.setTransferHandler(mediaTransferHandler); + mediaList.setLayoutOrientation(JList.HORIZONTAL_WRAP); + mediaList.setVisibleRowCount(-1); + mediaList.setDragEnabled(true); + mediaList.setDropMode(DropMode.INSERT); + mediaList.setCellRenderer(new MediaCellRenderer(cache, album)); + mediaScroll = new JScrollPane(mediaList); + mediaPanel = new MediaPropertyPanel(cache, album); + multipleMediaLabel = new JLabel("Multiple items selected.", JLabel.CENTER); + noMediaLabel = new JLabel("No items selected.", JLabel.CENTER); + splitPane = new JSplitPane(); + splitPane.setOneTouchExpandable(false); + splitPane.setRightComponent(noMediaLabel); + add(topPanel, BorderLayout.PAGE_START); + add(mediaScroll, BorderLayout.CENTER); + currentComp = mediaScroll; + loadMedia(); + } + + private void loadMedia() { + ProgressTracker pt = new ProgressTracker(null, "Loading Media", "Loading media...", (t) -> { + t.setMinimum(0); + t.setMaximum(album.getMedia().size()); + t.setIndeterminate(false); + for (MediaState media : album.getMedia()) { + cache.getMedia(album.getName(), media.getName(), t); + mediaModel.addElement(media); + t.setProgress(t.getProgress() + 1); + if (t.isCanceled()) { + canceled = true; + break; + } + } + }); + pt.start(); + } + + public boolean wasCanceled() { + return canceled; + } + + private void togglePropertyPanel() { + if (firstToggle) { + splitPane.setDividerLocation((int) (getWidth() / 2)); + firstToggle = false; + } + if (currentComp == splitPane) { + remove(currentComp); + add(mediaScroll, BorderLayout.CENTER); + currentComp = mediaScroll; + } else { + int div = splitPane.getDividerLocation(); + remove(currentComp); + add(splitPane, BorderLayout.CENTER); + currentComp = splitPane; + splitPane.setLeftComponent(mediaScroll); + splitPane.setDividerLocation(div); + } + revalidate(); + } + + private void refreshMedia() { + int numSel = mediaList.getSelectedIndices().length; + if (numSel == 0) { + if (splitPane.getRightComponent() != mediaPanel) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(noMediaLabel); + splitPane.setDividerLocation(div); + } + } else if (numSel == 1) { + if (splitPane.getRightComponent() != mediaPanel) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(mediaPanel); + splitPane.setDividerLocation(div); + } + mediaPanel.setCurrentMedia(mediaList.getSelectedValue()); + } else { + if (splitPane.getRightComponent() != multipleMediaLabel) { + int div = splitPane.getDividerLocation(); + splitPane.setRightComponent(multipleMediaLabel); + splitPane.setDividerLocation(div); + } + } + revalidate(); + boolean enabled = numSel != 0; + deleteButton.setEnabled(enabled); + exportButton.setEnabled(enabled); + setThumbnailButton.setEnabled(numSel == 1); + } + + public LibraryState getLibraryState() { + return library; + } + + public AlbumState getAlbum() { + return album; + } + + private void deleteMedia(MediaState media) { + album.getMedia().remove(media); + mediaModel.removeElement(media); + cache.removeMedia(album.getName(), media.getName()); + } + + private void deleteMediaHandler() { + List ma = mediaList.getSelectedValuesList(); + int response; + if (ma.size() == 1) { + response = JOptionPane.showConfirmDialog(this, "Are you sure you would like to delete '" + ma.get(0).getName() + "'?", + "Confirm Delete", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + } else { + response = JOptionPane.showConfirmDialog(this, "Are you sure you would like to delete " + ma.size() + " items?", + "Confirm Delete", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + } + if (response == JOptionPane.YES_OPTION) { + for (MediaState m : ma) { + deleteMedia(m); + } + } + } + + private void doSearch(String search) { + if (search.isBlank()) { + mediaList.setTransferHandler(mediaTransferHandler); + mediaList.setDragEnabled(true); + } else { + mediaList.setTransferHandler(null); + mediaList.setDragEnabled(false); + } + search = search.trim(); + mediaModel.clear(); + for (MediaState m : album.getMedia()) { + if (m.getName().trim().startsWith(search)) { + mediaModel.addElement(m); + } + } + } + + private final class MediaAddTransaction { + public MediaData data; + public MediaState state; + + public MediaAddTransaction(MediaData data, MediaState state) { + this.data = data; + this.state = state; + } + + public void apply() { + MediaState old = album.getMediaForName(state.getName()); + if (old != null) { + album.getMedia().remove(old); + } + cache.putMedia(album.getName(), state.getName(), data); + album.getMedia().add(state); + } + } + + private void addMedia() { + importMediaFromFiles(promptToAddFiles(), true); + } + + private void findFiles(File root, List files) { + if (root.isDirectory()) { + for (File child : root.listFiles()) { + findFiles(child, files); + } + } else { + files.add(root); + } + } + + private static class FileNameComparor implements Comparator { + @Override + public int compare(File f1, File f2) { + String n1 = getNameWithoutExtension(f1.getName()); + String n2 = getNameWithoutExtension(f2.getName()); + int i1; + try { + i1 = Integer.parseInt(n1); + } catch (NumberFormatException e) { + return 1; + } + int i2; + try { + i2 = Integer.parseInt(n2); + } catch (NumberFormatException e) { + return -1; + } + return i1 - i2; + } + + private String getNameWithoutExtension(String fn) { + int i = fn.lastIndexOf("."); + if (i == -1 || i == 0) { + return fn; + } + return fn.substring(0, i); + } + } + + private Set checkTransactionConflicts(List files) { + final HashSet conflicts = new HashSet(); + for (File f : files) { + String fn = f.getName(); + for (File o : files) { + if (o != f && fn.equals(o.getName())) { + conflicts.add(fn); + } + } + } + return conflicts; + } + + public void importMediaFromFiles(List utFiles, boolean reorder) { + final ArrayList files = new ArrayList(); + for (File f : utFiles) { + findFiles(f, files); + } + if (files.isEmpty()) { + return; + } + Set conflicts = checkTransactionConflicts(files); + if (!conflicts.isEmpty()) { + NameConflictListDialog diag = new NameConflictListDialog(SwingUtilities.getWindowAncestor(this), conflicts); + diag.setVisible(true); + return; + } + if (reorder) { + Collections.sort(files, new FileNameComparor()); + } + checkImportConflicts(files); + final ArrayList transactions = new ArrayList(); + AtomicReference f = new AtomicReference(); + ProgressTracker pt = new ProgressTracker(SwingUtilities.getWindowAncestor(this), "Adding Media", + "Adding media...", (t) -> { + try { + t.setIndeterminate(false); + t.setMinimum(0); + t.setMaximum(files.size()); + for (int i = 0; i < files.size(); ++i) { + f.set(files.get(i)); + transactions.add(importMediaFromFile(f.get(), t)); + t.setProgress(i + 1); + if (t.isCanceled()) { + transactions.clear(); + return; + } + } + } catch (Throwable e) { + LOGGER.error("Could not load media file!", e); + SwingUtilities.invokeLater(() -> { + ErrorDialog diag = new ErrorDialog("Error Addng Media", + "The media \"" + f.get().getPath() + "\" could not be loaded! No more media will be processed.", e); + diag.setVisible(true); + }); + transactions.clear(); + } + }); + pt.start(); + int origMediaCount = album.getMedia().size(); + for (MediaAddTransaction mat : transactions) { + mat.apply(); + } + if (!transactions.isEmpty()) { + if (origMediaCount == 0) { + album.setThumbnailName(album.getMedia().get(0).getName()); + } + // Refresh media and search + doSearch(searchField.getText()); + } + } + + private void checkImportConflicts(ArrayList files) { + final Window parent = SwingUtilities.getWindowAncestor(this); + int mode = MediaNameConflictDialog.RESPONSE_CANCEL; + for (Iterator i = files.iterator(); i.hasNext();) { + File f = i.next(); + String name = f.getName(); + if (name.equals("data.json")) { + JOptionPane.showMessageDialog(parent, "The name 'data.json' is invalid because it is reserved!" + + "\nThis file will not be preocessed.", + "Invalid Name", JOptionPane.ERROR_MESSAGE); + i.remove(); + } else if (album.getMediaForName(name) != null) { + if (mode == MediaNameConflictDialog.RESPONSE_CANCEL) { + MediaNameConflictDialog diag = new MediaNameConflictDialog(parent, + "Media with the name '" + name + "' already exists!"); + diag.setVisible(true); + if (diag.getResponse() == MediaNameConflictDialog.RESPONSE_CANCEL) { + files.clear(); + return; + } else if (diag.getApplyToAll()) { + mode = diag.getResponse(); + } + if (diag.getResponse() == MediaNameConflictDialog.RESPONSE_KEEP) { + i.remove(); + } else if (diag.getResponse() == MediaNameConflictDialog.RESPONSE_REPLACE) { + // We are replacing the version currently loaded, we therefore do nothing + continue; + } + } else if (mode == MediaNameConflictDialog.RESPONSE_KEEP /* keep all */) { + i.remove(); + } else if (mode == MediaNameConflictDialog.RESPONSE_REPLACE /* replace all */) { + // We are replacing the version currently loaded, we therefore do nothing + continue; + } + } + } + } + + private ArrayList promptToAddFiles() { + final FileNameExtensionFilter filter = new FileNameExtensionFilter("Video, audio, and image files", + "png", "jpg", "jpeg", "ico", "svg", "bmp", "gif", "mp4", "ogg", "webm", "mov", "qt", "mp3", "wav"); + final JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(filter); + chooser.setMultiSelectionEnabled(true); + chooser.setDialogTitle("Select Media"); + chooser.setAcceptAllFileFilterUsed(true); + chooser.setApproveButtonText("Add"); + chooser.setApproveButtonMnemonic(KeyEvent.VK_A); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setDialogType(JFileChooser.OPEN_DIALOG); + int resp = chooser.showDialog(this, null); + final ArrayList media = new ArrayList(); + if (resp == JFileChooser.APPROVE_OPTION) { + for (File f : chooser.getSelectedFiles()) { + media.add(f); + } + } + return media; + } + + private MediaAddTransaction importMediaFromFile(File file, ProgressManager t) throws IOException, + ImageProcessingException { + FileInputStream fin = new FileInputStream(file); + byte[] data = fin.readAllBytes(); + fin.close(); + ByteArrayInputStream bin = new ByteArrayInputStream(data); + String type = TIKA.detect(bin); + if (type.equals("video/quicktime")) { + return importMovFromFile(file, data, t); + } + return importStandardMedia(file, data, t); + } + + private MediaAddTransaction importStandardMedia(File file, byte[] data, ProgressManager t) + throws IOException, ImageProcessingException { + String fname = file.getName(); + MediaData mediaData = MediaLoader.loadBytes(file.getCanonicalFile(), data, true, t); + MediaMetadata metadata = MetadataLoader.fromBytes(data); + MediaState m = new MediaState(fname, metadata.getDescription(), metadata.getDate(), metadata.getTime(), + metadata.getLatitude(), metadata.getLongitude(), data.length); + m.setNameOnDisk(null); + return new MediaAddTransaction(mediaData, m); + } + + private MediaAddTransaction importMovFromFile(File file, byte[] data, ProgressManager t) throws IOException, + ImageProcessingException { + t.setIndeterminate(true); + byte[] mp4 = convertMovToMp4(data, t); + if (mp4 == null) { + return null; + } + String fname = file.getName(); + MediaData md = MediaLoader.loadBytes(null, mp4, true, t); + MediaMetadata metadata = MetadataLoader.fromBytes(data); + MediaState m = new MediaState(fname, metadata.getDescription(), metadata.getDate(), metadata.getTime(), + metadata.getLatitude(), metadata.getLongitude(), mp4.length); + m.setNameOnDisk(null); + return new MediaAddTransaction(md, m); + } + + private static byte[] convertMovToMp4(byte[] data, ProgressManager t) throws IOException, ImageProcessingException { + return FFmpeg.convertMedia(data, "mp4", t); + } + + private void exportMediaHandler() { + if (mediaList.getSelectedIndices().length == 0) { + return; + } + final File dest = promptToExportFiles(); + if (dest == null) { + return; + } + final List mediaStates = new ArrayList(mediaList.getSelectedValuesList()); + checkExportConflicts(dest, mediaStates); + if (mediaStates.isEmpty()) { + return; + } + ProgressTracker pt = new ProgressTracker(SwingUtilities.getWindowAncestor(this), "Export Progress", + "Exporting media...", (t) -> { + t.setIndeterminate(false); + t.setRange(0, mediaStates.size()); + try { + for (MediaState m : mediaStates) { + exportMedia(m, dest); + if (t.isCanceled()) { + break; + } + t.setProgress(t.getProgress() + 1); + } + } catch (IOException e) { + LOGGER.error("Could not export media!", e); + ErrorDialog ed = new ErrorDialog("Export Error", + "An error occured during export !Export did not fully complete!", e); + ed.setVisible(true); + } + }); + pt.start(); + } + + private static List listFileNames(final File file) { + final List names = new ArrayList(); + final File[] children = file.listFiles(); + if (children == null) { + return null; + } + for (File c : children) { + names.add(c.getName()); + } + return names; + } + + private void checkExportConflicts(final File dest, final List ml) { + final List aefn = listFileNames(dest); + int precedent = MediaNameConflictDialog.RESPONSE_CANCEL ; + for (Iterator i = ml.iterator(); i.hasNext();) { + MediaState m = i.next(); + if (!aefn.contains(m.getName())) { + continue; + } + if (precedent == MediaNameConflictDialog.RESPONSE_CANCEL) { + MediaNameConflictDialog diag = new MediaNameConflictDialog(SwingUtilities.getWindowAncestor(this), + "A file with the name '" + m.getName() + "' already exists!"); + diag.setVisible(true); + if (diag.getResponse() == MediaNameConflictDialog.RESPONSE_CANCEL) { + ml.clear(); + break; + } else if (diag.getApplyToAll()) { + precedent = diag.getResponse(); + // fallthrough + } else if (diag.getResponse() == MediaNameConflictDialog.RESPONSE_KEEP) { + i.remove(); + continue; + } + } + if (precedent == MediaNameConflictDialog.RESPONSE_KEEP) { + i.remove(); + } + } + } + + private File promptToExportFiles() { + final JFileChooser ch = new JFileChooser(); + ch.setMultiSelectionEnabled(false); + ch.setDialogTitle("Select Export Destination"); + ch.setApproveButtonText("Export"); + ch.setApproveButtonMnemonic(KeyEvent.VK_E); + ch.setDialogType(JFileChooser.SAVE_DIALOG); + ch.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int resp = ch.showDialog(this, null); + if (resp == JFileChooser.APPROVE_OPTION) { + return ch.getSelectedFile(); + } + return null; + } + + private void deleteRecursively(File root) throws IOException { + if (root.isDirectory()) { + for (File c : root.listFiles()) { + deleteRecursively(c); + } + } + root.delete(); + } + + private void exportMedia(MediaState media, File dest) throws IOException { + File of = new File(dest.getCanonicalPath() + File.separator + media.getName()); + if (of.isDirectory()) { + deleteRecursively(dest); + } + MediaData md = cache.getMedia(album.getName(), media.getName(), null); + FileOutputStream out = new FileOutputStream(of); + out.write(md.loadRawBytes()); + out.close(); + } +} diff --git a/src/main/java/zander/ui/media/MediaNameConflictDialog.java b/src/main/java/zander/ui/media/MediaNameConflictDialog.java new file mode 100644 index 0000000..34c7ff8 --- /dev/null +++ b/src/main/java/zander/ui/media/MediaNameConflictDialog.java @@ -0,0 +1,88 @@ +package zander.ui.media; + +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import zander.ui.UIUtils; + +public class MediaNameConflictDialog extends JDialog { + private static final long serialVersionUID = -6002060884734865425L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_KEEP = 1; + public static final int RESPONSE_REPLACE = 2; + + private final JLabel messageLabel; + private final JCheckBox applyToAllCheck; + private final JPanel messagePanel; + private final JButton cancelButton; + private final JButton keepButton; + private final JButton replaceButton; + private final JPanel buttonPanel; + private final JPanel content; + + private int response = RESPONSE_CANCEL; + + public MediaNameConflictDialog(Window parent, String message) { + super(parent, "Name Conflict"); + messageLabel = new JLabel(message); + messageLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); + applyToAllCheck = new JCheckBox("Apply to all conflicts"); + applyToAllCheck.setAlignmentX(JComponent.CENTER_ALIGNMENT); + applyToAllCheck.setMnemonic(KeyEvent.VK_A); + messagePanel = new JPanel(); + messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.Y_AXIS)); + messagePanel.add(messageLabel); + messagePanel.add(applyToAllCheck); + cancelButton = new JButton("Cancel Operation"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> dispose()); + keepButton = new JButton("Keep Original"); + keepButton.setMnemonic(KeyEvent.VK_K); + keepButton.addActionListener((e) -> { + response = RESPONSE_KEEP; + dispose(); + }); + replaceButton = new JButton("Replace"); + replaceButton.setMnemonic(KeyEvent.VK_R); + replaceButton.addActionListener((e) -> { + response = RESPONSE_REPLACE; + dispose(); + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(keepButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(replaceButton); + content = new JPanel(new BorderLayout()); + content.add(messagePanel, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + pack(); + setResizable(false); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setAlwaysOnTop(true); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + } + + public boolean getApplyToAll() { + return applyToAllCheck.isSelected(); + } + + public int getResponse() { + return response; + } + +} diff --git a/src/main/java/zander/ui/media/MediaPropertyPanel.java b/src/main/java/zander/ui/media/MediaPropertyPanel.java new file mode 100644 index 0000000..f112505 --- /dev/null +++ b/src/main/java/zander/ui/media/MediaPropertyPanel.java @@ -0,0 +1,528 @@ +package zander.ui.media; + +import java.awt.Window; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.NumberFormat; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Objects; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import com.github.lgooddatepicker.components.DatePicker; +import com.github.lgooddatepicker.components.DatePickerSettings; +import com.github.lgooddatepicker.components.DatePickerSettings.DateArea; +import com.github.lgooddatepicker.components.TimePicker; +import com.github.lgooddatepicker.components.TimePickerSettings; +import com.github.lgooddatepicker.components.TimePickerSettings.TimeArea; + +import org.jxmapviewer.viewer.GeoPosition; + +import zander.library.LibraryMediaCache; +import zander.library.media.ImageMediaComponent; +import zander.library.media.MediaData; +import zander.library.state.AlbumState; +import zander.library.state.MediaState; +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; +import zander.ui.media.map.LocationSelectDialog; + +public class MediaPropertyPanel extends JPanel { + private static final long serialVersionUID = -360830663146686639L; + + private static final NumberFormat NUM_FORMAT = NumberFormat.getInstance(); + private static final long GB_SIZE = (long) Math.pow(1024, 3); + private static final long MB_SIZE = (long) Math.pow(1024, 2); + private static final long KB_SIZE = 1024; + + static { + NUM_FORMAT.setGroupingUsed(false); + NUM_FORMAT.setMinimumFractionDigits(0); + NUM_FORMAT.setMaximumFractionDigits(2); + } + + private final LibraryMediaCache cache; + private final AlbumState album; + + private final JLabel previewLabel; + private final JPanel previewPanel; + private final JLabel thumbLabel; + private final JLabel thumbValue; + private final JLabel sizeLabel; + private final JLabel sizeValue; + private final JLabel typeLabel; + private final JLabel typeValue; + private final JLabel nameLabel; + private final JTextField nameField; + private final JButton nameReset; + private final JLabel descLabel; + private final JTextArea descArea; + private final JScrollPane descScroll; + private final JPanel descPanel; + private final JButton descReset; + private final JLabel dateLabel; + private final DatePicker datePicker; + private final JButton dateReset; + private final JLabel timeLabel; + private final TimePicker timePicker; + private final JButton timeReset; + private final JLabel mapLabel; + private final JLabel mapValue; + private final JButton mapButton; + + private final ImageMediaComponent missingImageIcon; + + private MediaState currentMedia; + private boolean disableUpdates = false; + + public MediaPropertyPanel(LibraryMediaCache cache, AlbumState album) { + super(new GridBagLayout()); + this.cache = cache; + this.album = album; + missingImageIcon = getMissingImageComponent(); + previewLabel = new JLabel("Preview:"); + previewPanel = new JPanel(new BorderLayout()); + thumbLabel = new JLabel("Is Thumbnail:"); + thumbValue = new JLabel("No"); + sizeLabel = new JLabel("File Size:"); + sizeValue = new JLabel("N/A"); + typeLabel = new JLabel("File Type:"); + typeValue = new JLabel("unknown"); + nameLabel = new JLabel("Name:"); + nameField = new JTextField(); + nameField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + nameReset.setEnabled(!Objects.equals(nameField.getText(), currentMedia.getOriginalName())); + } + }); + nameField.addActionListener((e) -> { + if (!disableUpdates) { + nameChanged(nameField.getText()); + } + }); + nameField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (!disableUpdates) { + nameChanged(nameField.getText()); + } + } + }); + nameReset = new JButton("↺"); + nameReset.addActionListener((e) -> { + nameField.setText(currentMedia.getOriginalName()); + nameChanged(nameField.getText()); + }); + descLabel = new JLabel("Description:"); + descArea = new JTextArea(); + descArea.setLineWrap(true); + descArea.setBorder(null); + descArea.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + + private void update() { + if (!disableUpdates) { + descriptionChanged(descArea.getText()); + } + } + }); + descScroll = new JScrollPane(descArea); + descScroll.setBorder(null); + descPanel = new JPanel(new BorderLayout()); + descPanel.setBorder(UIManager.getBorder("TextField.border")); + descPanel.add(descScroll, BorderLayout.CENTER); + descReset = new JButton("↺"); + descReset.addActionListener((e) -> { + descArea.setText(currentMedia.getOriginalDescription()); + }); + dateLabel = new JLabel("Date:"); + datePicker = new DatePicker(getDatePickerSettings()); + datePicker.addDateChangeListener((e) -> { + if (!disableUpdates) { + dateChanged(datePicker.getDate()); + } + }); + dateReset = new JButton("↺"); + dateReset.addActionListener((e) -> { + datePicker.setDate(currentMedia.getOriginalDate()); + }); + timeLabel = new JLabel("Time:"); + timePicker = new TimePicker(getTimePickerSettings()); + timePicker.addTimeChangeListener((e) -> { + if (!disableUpdates) { + timeChanged(timePicker.getTime()); + } + }); + timeReset = new JButton("↺"); + timeReset.addActionListener((e) -> { + timePicker.setTime(currentMedia.getOriginalTime()); + }); + mapLabel = new JLabel("Location:"); + mapValue = new JLabel("N/A"); + mapButton = new JButton("\u2026"); + mapButton.addActionListener(this::mapButtonHandler); + buildUI(); + } + + private Color getDatePickerWeekHeaderColor() { + Color sel = UIManager.getColor("List.selectionBackground"); + if (sel == null) { + return Color.white; + } + return UIUtils.scaleColor(sel, 0.9f, 0.9f, 0.9f); + } + + private DatePickerSettings getDatePickerSettings() { + DatePickerSettings s = new DatePickerSettings(); + s.setAllowEmptyDates(true); + s.setAllowKeyboardEditing(true); + s.setColorBackgroundWeekdayLabels(getDatePickerWeekHeaderColor(), true); + s.setColor(DateArea.BackgroundCalendarPanelLabelsOnHover, UIManager.getColor("List.selectionBackground")); + s.setColor(DateArea.BackgroundClearLabel, UIManager.getColor("Panel.background")); + s.setColor(DateArea.BackgroundMonthAndYearMenuLabels, UIManager.getColor("Panel.background")); + s.setColor(DateArea.BackgroundMonthAndYearNavigationButtons, UIManager.getColor("Panel.background")); + s.setColor(DateArea.BackgroundOverallCalendarPanel, UIManager.getColor("Panel.background")); + s.setColor(DateArea.BackgroundTodayLabel, UIManager.getColor("Panel.background")); + s.setColor(DateArea.BackgroundTopLeftLabelAboveWeekNumbers, UIManager.getColor("Panel.background")); + s.setColor(DateArea.CalendarBackgroundNormalDates, UIManager.getColor("Panel.background")); + s.setColor(DateArea.CalendarBackgroundSelectedDate, UIManager.getColor("List.selectionBackground")); + s.setColor(DateArea.CalendarBorderSelectedDate, UIManager.getColor("Tree.selectionBorderColor")); + s.setColor(DateArea.CalendarDefaultBackgroundHighlightedDates, UIManager.getColor("Panel.background")); + s.setColor(DateArea.CalendarDefaultTextHighlightedDates, UIManager.getColor("List.selectionBackground")); + s.setColor(DateArea.CalendarTextNormalDates, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.CalendarTextWeekNumbers, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.CalendarTextWeekdays, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.DatePickerTextValidDate, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.TextCalendarPanelLabelsOnHover, UIManager.getColor("List.selectionForeground")); + s.setColor(DateArea.TextFieldBackgroundValidDate, UIManager.getColor("TextField.background")); + s.setColor(DateArea.TextMonthAndYearMenuLabels, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.TextMonthAndYearNavigationButtons, UIManager.getColor("Label.foreground")); + s.setColor(DateArea.TextTodayLabel, UIManager.getColor("Label.foreground")); + return s; + } + + private TimePickerSettings getTimePickerSettings() { + TimePickerSettings s = new TimePickerSettings(); + s.setAllowEmptyTimes(true); + s.setAllowKeyboardEditing(true); + s.setColor(TimeArea.TextFieldBackgroundValidTime, UIManager.getColor("TextField.background")); + s.setColor(TimeArea.TimePickerTextValidTime, UIManager.getColor("Label.foreground")); + return s; + } + + private void buildUI() { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(1, 1, 1, 1); + gbc.weightx = 0.0; + gbc.weighty = 0.1; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.fill = GridBagConstraints.NONE; + add(previewLabel, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.CENTER; + add(previewPanel, gbc); + gbc.weightx = 0.0; + gbc.weighty = 0.1; + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.gridx = 0; + gbc.gridy = 1; + add(thumbLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + add(thumbValue, gbc); + gbc.anchor = GridBagConstraints.LINE_END; + gbc.gridx = 0; + gbc.gridy = 2; + add(sizeLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + add(sizeValue, gbc); + gbc.anchor = GridBagConstraints.LINE_END; + gbc.gridx = 0; + gbc.gridy = 3; + add(typeLabel, gbc); + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.LINE_START; + add(typeValue, gbc); + gbc.gridx = 0; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.LINE_END; + add(nameLabel, gbc); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridx = 1; + gbc.anchor = GridBagConstraints.CENTER; + add(nameField, gbc); + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_START; + add(nameReset, gbc); + gbc.gridx = 0; + gbc.gridy = 5; + gbc.anchor = GridBagConstraints.LINE_END; + add(descLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.CENTER; + gbc.weighty = 0.3; + add(descPanel, gbc); + gbc.weighty = 0.1; + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.weightx = 0.0; + add(descReset, gbc); + gbc.gridx = 0; + gbc.gridy = 6; + gbc.anchor = GridBagConstraints.LINE_END; + add(dateLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + add(datePicker, gbc); + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_START; + add(dateReset, gbc); + gbc.gridx = 0; + gbc.gridy = 7; + gbc.anchor = GridBagConstraints.LINE_END; + add(timeLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + add(timePicker, gbc); + gbc.fill = GridBagConstraints.NONE; + gbc.gridx = 2; + gbc.anchor = GridBagConstraints.LINE_START; + add(timeReset, gbc); + gbc.fill = GridBagConstraints.NONE; + gbc.anchor = GridBagConstraints.LINE_END; + gbc.gridx = 0; + gbc.gridy = 8; + add(mapLabel, gbc); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + add(mapValue, gbc); + gbc.gridx = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.CENTER; + add(mapButton, gbc); + } + + public LibraryMediaCache getMediaCache() { + return cache; + } + + public MediaState getCurrentMedia() { + return currentMedia; + } + + private static String getFileSizeString(long size) { + double ds = (double) size; + if (size >= GB_SIZE) { + return NUM_FORMAT.format(ds / GB_SIZE) + " GB"; + } else if (size >= MB_SIZE) { + return NUM_FORMAT.format(ds / MB_SIZE) + " MB"; + } else if (size >= KB_SIZE) { + return NUM_FORMAT.format(ds / KB_SIZE) + " KB"; + } else { + return size + " B"; + } + } + + public void setCurrentMedia(MediaState media) { + disableUpdates = true; + if (currentMedia != null && !nameField.getText().equals(currentMedia.getName())) { + nameChanged(nameField.getText()); + } + this.currentMedia = media; + updateIsThumbnail(); + sizeValue.setText(getFileSizeString(media.getFileSize())); + nameField.setText(media.getName()); + descArea.setText(media.getDescription()); + previewPanel.removeAll(); + MediaData md = cache.getMedia(album.getName(), media.getName(), null); + if (md != null) { + typeValue.setText(md.getType()); + md.getViewerComponent().setEnabled(true); + previewPanel.add(md.getViewerComponent(), BorderLayout.CENTER); + } else { + typeValue.setText("unknown"); + previewPanel.add(missingImageIcon, BorderLayout.CENTER); + } + datePicker.setDate(media.getDate()); + timePicker.setTime(media.getTime()); + mapValue.setText(formatLocation(media)); + revalidate(); + repaint(); + updateResetButtons(); + disableUpdates = false; + } + + public void updateIsThumbnail() { + if (currentMedia != null) { + if (album.getThumbnailName().equals(currentMedia.getName())) { + thumbValue.setText("Yes"); + } else { + thumbValue.setText("No"); + } + } + } + + private static String formatLocation(MediaState m) { + if (m.getLatitude() == null || m.getLongitude() == null) { + return "N/A"; + } + return m.getLatitude() + ", " + m.getLongitude(); + } + + private void nameChanged(String name) { + String oldName = currentMedia.getName(); + if (!name.equals(oldName)) { + if (album.getMediaForName(name) != null) { + disableUpdates = true; + nameField.setText(oldName); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "Media with the name \"" + name + "\" already exists!", + "Name Conflict", JOptionPane.ERROR_MESSAGE); + } else if (name.isBlank()) { + disableUpdates = true; + nameField.setText(oldName); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The name \"" + name + "\" is invalid!", "Invalid Name", + JOptionPane.ERROR_MESSAGE); + } else if (name.equals("data.json")) { + disableUpdates = true; + nameField.setText(oldName); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The name \"" + name + "\" is reserved!", "Invalid Name", + JOptionPane.ERROR_MESSAGE); + } else if (name.contains("/")) { + disableUpdates = true; + nameField.setText(oldName); + disableUpdates = false; + JOptionPane.showMessageDialog(this, "The name \"" + name + "\" is because the charater '/' is not allowed!", + "Invalid Name", JOptionPane.ERROR_MESSAGE); + } else { + currentMedia.setName(name); + cache.moveMedia(album.getName(), oldName, name); + if (album.getThumbnailName().equals(oldName)) { + album.setThumbnailName(name); + } + SwingUtilities.getWindowAncestor(this).repaint(); + } + } + nameReset.setEnabled(!Objects.equals(nameField.getText(), currentMedia.getOriginalName())); + } + + private void descriptionChanged(String description) { + currentMedia.setDescription(description); + descReset.setEnabled(!resetStringEquals(descArea.getText(), currentMedia.getOriginalDescription())); + } + + private void dateChanged(LocalDate date) { + currentMedia.setDate(date); + dateReset.setEnabled(!Objects.equals(date, currentMedia.getOriginalDate())); + } + + private void timeChanged(LocalTime time) { + currentMedia.setTime(time); + timeReset.setEnabled(!Objects.equals(time, currentMedia.getOriginalTime())); + } + + private static ImageMediaComponent getMissingImageComponent() { + try { + BufferedImage bi = UIUtils.loadBufferedImage(ClassLoader.getSystemResource("missing-image-icon.svg"), -1, -1); + return new ImageMediaComponent(bi); + } catch (IOException e) { + ErrorDialog ed = new ErrorDialog("Internal Error", "A fatal error has occured!", e); + ed.setVisible(true); + System.exit(0); + return null; + } + } + + private void updateResetButtons() { + nameReset.setEnabled(!resetStringEquals(nameField.getText(), currentMedia.getOriginalName())); + descReset.setEnabled(!resetStringEquals(descArea.getText(), currentMedia.getOriginalDescription())); + dateReset.setEnabled(!Objects.equals(datePicker.getDate(), currentMedia.getOriginalDate())); + timeReset.setEnabled(!Objects.equals(timePicker.getTime(), currentMedia.getOriginalTime())); + } + + private static boolean resetStringEquals(String s1, String s2) { + if (s1 != null && s1.isEmpty()) { + s1 = null; + } + if (s2 != null && s2.isEmpty()) { + s2 = null; + } + return Objects.equals(s1, s2); + } + + private void mapButtonHandler(ActionEvent e) { + Window wa = SwingUtilities.getWindowAncestor(this); + LocationSelectDialog diag = new LocationSelectDialog(wa, + currentMedia.getLatitude(), currentMedia.getLongitude(), currentMedia.getOriginalLatitude(), + currentMedia.getOriginalLongitude()); + if (wa != null) { + diag.setSize((2 * wa.getWidth()) / 3, (2 * wa.getHeight()) / 3); + UIUtils.centerWindow(diag, wa); + } + diag.setVisible(true); + if (diag.getResponse() == LocationSelectDialog.RESPONSE_SELECT) { + GeoPosition pos = diag.getCurrentLocation(); + if (pos == null) { + currentMedia.setLatitude(null); + currentMedia.setLongitude(null); + } else { + currentMedia.setLatitude(pos.getLatitude()); + currentMedia.setLongitude(pos.getLongitude()); + } + mapValue.setText(formatLocation(currentMedia)); + } + } + + public void updateName() { + nameChanged(nameField.getText()); + } +} diff --git a/src/main/java/zander/ui/media/NameConflictListDialog.java b/src/main/java/zander/ui/media/NameConflictListDialog.java new file mode 100644 index 0000000..5e68232 --- /dev/null +++ b/src/main/java/zander/ui/media/NameConflictListDialog.java @@ -0,0 +1,66 @@ +package zander.ui.media; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.util.Set; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; + +import zander.ui.UIUtils; + +public class NameConflictListDialog extends JDialog { + private static final long serialVersionUID = 1252139512341L; + + private final JLabel message; + private final JPanel names; + private final JScrollPane scroll; + private final JButton close; + private final JPanel buttons; + private final JPanel content; + + public NameConflictListDialog(Window parent, Set conflicts) { + super(parent, "Name Conflicts"); + message = new JLabel("The following names appear two or more times in this import!", SwingConstants.CENTER); + names = new JPanel(); + names.setLayout(new BoxLayout(names, BoxLayout.Y_AXIS)); + for (String name : conflicts) { + JLabel label = new JLabel(name, SwingConstants.LEFT); + label.setAlignmentX(JComponent.LEFT_ALIGNMENT); + names.add(label); + } + scroll = new JScrollPane(names); + close = new JButton("Close"); + close.addActionListener(e -> dispose()); + close.setMnemonic(KeyEvent.VK_C); + close.setAlignmentX(JComponent.RIGHT_ALIGNMENT); + buttons = new JPanel(); + buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); + buttons.add(Box.createHorizontalGlue()); + buttons.add(close); + content = new JPanel(new BorderLayout()); + content.add(message, BorderLayout.PAGE_START); + content.add(scroll, BorderLayout.CENTER); + content.add(buttons, BorderLayout.PAGE_END); + setContentPane(content); + Dimension size = new Dimension((int) (UIUtils.SCREEN_SIZE.getWidth() / 3), + (int) (UIUtils.SCREEN_SIZE.getHeight() / 2)); + setMinimumSize(size); + setSize(size); + setResizable(true); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + } + +} diff --git a/src/main/java/zander/ui/media/map/ListLayout.java b/src/main/java/zander/ui/media/map/ListLayout.java new file mode 100644 index 0000000..8aeac87 --- /dev/null +++ b/src/main/java/zander/ui/media/map/ListLayout.java @@ -0,0 +1,56 @@ +package zander.ui.media.map; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager; + +public class ListLayout implements LayoutManager { + @Override + public void addLayoutComponent(String name, Component comp) { + // unused + } + + @Override + public void removeLayoutComponent(Component comp) { + // unused + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + Dimension size = new Dimension(); + for (Component c : parent.getComponents()) { + Dimension cs = c.getPreferredSize(); + size.height += cs.height; + if (cs.width > size.width) { + size.width = cs.width; + } + } + return size; + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + Dimension size = new Dimension(); + for (Component c : parent.getComponents()) { + Dimension cs = c.getMinimumSize(); + size.height += cs.height; + if (cs.width > size.width) { + size.width = cs.width; + } + } + return size; + } + + @Override + public void layoutContainer(Container parent) { + int cw = parent.getWidth(); + int ly = 0; + for (Component c : parent.getComponents()) { + int h = c.getPreferredSize().height; + c.setSize(cw, h); + c.setLocation(0, ly); + ly += h; + } + } +} diff --git a/src/main/java/zander/ui/media/map/LocationEncoder.java b/src/main/java/zander/ui/media/map/LocationEncoder.java new file mode 100644 index 0000000..3de3784 --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationEncoder.java @@ -0,0 +1,14 @@ +package zander.ui.media.map; + +import java.io.IOException; +import java.util.List; + +public interface LocationEncoder { + + public String getLicenseString(); + + public List encode(String query) throws IOException; + + public void cleanOldCacheEntries(); + +} diff --git a/src/main/java/zander/ui/media/map/LocationEncoderCacheCleaner.java b/src/main/java/zander/ui/media/map/LocationEncoderCacheCleaner.java new file mode 100644 index 0000000..233a65c --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationEncoderCacheCleaner.java @@ -0,0 +1,70 @@ +package zander.ui.media.map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LocationEncoderCacheCleaner { + private static final Logger LOGGER = LoggerFactory.getLogger(LocationEncoderCacheCleaner.class); + + private final LocationEncoder encoder; + /** in miliiseconds */ + private final long frequency; + private final Thread cleanThread; + private boolean running = true; + private boolean paused = false; + private long lastClean = 0; + + public LocationEncoderCacheCleaner(LocationEncoder encoder, long milis) { + this.encoder = encoder; + frequency = milis; + cleanThread = new Thread(this::cleanLoop); + cleanThread.start(); + } + + public void dispose() { + running = false; + } + + public boolean isDisposed() { + return !running; + } + + public void pause() { + paused = true; + } + + public boolean isPaused() { + return paused; + } + + public void resume() { + paused = false; + } + + public long getFrequency() { + return frequency; + } + + public LocationEncoder getEncoder() { + return encoder; + } + + private void cleanLoop() { + LOGGER.info("Starting location cache clean loop with frequency of {}ms", frequency); + while (running) { + if (!paused && lastClean + frequency <= System.currentTimeMillis()) { + LOGGER.info("Cleaning location cache..."); + encoder.cleanOldCacheEntries(); + LOGGER.info("Done cleaning location cache"); + lastClean = System.currentTimeMillis(); + } + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // ignore + } + } + LOGGER.info("Stopped location cache clean loop!"); + } + +} diff --git a/src/main/java/zander/ui/media/map/LocationEntry.java b/src/main/java/zander/ui/media/map/LocationEntry.java new file mode 100644 index 0000000..0b0785b --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationEntry.java @@ -0,0 +1,33 @@ +package zander.ui.media.map; + +import org.jxmapviewer.viewer.GeoPosition; + +public class LocationEntry { + + private final String name; + private final double lat; + private final double lon; + + public LocationEntry(String name, double lat, double lon) { + this.name = name; + this.lat = lat; + this.lon = lon; + } + + public String getName() { + return name; + } + + public double getLongitude() { + return lon; + } + + public double getLatitude() { + return lat; + } + + public GeoPosition getPosition() { + return new GeoPosition(lat, lon); + } + +} diff --git a/src/main/java/zander/ui/media/map/LocationEntryComponent.java b/src/main/java/zander/ui/media/map/LocationEntryComponent.java new file mode 100644 index 0000000..d3dd863 --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationEntryComponent.java @@ -0,0 +1,103 @@ +package zander.ui.media.map; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +public class LocationEntryComponent extends JPanel { + private static final long serialVersionUID = 123157372342348205L; + + private final MouseAdapter actionMouseAdapter = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + fireActionEvent(); + } + }; + private final ArrayList actionListeners = new ArrayList(); + private final LocationEntry entry; + + private final JTextArea nameArea; + private final JTextArea coordArea; + + public LocationEntryComponent(LocationEntry entry) { + this.entry = entry; + nameArea = new JTextArea(entry.getName()); + nameArea.setEnabled(false); + nameArea.setDisabledTextColor(getForeground()); + coordArea = new JTextArea(roundDouble(entry.getLatitude(), 4) + + ", " + roundDouble(entry.getLongitude(), 4)); + coordArea.setEnabled(false); + coordArea.setDisabledTextColor(getForeground()); + setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, getForeground())); + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(1, 1, 1, 1); + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.LINE_START; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.gridx = 0; + gbc.gridy = 0; + add(nameArea, gbc); + gbc.gridy = 1; + add(coordArea, gbc); + addMouseListener(actionMouseAdapter); + nameArea.addMouseListener(actionMouseAdapter); + coordArea.addMouseListener(actionMouseAdapter); + } + + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + if (nameArea != null) { + nameArea.setBackground(bg); + coordArea.setBackground(bg); + } + } + + @Override + public void setForeground(Color fg) { + super.setForeground(fg); + if (nameArea != null) { + nameArea.setDisabledTextColor(getForeground()); + coordArea.setDisabledTextColor(getForeground()); + nameArea.setForeground(fg); + coordArea.setForeground(fg); + } + } + + public LocationEntry getEntry() { + return entry; + } + + private static String roundDouble(double d, int places) { + return String.format("%." + places + "f", d); + } + + public void addActionListener(ActionListener l) { + actionListeners.add(l); + } + + public void removeActionListener(ActionListener l) { + actionListeners.remove(l); + } + + private void fireActionEvent() { + ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "selected"); + for (ActionListener l : actionListeners) { + l.actionPerformed(event); + } + } +} diff --git a/src/main/java/zander/ui/media/map/LocationEntryList.java b/src/main/java/zander/ui/media/map/LocationEntryList.java new file mode 100644 index 0000000..4496dc1 --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationEntryList.java @@ -0,0 +1,119 @@ +package zander.ui.media.map; + +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.JPanel; +import javax.swing.UIManager; + +public class LocationEntryList extends JPanel { + private static final long serialVersionUID = -5819587120501L; + + private final ArrayList locationSelectListeners = new ArrayList(); + private final ActionListener childListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() instanceof LocationEntryComponent) { + selected = (LocationEntryComponent) e.getSource(); + updateColors(); + fireLocationSelectEvent(selected.getEntry()); + } + }; + }; + + private Color selectionForeground; + private Color selectionBackground; + private LocationEntryComponent selected; + + public LocationEntryList() { + setLayout(new ListLayout()); + selectionForeground = UIManager.getColor("List.selectionForeground"); + selectionBackground = UIManager.getColor("List.selectionBackground"); + } + + public void setSelectionForeground(Color c) { + selectionForeground = c; + updateColors(); + } + + public Color getSelectionForeground() { + return selectionForeground; + } + + public void setSelectionBackground(Color c) { + selectionBackground = c; + updateColors(); + } + + public Color getSelectionBackground() { + return selectionBackground; + } + + @Override + public void setForeground(Color fg) { + super.setForeground(fg); + updateColors(); + } + + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + updateColors(); + } + + public void add(LocationEntry e) { + LocationEntryComponent c = new LocationEntryComponent(e); + c.setForeground(getForeground()); + c.setBackground(getBackground()); + c.addActionListener(childListener); + add(c); + } + + public void addAll(Iterable entries) { + for (LocationEntry e : entries) { + add(e); + } + } + + private void updateColors() { + Color fg = getForeground(); + Color bg = getBackground(); + for (Component c : getComponents()) { + if (c == selected) { + c.setForeground(selectionForeground); + c.setBackground(selectionBackground); + } else{ + c.setForeground(fg); + c.setBackground(bg); + } + } + repaint(); + } + + private void fireLocationSelectEvent(LocationEntry entry) { + LocationSelectEvent e = new LocationSelectEvent(this, entry); + for (LocationSelectListener l : locationSelectListeners) { + l.locationSelected(e); + } + } + + public void addLocationSelectListener(LocationSelectListener l) { + locationSelectListeners.add(l); + } + + public void removeLocationSelectListener(LocationSelectListener l) { + locationSelectListeners.remove(l); + } + + public void clearSelection() { + if (selected != null) { + selected.setForeground(getForeground()); + selected.setBackground(getBackground()); + selected = null; + repaint(); + } + } +} diff --git a/src/main/java/zander/ui/media/map/LocationEntryListener.java b/src/main/java/zander/ui/media/map/LocationEntryListener.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/zander/ui/media/map/LocationSelectDialog.java b/src/main/java/zander/ui/media/map/LocationSelectDialog.java new file mode 100644 index 0000000..a3d21ed --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationSelectDialog.java @@ -0,0 +1,319 @@ +package zander.ui.media.map; + +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.InvocationTargetException; +import java.text.DecimalFormat; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.MouseInputListener; + +import org.jxmapviewer.JXMapViewer; +import org.jxmapviewer.OSMTileFactoryInfo; +import org.jxmapviewer.input.CenterMapListener; +import org.jxmapviewer.input.PanKeyListener; +import org.jxmapviewer.input.PanMouseInputListener; +import org.jxmapviewer.input.ZoomMouseWheelListenerCursor; +import org.jxmapviewer.viewer.DefaultTileFactory; +import org.jxmapviewer.viewer.GeoPosition; +import org.jxmapviewer.viewer.TileFactoryInfo; + +import zander.ui.UIUtils; +import zander.ui.docs.DocumentationViewer; + +public class LocationSelectDialog extends JDialog { + private static final long serialVersionUID = 817598179351293L; + + public static final int RESPONSE_CANCEL = 0; + public static final int RESPONSE_SELECT = 1; + + private static TileFactoryInfo mapTileInfo; + private static DefaultTileFactory mapTileFactory; + static { + final Runnable initAction = () -> { + mapTileInfo = new OSMTileFactoryInfo(); + mapTileFactory = new DefaultTileFactory(mapTileInfo); + mapTileFactory.setThreadPoolSize(8); + }; + try { + if (SwingUtilities.isEventDispatchThread()) { + initAction.run(); + } else { + SwingUtilities.invokeAndWait(initAction); + } + } catch (InvocationTargetException | InterruptedException e) { + throw new Error(e); + } + } + @SuppressWarnings("unused") + private static final LocationEncoderCacheCleaner locationEncoderCacheCleaner; + private static final LocationEncoder locationEncoder; + static { + locationEncoder = new OSMLocationEncoder(); + locationEncoderCacheCleaner = new LocationEncoderCacheCleaner(locationEncoder, TimeUnit.MINUTES.toMillis(5)); + } + + private final ActionListener searchActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (!searchField.getText().isBlank() && locationSelect.search(searchField.getText()) + && splitPane.getParent() == null) { + remove(mapComp); + splitPane.setLeftComponent(locationSelect); + splitPane.setRightComponent(mapComp); + add(splitPane, BorderLayout.CENTER); + revalidate(); + splitPane.setDividerLocation(0.3); + } + } + }; + private final GeoPosition origPos; + + private final JLabel searchLabel; + private final JTextField searchField; + private final JButton searchButton; + private final JPanel searchPanel; + private final JLabel locationLabel; + private final LocationSelectPanel locationSelect; + private final JPanel locationPanel; + private final MapSelectionPainter mapPainter; + private final JXMapViewer mapComp; + private final JSplitPane splitPane; + private final JButton cancelButton; + private final JLabel zoomLabel; + private final SpinnerNumberModel zoomModel; + private final JSpinner zoomField; + private final JLabel posLabel; + private final SpinnerNumberModel latModel; + private final JSpinner latField; + private final JLabel posSepLabel; + private final SpinnerNumberModel lonModel; + private final JSpinner lonField; + private final JButton resetButton; + private final JButton helpButton; + private final JButton clearButton; + private final JButton selectButton; + private final JPanel buttonPanel; + private final JPanel content; + + private int response = RESPONSE_CANCEL; + private GeoPosition currPos; + private boolean disableUpdates = false; + + public LocationSelectDialog(Window parent, Double lat, Double lon, Double origLat, Double origLon) { + super(parent, "Select Location"); + origPos = makeGeoPosition(origLat, origLon); + searchLabel = new JLabel("Search:"); + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener(){ + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + private void update() { + searchButton.setEnabled(!searchField.getText().isBlank()); + } + }); + searchField.addActionListener(searchActionListener); + searchButton = new JButton("Search"); + searchButton.setMnemonic(KeyEvent.VK_E); + searchButton.addActionListener(searchActionListener); + searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); + searchPanel.add(searchLabel); + searchPanel.add(searchField); + searchPanel.add(searchButton); + locationLabel = new JLabel("Results:"); + locationSelect = new LocationSelectPanel(locationEncoder); + locationSelect.addLocationSelectListener((e) -> updateSelection(e.getEntry().getPosition(), false, true, true)); + locationPanel = new JPanel(new BorderLayout()); + locationPanel.setVisible(false); + locationPanel.add(locationLabel, BorderLayout.PAGE_START); + locationPanel.add(locationSelect, BorderLayout.CENTER); + mapPainter = new MapSelectionPainter(); + mapComp = new JXMapViewer(); + mapComp.setTileFactory(mapTileFactory); + mapComp.setOverlayPainter(mapPainter); + mapComp.addPropertyChangeListener("zoom", new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + pushUpdate(() -> { + zoomModel.setValue(20 - mapComp.getZoom()); + }); + } + }); + MouseInputListener mapMia = new PanMouseInputListener(mapComp); + mapComp.addMouseListener(mapMia); + mapComp.addMouseMotionListener(mapMia); + mapComp.addMouseListener(new CenterMapListener(mapComp)); + mapComp.addMouseWheelListener(new ZoomMouseWheelListenerCursor(mapComp)); + mapComp.addKeyListener(new PanKeyListener(mapComp)); + mapComp.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateSelection(mapComp.convertPointToGeoPosition(e.getPoint()), true, false, true); + } + }); + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + splitPane.setOneTouchExpandable(true); + splitPane.setLeftComponent(locationPanel); + splitPane.setRightComponent(mapComp); + cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener((e) -> dispose()); + zoomLabel = new JLabel("Zoom:"); + zoomModel = new SpinnerNumberModel(13, 1, 20, 1); + zoomField = new JSpinner(zoomModel); + zoomField.addChangeListener((e) -> { + pushUpdate(() -> { + mapComp.setZoom(20 - zoomModel.getNumber().intValue()); + }); + }); + posLabel = new JLabel("Selection:"); + latModel = new SpinnerNumberModel(0.0, -90.0, 90.0, 1.0); + latField = new JSpinner(latModel); + final JSpinner.NumberEditor latEditor = (JSpinner.NumberEditor) latField.getEditor(); + final DecimalFormat latFormat = latEditor.getFormat(); + latFormat.setMaximumFractionDigits(4); + latFormat.setMinimumFractionDigits(0); + posSepLabel = new JLabel(","); + lonModel = new SpinnerNumberModel(0.0, -180.0, 180.0, 1.0); + lonField = new JSpinner(lonModel); + final JSpinner.NumberEditor lonEditor = (JSpinner.NumberEditor) lonField.getEditor(); + final DecimalFormat lonFormat = lonEditor.getFormat(); + lonFormat.setMaximumFractionDigits(4); + lonFormat.setMinimumFractionDigits(0); + final ChangeListener posListener = (e) -> updateSelection(latModel.getNumber().doubleValue(), + lonModel.getNumber().doubleValue(), true, true, false); + latField.addChangeListener(posListener); + lonField.addChangeListener(posListener); + resetButton = new JButton("↺"); + helpButton = new JButton("?"); + helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Media/Selecting Locations")); + clearButton = new JButton("Clear"); + clearButton.setMnemonic(KeyEvent.VK_L); + clearButton.addActionListener((e) -> { + int resp = JOptionPane.showConfirmDialog(this, "Are you sure you would like to clear the current selection?", + "Confirm Clear", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (resp == JOptionPane.YES_OPTION) { + currPos = null; + response = RESPONSE_SELECT; + dispose(); + } + }); + resetButton.addActionListener((e) -> updateSelection(origPos, true, true, true)); + selectButton = new JButton("Select"); + selectButton.setMnemonic(KeyEvent.VK_S); + selectButton.addActionListener((e) -> { + response = RESPONSE_SELECT; + dispose(); + }); + buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + buttonPanel.add(cancelButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(zoomLabel); + buttonPanel.add(zoomField); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(posLabel); + buttonPanel.add(latField); + buttonPanel.add(posSepLabel); + buttonPanel.add(lonField); + buttonPanel.add(resetButton); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(helpButton); + buttonPanel.add(clearButton); + buttonPanel.add(selectButton); + content = new JPanel(new BorderLayout()); + content.add(searchPanel, BorderLayout.PAGE_START); + content.add(mapComp, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.PAGE_END); + setContentPane(content); + setResizable(true); + setAlwaysOnTop(true); + setModalityType(JDialog.DEFAULT_MODALITY_TYPE); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + UIUtils.centerWindow(this, parent); + updateSelection(lat, lon, false, true, true); + mapComp.setZoom(7); + if (lat == null || lon == null) { + mapPainter.setCurrentPosition(null); + repaint(); + } + } + + public int getResponse() { + return response; + } + + public GeoPosition getCurrentLocation() { + return currPos; + } + + private void updateSelection(Double lat, Double lon, boolean clearListSel, boolean updateMap, boolean updateField) { + updateSelection(makeGeoPosition(lat, lon), clearListSel, updateMap, updateField); + } + + private void updateSelection(GeoPosition pos, boolean clearListSel, boolean updateMap, boolean updateField) { + currPos = pos; + updateResetButton(); + pushUpdate(() -> { + if (clearListSel) { + locationSelect.clearSelection(); + } + if (updateMap) { + mapComp.setAddressLocation(pos); + } + if (updateField) { + latModel.setValue(pos.getLatitude()); + lonModel.setValue(pos.getLongitude()); + } + }); + mapPainter.setCurrentPosition(pos); + repaint(); + } + + private void updateResetButton() { + resetButton.setEnabled(!Objects.equals(currPos, origPos)); + } + + private GeoPosition makeGeoPosition(Double lat, Double lon) { + if (lat == null || lon == null) { + return new GeoPosition(0.0, 0.0); + } + return new GeoPosition(lat, lon); + } + + private void pushUpdate(Runnable update) { + if (!disableUpdates) { + disableUpdates = true; + update.run(); + disableUpdates = false; + } + } +} diff --git a/src/main/java/zander/ui/media/map/LocationSelectEvent.java b/src/main/java/zander/ui/media/map/LocationSelectEvent.java new file mode 100644 index 0000000..b34941d --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationSelectEvent.java @@ -0,0 +1,22 @@ +package zander.ui.media.map; + +import java.util.EventObject; + +public class LocationSelectEvent extends EventObject { + + private LocationEntry entry; + + public LocationSelectEvent(Object source, LocationEntry entry) { + super(source); + this.entry = entry; + } + + public LocationEntry getEntry() { + return entry; + } + + public void setEntry(LocationEntry entry) { + this.entry = entry; + } + +} diff --git a/src/main/java/zander/ui/media/map/LocationSelectListener.java b/src/main/java/zander/ui/media/map/LocationSelectListener.java new file mode 100644 index 0000000..04f1352 --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationSelectListener.java @@ -0,0 +1,7 @@ +package zander.ui.media.map; + +import java.util.EventListener; + +public interface LocationSelectListener extends EventListener { + public void locationSelected(LocationSelectEvent e); +} diff --git a/src/main/java/zander/ui/media/map/LocationSelectPanel.java b/src/main/java/zander/ui/media/map/LocationSelectPanel.java new file mode 100644 index 0000000..0e6c1f9 --- /dev/null +++ b/src/main/java/zander/ui/media/map/LocationSelectPanel.java @@ -0,0 +1,78 @@ +package zander.ui.media.map; + +import java.awt.BorderLayout; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; +import javax.swing.UIManager; + +import zander.ui.ErrorDialog; +import zander.ui.UIUtils; + +public class LocationSelectPanel extends JPanel { + private static final long serialVersionUID = -1824218931983L; + + private final ArrayList locationSelectListeners = new ArrayList(); + + private final LocationEncoder encoder; + private final LocationEntryList list; + private final JScrollPane scroll; + private final JLabel licenseLabel; + + public LocationSelectPanel(LocationEncoder encoder) { + setLayout(new BorderLayout()); + this.encoder = encoder; + list = new LocationEntryList(); + scroll = new JScrollPane(list); + licenseLabel = new JLabel(encoder.getLicenseString(), SwingConstants.CENTER); + licenseLabel.setFont(UIUtils.scaleFont(UIManager.getFont("Label.font"), 0.4f)); + add(scroll, BorderLayout.CENTER); + add(licenseLabel, BorderLayout.PAGE_END); + } + + public LocationEncoder getEncoder() { + return encoder; + } + + public void addActionListener(LocationSelectListener l) { + locationSelectListeners.add(l); + } + + public void removeActionListener(LocationSelectListener l) { + locationSelectListeners.remove(l); + } + + public boolean search(String query) { + try { + final List entries = encoder.encode(query); + if (entries != null) { + list.removeAll(); + list.addAll(entries); + revalidate(); + repaint(); + return true; + } + } catch (IOException e) { + ErrorDialog ed = new ErrorDialog("Search Error", "Search did not complete successfully.", e); + ed.setVisible(true); + } + return false; + } + + public void addLocationSelectListener(LocationSelectListener l) { + list.addLocationSelectListener(l); + } + + public void removeLocationSelectListener(LocationSelectListener l) { + list.removeLocationSelectListener(l); + } + + public void clearSelection() { + list.clearSelection(); + } +} diff --git a/src/main/java/zander/ui/media/map/MapSelectionPainter.java b/src/main/java/zander/ui/media/map/MapSelectionPainter.java new file mode 100644 index 0000000..3c31997 --- /dev/null +++ b/src/main/java/zander/ui/media/map/MapSelectionPainter.java @@ -0,0 +1,37 @@ +package zander.ui.media.map; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Point2D; + +import org.jxmapviewer.JXMapViewer; +import org.jxmapviewer.painter.Painter; +import org.jxmapviewer.viewer.GeoPosition; + +import zander.ui.UIUtils; + +public class MapSelectionPainter implements Painter { + private GeoPosition currentPosition; + + @Override + public void paint(Graphics2D g, JXMapViewer map, int width, int height) { + if (currentPosition != null) { + Point2D p = map.convertGeoPositionToPoint(currentPosition); + int size = (int) Math.round(UIUtils.getSmallerScreenDimension() / 80.0); + int x = (int) Math.round(p.getX() - (size / 2.0)); + int y = (int) Math.round(p.getY() - (size / 2.0)); + g.setColor(Color.black); + g.fillOval(x, y, size, size); + g.setColor(Color.white); + g.drawOval(x, y, size, size); + } + } + + public void setCurrentPosition(GeoPosition currentPosition) { + this.currentPosition = currentPosition; + } + + public GeoPosition getCurentPosition() { + return currentPosition; + } +} diff --git a/src/main/java/zander/ui/media/map/OSMLocationEncoder.java b/src/main/java/zander/ui/media/map/OSMLocationEncoder.java new file mode 100644 index 0000000..32be2b1 --- /dev/null +++ b/src/main/java/zander/ui/media/map/OSMLocationEncoder.java @@ -0,0 +1,138 @@ +package zander.ui.media.map; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OSMLocationEncoder implements LocationEncoder { + private static final Logger LOGGER = LoggerFactory.getLogger(OSMLocationEncoder.class); + private static final JSONParser JSON_PARSER = new JSONParser(); + + private static final class LocationCacheEntry { + public final long time; + public final List entries; + + public LocationCacheEntry(List entries) { + this.entries = entries; + time = System.currentTimeMillis(); + } + + public boolean isValid() { + return time > System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1); + } + } + + private final Map locationCache = new ConcurrentHashMap(); + + @Override + public String getLicenseString() { + return "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright"; + } + + @Override + public List encode(String query) throws IOException { + LOGGER.info("Attempting to encode query: '{}'", query); + if (query.isBlank()) { + return new ArrayList(); + } + final List cacheResult = getCacheEntryIfValid(query); + if (cacheResult != null) { + LOGGER.info("Retrived result from cache."); + return cacheResult; + } + final URL url = getURLForQuery(query); + final String rawJson = makeRequest(url); + Object jsonRootObject; + try { + jsonRootObject = JSON_PARSER.parse(rawJson); + } catch (ParseException e) { + LOGGER.error("Unparseable response from server!", e); + throw new IOException("Unparseable response!", e); + } + final List queriedEntries = parseJsonResponse(jsonRootObject); + locationCache.put(query, new LocationCacheEntry(queriedEntries)); + LOGGER.info("Query returned " + queriedEntries.size() + " results."); + return queriedEntries; + } + + @Override + public void cleanOldCacheEntries() { + for (var i = locationCache.entrySet().iterator(); i.hasNext();) { + Entry entry = i.next(); + if (!entry.getValue().isValid()) { + i.remove(); + } + } + } + + private List getCacheEntryIfValid(String query) { + LocationCacheEntry e = locationCache.get(query); + if (e != null && e.isValid()) { + return e.entries; + } + return null; + } + + private static List parseJsonResponse(Object rootObject) { + try { + final ArrayList entries = new ArrayList(); + JSONArray rootArray = (JSONArray) rootObject; + for (Object entryObject : rootArray) { + entries.add(parseJsonEntry((JSONObject) entryObject)); + } + return entries; + } catch (ClassCastException | NullPointerException | NumberFormatException e) { + LOGGER.error("Invalid response!", e); + return null; + } + } + + private static LocationEntry parseJsonEntry(JSONObject jsonEntry) { + String name = (String) jsonEntry.get("display_name"); + String latStr = (String) jsonEntry.get("lat"); + String lonStr = (String) jsonEntry.get("lon"); + double lat = Double.parseDouble(latStr); + double lon = Double.parseDouble(lonStr); + return new LocationEntry(name, lat, lon); + } + + private static String makeRequest(URL url) throws IOException { + LOGGER.info("Making request: '{}'", url.toExternalForm()); + URLConnection conn = url.openConnection(); + conn.setRequestProperty("User-Agent", "Zander/Curator - OSMLocationEncoder"); + conn.setDoOutput(false); + conn.connect(); + String response = ""; + try (InputStream in = conn.getInputStream()) { + response = new String(in.readAllBytes()); + } + return response; + } + + private static URL getURLForQuery(String query) { + try { + final String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); + return new URL("https://nominatim.openstreetmap.org/search.php?format=jsonv2&q=" + encodedQuery); + } catch (MalformedURLException e) { + LOGGER.error("Could not generate url for query: '{}'", query, e); + throw new Error("Could not create URL!", e); + } + } +} diff --git a/src/main/java/zander/ui/transferhandlers/ListArrangeTransferHandler.java b/src/main/java/zander/ui/transferhandlers/ListArrangeTransferHandler.java new file mode 100644 index 0000000..c32881d --- /dev/null +++ b/src/main/java/zander/ui/transferhandlers/ListArrangeTransferHandler.java @@ -0,0 +1,139 @@ +package zander.ui.transferhandlers; + +import java.awt.Component; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DragSource; +import java.io.IOException; +import java.util.Objects; + +import javax.swing.DefaultListModel; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.TransferHandler; + +/** From this StackOverflow post: {@link https://stackoverflow.com/questions/16586562/reordering-jlist-with-drag-and-drop} */ +public class ListArrangeTransferHandler extends TransferHandler { + private static final long serialVersionUID = 1L; + + private final DataFlavor objectArrayDataFlavor = new DataFlavor(Object[].class, "An object"); + + private int[] indices; + private int addIndex = -1; + private int addCount; + + private final Runnable before; + private final Runnable after; + + public ListArrangeTransferHandler() { + this(null, null); + } + + public ListArrangeTransferHandler(Runnable before, Runnable after) { + this.before = before; + this.after = after; + } + + @Override + protected Transferable createTransferable(JComponent c) { + if (before != null) { + before.run(); + } + JList source = (JList) c; + c.getRootPane().getGlassPane().setVisible(true); + indices = source.getSelectedIndices(); + Object[] transferObjects = source.getSelectedValuesList().toArray(); + return new Transferable() { + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { objectArrayDataFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return Objects.equals(objectArrayDataFlavor, flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return transferObjects; + } + }; + } + + @Override + public boolean canImport(TransferSupport info) { + return info.isDrop() && info.isDataFlavorSupported(objectArrayDataFlavor); + } + + @Override + public int getSourceActions(JComponent c) { + Component glassPane = c.getRootPane().getGlassPane(); + glassPane.setCursor(DragSource.DefaultMoveDrop); + return MOVE; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public boolean importData(TransferSupport info) { + TransferHandler.DropLocation tdl = info.getDropLocation(); + if (!canImport(info) || !(tdl instanceof JList.DropLocation)) { + return false; + } + JList.DropLocation dl = (JList.DropLocation) tdl; + JList target = (JList) info.getComponent(); + DefaultListModel listModel = (DefaultListModel) target.getModel(); + int max = listModel.getSize(); + int index = dl.getIndex(); + index = index < 0 ? max : index; + index = Math.min(index, max); + addIndex = index; + try { + Object[] values = (Object[]) info.getTransferable().getTransferData(objectArrayDataFlavor); + for (int i = 0; i < values.length; ++i) { + int idx = index++; + listModel.add(idx, values[i]); + target.addSelectionInterval(idx, idx); + } + addCount = values.length; + return true; + } catch (UnsupportedFlavorException | IOException e) { + throw new Error("Flavor was invalid or an IOException occured", e); + } + } + + @Override + protected void exportDone(JComponent c, Transferable data, int action) { + c.getRootPane().getGlassPane().setVisible(false); + cleanup(c, action == MOVE); + } + + @SuppressWarnings("rawtypes") + private void cleanup(JComponent c, boolean remove) { + if (remove && Objects.nonNull(c)) { + if (addCount > 0) { + for (int i = 0; i < indices.length; ++i) { + if (indices[i] >= addIndex) { + indices[i] += addCount; + } + } + } + JList source = (JList) c; + DefaultListModel model = (DefaultListModel) source.getModel(); + for (int i = indices.length - 1; i >= 0; --i) { + model.remove(indices[i]); + } + } + indices = null; + addCount = 0; + addIndex = -1; + if (after != null) { + after.run(); + } + } + +} diff --git a/src/main/resources/docs.xml b/src/main/resources/docs.xml new file mode 100644 index 0000000..31ee563 --- /dev/null +++ b/src/main/resources/docs.xml @@ -0,0 +1,21 @@ + + + + docs/library-select-window.html + docs/local-library-settings.html + docs/global-settings.html + + + docs/album-manage-window.html + docs/album-settings.html + docs/library-settings.html + + + docs/managing-media.html + docs/media-types.html + docs/media-properties.html + docs/selecting-locations.html + docs/import-external.html + + docs/gpl3.html + diff --git a/src/main/resources/docs/album-create-dialog.png b/src/main/resources/docs/album-create-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..25294d953af818f5f802f373c963db2701b561c7 GIT binary patch literal 4460 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 + + +

Managing Alubms

+

Overview

+

+ From the album management window, you can open, edit the properties of, and create albums. + Albums are the first thing a user is prompted to select when they visit the Art Museum website. + An album hols media. Media is the images, videos, and audio clips uploaded to Art Museum. Each + album has a thumbnail, name, and title. In this section you will discuss more about albums and how + to manage them. +

+ +

An example of the album management window.

+

Buttons

+
    +
  • Back: return to the library selection screen. + NOTE: you will lose any unsaved changes to the current library.
  • +
  • Upload: upload any changes made to the current library to the FTP server. Also refered to as + saving the library.
  • +
  • Library Properties: control properties of the current library that affect the Art Muesum website. + See: Library Settings
  • +
  • New: crate a new album. See: Album Settings
  • +
  • Delete: delete an album.
  • +
  • Open: open an album. This allows you to manage the album's media. + See: Managing Media
  • +
  • Toggle Property Panel: toggle the album property panel. + See: Album Settings
  • +
+

Rearranging Albums

+

+ Albums can be rearranged by dragging and dropping them into the desired order. +

+ + diff --git a/src/main/resources/docs/album-property-window.png b/src/main/resources/docs/album-property-window.png new file mode 100644 index 0000000000000000000000000000000000000000..af4ee0cde18a4c0cb5e985de708e0a10995e4c42 GIT binary patch literal 41846 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/album-select-window.png b/src/main/resources/docs/album-select-window.png new file mode 100644 index 0000000000000000000000000000000000000000..37348ac6456a2efd8b22bbcac79cf0b7bcdade81 GIT binary patch literal 23339 zcmeIabySpJyD)qcf+zxt2q+%3=u-}lewtmR_%zW2Sa*jHbBeEmR0?%bKnXCMeVryzg# zAq1V4f*``>)A(Sf^%T=8_(SqsUdI7~=mW9;@Iq$RpF+@8Na5}s4Oav7h;@s#jwj8I z)9Ubpv@hRy1b759b!8PE6)U!co~`5cotWYK?v$$J_r2tX+U--thM(_ITyGCydiSQM zcQNWkUGT_YztnSHw$SFMG)MxP54_Hy=(OBvv8Fx0nW`ZmO^mz8((&{$c?)XAsx_EAx>!C9t? zsqABUJ^Yr%W7y^d39JD~y{NO7Pj;`k41Ei;zR9d!ZtH z&stquc3W@rjbE7COZ;ca2M9-$s$(hp>d-&0U!CnTu+dS~6BzGzQ-gm@>RUQsq{MT!D^+_as@s`%bPd}(i*UI(} z5;=`ztFRg@Hm(wuoYjd!wycH--i+?So84$(=Y3!P=_skb`h4I=IHr-nTapabq$Cp2 z#((i6-Ad!3bIWdB{q3a-ANG%Pzq00wA@x?+cs;(6`BS|2=H9!^$uZW$spSC?e54o^ zT>cmIJ@$OP>v`7fuJ>Bx7-ir`UdF>CTmPBFvyU)JA^35VRp}RKpyahIi?QJS(Y+65 z`uP0Su-*a_P2vyDo-Q!o^VBU?3nxb0N`~pb-uUrv@yg2DWF1x7C3ny{c)YE3^^-@! zCurum>gYdHZSNGniqtOi{?OdnmsX8WGP6l~iX#xQQANL5xqhpyjL_7}JHw(egN!#S zeaVBkuJgD??|qf#F_k<=q8W#j=y3S%@;?d6@Ej#gd%;%Wq6@{x{{r-OQ)IdwDNsfT zf?M@TpQetAvUAmR!i*h;QIF}`hh%$fTiKZ#p{8`aw@B{MKhLt~Hu$(r?i4@wqcf@d zo-!OvR2h%Bq&@%OMq^_}t#C-|zB!BHcv$Xv=u9WZh}sleqQrB~@KiOBU$xS)4VW-R zTXO5tcfMSG8{2z(CTClRUDY}LEd1$EtB1*`n`Fb8qRfh~?{+RQT5cdRi^P@#<~29? zZXWBc#HX_ov7*gqqpI#YaC7ry2T&wbY45z`%fVP)s@k$Le0x^&*4ou@gPqQ(ZdkVS zQ4*PmmMAq(yI5e^765&f=ad#IGt z7$anM{h{r0d@cH>b$Ac$XQYhc;Is(BklFcbTrqV9d>3_8x);9jtEkE?))4P~ zPCG}pB(l63z2mLRf)8*^bs}?GP}4l5_b*cOo4qkNNnFqqZ|P9kCuJM3A*lLE9D#@I z6mrv9pf4!?@togTFiMczqGq?wI=!-d=qRH+aD`jKDz?o-x6nmsH9)wJq_wB*bFX0+ zan&kf>Oqs`Zg^N`wHN`FtGt3P{hU$uXeL{QvonA7>>GWJL}ZD!h*=mKbJp;v@L*1> z;wbrTnB#FWN^t)FL@@0nih0Yqls>Z}7C$Ch-L1(NJ#(^;+9T#6sHBdYM9r#2OL#z?(Z z)(sJBzGl>1I^Grz=ft#NmF~m`!&7YrTL4#Ark8O)D&8|BFM2Pig}D>c^9=#~WmEKi zpe%J8)fw#9igxJG4NFa(dygou7KHPrwaE!j$2ybiMawdRM_d-uYUdY>dAq z>iKH(qjIBiuku!@MjcDb;K(bw%Y6LR%)P%}^kKO{wTEoLF2{kaPGXq=;UDh-9b;b z^Z*T1Xm&;Difl*$<&{Y1C(K&>Uv326Ki=uHDP}I580js8IX+yVIS+SET4{7xpm*H6 z;v@DUoG$&?vcE&2+@40gTA$xd{_FtIlTI+dq|}rB|`-U?L$5*VOtorrIQ*r z!(&;c{^zqpev$_PJi@Bg`k(krW|N8A5mh<>6N=S zdRIh-Qb=?C1+`LSR`E#VMiuQ9hxo??J}I!eO(z8x>E>Yu*V2o3O~Y>ga3*hjtr%nV zO5U++Nit!j)27C(D(p6T`c-90+_Og(A)RWaos!)%w<{-H-~xWahWmN<6!c&>tkY(9 zcjl@u)|ST>ECtqmR2v#f#5~;C3!E3Jf4p+h{jFG<-ptOa$EU=DQ%1fl-gbFmgcO}U zK);%PsT3?&VUHMgdvwerq1c&v$02fv^U*1GE*l#)X-oZU%aXRY(iFyj~c z6O$Wx`+8Z|-fkcqhbh9!IE9rQ=B|8A*r>=K^t`rNR#H)MS-Pa8L~nk*g?INP-+nWa zC!GJTO8#={K00%lj#I3GmeC0l6nsHq(SeT7Jjl zVvBN7>RFF;^W26~qciR*zo)szKF~Prw~#rEcKakJ(O$_6n=+NrbJmhmOD_JgFiZbr zbQG$y980Hm-y(}iZ+|oASg|KGRml@Tf6OAtNxo2hq44p%T1esGtBxttC5+6Fld1PJ z``!-GUYIaX%Du>izG*;LKbN9&LeTDs_wMTu_xV{ou4D5AczHp2Yie|2a$?E20i~GU zf!aB(<92TS$&TA7XA4`DDKC3WWo|w_bxKCQRF8HK`En-}14`W{N;4$4Ye=`+{z=g_kYRI)PDj|*92i{rgYxo_Q7DGxMl8bE# zRrOPxU1wiCM3O<)024#Jn8@hf(>z3Fsn)S%xG81-Dk!MgG?B?)RpP)k&s4p~E#JtY z#AMSnz_i9>hAe5(MI*uawp047N0FA%9$&!y#6ssUe2aTS)G{T-rg=SY{&ocPk#fqe zH%m*FmnI_4&H``-!nQ?k6Z%H#2X@-i4HyDf4m8GYUUTa$B;gk=YV1FVoHf#d2@eS! z-ror)eff@<@}}qEx3W}#PCAo6h0R!1{7_Z)6g}eu^Gh=aB;OcfB5ij(i9U0q&fPlV zXROr6%djns^;Tc~NKbXIax}oh0}fjjRw5Wvo;e`TWV-a|ZQ<;z$oYu$0qtepBC#dl zBq~>XX|Qo6cxz9TqfK*3{FM+ouN3?wrfvz6M_TeNz`F5*Q4u)B)lj2cQYQ)hiE2`6wz8NTKBJ;TSf^Xg3GKE zWDkE1g?x2hKsG58gs^u$dxVLL%N^!BhtB`TO)s9P+`$4~4v{k!RGRbbKIRcq+ybwX zWef|L{77PB$}dUR=T&aJGZex!sPCka>cxOqxaUb`VLwU1Z)us*G&tZXLzeHp`ynFjN97jZ?$1D>gONl=gucR#0v9#hlcDTm1@93D zeA{+oTWX24&r6_CDUktINw5^Su8MrVdY)~Hp#1Vi_myjxKabofD@#al^jDUNi4(ai zPw%XeGGLY$ZjvLR-&jwvoHh9FM*p+OE39H;rfT#sJI&bbC`7lh{@Lca`(el4M_tu} zM{`#UcB2*(y~hj5?UAM~hw=EAvsk2#>%Gu3+@C8&!ayFiU ztqmehcA5FUC^#mw#fwgq z6N^d^_)Sknnk=lrZ{CPsv$;W=;S|-He%T6jo>^w}sKurx--cX_<-%jzmTRSKD3_`2 zwxBAdRn6{}V%DYmANEb2XL(ry6Z1pUO3dS@3Cbj=mph(!s1cr=WNx6Fsq$qPD&s6r z8hZED#DY(|Ig)LPlKZ8ayWu4S=Y0-kBCF+2lJIs!KT}tZMMqzDFmv3R3eWTule&PB zX8892y^9;HqfGuGBL_doCAa=MNJMp--ZpEXs8P0|IT%cMcsV*5BnJxb+59H_pPt!w z?Ftopm!{xoAv?NOR=t(0YnX}=HHuLC+i+l`GFF`!(w9%UKLiiSpPn!-t_rp4|K#S@ z?S3elE1f5RrM`%DiQVtrs8}V{U>aLeLRt`TE9|~%&7OZvUUb+d$kC!S&{XLYH_wvB zf+i#HUF;0*Lp{z*2ej*pWm{$l%Ds8UxAoFYpZsz4s%3R;%yam%O_FPUo6%zV7K!4A zeKMKFyJ5;<-)$P%8powl3h1Av3(`wI$us5CUgEY^>QyD;q?g?sv~;>wN!+ggNq;a{ zfK@5{`{OJD>Q&w}g$+zL0wMChqiYf$+*ioEy=}GEqUx1Gy|Lb|6A}I_jFfwN(!P8Z zDNkhOv~a}`(}Ym!469`IlQ|lf$=*v=^^^=I>yntip8QS+#Q%}tj9i=e0z4`# zz8IjU?S!HbKRA*!{t<9EtSbUG-RHimn`hln&)iYz1y65~&Mf0(!D8rQp_}K$N3)RG zrTZziMIj5=ejbt1B+50Y!ObZt={_9#=hjJB&0x8&Kk?&M-boX z9Dxllyzjr{vifiFu@Pv)sOAV&SaKsgv%KJ@4vh8<>9_n@?yl>rThfvYVnO!ULkiFX z$%?D{M4Un%#w6!G*Kq?otnX+pwhHt8V9w-NxauTQEu4&Te|j#4KGXtITa&OzL(K^olh~B-rh5&wVX?UM``A(7x_41iW zy^1g{dWMWbrTVJa|HRJ=&s=*^cb1A#9hVd~kabm2=>k)wi9;AY6()W|T34-T-p$5w zz2j~+#}a?RK7j)mipg(w5f}zwRt8jX;`rFXNow6*MG)MnQ)LVqs^$#e(iANaYXZky5VXY)3<|MJK4Hoh_N zXed-nwDNVRGvP;*WyH30$Vyb^*Lh{xu2AZwR0Vpph>rZZ(Z`CGo82#dmQvsgJg0oJM+O8FWwYL-;`X;6*?q)BaA`%+0ADe?nTzu2`Thu7wEEbYJJBJz4o#z(#Wdzi5ZRp z4BgNJiUb0IN=4R;s;q7r^!CRr#LM%y`&6?OxDA?}Xf~`J*1zh-4?kBs)+j=x7Uu#Bn&MsMhH{nbdfRGa-EHkkRfrhwXFX=^+ph>qazr1_ zNd~lce6iM;QG35c3qRF}*f=9*5K=~dy^pu=1~p@kYLH?`l66HT+lS=+EHf{CSj`1e$*(~ zB1o$Xqb84+-F)okWd9PIfuMTG^Mx8~9rR}w=iX!*(so+@I3H}ySLDS#+HKcm1>3F3 zkql=OXoGJCytK4*INRH8I*nrlr4(_ypWgDVmGBgBGU$|I`{a(}2)5>L^^dK84j!=U4ozp2w-xIQX7NwQSoCK96cig<6&$>o ziWPJob@rj1?;+kro?4}Ju+7rFelPoN1gC)Df$jr|UQ1@pmn~BEI#Y_*)zX=a1()U~ zM`V>!i65Jl6fw;WiC0p-XC_5R%f=|%uQTa`$2exE?Ve8Be-ot6#&stja6Dp(KdVn{ ze8-_e7fb6q@gn(r1@2Kb0h0(*z;w}VZNT*yV&3YBW6Us*w@|?%_RmiBo~j|etn_IV zyj-EASbBNRYd^nn%7Q5A{Mf-uEiHOyb4C;MZQ6%{5w~a5S%AxtnN77<*Jz|FH2XN> zmSxR7Dq#6qCh24?rlUSl@7=x&yv-aF$h4MA-?0#yGQPsb4%5l+ zlu+bmc(_Exx1BV;=0|JbV6iRri0$ew$xFS2k4cZp8<%^dd|d<>vy#GhZb1>xKq4{o zlRC*{6PsC&FPYkJ?W8-D?=L9-N+aGWzZ_l581gIPdA%b@`LNHVLGQf+y_48qrl9mw zyxlrF9SyTPH57HXq`0-8jJmKf#<@l6^u(Ol>VBjNdE?8*n8=lZ)>;{}jtU{2_@ZEf z24!0to1wN~XYxwb)KRuVXDueD0w!2m8_jN=GkiV8HNj)?MALc zJGq5sSNn9a1(an;t8gblrgHD2W7x)f;d)m7{R7(*=hG9in2yA^XFu|S(liVc z#CV43s6lu1XtR3n(@cV$#z`{)#b3P@?RkGsvUQT{gQbssT%W{#MQ{;ey@#zwOFp-6 z^jIRlepkNuxJ9d@lh{$AaMg~P*8=}Cd^53QU&Y*B%1oG`eBh}VGj;kg$+zm0#IemI zy7?5?Wmub$8s}~5MGUtkn%{=8t#Dc=n|ElbAN4w-O@ko9H~?Y4aa<$SA)ZPV>};lq zu+@v`X?3r&LNJ03M%+S7)^j8)hqKclC!byf;?>cX9IQ)nj6^y3>ol#rfj%qant{IF z%g-?1Tcu3*^S4;F(5qK3y2|IfPBRXVqP{K9-fc2V!)sF8lMofF_z_YhRa+%&(5(~a zG!ZEL_7_pkCQ*Os#|MyE{FtrrXDfF!2#5Cw*{y^A#w8U7eLX#|@tVZoBmyo^IEBtZ zSUOg=DSq^u4W9`ts3P~+>q3ZSOT;TpaC39Zlw6R*>;G|v$J=;JP*66o_(#V|i{uS5-1}e|wpAwLz6*to;J7G2ET#~ipYPof8sh~7}jbh-)NeZ zn74iE^bDqqq5EEXn|Y_W+3_oP-#!_s2(=Q78tZRPi3I%H?t~uKF+Gj-GR0Y1v4MDT7q7{8AwSi8DE~G5ibHxDJG>nK z#nM??V%AO4_}SKYal6ywCjYW?#m!w)NbzG?^~7EepKE+_lFzm7&OqAS^`wf;3E=>W zEW_%PQI|>yZUPZs7v+D^`2xa`U3Uw9>*RzvqHWVLW~nc`o(YZH+$bb%XWXWcmimSFWMTl^Xpq%U8(%8l=1aSlt{+fC~ zNAjyl^ic#f$gm$NI~HEt7oSPc`NMr`-ywf}`HR^X5BKe&96kZ=3rB0ANY3Y2VcWV- z6w;@QZO4LTnEcnbajSgz1qFQHDKT`LoX4&8Dcz$4{bKNuvLj;Ub%%MKf7$bEKbj{G zes2g@J|=lH!I(*7K#5!WQO5i4Cr|%@q5B0IW6X{2{sY_+$hvP5$qQg}3u(Q(oikKy zTmQ+ST{3Eg(xQ*tQ(24!k*>UQ3AfZ;`93`2n{iF$jmvp+2}@tfm-{sUZ8u>ml)b)E#?y2yXYuWSLwDn8)`Xp**;V)e)PcTc|23k zwXVlF8l(cV2=A$E>u$~*tsEr?wX?@Htg(##h$Cq!j4i0mfw5zXkOh z`<&O$@QugNQ*!rD;~>h?9)|I=s*VKXMo=8bIz5fUv2}9pSRt>@E=TdEVR!D} zc{1S%S2h9Ktb{U3Ini8E+*-MCdAO9WI0*|oj!>liB#fzNG$m_BD8pgBec+1hg7(k2 zSeM#U*wwq8dx*d90zl96M0~#+DK{8te7S|&>chw@7}L{m_g%3no-ci`hWGf(-Y{Q}C{K< zLj|~h^iHqsSk*{}s$53`-LwD>XO7;U_EO!E_1HZH`fc%hI)?Rje9<~M#=yA|dly!7 z4`(Yugx7BR3;z!t3>RpOT2}UvF$C!W*b=Hl#fCvNZZBxosc?!%TzM#R#k}WM?}Aue z=DBa`S01{_$LBv+yliSOgTu9UqC6Q+r4)vnNFa#hoP~rJ3IPO_lbG?pp0m z8t8!}3F*AB*3Rc^fj|se*(} z{=W!`Zd(m;{U}NTCwVH*$>0^B!M70d*;arf`4t-&Bc6keq)5;os2l!*kWDPSSN+5I zh-c_XASmbcoaeHVP*c!$`!SE}Nfe9`$k)@o3$`5GX5-?Oc+hyx7w#sPscJW?HPk4S zb0!fvKCcMqZn}K~h@M&=wb{=nc*rv>ydni*n}n>4S0IR2{K}+ReHgfi0A{l`_z7ni znZWE>-3ha%A9%>mU^dmE@myT99pHNS03pokJvsD#N~uEh^JdeTJr!FB>gPm^9X`lR z%HAgO8Q3Qz3Un|4$0W+Y4?*q9J5S8GIG`34d_QB+vHQq<2H-l{P4f{^T%1sg622dS z=q??ylo6PM2cWU##YdJ)?Y)ufO&19Hd;_rm@W3^5JZPcbl(aeJKi0omm7#$JwdGQfRp)9TPOmR=U3BX zfbx!!McG{hflw@uyh!jNW;Wy{g^k3%h8_gHnV_UW51-^wM^+|KLl8O=U5lsV2#LHS zBtjIe>YsQf(u3L8Z_zb*ThQ|}B&5zurwzO^!{ z7J?|{;W?agqxGVIR3A9W6L3`dS1^}l0megjk)g3RRb{rHV} zye@A7Z`r(#*t$PEiRVKG@SrSTOW_6KK@Q=S_P7RCLP!)$o;zGk;RhE9d9&v{HpwR` z-t5MSkG+wBd;-${-J7VQK2 z6ZJ8O@>Qlj^dedY39=R7(#s^&4U0l{NIW`f*85O6vX$Y3{%3|y3$BT7OlWSU)ZzZXOga9v4% zJm><=vqo+=aG4B(1O}9eAnj;EqS#!Rfut2KhQ9H2(#b1f(SkulDQ8RnHs{P}04%RWo(qmWIfH zstfjSk5Z-sI|e}+Quuzg1FNHqR=A{uOBvtq>A=D$C+ArRd48`;;^L3w)5u#2@SLlN z;X_kTtn~QI0FAg+$@buwey-_9}ut44gfuBpR;)#HBm^G6-G|v#hg5eb> zAvHDSIsA|zjvMlTrzX^g4H=RUgs{hWANEaAJX=Pb^ombXKgK2NYyQ}I|>Fz!Hj>&Kso9cXh@&# zMt7C|1;h^IhTg2;VZv??PaPA~f1QMsD0j6*PfrPpJd)x9%~t+YONE{qb|?i{&|Bq=8Eubl}vFiqK5l$c3KSBw~= zGfQ@`P<+@(NE4nCM_ZTTV`=pP=hBwra-T#Gs5ih6NHYccvib!rX6%rHh4=JM(De|j z3)oR8QbbI=Hli^SCuKm!?aX&xU5{SEC6vMU@uy1AWa^Sy*RgBq-V=h{oO+haLtGCa z0Y2k2EWBh*RlG9}k-*tO$ae~Gx~LH&#n}U_9{Ko%BSi!dKmXoc9!?h@|3q;r2B%~% z2#LOX-D0&9#J&kpk&sH~Sly@njWQ5#vwm??-7G2W!;cDJIB>_5GfM(Hyn4;|y9Am! zUs4M=mzk1^)yH%%HuP@Ki2`4;14QY=h)M6XI|1~OBhhQpWr`wVZb!?DoeU4jpa2hr ztNw%iAf4|-bL9*4;gb2kkE*^0r{BO5e+<$$eLr4?!_Qp--dretvr(=2Ct^%kk;9^hvRb%Y2e9o!-KUXV528D2*w-04zVL^n@=jSnE5CJyNQRGocp1O2T9cybL5l! z_69=eH&JbuaRP7!7#sebdT`Gji07os1RkWx=KK9UIxMQO4D9htrIi$7;qCuWxzVYO zEAV_I1U{%O*iAG8Q0BOHWj&*=mIwmYLb_l0Z{OAgeA{(&f^G8=Fj*8{JPA}KEzrkn zzIX1RKiSq|!xsojeuw~!^+6yaFF2i*6y^2H_I zHgKYE$KIJSao>c@!;l{z?DR&Nz64TvvAF7lXObeWPWBXZ9{Av=fu5qXmq1qFG^0r8 zOIexB`xrmbd6&SSqFg!H6$IT9z13-?>Lvv3hae(r{`OxOfNmBCzYqIH%?ihO| z!#ipm;>ir6Cm?la#ec^q^y zqFHw5iIiKmKw3DiwErXap+&44N8_p><8&b0@2cQdA@@^^f zP?JEUKn4f$4S$7%4u*$Fxk-xQ?ViK~b5Jzo1BXgbzC*PMwq)Jw4KlnGIG!cW<8JZ$ z83|(76g+C0uqAvq{R8q*1M5SWSH~Imhw-3~_lweAm#{!^E)eC;d^p^zzYSa|N8&jv zOKu_vtNWFrgEl7}t-zJE23cFZXojG#K+pn%^HaRz_)meH>gndeICr)_2r7`gAkytR z@4OT89E1_j+)9_>;RpytNSS@V+bkozy~oUezKc)E&;&kRz#G2^Ru34s?C-i80PY$K z<&ighCP2Oh=#?HXZ?CSt0e0gm2=cr8Few-q}GOWg~6BpPfLx7kD-*Vh5gFKO6)<6E;;-z3M}>_(MU z9yN%SSHnd<+Xgm+l*X>$Hkg5O5Q!&)t1l@S$}p$w@XVwa@jjUi!4JdOM=?wTXCduCLZXWi{2X)gz`kT+{l#(!^-^!Rs^=yv z`A;JJ9boyavU}@Sj2c^8R0o5t4`+(M>8A=)oUH+t; zS#6*OI{DF6e`o0O+H(#xSPmgMK+$C}88i03HhKN!)~wUg66Tto&a^j#=hNsxOysC@tt;nl zA^>q~|0j|Q2wMa|@nQogOVn2+)`ViM@9azxO-d*pzE79}T&lYC_ioZ(ZqG67+QGVWbwmIH*g1`!gy;l25LGJ7;5sxh<} z^5F+npQ$$}qXwV^&`?^@YV~TS_kIYbS9mf{|IquS#e1zbL=r2en@qlVN$1*rtqcIA zDvaHB&3Vbt31%Qyu2KDo#P@1VW;?FY&)ubPJ!fs(MYW9+~hzYz^f9!U-H5 zuJ-i{SPr||;JB>!l(qNd*+h$bmo|6|_a?KCjzenIpm8{;ArU5OTi-jRn>q8g%8I_+_C zIj0XpebK zj+7QDm!Xx1J|wM6bz!#$P1YW$MEkB3jU+?F5yRT5MC3COwDUGY`e&eSP?7iF;kkK$ z@rW7JlIQD34D3DC9(P2=hUhjK6dFDW`#yRWxkQ%pHQ2Dnbbrth$yVj zj6X4YH#+r9bAGR1F+Y{wR_;cr2=ttrg*VuyE zcb$)&O=6rS;dv(PDQ0)CEvuz5*{^em{9MzN*mNJ5keBQoESNzT`^laH?y@OMz7c z^F2e05vX926#hFlMD})&irr*oW%27idLRDCp}`d)A+=jYUb?pF>OupmunML{wa-jo z)%Y7wnw;$(=J@7iv!ZkNkf%{UGIyJKnLtE#q-H@sK$BfY#q7oKr`gG+59@?@M2}Zm z9VV3?5oe1#M1%3{_NbiX==yq4Do5!dEaybPi9E%H>Tx`a>YQV#463&8))Wao@;fLv z<}Zw-(UiyX7D>hiW%E);s?0zEBoD$I@dl0-?wn^%7hgSRT=3`IzZW_6+Mo38p4S_lvLAu(; zV*+IK2FaTw#N=!CMeFm`=`^`Ugvc0hhUgvyX4z z8da0>n^*CnAL^vbFp&m)<|?t722?kdM%T1IRG<4(lv<+K}7&-wa4 zAujsJ_B6g3%`Wl+>eb+B=b`}{sHjOjNV=gVM#dAB_F6KflS4;$g^SkMWH^-3`AHblj()?5`wVX4VWezB zSs7DMS&*1wERiMw&67P;N@v#akc?bF@aLGmcmsNH-M3q9P$1_r(*85-Tg1?Y$|Y9j zWq2Tycl-5WU4fk>f<_rjxv=jhLy=B&QZ8z87F|e75b)uCH|#xEV_OD($E%0`Jll83 zx&}4M1R?kin}4F|?J0`Aj&~<1o|6)E-Nrs3-fBNv%}<7hZbgjl`tDXOE#ul-z)taY z+an~Jw@H!vk5_t(N)zzzp8bqf+8%`?Wl&>RBa zka+q*1dtp5Nz>vReY_kk{wnR3ek!Q`)|4EpDf$c)T*Lmmhxgy=-~SJt+dp3f__NXf z(~(5!C2TdLWa$&Nml^glhbCxD;9&e;UeWk}?PT^a1Np|dEcwn!Z&$zCWxO2b-?B&l z`=SW?G${^cxEvh!7ZU${o1_0cm)-`bnq02M{=Wdf=t2e~e=TzUeLLmOy>;A8#^xAaFx{^-ab9r>dp*htjpzY)g%u@`^r#UFd|$6oxg z7r5&k(4U+gg8tMmpg*-7(4jp4Cph>M9Q=P4927d#3z63FLWESvv#zUeLeJ&Jmc9tm zSjRt@%sMaicI_&z>3vlWiFH*FWKa03H2Y`b4?q5h!yj$HX~F+rjY04jKmTgmF6Jzb z3-FLWC;d4$$@*WGA-3hs-#=V#&Fzhep8Mt2;fz;)eUWNC*Fk6W$iJ?#0{eo3tjgUi IY50r(1^f=L_W%F@ literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/album-settings.html b/src/main/resources/docs/album-settings.html new file mode 100644 index 0000000..e58a4a2 --- /dev/null +++ b/src/main/resources/docs/album-settings.html @@ -0,0 +1,34 @@ + + + +

Album Settings

+

Overview

+

+ Albums have three different settings. They are: thumbnail, name, and title. This section will explain + what each of these settings do and how to manage them. +

+ +

The panel on the right of the album management window allows you to modify album settings. Clicking the + thumbnail preview will open a visual thumbnail select dialog. + See: Managing Albums

+
+
+ +

The album creation dialog.

+

The Settings In Detail

+
    +
  • Thumbnail: this setting controls which media is used a the thumbnail for the album. + It can be any media present in the album. If an audio clip is selected to be a thumbnail, the thumbnail + will use an image of a music note to represent this.
  • +
  • Album title: the title of the album is the text displayed under it on the Art Museum website's album + select page. It may be the same as other albums. It may also be blank and may contain any character. It is + reccomended to only use printable characters in the album title.
  • +
  • Custom URL: the name of the album. This must be unique among albums, cannot be blank, + and cannot contain the '/' character. It is reccommended to only use printable characters in the + album name. It is used in the URL of the album on the Art Museum website.
  • +
  • Different Title and URL: if disabled, the URL will be taken from the title of the ablum. However, + because of the restrictions placed on URLs, it may be required to have a different title and URL. If this is + the case, this option should be enabled.
  • +
+ + diff --git a/src/main/resources/docs/global-settings-dialog.png b/src/main/resources/docs/global-settings-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..744983f0735a3687a3600552b2dfb91fe0508cab GIT binary patch literal 14832 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 + + +

Global Settings

+

Overview

+

+ This dialog controls the global settings of Curator. These settings do not affect any instances of the + Art Museum website. +

+ +

The global settings dialog.

+

Options

+
    +
  • Default Library Location: default location to store library caches in.
  • +
  • Password Store Backend: the backend to use to store FTP passwords. The next section + contains information about the different backends.
  • +
  • Theme: the theme to use with Curator. Dependent on installed Java Swing Look-and-Feels.
  • +
  • Default Library: the library to automatically open when curator is open. If this is set to + 'Do not automatially open any libraries', the library select dialog will open instead.
  • +
  • Default Upload Action: controls what acction to take after uploading is complere. Options are: + "Always ask", "Never open library", "Always open library". "Always ask" will cause a popup to ask what + option to take after uploading is complete. "Never open library" will simply return you to the library + select window. "Always open library" will open the remote library in your web browser. A URL MUST + be set in the remote library settings for this option to have any effect.
  • +
  • Ask Before Exiting: if enabled, Curator will ask before exiting or returning to the library + select screen.
  • +
+

Password Store Backends

+
    +
  • Java Preferences: the Java Preferences API. Works on almost all systems, however, is unencrypted. + his is probably fine unless extra security is required for the FTP server.
  • +
  • Pass: Pass is a password manager for Unix-like systems. It relies on the GnuPG software for + encryption and decryption of passwords. It is only available is Pass is installed and configured + on your system.
  • +
+ + diff --git a/src/main/resources/docs/gpl3.html b/src/main/resources/docs/gpl3.html new file mode 100644 index 0000000..c4cafeb --- /dev/null +++ b/src/main/resources/docs/gpl3.html @@ -0,0 +1,693 @@ + + + + GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF) + + + +

GNU GENERAL PUBLIC LICENSE

+

Version 3, 29 June 2007

+ +

Copyright © 2007 Free Software Foundation, Inc. + <https://fsf.org/>

+ Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed.

+ +

Preamble

+ +

The GNU General Public License is a free, copyleft license for +software and other kinds of works.

+ +

The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too.

+ +

When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things.

+ +

To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others.

+ +

For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights.

+ +

Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it.

+ +

For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions.

+ +

Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users.

+ +

Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free.

+ +

The precise terms and conditions for copying, distribution and +modification follow.

+ +

TERMS AND CONDITIONS

+ +
0. Definitions.
+ +

“This License” refers to version 3 of the GNU General Public License.

+ +

“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks.

+ +

“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations.

+ +

To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work.

+ +

A “covered work” means either the unmodified Program or a work based +on the Program.

+ +

To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well.

+ +

To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying.

+ +

An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion.

+ +
1. Source Code.
+ +

The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work.

+ +

A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language.

+ +

The “System Libraries” of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it.

+ +

The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work.

+ +

The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source.

+ +

The Corresponding Source for a work in source code form is that +same work.

+ +
2. Basic Permissions.
+ +

All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law.

+ +

You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you.

+ +

Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary.

+ +
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+ +

No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures.

+ +

When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures.

+ +
4. Conveying Verbatim Copies.
+ +

You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program.

+ +

You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee.

+ +
5. Conveying Modified Source Versions.
+ +

You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions:

+ +
    +
  • a) The work must carry prominent notices stating that you modified + it, and giving a relevant date.
  • + +
  • b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + “keep intact all notices”.
  • + +
  • c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it.
  • + +
  • d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so.
  • +
+ +

A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate.

+ +
6. Conveying Non-Source Forms.
+ +

You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways:

+ +
    +
  • a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange.
  • + +
  • b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge.
  • + +
  • c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b.
  • + +
  • d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements.
  • + +
  • e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d.
  • +
+ +

A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work.

+ +

A “User Product” is either (1) a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product.

+ +

“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made.

+ +

If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM).

+ +

The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network.

+ +

Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying.

+ +
7. Additional Terms.
+ +

“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions.

+ +

When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission.

+ +

Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms:

+ +
    +
  • a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or
  • + +
  • b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or
  • + +
  • c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or
  • + +
  • d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or
  • + +
  • e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or
  • + +
  • f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors.
  • +
+ +

All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying.

+ +

If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms.

+ +

Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way.

+ +
8. Termination.
+ +

You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11).

+ +

However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation.

+ +

Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice.

+ +

Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10.

+ +
9. Acceptance Not Required for Having Copies.
+ +

You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so.

+ +
10. Automatic Licensing of Downstream Recipients.
+ +

Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License.

+ +

An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts.

+ +

You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it.

+ +
11. Patents.
+ +

A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”.

+ +

A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License.

+ +

Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version.

+ +

In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party.

+ +

If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid.

+ +

If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it.

+ +

A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007.

+ +

Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law.

+ +
12. No Surrender of Others' Freedom.
+ +

If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program.

+ +
13. Use with the GNU Affero General Public License.
+ +

Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such.

+ +
14. Revised Versions of this License.
+ +

The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns.

+ +

Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation.

+ +

If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program.

+ +

Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version.

+ +
15. Disclaimer of Warranty.
+ +

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

+ +
16. Limitation of Liability.
+ +

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES.

+ +
17. Interpretation of Sections 15 and 16.
+ +

If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee.

+ +

END OF TERMS AND CONDITIONS

+ +

How to Apply These Terms to Your New Programs

+ +

If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms.

+ +

To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found.

+ +
    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+ +

Also add information on how to contact you by electronic and paper mail.

+ +

If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode:

+ +
    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+ +

The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an “about box”.

+ +

You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>.

+ +

The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>.

+ + + \ No newline at end of file diff --git a/src/main/resources/docs/import-external.html b/src/main/resources/docs/import-external.html new file mode 100644 index 0000000..f88cc82 --- /dev/null +++ b/src/main/resources/docs/import-external.html @@ -0,0 +1,24 @@ + + + +

Importing From External Programs

+

Overview

+

+ Some external programs will be able to send media straight to Curator. Typically this will require + a plugin to be installed in the external program to allow it to interface with Curator. When importing media, + you will first need to select the library into which you would like to import. If you have a default library + (See: Global Settings) set, you will skip this step. + You will then need to select an album into which to import. You can select an already existing album, or, create + a new album. Once you have selected these two options, you will be given a chace to review your changes. You + can also modify other albums and media in the library like normal at this point. Finally, use the upload button to + save your changes to the Ary Museum website. +

+ +

The library select window when importing media.

+
+
+ +

The album select window when importing media. The 'New' button can be used to create a new album + instead of importing into an existing one.

+ + diff --git a/src/main/resources/docs/import-select-album-window.png b/src/main/resources/docs/import-select-album-window.png new file mode 100644 index 0000000000000000000000000000000000000000..36cd0d4543c7e608a6374904175e0a7f90eb58a8 GIT binary patch literal 17261 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/import-select-library-window.png b/src/main/resources/docs/import-select-library-window.png new file mode 100644 index 0000000000000000000000000000000000000000..07e4a0c68c59ef16a459a8a959bdfc3210d13d40 GIT binary patch literal 9872 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/info.html b/src/main/resources/docs/info.html new file mode 100644 index 0000000..ffea260 --- /dev/null +++ b/src/main/resources/docs/info.html @@ -0,0 +1,27 @@ + + + +

+ Curator is a program designed to manage one or more instances + of the Art Museum web site. It was created by Alexander (me) + for his father. The hope was that it would be a simple and attractive website + for posting family photos. I hope you enjoy it! ^_^ +

+

+ Curator is licensed under the GPL3 license. See the 'License' + entry in the help window or check the LICENSE file for more information. + Art Museum is licensed under the AGPL3. Check the following + websites for more infomation and version control repositories. +

+
+

+ Curator Source: https://gitlab.com/Zander671/curator +
+ Art Museum Source: https://gitlab.com/Zander671/art-museum +

+ + diff --git a/src/main/resources/docs/library-create-dialog.png b/src/main/resources/docs/library-create-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a0909372bdfc6574d7e0864e28c51b1cc2a489 GIT binary patch literal 7778 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

^4%#J)yRL4^`t{!Q4sC*S+f_PZ4F z9yk8Dfj@LbQZ0D%zS3DqkdJR;9f%2oWk81Vu@9=5Bt+WNtWAsJeMO%gbSaR z2v4MoK9vtqqfJP${zVvNIB+>n!Q>hp)46BAHS?sVoDOM2mM;5;l%UMOZ48K$bi$95 zh{<1qL|jpf|567@$oS^8yIP~S){vEzRlvIg3B)mY(;tjf5OFoUZVNMwlR?Ap7E>QJ#UZa( zkDyceTc&?A;-I$L@|SBEwj!2qpQy?#7XI)c;?Iq+5iCif(3l}euAv`i)pGr)(P8ps z$5q(TEAY6(fkeV}>7qyIShU5$jJaV~{oCxBZ7R>e-o`eo!Z%%ppek?6V{+I|{~5%R z;%;W(;VIJu!%6YHkC@!KPkB}gpEr$)<`~ZLG(R>oQ~u!n#PQ^uzOUilXfsoF#>?0m z1FcS!2x;Wyd6Azsyr)mH!Kuo)IXEki)@puuis)$}4=?Pb_2dA?9k|D{8#yc1lRK~c zii6&a&dUB|a=gwkQqu3v{dfw8XUZ8#FHPC11`ouEDm4<-B08kZSRHfDH z-P>4dL$}KhE_uD`0S|I*kltgR7k`@^rSFJ`S_s}yP73LDAQAhIzNzPt;EBEYIVLCw z~V~To`a(w1~gZs?j%-h>xge!Ls@@@+Mr4!E=-Jv>Euo$b{ExV3PuJwZbuFt&6B7mEO5gGDiFV><0d%U)R%fry zlh=qXc!8h2J7!6){0!wbd&d2A5^0Wg0K$v%T|p z{EZ``XOTsxaXcR}RWf6TZzHe5>}=8owSYrXR-bE^Lb)|qdR7X;<*kzTyA?_lAH@kR zm4h2n`I1>fY_V`ySHB2KtjUe8&A+P%jofO3TGiu1aD+r~G;t=_8Li&Ft4CIo?=4B|hL3KJeO3VrP%FsGuh1#s`nH^8hInxEhiGZ!$k9%Z zkapl+IhYL;la>3P8Y0(3dU#K_|8B0uQtqo`Q!lD1F`ksx4{N&6?a6sJBj9zRvK~9$ z#dlP^trbaCE7Edl_0_lO@^(evmFhT`y&+G1>+0~H&KFMTR@-E^gRZ{4L9HB?3LMVe z-_!QqKE439`r60mzTipwwgs>!A_Uysj7)YpeWx*3jsJnY}KSgJmyWky-%k6*qJ7fhxxR z8f(n+U`ynB(Q7NeFO&4qshbG;(wmigHf3`p_p|05ZXfh^zXgBcx>zu8d6M{!X)-{+ zdtgA%<#=k~H$!!BUzRSZ0$zxG#b)hsLO34xnlLo!5*SyfIqkE5MQG5O3mRf_w-%1X zr2ZW65ysrdTD+)z`EPzz!Ca}0nX(r5lmoa-`*b}(r1l#x7^TWYU}SaK@(v`fJNvH} zAlUs@Yo`6wRO`3PiU|Xouj@WWeTdT0UiDe@Dqg8^uFaIxvk}ZW$HmEcDbOe+(dAD( zzOzI%X%(>pq%gZ+B>O{=eqONe~zW*1*J|j5O zR}k0R5yC1@In4`pcj>XgqRYYU%vxf)7y#k5R_i3zK%8e^dqB>uRa(TCP$ z{|lv`6AEdv{-T8&m&}CTJ2H?Gq>R(qu-ld%!Fm$)7m6GG4exdO{~hnID_3a$MHF}0 zVh+@ctM9EGiZkk!T$v+zRL0d literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/library-edit-dialog.png b/src/main/resources/docs/library-edit-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..7a47b0ef807e7ed2d9d89f86737fa18488046f94 GIT binary patch literal 8501 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 + + +

Managing Libraries

+

Overview

+

+ When opening curator without a default library, this is the first window you will see. + From the library management window, you can open, create, import, and delete libraries. + A library is your colletion of media on any given instance of the Art Museum website. + Libraries are made up of albums, which, in turn, contain the media. Each instsance of Art Museum + houses one library. +

+ +

The library manage window.

+

Buttons in Detail

+

+ This section details the buttons found at the bottom of the library management window. +

+ + + diff --git a/src/main/resources/docs/library-select-window.png b/src/main/resources/docs/library-select-window.png new file mode 100644 index 0000000000000000000000000000000000000000..bd7e66c67c9c2ebb31d768873b4f039d48734e95 GIT binary patch literal 19801 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 + + +

Library Settings

+

Overview

+

+ These settings are global to the entire library. Unlike the local library settings, they affect + the Art Museum website as well. +

+ +

The library settings dialog.

+

The Settings In Detail

+
    +
  • Name: this text appears at the top of the Art Muesum website. It is the title of the overall library.
  • +
  • License: this text appears at the bottom of the Art Museum website. It is the license + you would like to upload your media under.
  • +
+ + diff --git a/src/main/resources/docs/local-library-settings.html b/src/main/resources/docs/local-library-settings.html new file mode 100644 index 0000000..6be9f2d --- /dev/null +++ b/src/main/resources/docs/local-library-settings.html @@ -0,0 +1,45 @@ + + + +

Local Library Settings

+

Overview

+

+ These dialogs create, import or alter the settings of libraries. These settings are primarily + for use of Curator and will not affect the Art Museum website. +

+

+ Local vs Remote Library +
+ Remore libraries are instances of the Art Museum website. They are connected to by FTP. + Any changes made to them will be reflected in their respective Art Museum instance. +
+ Local libraries, or caches, are instances of libraries located on your computer. When you connect + to a remote library, a local library is automatically created and sync with its remote counterpart. + Every time you connect to a remote library, its local counterpart is updated in this way. +

+ +

The library creation and remote import dialog.

+
+
+ +

The library edit dialog.

+
+
+ +

The local library import dialog.

+

Options

+
    +
  • Name: library name in the select dialog. Does not affect website.
  • +
  • URL: url in the select dialog. Can be blank. Does not affect the website.
  • +
  • FTP: FTP server to connect to. Must be in the format of 'ftp://hostname:port/path/to/library'. + Protocol can also be FTPS by replacing 'ftp://' with 'ftps://'.
  • +
  • Username & Password: username and password to use to connect to FTP server.
  • +
  • Library Location (Source): location to save caches in. If 'User default library location' + is checked, it will be stored in the directory set in the global settings menu. + See: Global Settings
  • +
  • Clear Cahce: clear library cahce. This does NOT affect the remote library. It only + clears the local cache. Doing this will cause a full re-download of the remote library next time it is + oppened.
  • +
+ + diff --git a/src/main/resources/docs/location-select-dialog.png b/src/main/resources/docs/location-select-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8780e347a5de910947d843e56f7f7a93dd70c7 GIT binary patch literal 331091 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 z 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 z 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 fGt#rqcMp$mYn + + +

Managing Media

+

Overview

+
+ +

An example of the media manage window. The media red.png is the thumbnail for this album.

+

The Buttons In Detail

+
    +
  • Upload: upload any changes made to the current library to the FTP server. Also refered to as + saving the library.
  • +
  • Set As Thumbnail: make the currently selected media the thumbnail for its album. The current thumbnail + is denoted by a small star. See: Album Settings
  • +
  • Delete: delete the slected media.
  • +
  • Export: export the currently selected media. If multiple items are selected, they will all + be exported to the same directory.
  • +
  • Add: add media from files. Any number of files and/or directories can be selected. ALL selected files + and any files located in any selected directories or their sub-directories will then be added. If there are two files + that have the same name, the addition will fail and an error message will be displayed. Any media files which has + a name (without the last '.' extension) that is a number, will be sorted and placed before other media. + See: Media Types
  • +
  • Toggle Property Panel: toggle the media property panel. See: + Media Properties
  • +
+

Rearranging Media

+

+ Media can be rearranged by dragging and dropping it into the desired order. +

+ + diff --git a/src/main/resources/docs/media-properties.html b/src/main/resources/docs/media-properties.html new file mode 100644 index 0000000..f3e9df8 --- /dev/null +++ b/src/main/resources/docs/media-properties.html @@ -0,0 +1,28 @@ + + + +

Media Properties

+

Overview

+

+ Each piece of media has various properties that can be set to tell visitors of the Art Museum website + more information about the image. When media is imported, if the media is of a compatible type + (See: Media Types), these values may be initialized using + metadata from the media file itself. This section will explain what each property means in detail. +

+ +

The right panel of the media manage window allows you to modify media properties.

+

The Properties In Detail

+
    +
  • Is Thumbnail: whether or not this media is the thumbnail for its album.
  • +
  • File Size: the size of the media file.
  • +
  • File Type: the type of data the file contains. This may be different from its name's extension. + See: Media Types
  • +
  • Name: the name of the file this media is contained in. Affects the name when this media is exported and + the name of the file when downloaded from the Art Museum website.
  • +
  • Date: the date on which the media was created.
  • +
  • Time: the time at which the media was created.
  • +
  • Location: the location at which the media was created. "N/A" means that no location is + currently selected. See: Selecting Location
  • +
+ + diff --git a/src/main/resources/docs/media-property-window.png b/src/main/resources/docs/media-property-window.png new file mode 100644 index 0000000000000000000000000000000000000000..1d838fbd59f54ac2fa9b075a89a418eb49d04705 GIT binary patch literal 48532 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 LkK5gSVQ2payF{u* literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/media-select-window.png b/src/main/resources/docs/media-select-window.png new file mode 100644 index 0000000000000000000000000000000000000000..e137c9877dbc9d2bc8d62e7d5d8b797d508b6ae6 GIT binary patch literal 28151 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/media-types.html b/src/main/resources/docs/media-types.html new file mode 100644 index 0000000..dba0fe1 --- /dev/null +++ b/src/main/resources/docs/media-types.html @@ -0,0 +1,76 @@ + + + +

Media Types

+

Overview

+

+ Curator supports most common, and some uncommon, media types. However, because every web browser is different, + even if media is supported in Curator, it may not be supported on the Art Museum website. +

+

Image Media

+

+ Unlike other formats (see below), image files are relatively simple. Thus, any valid file of one + of the following formats will be supported by any modern browser without issue. However, any other formats + are very unlikely to work. +
+ HTML5 Supported Image Types: +

    +
  • Animated Portable Network Graphics (.apng)
  • +
  • Graphics Interchange Format (.gif)
  • +
  • Microsoft Icon (.ico, .cur)
  • +
  • Joint Photographic Expert Group image (.jpeg, .jpg)
  • +
  • Portable Network Graphics (.png)
  • +
  • Scalable Vector Graphics (.svg)
  • +
+

+

Video Media

+

+ Unlike images, video files contain multple types of data. Because of this, common file types (such + as .mp4) are just containers. The acctaul video and audio data inside are then encoded using + standard encodings (such as h264, vp9, and Vorbis). While this can sound like a complex problem (and + it can be at times) most browsers will support any format that these containers support. However, if you or another + visitor of the Art Museum website is having trouble playing media, it may be a good idea to try re-exporting the files + from your media creation tool with different settings. +
+ HTML5 Supported Video Container Types: +

    +
  • Moving Pictures Experts Group video (.mp4)
  • +
  • WebM (.webm)
  • +
  • Xiph.Org Formats (.ogg, .ogv)
  • +
+ Curator will automatically convert the following video container types into an Mpeg4 container. Note that as the Mpeg4 + container type is a subtype of quicktime, the type field may still show 'video/quicktime'. +
+ Types converted By Curator +
    +
  • Apple QuickTime Video (.mov, .qt)
  • +
+

+

Audio Media

+

+ Audio files can suffer from many of the same problems as video files (see above), however, they generally + are a lot simpler as their is only one type of data contained within the audio container file. Thus, a browser will + generally be able to play audio data no matter what encoding it uses within a supported container. +
+ HTML5 Supported Audio Container Types: +

    +
  • Moving Pictures Experts Group audio (.mp3)
  • +
  • Xiph.Org Formats (.ogg)
  • +
  • Waveform Audio File Format (.wav)
  • +
+

+

Metadata

+

+ While most media types support metadata of some kind. Curator can only load metadata from a few types. +
+ Curator Supported Metadata Types +

    +
  • PNG: date, time, geolocation
  • +
  • JPEG: description, date, time, geolocation
  • +
  • Tiff: description, date, time, geolocation
  • +
  • Mpeg4: geolocation
  • +
  • QuickTime Video: description, date, time, geolocation
  • +
+

+ + diff --git a/src/main/resources/docs/remote-library-settings.png b/src/main/resources/docs/remote-library-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6f9d061ea43f489c1e72bdec9309579800dfc0 GIT binary patch literal 5038 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 ayg19KWE0m^62bpa0-ngJ$`(q)1OEke;;>-= literal 0 HcmV?d00001 diff --git a/src/main/resources/docs/selecting-locations.html b/src/main/resources/docs/selecting-locations.html new file mode 100644 index 0000000..e77c4f2 --- /dev/null +++ b/src/main/resources/docs/selecting-locations.html @@ -0,0 +1,40 @@ + + + +

Selecting Locations

+

Overview

+

+ This dialog allows you to specify the location that a given piece of media was created at + (See: Media Properties). Locations can be selected by + enterering positional coordinates (longitude and latitude), selecing a point on an interactive map, searching the + name of a location, or any combination of the three. +

+ +

The location select dialog. The user has searched and selected the city of San Fransisco.

+

Clearing the Location

+

+ The location can be cleared by selecing the 'Clear' button at the bottom of the dialog. This will remove + the location information from the media. This is different from giving the media a position of 0°, 0°. +

+

Positional Coordinate Selection

+

+ To select a location via latitude and longitude, enter the desired coordinates in the boxes labled 'Selection'. + The first box represents the latitude and the second in the longitude. Negative and positive numbers are used in + place of the standard '°E', '°N', '°S', and '°W'. +

+

The Interactive Map

+

+ The map can be interacted with using the mouse. Clicking on a location will select that location. Clicking and + dragging the map will move the currently viewed area, but will not change the selection. Using the scroll wheel + will zoom in and out. The zoom can also be changed using the 'Zoom' field under the map. The zoom is represented + by an integer value from 1 to 20, with 20 being the most zoomed in. +

+

The Location Search Field

+

+ Entering the name of a location, such as a home address, or a point of interest, such as a city, will center the map + on that location and then select it. This process, known as reverse geoencoding, is done by OpenStreetMap's Nominatim + API. More information can be found at + https://wiki.openstreetmap.org/wiki/Nominatim. +

+ + diff --git a/src/main/resources/eye-closed-icon.svg b/src/main/resources/eye-closed-icon.svg new file mode 100644 index 0000000..7a72208 --- /dev/null +++ b/src/main/resources/eye-closed-icon.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + diff --git a/src/main/resources/eye-open-icon.svg b/src/main/resources/eye-open-icon.svg new file mode 100644 index 0000000..49ba201 --- /dev/null +++ b/src/main/resources/eye-open-icon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + diff --git a/src/main/resources/logo.svg b/src/main/resources/logo.svg new file mode 100644 index 0000000..ea618f9 --- /dev/null +++ b/src/main/resources/logo.svg @@ -0,0 +1,76 @@ + + + + + + + + image/svg+xml + + + + + + + + C + + diff --git a/src/main/resources/menu-icon.svg b/src/main/resources/menu-icon.svg new file mode 100644 index 0000000..1f49fc2 --- /dev/null +++ b/src/main/resources/menu-icon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/src/main/resources/missing-image-icon.svg b/src/main/resources/missing-image-icon.svg new file mode 100644 index 0000000..797db5b --- /dev/null +++ b/src/main/resources/missing-image-icon.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + ! + + This image couldnot be loaded! + + diff --git a/src/main/resources/music-icon.svg b/src/main/resources/music-icon.svg new file mode 100644 index 0000000..6ddbd2d --- /dev/null +++ b/src/main/resources/music-icon.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/pause.svg b/src/main/resources/pause.svg new file mode 100644 index 0000000..4843c31 --- /dev/null +++ b/src/main/resources/pause.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + diff --git a/src/main/resources/play.svg b/src/main/resources/play.svg new file mode 100644 index 0000000..17c78e3 --- /dev/null +++ b/src/main/resources/play.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/src/main/resources/reset-arrow.svg b/src/main/resources/reset-arrow.svg new file mode 100644 index 0000000..f3e660d --- /dev/null +++ b/src/main/resources/reset-arrow.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/star.svg b/src/main/resources/star.svg new file mode 100644 index 0000000..e67f37e --- /dev/null +++ b/src/main/resources/star.svg @@ -0,0 +1,62 @@ + + + + + + + + + + diff --git a/src/main/resources/video-icon.png b/src/main/resources/video-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e37fe67941a1705b9bee0fcd7941f3ab89dd028f GIT binary patch literal 95229 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ʯ)7IIo#8SsC5mkP<2(jSm%xZKmi#RoblS#Bh; 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%0DJMbhl}wHqgLgOX+_7>t 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 + + + + + + + + + + + + + + + + + This video's thumbnail could not be loaded! + +

+ The media manage window allows you to add, remove, and modify the properties of media. Media is the main + content of the Art Museum website. It is the images, video, and audio clips that you are trying to display + and share. As there are many different file formats that media can come in, a general guide can be found in + Media Types. +

+ GPL3: https://www.gnu.org/licenses/gpl-3.0.en.html +
+ AGPL3: https://www.gnu.org/licenses/agpl-3.0.en.html +