This page is intended to provide information on small chunks of code that are of general use, but are not necessarily fully functional blocks. If you are stuck on a coding problem then search this page for the topic/function/keyword of interest, an index is not provided as the list of contents is expected to grow unwieldy.
Important note:If a skin calls getVars() during processing, that triggers the generation of all of the variables for the object, include things like the thumbnail dimensions. If the skin later does something that will alter those variables, as, for example, adding a filter to the engine, which might change those dimensions, the reported values will now be incorrect. The skin should, therefore, avoid using getVars() until all other actions that might change the object's variables have been done. If this can't be avoided, it is possible to cause the core to regenerate the variables for an object by executing obj.setVars(null).
Note, if some of the code is not visible in the examples below just copy the code and paste it into a text editor to see it all.
Also, for some Pro tips see this post
Frog chorus by Dor.
dor = new Frog(); dor.ribbit();
ComponentUtilities, StateMonitors, Lambda expressions by David Ekholm
The 'conventional way of implementing a StateMonitor is something like this:
//StateMonitor to update the GUI when folderImageSize is changed new StateMonitor() { public void onChange() { //try to split the string into two parts String[] parts = folderImageSize.getText().split("x"); if(parts.length > 1) { //Two dimensions entered, so set the new width and height int widthValue = Integer.parseInt(parts[0]); int heightValue = Integer.parseInt(parts[1]); //Now update the engine so the GUI reflects the new values engine.setThemeImageDim(new Dimension(widthValue, heightValue)); } } }.add(folderImageSize).done(); //Monitoring the folderImageSize element
By using the convenience methods of ComponentUtilities this can be reduced to much easier to read code, however the StateMonitor is more flexible. The use of a lambda expression below is the 'parameter -> expression body'. Advantages are (with example used in parenthesis):
No need to declare the type of a parameter (JTextField)
No need to declare a single parameter (folderImageSize) in parenthesis. For multiple parameters, parentheses are required.
No need to use curly braces in expression body if the body contains a single statement (this example has two)
Automatically returns the value, if the body has a single expression to return a value (no return value used). Curly braces are required to indicate that expression returns a value.
StateMonitor.monitoring(themeImageHeight, themeImageWidth).onChange(folderImageSize -> { folderImageSize.setText(themeImageWidth.getValue().toString() + "x" + themeImageHeight.getValue().toString()); engine.setThemeImageDim(new Dimension((int)themeImageWidth.getValue(), (int)themeImageHeight.getValue())); });
Support new Class in new versions, fallback to old class in old versions. by David Ekholm
When jAlbum introduces a new class, skin developers normally would have to make their skins work for version n and upwards. This describes how to make skins work with new classes, when supported, and fallback to old classes for older versions of jAlbum.
The structure to do this is to have two methods, they share the same attributes, the first method is defined in the skin's .java file, the second is defined in a 'Util' class file, e.g. Util.java
The first method uses a try - catch and checks for Class.forName("class path"). if OK it calls the second method (in Util.java), which implements the new class object. If the check fails the 'catch' statement implements the 'old' class object. This ensure that the classloader does a dynamic test for its existence, before loading Util. Only after passing such a test is it safe to refer to the Util method.
Abbreviated (only one imports shown etc.) example using JSmartTextArea (old class) and JSmartSyntaxTextArea (new class). A full implementation can be found in the Journal skin, which includes the source code and Netbeans project.
In skin name.java:
// A needed import that Netbeans does not prompt for import static org.fife.ui.rsyntaxtextarea.SyntaxConstants.*; //The control panel component, a TextArea for custom CSS code entry JTextArea insertCss = makeTA(18, 75, "", "CSS", true, true); ... //Method to determine supported text area type static JTextArea makeTA(int rows, int cols, String defaultText, String syntax, boolean lWrap, boolean wWrap) { try { //If the JSmartSyntaxTextArea class exists call the Util method makeTA Class.forName("se.datadosen.component.JSmartSyntaxTextArea"); return Util.makeTA(rows, cols, defaultText, syntax, lWrap, wWrap); } catch (ClassNotFoundException | java.lang.NoClassDefFoundError e) { //No JSmartSyntaxTextArea class (jAlbum < version22) so use JSmartTextArea JSmartTextArea TA = new JSmartTextArea(defaultText, rows, cols); TA.setLineWrap(lWrap); TA.setWrapStyleWord(wWrap); return TA; } }
In Util.java:
//Utility class for methods public class Util { //Method to make syntax supported text areas, requires min of jAlbum 22 static JTextArea makeTA(int rows, int cols, String defaultText, String syntax, boolean lWrap, boolean wWrap) { JSmartSyntaxTextArea STA = new JSmartSyntaxTextArea(defaultText, rows, cols); switch (syntax) { case "CSS": STA.setSyntaxEditingStyle(SYNTAX_STYLE_CSS); break; case "HTML": STA.setSyntaxEditingStyle(SYNTAX_STYLE_HTML); break; case "JAVASCRIPT": STA.setSyntaxEditingStyle(SYNTAX_STYLE_JAVASCRIPT); } STA.setLineWrap(lWrap); STA.setWrapStyleWord(wWrap); System.out.println("Text is : " + STA.getText()); return STA; } }
In SkinModel.java:
public String insertCss=""; //Or enter a remed out sample CSS block as an illustration.
SmartText, Spell check by David Ekholm
If you only want to support spell checking on jAlbum 18.5 and above use, for example:
JSmartTextField aboutHeader = new JSmartTextField(texts.getString("ui.AboutMyPortfolio"), 30).spelling(); JSmartTextArea aboutText = new JSmartTextArea(8,30).spelling();
To allow your skin to run with spell checking on jAlbum versions prior to 18.5 use:
JSmartTextField aboutHeader = new JSmartTextField(texts.getString("ui.AboutMyPortfolio"), 30).putClientProperty("spelling", true); JSmartTextArea aboutText = new JSmartTextArea(8,30).putClientProperty("spelling", true);
Stop processing, abort, OperationAbortedException To stop a skin or tool from continuing to execute code, for example to ensure jAlbum version used is 17 or greater (16.1 was the previous release) use:
import se.datadosen.util.VersionNumber; int supported = internalVersion.compareTo("16.1"); if(supported == -1) { JOptionPane.showMessageDialog(window, "This tool requires jAlbum version 17 or greater", "Unsupported version", JOptionPane.INFORMATION_MESSAGE); throw new OperationAbortedException(); //or use //throw new OperationAbortedException("String message goes here"); }
Another method by Dschuwi which uses FileNameComparator. FileNameComparator will order numeric sections numerically so that "12" is ordered after "4" for. With a plain alphabetical ordering it would be the reverse as "4" is more than "1".
import se.datadosen.jalbum.FileNameComparator; int versionCompare(String v1, String v2) { FileNameComparator cmp = new FileNameComparator(false); return cmp.compare(new File(v1), new File(v2)); } boolean minVersion(String v) { return versionCompare(v, internalVersion) <= 0; } String versionRequired = "8.1"; if (!minVersion(versionRequired)) { setAccessibility(true); window.progressDialog.setVisible(false); JOptionPane.showMessageDialog(null, "Minimum Jalbum version required: " + versionRequired, "Version mismatch", JOptionPane.ERROR_MESSAGE); throw new OperationAbortedException(); }
Metadata, video, audio by RobM from Drew Noakes Metadata Extractor and based on code from David Ekholm
If you want to support metadata, for video and audio files, that is not part of the 'jAlbum core' then here are two similar methods of doing so.
Method 1:
In init.bsh add
//Get video metadata, needs drew metadata library import com.drew.metadata.*; import com.drew.imaging.ImageProcessingException; //Method to get the required metadata String getMetadataTag(AlbumObject ao, String tagPart) { //get raw metadata for the album object metadata = ao.getMetadata().getMetadata(); //Declare a string for the metadata tag converted to a string String tagText; //Declare an integer for the length of the tag part search string int tagPartLn = tagPart.length(); //Iterate through the metadata directories for (directory : metadata.getDirectories()) { //Iterate through the tags in the directory for (Tag tag : directory.getTags()) { //Convert the tag to a string tagText = tag.toString(); //System.out.println(tagText); //Look for the tag search string within the current tag if(tagText.indexOf(tagPart) != -1) { //If a match then get the remainder of the tag string String tagValue = tag.toString().substring(tagPartLn); //Return the tag part with the required data return tagValue; } } } //If no match return an empty string return ""; }
Then wherever you want to get the metadata use, for example, in slide.htt and getting a video's audio format
<% String tagData = getMetadataTag(currentObject, "[MP4 Sound] Format - "); out.print(tagData); %>
A typical result for the above would be 'MPEG-4, Advanced Audio Coding (AAC)'.
To list all possible tags, in the System console, un-remark the line
//System.out.println(tagText);
then use a search string like "xxx" in slide.htt, so the method will go through all of the tags. You can then decide which tags to support and what search strings to use.
Method 2: in init.bsh
String getMetaObjectContent(AlbumObject ao, String dirTarget, String tagTarget) { imageInfo = ao.getMetadata().getMetadata(); for (dir : imageInfo.getDirectories()) { thisDir = dir.getName(); for (Tag tag : dir.getTags()) { try { if(dirTarget.equals("") && tagTarget.equals("")) { System.out.println("Dir=" + thisDir + ", TagName=" + tag.getTagName() + ", TagDesc=" + tag.getDescription()); } if(thisDir.equals(dirTarget) && tag.getTagName().equals(tagTarget)) { return tag.getDescription(); } } catch (MetadataException ex) { } // It breakes on some tags with some images here catch (Throwable t) { t.printStackTrace(System.err); // For increased robustness } } } return ""; }
In slide.htt
<% out.print("Frame Rate=" + getMetaObjectContent(currentObject, "MP4 Video", "Frame Rate")); %>
To print all possible tags to the system console use
<% getMetaObjectContent(currentObject, "", ""); %>
JScrollPane, SkinUI by David Ekholm To wrap a skin’s user interface in a scrollable panel
window.setSkinUI(new JScrollPane(skinUIHere));
MonitoredWorkQueue, long tasks by David Ekholm.
Uses a support class that deals with tasks that take a long time to complete. With long running tasks you shouldn't lock the UI but instead run the task on a background thread, but you should also inform the user that a slow task is underway and allow the user to abort the task. All this (but the slow work itself) can now be handled by the new MonitoredWorkQueue class.
Here's a usage example:
MonitoredWorkQueue workQueue = new MonitoredWorkQueue(window, "Convert link to copy"); for (AlbumObject ao : selected) { workQueue.submit(() -> { File src = LinkFile.targetOf(ao.getFile()); workQueue.setMessage(ao.getName()); File target = new File(currentFolder.getFile(), ao.getFile().getName()); if (src.isDirectory()) { target.mkdir(); } IO.copyFile(src, target); }); } workQueue.awaitCompletion();
For .bsh scripting replace
workQueue.submit(() -> {
with
workQueue.submit(new Task() { public void call() {
FileChooser Filter, DeferredChooser, FileFilter, FileNameExtensionFilter By monkeyboy
How to limit the file types that can be selected in a file chooser
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; import se.datadosen.jalbum.DeferredChooser; DeferredChooser fc = new DeferredChooser(JFileChooser.class); FileFilter filter = new FileNameExtensionFilter( "JPEG file", new String[] {"jpg", "jpeg"} ); fc.setFileFilter(filter); ui.fc.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int fcReturn = fc.showOpenDialog(window); if (fcReturn == JFileChooser.APPROVE_OPTION) { //Your code } } });
Engine, UI, UI2Bean, bean2UI, get settings by David Ekholm
Settings in the user interface don’t get transferred to the JAlbum engine until album generation starts. To update the engine, so all gui settings are correctly reflected use UI2Bean before accessing the engine, e.g.
window.UI2Bean(); engine.getDirectory();
Or query the UI components directly, e.g.
window.directory.getText()
window.bean2UI(), UI, to refresh the interface from a script
window.bean2UI()
It will copy the settings from the engine object to the user interface.
Example code within a State monitor to change a jAlbum setting based on a skin setting
window.ui2Engine(); engine.setProcessFolderThumbs(skinCheckbox.isSelected()); window.engine2UI();
Rows, cols, override, reset, disable by David Ekholm
To reset the number of rows and cols during album generation, for example to have the root index with 6 rows and 2 cols, but sub-directories indexes with 5 cols and 4 rows. Use hints.jap to set the rows and cols to 6 and 2 then in index.htt use
If(level == 0) { engine.setRows(4); engine.setCols(5); }
To disable the row and col settings use:
engine.setRows(0) in init.bsh
and set the skin property enableThumbnailLayout to false
ProcessTemplateFile, templates, vars, custom variables by David Ekholm
To pass skin variables from one template to another for processing, e.g. pass variableA from index.htt to MyTemplate.htt:
Map vars = new HashMap(); if(variableA != void) vars.put("variableA", variableA); if(variableB != void) vars.put("variableB", variableB); if(variableC != void) vars.put("variableC", variableC); engine.processTemplateFile( new File(skinDirectory, "MyTemplate.htt"), new File(outputDirectory, "MyTemplate.html"), vars);
AlbumBean, jAlbumListener, event, engine, JAlbumAdapter , onload, UI, get, set by David Ekholm
To monitor and change settings in onload.bsh when album creation starts and stops. In the example below the page extentension, normally ‘.html’ is changed to ‘.php’ if a skin setting is selected.
import se.datadosen.jalbum.event.*; window.addJAlbumListener(new JAlbumAdapter() { String pageExtension; public void albumCreationStarted(JAlbumEvent e) { pageExtension = engine.getPageExtension(); if (ui.skinSettingName.isSelected()) engine.setPageExtension(".php"); } public void albumCreationFinished(JAlbumEvent e) { engine.setPageExtension(pageExtension); engine.bean2UI(); } });
You can also use similar code to detect what jAlbum settings have been selected and then do something e.g.
window.addJAlbumListener ( new JAlbumAdapter() { if(engine.getImageLinking().equals("LinkOriginals")) { //Do something } } );
AlbumObject, createInstance, file to object by David Ekholm
If you want to convert a file to an album object then this example shows how to do it, with for example a project's root's folder parent folder
File masterProjectFolder = new File(rootFolder.getFile().getParentFile().getParentFile(), rootFolder.getFile().getParentFile().getName()); // Make the master project's folder an object AlbumObject masterProjectObject = rootFolder.getFactory().createInstance(masterProjectFolder);
Read, Write, File, FileInputStream, DataInputStream, BufferedReader, BufferedWriter based on code by David Ekholm
If you want to read a text file and either do something with or based on its content, and write a changed content back to the file, then one way is:
//Where f is a text file FileInputStream fStream = new FileInputStream(f); // make an empty string ready to hold the text of the file fContent = ""; // Get the object of DataInputStream DataInputStream fIn = new DataInputStream(fStream); BufferedReader fBR = new BufferedReader(new InputStreamReader(fIn)); // read the text file line by line String strLine; while ((strLine = fBR.readLine()) != null) { //Read the whole file (or do something with each strLine) fContent = fContent + strLine + "\n"; } //Close the input stream fIn.close(); // If the file is to be changed then write the modified file content back out BufferedWriter newf = new BufferedWriter(new FileWriter(f)); newf.write(fContent); newf.close();
JTextField, JNumberField , Integer only by Heinz-Peter Bader
If you want to ensure that a JTextField can only contain an integer value use the form below where "yourVariable" accepts Integer only, otherwise the default value "23" is used:
JTextField yourVariable = new JFormattedTextField(new Integer(23));
Another method is to use jAlbum's se.datadosen.component.JNumberField, it has two methods to set whether to allow negative numbers and/or decimals. These setter methods return the JNumberField itself so they can be chained for ease of use:
JNumberField temperature = new JNumberField(10).setAllowNegative(true).setAllowDecimals(true);
JComboBox, listFiles, ChainedDirectory by David Ekholm
Fill a combobox with a list of files of a certain extension, example shows how to get the available styles from a skin's folder.
Note That the ChainedDirectory class is an abstraction for a chained directory structure - it looks first for files in a certain directory, and if not found there, in the chained directory etc.
JComboBox style = new JSmartComboBox(); ... fillCombo(style, new File(skinDir, "styles"), new StyleFileFilter()); ... static void fillCombo(JComboBox combo, File dir, FileFilter filter) { if (!dir.isDirectory()) { return; } fillCombo(combo, new ChainedDirectory(dir), filter); } static void fillCombo(JComboBox combo, ChainedDirectory dir, FileFilter filter) { File[] files = dir.listFiles(filter); Arrays.sort(files, new Comparator() { public int compare(Object o1, Object o2) { return String.CASE_INSENSITIVE_ORDER.compare(((File) o1).getName(), ((File) o2).getName()); } }); for (File f : files) { if (f.getName().toLowerCase().endsWith(".css")) { combo.addItem(new Item(f.getName(), IO.baseName(f))); } else { combo.addItem(f.getName()); } } } ... class StyleFileFilter implements FileFilter { public boolean accept(File file) { String name = file.getName().toLowerCase(); if (file.isDirectory()) { return false; } return name.endsWith(".css"); } }
Do you know about @Override? by David Ekholm
In Java the @Override annotations won't affect the final code. They only serve as a hint to you as developer that those methods override corresponding methods in super classes. When adding the @Override annotation, the compiler will make sure that you're indeed overriding a method from a super class.
JComboBox , getSelectedIndex() by David Ekholm
Jalbum only automatically passes the string value of a combo box to the album making process (including init.bsh). If you wish to also get the index, you can add a reference to that combo box to the "application" object (a Map type object that has a lifetime as long as Jalbum) and later retrieve it in init.bsh, so in onload.bsh, write:
JComboBox testCombo = new JComboBox(new Object[] { "First value", "Second value", "Third value" } ); application.put("testCombo", testCombo);
and inside init.bsh, write:
JComboBox testCombo = (JComboBox)application.get("testCombo");
You can now call getSelectedIndex() on that testCombo reference.
Properties, setProperty, getProperty, load, store by TomCee
Example of one of many ways to deal with properties.
import se.datadosen.component.*; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Properties; Properties props=new Properties(); // Controls that are to be imported into JAlbum as variables ControlPanel ui = new ControlPanel() { JTextField key=new JTextField(15); JTextField value=new JTextField(15); JButton savePropsButton=new JButton("save props"); JButton loadPropsButton=new JButton("load props"); JButton setPropButton=new JButton("set"); JButton getPropButton=new JButton("get"); JTextField retrieveKey=new JTextField(15); JTextField retrievedValue=new JTextField(15); }; // Layout controls easily similar to how text is added in a word processor ui.add(new JLabel("Properties handling example:")); ui.add("br",new JLabel("key:")); ui.add(ui.key); ui.add("tab",new JLabel("value:")); ui.add("tab",ui.value); ui.add("br",ui.setPropButton); ui.add(ui.savePropsButton); ui.add("br",ui.loadPropsButton); ui.add("br",new JLabel("type key:")); ui.add(ui.retrieveKey); ui.add(ui.getPropButton); ui.add("br",new JLabel("found value:")); ui.add(ui.retrievedValue); // Actions ui.setPropButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { props.setProperty(ui.key.getText(), ui.value.getText()); } }); ui.savePropsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { File f = new File("Testprops.properties"); props.store(new FileOutputStream(f.getName()), "test file for handling properties"); } }); ui.loadPropsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { File f = new File("Testprops.properties"); if (f.exists()) { props.load(new FileInputStream(f.getAbsolutePath())); }else{ JOptionPane.showMessageDialog(window, "file not found"); } } }); ui.getPropButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { ui.retrievedValue.setText(props.getProperty(ui.retrieveKey.getText())); } }); // Finally install components into JAlbum window.setSkinUI(ui);
Properties ,.loadFromXML, XML, Read by TomCee
To read an XML file as a properties map.
//init.bsh import java.util.Properties; Properties prop = new Properties(); FileInputStream in = null; File f=new File(skinDirectory, "xbfuser.xml"); in = new FileInputStream(f); prop.loadFromXML(in); in.close();
engine, addFilter ,.JAFilter by David Ekholm In init.bsh add a jAlbum filter using the constand field values.
engine.addFilter(filter, JAFilter.ALL_PRESCALE_STAGE);
Cameras, putValue, FOCAL_LENGTH_MULTIPLIER by David Ekholm
Internally, the system cameras.properties file is represented by a singleton class called Cameras. You can use a 'putValue' method to it, so skin developers can update and insert new properties to it. Example:
import se.datadosen.jalbum.*; Cameras cameras = Cameras.getInstance(); cameras.putValue("EASTMAN KODAK COMPANY", "KODAK DC240 ZOOM DIGITAL CAMERA", Cameras.FOCAL_LENGTH_MULTIPLIER, "6.5"); cameras.putValue("Canon", "Canon EOS 450D", Cameras.FOCAL_LENGTH_MULTIPLIER, "1.6216216160602388");
getUserVariables by David Ekholm
Map map = context.getJAlbumContext().getEngine().getUserVariables();
Get the language selected in jAlbum's preference settings by David Ekholm.
import se.datadosen.jalbum.Config; String language = Config.getConfig().getLanguage(); if (language.equals("default")) { language = System.getProperty("user.language"); }
Or for a shortened method use
<%= Config.getConfig().getInterpretedLanguage() %>
Shorthand for if-then-else script by David Ekholm et al
<%= BooleanVariable ? True condition : False condition %> <%= translucentBackground ? "background-image: url('back.png')" : "background-color: beige" %>;
ProcessTemplateFile, source, destination by David Ekholm
To have Jalbum process a template file and write the result to another file, use this call
engine.processTemplateFile(source, dest);
where source and dest are two File type variables.
VersionNumber, Compare jAlbum API
To compare jAlbum versions, for example a skin might need the latest version but the user might have an older version.
se.datadosen.util.VersionNumber; int someVariable = VersionNumber.compareTo(otherVersionNumber); //result is 1 if newer, 0 if older
JComboBox, StyleFileFilter by JeffTucker
To fill a combo box with the styles available within the current skin.
JComboBox<String> showStyles = new JComboBox(new String[]{""}); { JAlbumUtilities.fillCombo(showStyles, new File(skinDirectory, "styles"), new StyleFileFilter()); }
Accessibility, Class, Field, setAccessibility by David Ekholm
In BeanShell there is however a trick that gives you access to any class, field or method no matter the privacy setting, and that is the setAccessibility(true);
AlbumImage, JAFilter, crop by JeffTucker
This is to make a new square crop image, for a custom folder image for example, - to do something like a 3:2, you'd need to do a little math to get the setBounds() values. You need to crop the image to the desired aspect ratio before you scale it, or you get very unpleasant results, and to do that, you need to know what its actual dimensions are.
rif = currentObject.getRepresentingImageFile(); if(rif != null) { AlbumImage ai = new AlbumImage(rif, engine); minDim = Math.min(ai.getBufferedImage().getWidth(), ai.getBufferedImage().getHeight()); JAFilter sq = new CropFilter(); sq.setBounds(new Dimension(minDim, minDim)); ai = ai.applyFilter(sq); ai = ai.scaleToFit(new Dimension(400,400)); ai.saveImage(new File(outputDirectory, "folderthumb.jpg")); }
Apply xWeight and yWeight to crop or constrain ratio filters
//Get the folder's (ao) representing image file AlbumObject aoThumb = ao.getRepresentingAlbumObject(); if (aoThumb == null) { aoThumb = ao; } AlbumObjectProperties props = aoThumb.getProperties(); Map userVars = props.get(AlbumObjectProperties.USER_VARIABLES); if(userVars != null && vars.get("thumbWidth") != null { //Make new width and height variables tocWidth = vars.get("thumbWidth"); tocHeight = vars.get("thumbHeight"); originalWidth = ai.getImage().getWidth(); originalHeight = ai.getImage().getHeight(); minDim = Math.min(originalWidth, originalHeight); JAFilter CRFilt = new CropFilter(); CRFilt.setXWeight(xWeight); CRFilt.setYWeight(yWeight); CRFilt.setBounds(new Dimension(minDim, minDim)); ai = ai.applyFilter(CRFilt); //Scale thumbnail to 200px x 200px ai = ai.scaleToFit(new Dimension(200,200)); ai.saveImage(dest); }
themeImage by JeffTucker
Let's say you've got a couple of JSpinner()'s in your UI for theme image height and width. Now, you want to monitor those, and on any change, alter the theme image dimensions in the engine. Some code that will help:
new StateMonitor() { public void onChange() { folderImageSize.setText(themeImageWidth.getValue().toString() + "x" + themeImageHeight.getValue().toString()); engine.setThemeImageDim(new Dimension((int)themeImageWidth.getValue(), (int)themeImageHeight.getValue())); } }.add(themeImageHeight).add(themeImageWidth).done();
Album, Made by David Ekholm
A method of checking if the project has an album output or not.
window.ui2Engine(); //or via JAlbumContext use context.getJAlbumContext().getFrame().ui2Engine(); File indexFile = new File(engine.getInterpretedOutputDirectory(), engine.getIndexPageName() + engine.getPageExtension()); if (indexFile.exists()) { //Do something }
Show messages in the "Making album" window by ctwist
The method that shows a message in the "Making album" window is not public. This code calls the method via reflection, which allows a skin to show messages. This is for compiled Java. The technique in Beanshell would be different.
1) Run once: private Method mFireImageProcessingStarted; // An AlbumBean method that is called through reflection // Set up the album generation progress display (called via reflection) try { mFireImageProcessingStarted = AlbumBean.class.getDeclaredMethod("fireImageProcessingStarted", AlbumBeanEvent.class); } catch (NoSuchMethodException | SecurityException e) { throw new RuntimeException(e); } mFireImageProcessingStarted.setAccessible(true); 2) // Show a message in the progress window protected void showProgress(AlbumObject pAlbumObject, String pMessage) { String pathFromRoot = pAlbumObject.getPathFromRoot(); try { mFireImageProcessingStarted.invoke (engine, new AlbumBeanEvent (pAlbumObject.getFile(), rootImageDirectory.getName() + (pathFromRoot == "" ? "" : "/" + pathFromRoot), pMessage, 0, 0, 0, 0) ); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } 3) // Show messages showProgress(rootFolder, "Skin initialization"); showProgress(albumObject.getParent(), "a message");
AlbumObjectMetadata, RealCameraDate, currentObject
aoM = AlbumObjectMetadata.getInstance(currentObject.getFile()); System.out.println(aoM.getRealCameraDate());
UserVariables, get, set
Set and get user variables to/from Advanced Settings/User Variables
Map userVars = engine.getUserVariables(); userVars.put("newKey", newValue); engine.getUserVariables().get("newKey")
Get an object's (ao) user variable
AlbumObjectProperties properties = ao.getProperties(); if (properties != null) { HashMap userVariables = (HashMap) properties.get(AlbumObjectProperties.USER_VARIABLES); keyValue = userVariables.get("keyName"); }
Set an object's (ao) user variable
AlbumObjectProperties aoProperties = ao.getProperties(); HashMap aoUserVariables = (HashMap) aoProperties.get(AlbumObjectProperties.USER_VARIABLES); if (aoUserVariables == null) { aoUserVariables = new HashMap(); } aoUserVariables.put("keyName", value); //Save the variable name and its value aoProperties.put(AlbumObjectProperties.USER_VARIABLES, aoUserVariables); aoProperties.save();
AlbumObjectProperties, get, set
Set and get an album object's property
AlbumObjectProperties props = ao.getProperties(); props.put("newKey", newValue); props.save(); ao.Properties.get("newKey");
List all keys and values for a map
for (String name : mapName.keySet()) { System.out.println("key: " + name); System.out.println("value = " + mapName.get(name)); }
Read a text file All of the text in one go
import java.nio.file.*; String readFile(File dir, String fileName) { String fileContent = ""; fileContent = new String(Files.readAllBytes(Paths.get(dir + fileName))); return fileContent; }
Or
String text = IO.readTextFile("pathToFTextFile"); System.out.println(text);
Line by line
BufferedReader reader = new BufferedReader(new StringReader(IO.readTextFile("pathToTextFile"))); String line; while ((line = reader.readLine()) != null) { System.out.println(line); }
Create a linked object in a project
import se.datadosen.io.*; f = new File("/Users/david/My Albums/Welcome to jAlbum/suganth.jpg") ao = rootFolder.getFactory().createInstance(new LinkFile(rootFolder.getFile(), f.getName(), f), rootFolder); rootFolder.add(ao);
Get the active project's jap file
File projectFile = window.projectChooser.getSelectedFile();
Use jAlbum's API to format any date in epoch format (a long) to the currently set date format by David Ekholm
long lastAdded = JAlbumUtilities.getDeepLastAdded(currentFolder); String formatted = new FormattedDate(lastAdded, engine.getDateFormatAsObject()).toString();
Get the contents of the clipboard by David Ekholm
import java.awt.datatransfer.*; Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable t = clipboard.getContents(null); String content = (String) t.getTransferData(DataFlavor.stringFlavor);
JSpinner with non-integer numbers To setup and use a JSpinner, for example a frame rate variable frameRate
setframeRate = 5.0; //If reading a stored value if(ffSlideshowProperties.get("frameRate") != null) setframeRate = Double.parseDouble(ffSlideshowProperties.get("frameRate")); //in the controlPanel double current = setframeRate; double min = (double) 1; double max = (double) 10; double step = 0.1; JSpinner frameRate = new JSpinner(new SpinnerNumberModel(current, min, max, step)); //Get the set value duration = panel.frameRate.getValue();
jAlbum uses a json library called json-simple. Here's a usage example. Try it in jAlbum's system console:
import com.github.cliftonlabs.json_simple.*; jsonString = '''{ "geoObjects": [ { "name": "Summit 1", "latlon": "36.12345, 6.123456", }, { "name": "Summit 2", "latlon": "36.6789, 6.6789", }, ] }''' data = Jsoner.deserialize(jsonString) data.geoObjects[0].name // Prints "Summit 1" data.geoObjects[0].latlon // Prints "36.12345, 6.123456"
Saving hidden property values, AlbumObjectProperties, AlbumObjectProperties.HiddenProperty Example changes the image URI of a webLocation, a hidden property value
for (AlbumObject ao : selected) { AlbumObjectProperties props = ao.getProperties(); props.putHidden("imageURI", IO.urlEncode("requiredURI")); props.save(); }