diff --git a/src/main/java/featurecat/lizzie/Config.java b/src/main/java/featurecat/lizzie/Config.java index 780044a54..8d9f9b840 100644 --- a/src/main/java/featurecat/lizzie/Config.java +++ b/src/main/java/featurecat/lizzie/Config.java @@ -1,5 +1,7 @@ package featurecat.lizzie; +import featurecat.lizzie.theme.Theme; +import java.awt.Color; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; @@ -13,9 +15,12 @@ public class Config { public boolean showMoveNumber = false; public boolean showWinrate = true; + public boolean largeWinrate = false; + public boolean showBlunderBar = true; + public boolean weightedBlunderBarHeight = false; + public boolean dynamicWinrateGraphWidth = false; public boolean showVariationGraph = true; - public boolean showComment = false; - public int commentFontSize = 0; + public boolean showComment = true; public boolean showRawBoard = false; public boolean showCaptured = true; public boolean handicapInsteadOfWinrate = false; @@ -37,6 +42,21 @@ public class Config { private String configFilename = "config.txt"; private String persistFilename = "persist"; + public Theme theme; + public float winrateStrokeWidth = 3; + public int minimumBlunderBarWidth = 3; + public int shadowSize = 100; + public String fontName = null; + public String uiFontName = null; + public String winrateFontName = null; + public int commentFontSize = 0; + public Color commentFontColor = null; + public Color commentBackgroundColor = null; + public Color winrateLineColor = null; + public Color winrateMissLineColor = null; + public Color blunderBarColor = null; + public boolean solidStoneIndicator = false; + private JSONObject loadAndMergeConfig( JSONObject defaultCfg, String fileName, boolean needValidation) throws IOException { File file = new File(fileName); @@ -127,13 +147,18 @@ public Config() throws IOException { leelazConfig = config.getJSONObject("leelaz"); uiConfig = config.getJSONObject("ui"); + theme = new Theme(uiConfig); + showMoveNumber = uiConfig.getBoolean("show-move-number"); showStatus = uiConfig.getBoolean("show-status"); showBranch = uiConfig.getBoolean("show-leelaz-variation"); showWinrate = uiConfig.getBoolean("show-winrate"); + largeWinrate = uiConfig.optBoolean("large-winrate", false); + showBlunderBar = uiConfig.optBoolean("show-blunder-bar", true); + weightedBlunderBarHeight = uiConfig.optBoolean("weighted-blunder-bar-height", false); + dynamicWinrateGraphWidth = uiConfig.optBoolean("dynamic-winrate-graph-width", false); showVariationGraph = uiConfig.getBoolean("show-variation-graph"); - showComment = uiConfig.optBoolean("show-comment", false); - commentFontSize = uiConfig.optInt("comment-font-size", 0); + showComment = uiConfig.optBoolean("show-comment", true); showCaptured = uiConfig.getBoolean("show-captured"); showBestMoves = uiConfig.getBoolean("show-best-moves"); showNextMoves = uiConfig.getBoolean("show-next-moves"); @@ -142,6 +167,20 @@ public Config() throws IOException { handicapInsteadOfWinrate = uiConfig.getBoolean("handicap-instead-of-winrate"); startMaximized = uiConfig.getBoolean("window-maximized"); showDynamicKomi = uiConfig.getBoolean("show-dynamic-komi"); + + winrateStrokeWidth = theme.winrateStrokeWidth(); + minimumBlunderBarWidth = theme.minimumBlunderBarWidth(); + shadowSize = theme.shadowSize(); + fontName = theme.fontName(); + uiFontName = theme.uiFontName(); + winrateFontName = theme.winrateFontName(); + commentFontSize = theme.commentFontSize(); + commentFontColor = theme.commentFontColor(); + commentBackgroundColor = theme.commentBackgroundColor(); + winrateLineColor = theme.winrateLineColor(); + winrateMissLineColor = theme.winrateMissLineColor(); + blunderBarColor = theme.blunderBarColor(); + solidStoneIndicator = theme.solidStoneIndicator(); } // Modifies config by adding in values from default_config that are missing. @@ -181,6 +220,10 @@ public void toggleShowWinrate() { this.showWinrate = !this.showWinrate; } + public void toggleLargeWinrate() { + this.largeWinrate = !this.largeWinrate; + } + public void toggleShowVariationGraph() { this.showVariationGraph = !this.showVariationGraph; } @@ -209,6 +252,10 @@ public boolean showLargeSubBoard() { return showSubBoard && largeSubBoard; } + public boolean showLargeWinrate() { + return showWinrate && largeWinrate; + } + /** * Scans the current directory as well as the current PATH to find a reasonable default leelaz * binary. @@ -264,6 +311,14 @@ private JSONObject createDefaultConfig() { ui.put("show-status", true); ui.put("show-leelaz-variation", true); ui.put("show-winrate", true); + ui.put("large-winrate", false); + ui.put("winrate-stroke-width", 3); + ui.put("show-blunder-bar", true); + ui.put("minimum-blunder-bar-width", 3); + ui.put("weighted-blunder-bar-height", false); + ui.put("dynamic-winrate-graph-width", false); + ui.put("show-comment", true); + ui.put("comment-font-size", 0); ui.put("show-variation-graph", true); ui.put("show-captured", true); ui.put("show-best-moves", true); diff --git a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java index 411ea753e..5f18005eb 100644 --- a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java +++ b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.imageio.ImageIO; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -76,8 +75,8 @@ public class BoardRenderer { private int maxAlpha = 240; public BoardRenderer(boolean isMainBoard) { - uiConfig = Lizzie.config.config.getJSONObject("ui"); - uiPersist = Lizzie.config.persisted.getJSONObject("ui-persist"); + uiConfig = Lizzie.config.uiConfig; + uiPersist = Lizzie.config.persisted; try { maxAlpha = uiPersist.getInt("max-alpha"); } catch (JSONException e) { @@ -211,7 +210,7 @@ private void drawGoban(Graphics2D g0) { g, x + scaledMargin + squareLength * i, y + scaledMargin / 2, - LizzieFrame.OpenSansRegularBase, + LizzieFrame.uiFont, "" + alphabet.charAt(i), stoneRadius * 4 / 5, stoneRadius); @@ -219,7 +218,7 @@ private void drawGoban(Graphics2D g0) { g, x + scaledMargin + squareLength * i, y - scaledMargin / 2 + boardLength, - LizzieFrame.OpenSansRegularBase, + LizzieFrame.uiFont, "" + alphabet.charAt(i), stoneRadius * 4 / 5, stoneRadius); @@ -229,16 +228,16 @@ private void drawGoban(Graphics2D g0) { g, x + scaledMargin / 2, y + scaledMargin + squareLength * i, - LizzieFrame.OpenSansRegularBase, - "" + (Board.boardSize - i), + LizzieFrame.uiFont, + "" + (Board.BOARD_SIZE - i), stoneRadius * 4 / 5, stoneRadius); drawString( g, x - scaledMargin / 2 + +boardLength, y + scaledMargin + squareLength * i, - LizzieFrame.OpenSansRegularBase, - "" + (Board.boardSize - i), + LizzieFrame.uiFont, + "" + (Board.BOARD_SIZE - i), stoneRadius * 4 / 5, stoneRadius); } @@ -457,7 +456,12 @@ private void drawMoveNumbers(Graphics2D g) { boolean isWhite = board.getStones()[Board.getIndex(lastMove[0], lastMove[1])].isWhite(); g.setColor(isWhite ? Color.BLACK : Color.WHITE); - drawCircle(g, stoneX, stoneY, lastMoveMarkerRadius); + if (Lizzie.config.solidStoneIndicator) { + // Use a solid circle instead of + fillCircle(g, stoneX, stoneY, (int) (lastMoveMarkerRadius * 0.65)); + } else { + drawCircle(g, stoneX, stoneY, lastMoveMarkerRadius); + } } else if (lastMove == null && board.getData().moveNumber != 0 && !board.inScoreMode()) { g.setColor( board.getData().blackToPlay ? new Color(255, 255, 255, 150) : new Color(0, 0, 0, 150)); @@ -472,7 +476,7 @@ private void drawMoveNumbers(Graphics2D g) { g, x + boardLength / 2, y + boardLength / 2, - LizzieFrame.OpenSansRegularBase, + LizzieFrame.uiFont, "pass", stoneRadius * 4, stoneRadius * 6); @@ -519,7 +523,7 @@ private void drawMoveNumbers(Graphics2D g) { g, stoneX, stoneY, - LizzieFrame.OpenSansRegularBase, + LizzieFrame.uiFont, moveNumberString, (float) (stoneRadius * 1.4), (int) (stoneRadius * 1.4)); @@ -635,7 +639,7 @@ private void drawLeelazSuggestions(Graphics2D g) { g, suggestionX, suggestionY, - LizzieFrame.OpenSansSemiboldBase, + LizzieFrame.winrateFont, Font.PLAIN, text, stoneRadius, @@ -646,7 +650,7 @@ private void drawLeelazSuggestions(Graphics2D g) { g, suggestionX, suggestionY + stoneRadius * 2 / 5, - LizzieFrame.OpenSansRegularBase, + LizzieFrame.uiFont, getPlayoutsString(move.playouts), (float) (stoneRadius * 0.8), stoneRadius * 1.4); @@ -682,12 +686,9 @@ private void drawNextMoves(Graphics2D g) { private void drawWoodenBoard(Graphics2D g) { if (uiConfig.getBoolean("fancy-board")) { + // fancy version if (cachedBoardImage == null) { - try { - cachedBoardImage = ImageIO.read(getClass().getResourceAsStream("/assets/board.png")); - } catch (IOException e) { - e.printStackTrace(); - } + cachedBoardImage = Lizzie.config.theme.board(); } int shadowRadius = (int) (boardLength * MARGIN / 6); @@ -711,6 +712,7 @@ private void drawWoodenBoard(Graphics2D g) { g.setStroke(new BasicStroke(1)); } else { + // simple version JSONArray boardColor = uiConfig.getJSONArray("board-color"); g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); g.setColor(new Color(boardColor.getInt(0), boardColor.getInt(1), boardColor.getInt(2))); @@ -755,8 +757,8 @@ private void drawShadow( Graphics2D g, int centerX, int centerY, boolean isGhost, float shadowStrength) { if (!uiConfig.getBoolean("shadows-enabled")) return; - final int shadowSize = (int) (stoneRadius * 0.3 * uiConfig.getInt("shadow-size") / 100); - final int fartherShadowSize = (int) (stoneRadius * 0.17 * uiConfig.getInt("shadow-size") / 100); + final int shadowSize = (int) (stoneRadius * 0.3 * Lizzie.config.shadowSize / 100); + final int fartherShadowSize = (int) (stoneRadius * 0.17 * Lizzie.config.shadowSize / 100); final Paint TOP_GRADIENT_PAINT; final Paint LOWER_RIGHT_GRADIENT_PAINT; @@ -848,18 +850,12 @@ private void drawStone( } /** Get scaled stone, if cached then return cached */ - private BufferedImage getScaleStone(boolean isBlack, int size) { + public BufferedImage getScaleStone(boolean isBlack, int size) { BufferedImage stone = isBlack ? cachedBlackStoneImage : cachedWhiteStoneImage; if (stone == null || stone.getWidth() != size || stone.getHeight() != size) { stone = new BufferedImage(size, size, TYPE_INT_ARGB); - String imgPath = isBlack ? "/assets/black0.png" : "/assets/white0.png"; - Image img = null; - try { - img = ImageIO.read(getClass().getResourceAsStream(imgPath)); - } catch (IOException e) { - e.printStackTrace(); - } Graphics2D g2 = stone.createGraphics(); + Image img = isBlack ? Lizzie.config.theme.blackStone() : Lizzie.config.theme.whiteStone(); g2.drawImage(img.getScaledInstance(size, size, java.awt.Image.SCALE_SMOOTH), 0, 0, null); g2.dispose(); if (isBlack) { @@ -873,12 +869,7 @@ private BufferedImage getScaleStone(boolean isBlack, int size) { public BufferedImage getWallpaper() { if (cachedWallpaperImage == null) { - try { - String wallpaperPath = "/assets/background.jpg"; - cachedWallpaperImage = ImageIO.read(getClass().getResourceAsStream(wallpaperPath)); - } catch (IOException e) { - e.printStackTrace(); - } + cachedWallpaperImage = Lizzie.config.theme.background(); } return cachedWallpaperImage; } diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java index bb3b89adb..c95481114 100644 --- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java +++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java @@ -78,8 +78,8 @@ public class LizzieFrame extends JFrame { private static VariationTree variationTree; private static WinrateGraph winrateGraph; - public static Font OpenSansRegularBase; - public static Font OpenSansSemiboldBase; + public static Font uiFont; + public static Font winrateFont; private final BufferStrategy bs; @@ -90,9 +90,6 @@ public class LizzieFrame extends JFrame { public boolean playerIsBlack = true; public int winRateGridLines = 3; - // Get the font name in current system locale - private String systemDefaultFontName = new JLabel().getFont().getFontName(); - private long lastAutosaveTime = System.currentTimeMillis(); // Save the player title @@ -108,13 +105,13 @@ public class LizzieFrame extends JFrame { static { // load fonts try { - OpenSansRegularBase = + uiFont = Font.createFont( Font.TRUETYPE_FONT, Thread.currentThread() .getContextClassLoader() .getResourceAsStream("fonts/OpenSans-Regular.ttf")); - OpenSansSemiboldBase = + winrateFont = Font.createFont( Font.TRUETYPE_FONT, Thread.currentThread() @@ -139,6 +136,14 @@ public LizzieFrame() { JSONArray windowSize = Lizzie.config.uiConfig.getJSONArray("window-size"); setSize(windowSize.getInt(0), windowSize.getInt(1)); // use config file window size + // Allow change font in the config + if (Lizzie.config.uiFontName != null) { + uiFont = new Font(Lizzie.config.uiFontName, Font.PLAIN, 12); + } + if (Lizzie.config.winrateFontName != null) { + winrateFont = new Font(Lizzie.config.winrateFontName, Font.BOLD, 12); + } + if (Lizzie.config.startMaximized) { setExtendedState(Frame.MAXIMIZED_BOTH); // start maximized } @@ -147,8 +152,8 @@ public LizzieFrame() { commentPane = new JTextPane(); commentPane.setEditable(false); commentPane.setMargin(new Insets(5, 5, 5, 5)); - commentPane.setBackground(new Color(0, 0, 0, 200)); - commentPane.setForeground(Color.WHITE); + commentPane.setBackground(Lizzie.config.commentBackgroundColor); + commentPane.setForeground(Lizzie.config.commentFontColor); scrollPane = new JScrollPane(); scrollPane.setViewportView(commentPane); scrollPane.setBorder(null); @@ -553,7 +558,7 @@ private void drawVariationTreeContainer(Graphics2D g, int vx, int vy, int vw, in private void drawPonderingState(Graphics2D g, String text, int x, int y, double size) { int fontSize = (int) (max(getWidth(), getHeight()) * size); - Font font = new Font(systemDefaultFontName, Font.PLAIN, fontSize); + Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize); FontMetrics fm = g.getFontMetrics(font); int stringWidth = fm.stringWidth(text); // Truncate too long text when display switching prompt @@ -649,7 +654,7 @@ void drawControls() { Graphics2D g = cachedImage.createGraphics(); int maxSize = min(getWidth(), getHeight()); - Font font = new Font(systemDefaultFontName, Font.PLAIN, (int) (maxSize * 0.034)); + Font font = new Font(Lizzie.config.fontName, Font.PLAIN, (int) (maxSize * 0.034)); g.setFont(font); FontMetrics metrics = g.getFontMetrics(font); @@ -712,7 +717,7 @@ private void drawCommandString(Graphics2D g) { int maxSize = (int) (min(getWidth(), getHeight()) * 0.98); - Font font = new Font(systemDefaultFontName, Font.PLAIN, (int) (maxSize * 0.03)); + Font font = new Font(Lizzie.config.fontName, Font.PLAIN, (int) (maxSize * 0.03)); String commandString = resourceBundle.getString("LizzieFrame.prompt.showControlsHint"); int strokeRadius = 2; @@ -930,7 +935,7 @@ private void drawCaptured(Graphics2D g, int posX, int posY, int width, int heigh } private void setPanelFont(Graphics2D g, float size) { - Font font = OpenSansRegularBase.deriveFont(Font.PLAIN, size); + Font font = uiFont.deriveFont(Font.PLAIN, size); g.setFont(font); } @@ -1169,7 +1174,7 @@ private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full) } else if (fontSize < 16) { fontSize = 16; } - Font font = new Font(systemDefaultFontName, Font.PLAIN, fontSize); + Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize); commentPane.setFont(font); commentPane.setText(comment); commentPane.setSize(w, cHeight); diff --git a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java index 3b4915c96..0ab6351af 100644 --- a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java +++ b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java @@ -73,8 +73,8 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { g.drawLine(posx, y, posx + width, y); } - g.setColor(Color.green); - g.setStroke(new BasicStroke(3)); + g.setColor(Lizzie.config.winrateLineColor); + g.setStroke(new BasicStroke(Lizzie.config.winrateStrokeWidth)); BoardHistoryNode topOfVariation = null; int numMoves = 0; @@ -139,8 +139,8 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { wr = lastWr; } - if (lastNodeOk) g.setColor(Color.green); - else g.setColor(Color.blue.darker()); + if (lastNodeOk) g.setColor(Lizzie.config.winrateLineColor); + else g.setColor(Lizzie.config.winrateMissLineColor); if (lastOkMove > 0) { g.drawLine( @@ -151,7 +151,7 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { } if (node == curMove) { - g.setColor(Color.green); + g.setColor(Lizzie.config.winrateLineColor); g.fillOval( posx + (movenum * width / numMoves) - DOT_RADIUS, posy + height - (int) (convertWinrate(wr) * height / 100) - DOT_RADIUS, diff --git a/src/main/java/featurecat/lizzie/theme/Theme.java b/src/main/java/featurecat/lizzie/theme/Theme.java new file mode 100644 index 000000000..b55674692 --- /dev/null +++ b/src/main/java/featurecat/lizzie/theme/Theme.java @@ -0,0 +1,191 @@ +package featurecat.lizzie.theme; + +import static java.io.File.separator; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import javax.imageio.ImageIO; +import javax.swing.JLabel; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +/** Theme Allow to load the external image & theme config */ +public class Theme { + BufferedImage blackStoneCached = null; + BufferedImage whiteStoneCached = null; + BufferedImage boardCached = null; + BufferedImage backgroundCached = null; + + private String configFile = "theme.txt"; + private String pathPrefix = "theme" + separator; + private String path = null; + private JSONObject config = new JSONObject(); + private JSONObject uiConfig = null; + + public Theme(JSONObject uiConfig) { + this.uiConfig = uiConfig; + String themeName = uiConfig.optString("theme"); + this.path = this.pathPrefix + (themeName.isEmpty() ? "" : themeName + separator); + File file = new File(this.path + this.configFile); + if (file.canRead()) { + FileInputStream fp; + try { + fp = new FileInputStream(file); + config = new JSONObject(new JSONTokener(fp)); + fp.close(); + } catch (IOException e) { + } catch (JSONException e) { + } + } + } + + public BufferedImage blackStone() { + if (blackStoneCached == null) { + blackStoneCached = getImageByKey("black-stone-image", "black.png", "black0.png"); + } + return blackStoneCached; + } + + public BufferedImage whiteStone() { + if (whiteStoneCached == null) { + whiteStoneCached = getImageByKey("white-stone-image", "white.png", "white0.png"); + } + return whiteStoneCached; + } + + public BufferedImage board() { + if (boardCached == null) { + boardCached = getImageByKey("board-image", "board.png", "board.png"); + } + return boardCached; + } + + public BufferedImage background() { + if (backgroundCached == null) { + backgroundCached = getImageByKey("background-image", "background.png", "background.jpg"); + } + return backgroundCached; + } + + /** Use custom font for general text */ + public String fontName() { + String key = "font-name"; + return config.optString(key, uiConfig.optString(key, new JLabel().getFont().getFontName())); + } + + /** Use custom font for the UI */ + public String uiFontName() { + String key = "ui-font-name"; + return config.optString(key, uiConfig.optString(key, null)); + } + + /** Use custom font for the Leela Zero winrate on the stone */ + public String winrateFontName() { + String key = "winrate-font-name"; + return config.optString(key, uiConfig.optString(key, null)); + } + + /** Use solid current stone indicator */ + public boolean solidStoneIndicator() { + String key = "solid-stone-indicator"; + return config.optBoolean(key, uiConfig.optBoolean(key)); + } + + /** The size of the shadow */ + public int shadowSize() { + return getIntByKey("shadow-size", 100); + } + + /** The stroke width of the winrate line */ + public int winrateStrokeWidth() { + return getIntByKey("winrate-stroke-width", 3); + } + + /** The minimum width of the blunder bar */ + public int minimumBlunderBarWidth() { + return getIntByKey("minimum-blunder-bar-width", 3); + } + + /** The font size of the comment */ + public int commentFontSize() { + return getIntByKey("comment-font-size", 3); + } + + /** + * The background color of the comment panel + * + * @return + */ + public Color commentBackgroundColor() { + return getColorByKey("comment-background-color", new Color(0, 0, 0, 200)); + } + + /** The font color of the comment */ + public Color commentFontColor() { + return getColorByKey("comment-font-color", Color.WHITE); + } + + /** The color of the winrate line */ + public Color winrateLineColor() { + return getColorByKey("winrate-line-color", Color.green); + } + + /** The color of the line that missed the winrate */ + public Color winrateMissLineColor() { + return getColorByKey("winrate-miss-line-color", Color.blue.darker()); + } + + /** The color of the blunder bar */ + public Color blunderBarColor() { + return getColorByKey("blunder-bar-color", new Color(255, 0, 0, 150)); + } + + private Color getColorByKey(String key, Color defaultColor) { + Color color = array2Color(config.optJSONArray(key), null); + if (color == null) { + color = array2Color(uiConfig.optJSONArray(key), defaultColor); + } + return color; + } + + /** Convert option color array to Color */ + private Color array2Color(JSONArray a, Color defaultColor) { + if (a != null) { + if (a.length() == 3) { + return new Color(a.getInt(0), a.getInt(1), a.getInt(2)); + } else if (a.length() == 4) { + return new Color(a.getInt(0), a.getInt(1), a.getInt(2), a.getInt(3)); + } + } + return defaultColor; + } + + private BufferedImage getImageByKey(String key, String defaultValue, String defaultImg) { + BufferedImage image = null; + String p = this.path + config.optString(key, defaultValue); + try { + image = ImageIO.read(new File(p)); + } catch (IOException e) { + try { + p = this.pathPrefix + uiConfig.optString(key, defaultValue); + image = ImageIO.read(new File(p)); + } catch (IOException e1) { + try { + image = ImageIO.read(getClass().getResourceAsStream("/assets/" + defaultImg)); + } catch (IOException e2) { + e2.printStackTrace(); + } + } + } + return image; + } + + private int getIntByKey(String key, int defaultValue) { + return config.optInt(key, uiConfig.optInt(key, defaultValue)); + } +}