How to implement something ("Best practices")
Do not repeat bad code from (hopefully) old parts of JPhotoTagger!
For example, in most cases it's better to use Action
s instead
ActionListener
s. Use the org.openide.util.Lookup
to provide e.g. selected content together with it's support in
JPhotoTaggers's Lib Project. You can see usage examples
in the code of some modules, e.g. in Display Files Without Metadata
or Module RepositoryFileBrowser.
Do not add code to the Program project. There should be only bugs fixed, code refactored or the functionality enhanced (only if not possible via the Java Service Provider Interface).
Start a new project for adding features. Refactorings are always welcome, if they will improve the design.
Don't hesitate to fix bugs in code written by others. Write unit tests for every method, NetBeans automatically generates test classes and method skeletons.
Use JPhotoTagger's API and Domain Projects (Java Service Provider Interface in general)
Do not use implementations, use interfaces. We are using the Java Service Provider Interface (SPI) to get implementations. With that lines of code you get an implementation:
MyInterface implementation = Lookup.getDefault().lookup(MyInterface.class);
As an example, the currently used database implements Repository
interfaces.
To use a different database and/or the JPA instead of JDBC, only the project implementing these
interfaces has to be replaced and no other code has to be changed. Ideally the user does not
recognize any change.
Another example: JPhotoTagger has limited capabilities to generate thumbnails
of different camera image file formats or other media formats such as videos. You can add an
implementation of ThumbnailCreator
in a separate project, e.g. for your special
camera RAW format, and it will be automatically used (if no thumbnail could be created by the
default implementation).
Do not create files in META-INF/services
for your service providers. This is
very error prone on Move/Rename refactorings. Use the @ServiceProvider
annotation
instead, grep the source code for usage examples.
Extending JPhotoTagger
Extensions processing files
If the thumbnails panel shall offer a menu entry for processing selected files, implement in
a separate project the interface FileProcessorPlugin
. Examples may be a HTML Gallery
generator or FTP upload. Existing example implementations are the projects Copy Filenames
To Clipboard (easy and a good starting point to learn) or Flickr Upload.
Complex extensions
A complex extension implements Module
. Modules will be initialized during the
application startup without user interaction (file processors will be invoked only if the user
calls them). Examples are FileEventHooks
or RepositoryFileBrowser
.
GUI (Graphical User Interface)
GUI elements should be created either via org.jphototagger.resources.UiFactory
or derived Components, i.e. PanelExt
or DialogExt
(not JPanel
or JDialog). The UI Factory and derived components keeps track of High DPI monitors and
behave in a consistent way. Search for usages in the source code.
Thumbnails for a specific file format not supported by JPotoTagger
Implement org.jphototagger.domain.thumbnails.ThumbnailCreator
.
JPhotoTagger will find it through the Java Service Provider Interface.
EXIF for a specific file format not supported by JPotoTagger
Implement org.jphototagger.domain.metadata.exif.ExifReader
.
JPhotoTagger will find it through the Java Service Provider Interface.
EXIF Maker Notes
Implement org.jphototagger.domain.metadata.exif.ExifMakerNoteTags
.
JPhotoTagger will find it through the Java Service Provider Interface.
In Opposite to an ExifReader
, an ExifMakerNoteTags
instance
gets the already read Maker Notes byte array and has to use the specification of
the maker to decode them.
Remarks
You can add menu entries, panels etc. to the application window, get thumbnails etc. – take a look into the API project. For querying or modifying a repository (part of a database), have also a look into the Domain project.
Events
JPhotoTagger publishes many events through EventBus. Events are defined in the API and Domain projects. If you are interested into an event, e.g. if the user selects an image, you can do something like that:
public final class MyImageProcessor { public MyImageProcessor() { // Registering to EventBus AnnotationProcessor.process(this); } @EventSubscriber(eventClass = ThumbnailsSelectionChangedEvent.class) public void thumbnailsSelectionChanged(ThumbnailsSelectionChangedEvent event) { List<File> selectedFiles = event.getSelectedFiles(); for (File selectedFile : selectedFiles) { processFile(selectedFile); } } }
Processes taking some time should create a separate thread and do their tasks in this thread since the listeners will be notified in the Event Dispatch Thread and as long as they do something, the GUI is not accessable.
Persist Preferences (Settings)
Preferences can be persisted and restored through
org.jphototagger.api.preferences.Preferences
:
private void persistPreferences() { Preferences prefs = Lookup.getDefault().lookup(Preferences.class); prefs.setString("MyModule.PreviousDirectory", directory.getAbsolutePath()); prefs.setBoolean("MyModule.DeleteAlways", deleteAlways); prefs.setLocation("MyModule.MyDialogLocation", dialog); } private void restorePreferences() { Preferences prefs = Lookup.getDefault().lookup(Preferences.class); String previousDirectory = prefs.getString("MyModule.PreviousDirectory"); boolean deleteAlways = prefs.getBoolean("MyModule.DeleteAlways"); prefs.applyLocation("MyModule.MyDialogLocation", dialog); }
If you want react immediately to changes in user settings, subscribe to the
Eventbus
(see obove), you will get a PreferencesChangedEvent
with the old and new value.
Commit and push changes
Use Mercurial's commit and push command to share your code. I don't want spent time to integrate patches sent by e mail.
Localization
User Interface strings stored in files named Bundle.properties
within the same package as the class using them. They accessed through
org.jphototagger.lib.util.Bundle
:
String message = Bundle.getString(UiClass.class, "UiClass.Key");
Author: Elmar Baumann
Write e-Mail
Status of this document: 2018-12-15