package org.jabref.gui; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.FontFormatException; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.swing.Icon; import javax.swing.ImageIcon; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.text.Text; import org.jabref.logic.groups.DefaultGroupsFactory; import org.jabref.preferences.JabRefPreferences; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class IconTheme { public static Font FONT; /** * JabRef's default color */ public static final Color DEFAULT_COLOR = JabRefPreferences.getInstance().getColor(JabRefPreferences.ICON_ENABLED_COLOR); public static final Color DEFAULT_DISABLED_COLOR = JabRefPreferences.getInstance().getColor(JabRefPreferences.ICON_DISABLED_COLOR); private static final String DEFAULT_ICON_PATH = "/images/external/red.png"; private static final Log LOGGER = LogFactory.getLog(IconTheme.class); private static final Map<String, String> KEY_TO_ICON = readIconThemeFile( IconTheme.class.getResource("/images/Icons.properties"), "/images/external/"); // Christmas edition //public static final Color DEFAULT_COLOR = new Color(0x155115); //public static final Color DEFAULT_DISABLED_COLOR = new Color(0x990000); private static Font FONT_16; private static javafx.scene.text.Font FX_FONT; static { try (InputStream stream = getMaterialDesignIconsStream()) { FONT = Font.createFont(Font.TRUETYPE_FONT, stream); FONT_16 = FONT.deriveFont(Font.PLAIN, 16f); try (InputStream stream2 = getMaterialDesignIconsStream()) { FX_FONT = javafx.scene.text.Font.loadFont(stream2, JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_LARGE)); } } catch (FontFormatException | IOException e) { LOGGER.warn("Error loading font", e); } } private static InputStream getMaterialDesignIconsStream() { return FontBasedIcon.class.getResourceAsStream("/fonts/materialdesignicons-webfont.ttf"); } public static javafx.scene.paint.Color getDefaultColor() { return javafx.scene.paint.Color.rgb(DEFAULT_COLOR.getRed(), DEFAULT_COLOR.getGreen(), DEFAULT_COLOR.getBlue(), DEFAULT_COLOR.getAlpha() / 255.0); } /** * Constructs an ImageIcon for the image representing the given function, in the resource * file listing images. * * @param name The name of the icon, such as "open", "save", "saveAs" etc. * @return The ImageIcon for the function. */ public static ImageIcon getImage(String name) { return new ImageIcon(getIconUrl(name)); } public static Image getJabRefImageFX() { return getImageFX("jabrefIcon48"); } /** * Constructs an {@link Image} for the image representing the given function, in the resource * file listing images. * * @param name The name of the icon, such as "open", "save", "saveAs" etc. * @return The {@link Image} for the function. */ public static Image getImageFX(String name) { return new Image(getIconUrl(name).toString()); } /** * Looks up the URL for the image representing the given function, in the resource * file listing images. * * @param name The name of the icon, such as "open", "save", "saveAs" etc. * @return The URL to the actual image to use. */ public static URL getIconUrl(String name) { String key = Objects.requireNonNull(name, "icon name"); if (!KEY_TO_ICON.containsKey(key)) { LOGGER.warn("Could not find icon url by name " + name + ", so falling back on default icon " + DEFAULT_ICON_PATH); } String path = KEY_TO_ICON.getOrDefault(key, DEFAULT_ICON_PATH); return Objects.requireNonNull(IconTheme.class.getResource(path), "Path must not be null for key " + key); } /** * Read a typical java property url into a Map. Currently doesn't support escaping * of the '=' character - it simply looks for the first '=' to determine where the key ends. * Both the key and the value is trimmed for whitespace at the ends. * * @param url The URL to read information from. * @param prefix A String to prefix to all values read. Can represent e.g. the directory where icon files are to be * found. * @return A Map containing all key-value pairs found. */ // FIXME: prefix can be removed?! private static Map<String, String> readIconThemeFile(URL url, String prefix) { Objects.requireNonNull(url, "url"); Objects.requireNonNull(prefix, "prefix"); Map<String, String> result = new HashMap<>(); try (BufferedReader in = new BufferedReader( new InputStreamReader(url.openStream(), StandardCharsets.ISO_8859_1))) { String line; while ((line = in.readLine()) != null) { if (!line.contains("=")) { continue; } int index = line.indexOf('='); String key = line.substring(0, index).trim(); String value = prefix + line.substring(index + 1).trim(); result.put(key, value); } } catch (IOException e) { LOGGER.warn("Unable to read default icon theme.", e); } return result; } public static List<java.awt.Image> getLogoSet() { List<java.awt.Image> jabrefLogos = new ArrayList<>(); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon16")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon20")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon32")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon40")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon48")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon64")).getImage()); jabrefLogos.add(new ImageIcon(getIconUrl("jabrefIcon128")).getImage()); return jabrefLogos; } public enum JabRefIcon { ADD("\uf416") /*css: mdi-plus-box*/, ADD_NOBOX("\uf415") /*css: plus */, ADD_ENTRY("\uf527") /*css: tooltip-outline-plus */, EDIT_ENTRY("\uf524") /*css: tooltip-edit */, EDIT_STRINGS("\uf528") /*css: tooltip-text */, FOLDER("\uf24b"), /*css: folder */ REMOVE("\uf375"), /*css: minus-box */ REMOVE_NOBOX("\uf374") /*css: minus */, FILE("\uf214"), /* css: file*/ PDF_FILE("\uf225"), /* css: file-pdf*/ DOI("\uF072") /*css: barcode-scan*/, DUPLICATE("\uf191") /*css: content-duplicate */, EDIT("\uf3eb") /*css: pencil */, NEW("\uf224") /* css: file-outline */, SAVE("\uf193") /*css: content-save*/, SAVE_ALL("\uf194") /*css: content-save-all*/, CLOSE("\uf156") /* css: close */, PASTE("\uf192") /*css: content-paste*/, CUT("\uf190") /*css: content-cut*/, COPY("\uf18f") /*css: content-copy */, COMMENT("\uF188") /*css: comment*/, REDO("\uf44e") /*css: redo*/, UNDO("\uf54c") /*css: undo*/, MARK_ENTRIES("\uf0c0") /*css: bookmark */, MARKER("\uF524") /*css: marker */, UNMARK_ENTRIES("\uf0c3") /*css: bookmark-outline */, REFRESH("\uf450") /*css: refresh */, DELETE_ENTRY("\uf1c0") /*css: delete */, SEARCH("\uf349") /*css: magnify */, PREFERENCES("\uf493") /*css: settings */, HELP("\uf2d7") /*css: help-circle*/, UP("\uf143") /*css: chevron-up */, DOWN("\uf140") /*css: chevron-down */, LEFT("\uf04e") /* css; arrow-left-bold */, RIGHT("\uf055") /*css: arrow-right-bold */, SOURCE("\uf169") /*css: code-braces*/, MAKE_KEY("\uf30b") /*css: key-variant */, CLEANUP_ENTRIES("\uf0e2") /*css: broom */, PRIORITY("\uf23b") /*css: flag */, PRIORITY_HIGH("\uF23B", Color.RED) /*css: flag */, PRIORITY_MEDIUM("\uF23B", Color.ORANGE) /*css: flag */, PRIORITY_LOW("\uF23B", new Color(111, 204, 117)) /*css: flag */, PRINTED("\uf42a") /*css: printer */, RANKING("\uf4ce") /*css: star + star-outline: f4d2*/, RANK1("\uF4CE\uF4D2\uF4D2\uF4D2\uf4d2"), RANK2("\uF4CE\uF4CE\uF4D2\uF4D2\uF4D2"), RANK3("\uF4CE\uF4CE\uF4CE\uF4D2\uF4D2"), RANK4("\uF4CE\uF4CE\uF4CE\uF4CE\uF4D2"), RANK5("\uF4CE\uF4CE\uF4CE\uF4CE\uF4CE"), WWW("\uf59f") /*css: web*/, GROUP_INCLUDING("\uf233") /*css: filter-outline*/, GROUP_REFINING("\uf232") /*css: filter*/, AUTO_GROUP("\uf068"), /*css: auto-fix*/ EMAIL("\uf1ee") /*css: email*/, EXPORT_TO_CLIPBOARD("\uf14b") /*css: clipboard-arrow-left */, ATTACH_FILE("\uf3e2") /*css: paperclip*/, AUTO_FILE_LINK("\uf21e") /*css: file-find */, QUALITY_ASSURED("\uf124"), /*css: certificate */ QUALITY("\uF124"),/*css: certificate */ OPEN("\uf24b") /*css: folder */, ADD_ROW("\uf490") /* css: server-plus*/, REMOVE_ROW("\uf48c") /*css: server-minus */, PICTURE("\uf21f") /*css: file-image */, READ_STATUS_READ("\uf208", new Color(111, 204, 117)), /*css: eye */ READ_STATUS_SKIMMED("\uF208", Color.ORANGE), /*css: eye */ READ_STATUS("\uF208"),/*css: eye */ RELEVANCE("\uf4cf"),/*css: star-circle */ MERGE_ENTRIES("\uf18a"), /* css: compare */ CONNECT_OPEN_OFFICE("\uf3cb") /*css: open-in-app */, PLAIN_TEXT_IMPORT_TODO("\uf130") /* css: checkbox-blank-circle-outline*/, PLAIN_TEXT_IMPORT_DONE("\uf134") /* checkbox-marked-circle-outline */, DONATE("\uf2a1"), /* css: gift */ MOVE_TAB_ARROW("\uf05e"), /*css: arrow-up-bold */ OPTIONAL("\uf316"), /*css: label-outline */ REQUIRED("\uf315"), /*css: label */ INTEGRITY_FAIL("\uf159", Color.RED), /*css: close-circle */ INTEGRITY_INFO("\uf2fc"), /*css: information */ INTEGRITY_WARN("\uf028"), /*css alert-circle */ INTEGRITY_SUCCESS("\uF134") /*css: checkbox-marked-circle-outline */, GITHUB("\uf2a4"), /*css: github-circle*/ TOGGLE_ENTRY_PREVIEW("\uf332"), /*css: library-books */ TOGGLE_GROUPS("\uf572"), /*css: view-list */ WRITE_XMP("\uf2fa"), /* css: import */ FILE_WORD("\uf22c"), /*css: file-word */ FILE_EXCEL("\uf21b"), /*css: file-excel */ FILE_POWERPOINT("\uf227"), /*css: file-powerpoint */ FILE_TEXT("\uf219"), /*css: file-document */ FILE_MULTIPLE("\uf222"), /*css: file-multiple */ KEY_BINDINGS("\uf30c"), /*css: keyboard */ FIND_DUPLICATES("\uf16b"), /*css: code-equal */ PULL("\uf4c2"), /*source-pull*/ OPEN_IN_NEW_WINDOW("\uf3cc"), /*css: open-in-new */ CASE_SENSITIVE("\uf02c"), /* css: mdi-alphabetical */ REG_EX("\uf451"), /*css: mdi-regex */ CONSOLE("\uf18d"), /*css: console */ FORUM("\uf28c"), /* css: forum */ FACEBOOK("\uf20c"), /* css: facebook */ BLOG("\uf46b"), /* css: rss */ GLOBAL_SEARCH("\uF1E7"), /* css: earth */ DATE_PICKER("\uF0ED;"), /* css: calendar */ DEFAULT_GROUP_ICON("\uF316"), /* css: label-outline */ ALL_ENTRIES_GROUP_ICON(DefaultGroupsFactory.ALL_ENTRIES_GROUP_DEFAULT_ICON), // STILL MISSING: GROUP_REGULAR("\uF4E6", Color.RED); private final String code; private final Color color; JabRefIcon(String code) { this(code, IconTheme.DEFAULT_COLOR); } JabRefIcon(String code, Color color) { this.code = code; this.color = color; } public FontBasedIcon getIcon() { return new FontBasedIcon(this.code, this.color); } public FontBasedIcon getSmallIcon() { return new FontBasedIcon(this.code, this.color, JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL)); } public Node getGraphicNode() { Text graphic = new Text(this.code); graphic.getStyleClass().add("icon"); return graphic; } public String getCode() { return this.code; } } public static class FontBasedIcon implements Icon { private final String iconCode; private final Color iconColor; private final int size; public FontBasedIcon(String code, Color iconColor) { this.iconCode = code; this.iconColor = iconColor; this.size = JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_LARGE); } public FontBasedIcon(String code, Color iconColor, int size) { this.iconCode = code; this.iconColor = iconColor; this.size = size; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2 = (Graphics2D) g.create(); RenderingHints rh = new RenderingHints( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHints(rh); g2.setFont(FONT.deriveFont(Font.PLAIN, size)); g2.setColor(iconColor); FontMetrics fm = g2.getFontMetrics(); g2.translate(x, y + fm.getAscent()); g2.drawString(iconCode, 0, 0); g2.dispose(); } @Override public int getIconWidth() { return size; } @Override public int getIconHeight() { return size; } public FontBasedIcon createDisabledIcon() { return new FontBasedIcon(this.iconCode, IconTheme.DEFAULT_DISABLED_COLOR, this.size); } public FontBasedIcon createSmallIcon() { return new FontBasedIcon(this.iconCode, this.iconColor, JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL)); } public FontBasedIcon createWithNewColor(Color newColor) { return new FontBasedIcon(this.iconCode, newColor, this.size); } } }