View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing.util;
12  
13  import com.formdev.flatlaf.FlatLaf;
14  import com.formdev.flatlaf.intellijthemes.FlatLightFlatIJTheme;
15  import com.jsql.util.I18nUtil;
16  import com.jsql.util.LogLevelUtil;
17  import com.jsql.view.swing.console.JTextPaneAppender;
18  import com.jsql.view.swing.sql.SqlEngine;
19  import com.jsql.view.swing.text.action.DeleteNextCharAction;
20  import com.jsql.view.swing.text.action.DeletePrevCharAction;
21  import org.apache.commons.lang3.StringUtils;
22  import org.apache.logging.log4j.LogManager;
23  import org.apache.logging.log4j.Logger;
24  import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
25  import org.fife.ui.rsyntaxtextarea.Theme;
26  
27  import javax.swing.*;
28  import javax.swing.border.Border;
29  import javax.swing.text.DefaultCaret;
30  import javax.swing.text.DefaultEditorKit;
31  import javax.swing.text.JTextComponent;
32  import javax.swing.text.StyleConstants;
33  import java.awt.*;
34  import java.io.BufferedInputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Objects;
40  
41  /**
42   * Build default component appearance, keyboard shortcuts and icons.
43   */
44  public class UiUtil {
45      
46      private static final Logger LOGGER = LogManager.getRootLogger();
47  
48      public static final Border BORDER_5PX = BorderFactory.createEmptyBorder(5, 5, 5, 5);
49  
50      public static final ImageIcon ICON_FLAG_AR = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/ar.png")));
51      public static final ImageIcon ICON_FLAG_ZH = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/zh.png")));
52      public static final ImageIcon ICON_FLAG_RU = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/ru.png")));
53      public static final ImageIcon ICON_FLAG_TR = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/tr.png")));
54      public static final ImageIcon ICON_FLAG_EN = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/en.png")));
55      public static final ImageIcon ICON_FLAG_FR = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/fr.png")));
56      public static final ImageIcon ICON_FLAG_HI = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/hi.png")));
57      public static final ImageIcon ICON_FLAG_CS = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/cs.png")));
58      public static final ImageIcon ICON_FLAG_DE = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/de.png")));
59      public static final ImageIcon ICON_FLAG_NL = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/nl.png")));
60      public static final ImageIcon ICON_FLAG_ID = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/id.png")));
61      public static final ImageIcon ICON_FLAG_IT = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/it.png")));
62      public static final ImageIcon ICON_FLAG_ES = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/es.png")));
63      public static final ImageIcon ICON_FLAG_PT = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/pt.png")));
64      public static final ImageIcon ICON_FLAG_PL = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/pl.png")));
65      public static final ImageIcon ICON_FLAG_JA = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/ja.png")));
66      public static final ImageIcon ICON_FLAG_KO = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/ko.png")));
67      public static final ImageIcon ICON_FLAG_RO = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/ro.png")));
68      public static final ImageIcon ICON_FLAG_LK = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/lk.png")));
69      public static final ImageIcon ICON_FLAG_SE = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/se.png")));
70      public static final ImageIcon ICON_FLAG_FI = new ImageIcon(Objects.requireNonNull(UiUtil.class.getClassLoader().getResource("swing/images/flags/fi.png")));
71  
72      public static final ModelSvgIcon DATABASE_BOLD = new ModelSvgIcon("database-bold", 0x1C274C)
73          .withTab("DATABASE_TAB", "DATABASE_TOOLTIP");
74      public static final ModelSvgIcon ADMIN = new ModelSvgIcon("admin", 0.02f)
75          .withTab("ADMINPAGE_TAB", "ADMINPAGE_TOOLTIP");
76      public static final ModelSvgIcon DOWNLOAD = new ModelSvgIcon("download", 0.55f)
77          .withTab("FILE_TAB", "FILE_TOOLTIP");
78      public static final ModelSvgIcon TERMINAL = new ModelSvgIcon("terminal", 0.50f)
79          .withTab("EXPLOIT_TAB", "EXPLOIT_TOOLTIP");
80      public static final ModelSvgIcon UPLOAD = new ModelSvgIcon("upload", 0.55f);
81      public static final ModelSvgIcon LOCK = new ModelSvgIcon("lock", 0.02f)
82          .withTab("BRUTEFORCE_TAB", "BRUTEFORCE_TOOLTIP");
83      public static final ModelSvgIcon TEXTFIELD = new ModelSvgIcon("textfield", 0.02f)
84          .withTab("CODER_TAB", "CODER_TOOLTIP");
85      public static final ModelSvgIcon BATCH = new ModelSvgIcon("batch", 0.02f)
86          .withTab("SCANLIST_TAB", "SCANLIST_TOOLTIP");
87  
88      public static final ModelSvgIcon TABLE_LINEAR = new ModelSvgIcon("table-linear", 0x212121);
89      public static final ModelSvgIcon TABLE_BOLD = new ModelSvgIcon("table-bold", 0x212121);
90      public static final ModelSvgIcon NETWORK = new ModelSvgIcon("network", 0.02f);
91      public static final ModelSvgIcon DATABASE_LINEAR = new ModelSvgIcon("database-linear", 0x1C274C);
92      public static final ModelSvgIcon CUP = new ModelSvgIcon("cup", 0.02f);
93      public static final ModelSvgIcon CONSOLE = new ModelSvgIcon("console", 0.02f);
94      public static final ModelSvgIcon BINARY = new ModelSvgIcon("binary", 0.02f);
95      public static final ModelSvgIcon CHUNK = new ModelSvgIcon("chunk", 0.02f);
96      public static final ModelSvgIcon COG = new ModelSvgIcon("cog", 0.02f);
97  
98      public static final ModelSvgIcon CROSS_RED = new ModelSvgIcon("cross", new Color(0x0F0F0F), null, LogLevelUtil.COLOR_RED, 0.025f);
99      private static final String NAME_ARROW = "arrow";
100     private static final String NAME_ARROW_LEFT = "arrow-left";
101     public static final String COMBOBOX_BUTTON_ARROW_COLOR = "ComboBox.buttonArrowColor";
102     public static final ModelSvgIcon ARROW = new ModelSvgIcon(UiUtil.NAME_ARROW, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_ARROW_COLOR, 1f);
103     public static final String COMBOBOX_BUTTON_HOVER_ARROW_COLOR = "ComboBox.buttonHoverArrowColor";
104     public static final ModelSvgIcon ARROW_HOVER = new ModelSvgIcon(UiUtil.NAME_ARROW, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_HOVER_ARROW_COLOR, 1f);
105     public static final String COMBOBOX_BUTTON_PRESSED_ARROW_COLOR = "ComboBox.buttonPressedArrowColor";
106     public static final ModelSvgIcon ARROW_PRESSED = new ModelSvgIcon(UiUtil.NAME_ARROW, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_PRESSED_ARROW_COLOR, 1f);
107     public static final ModelSvgIcon ARROW_LEFT = new ModelSvgIcon(UiUtil.NAME_ARROW_LEFT, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_ARROW_COLOR, 1f);
108     public static final ModelSvgIcon ARROW_LEFT_HOVER = new ModelSvgIcon(UiUtil.NAME_ARROW_LEFT, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_HOVER_ARROW_COLOR, 1f);
109     public static final ModelSvgIcon ARROW_LEFT_PRESSED = new ModelSvgIcon(UiUtil.NAME_ARROW_LEFT, new Color(0x005a96), UiUtil.COMBOBOX_BUTTON_PRESSED_ARROW_COLOR, 1f);
110     private static final String NAME_EXPAND = "expand";
111     public static final ModelSvgIcon EXPAND = new ModelSvgIcon(UiUtil.NAME_EXPAND, Color.BLACK, UiUtil.COMBOBOX_BUTTON_ARROW_COLOR, 0.02f);
112     public static final ModelSvgIcon EXPAND_HOVER = new ModelSvgIcon(UiUtil.NAME_EXPAND, Color.BLACK, UiUtil.COMBOBOX_BUTTON_HOVER_ARROW_COLOR, 0.02f);
113     public static final ModelSvgIcon EXPAND_PRESSED = new ModelSvgIcon(UiUtil.NAME_EXPAND, Color.BLACK, UiUtil.COMBOBOX_BUTTON_PRESSED_ARROW_COLOR, 0.02f);
114 
115     public static final ModelSvgIcon TARGET = new ModelSvgIcon("target", 0.02f);
116     public static final ModelSvgIcon HOURGLASS = new ModelSvgIcon("hourglass", 0.02f);
117     public static final ModelSvgIcon ARROW_UP = new ModelSvgIcon("arrow-up", 0.02f);
118     public static final ModelSvgIcon ARROW_DOWN = new ModelSvgIcon("arrow-down", 0.02f);
119     public static final ModelSvgIcon SQUARE = new ModelSvgIcon("square", 0.01f);
120     public static final ModelSvgIcon TICK_GREEN = new ModelSvgIcon("tick", Color.BLACK, null, LogLevelUtil.COLOR_GREEN, 0.02f);
121     public static final ModelSvgIcon GLOBE = new ModelSvgIcon("globe", 0.025f);
122     public static final ModelSvgIcon APP_ICON = new ModelSvgIcon("app", 0.04f);
123     public static final ModelSvgIcon APP_BIG = new ModelSvgIcon("app", 0.5f);
124     public static final ModelSvgIcon APP_MIDDLE = new ModelSvgIcon("app", 0.25f);
125 
126     public static final String PATH_PAUSE = "swing/images/icons/pause.png";
127 
128     public static final String FONT_NAME_MONO_NON_ASIAN = "Ubuntu Mono";
129     public static final int FONT_SIZE_MONO_NON_ASIAN = 14;
130     public static final String FONT_NAME_MONO_ASIAN = "Monospace";
131     public static final int FONT_SIZE_MONO_ASIAN = 13;
132     
133     // Used in Translation Dialog
134     // HTML engine considers Monospaced/Monospace to be the same Font
135     // Java engine recognizes only Monospaced
136     public static final String FONT_NAME_MONOSPACED = "Monospaced";
137     public static final String TEXTAREA_FONT = "TextArea.font";
138     public static final String TEXTPANE_FONT = "TextPane.font";
139     public static final Font FONT_MONO_NON_ASIAN = new Font(
140         UiUtil.FONT_NAME_MONO_NON_ASIAN,
141         Font.PLAIN,
142         UIManager.getDefaults().getFont(UiUtil.TEXTAREA_FONT).getSize() + 2
143     );
144     
145     public static final Font FONT_MONO_ASIAN = new Font(
146         UiUtil.FONT_NAME_MONO_ASIAN,
147         Font.PLAIN,
148         UIManager.getDefaults().getFont(UiUtil.TEXTPANE_FONT).getSize()
149     );
150     
151     public static final Font FONT_MONO_ASIAN_BIG = new Font(
152         UiUtil.FONT_NAME_MONO_ASIAN,
153         Font.PLAIN,
154         UIManager.getDefaults().getFont(UiUtil.TEXTPANE_FONT).getSize() + 2
155     );
156 
157     public static final Font FONT_NON_MONO = new Font(
158         "Segoe UI",
159         Font.PLAIN,
160         UIManager.getDefaults().getFont(UiUtil.TEXTPANE_FONT).getSize()
161     );
162     
163     public static final Font FONT_NON_MONO_BIG = new Font(
164         UIManager.getDefaults().getFont("TextField.font").getName(),
165         Font.PLAIN,
166         UIManager.getDefaults().getFont("TextField.font").getSize() + 2
167     );
168 
169     private UiUtil() {
170         // Utility class
171     }
172 
173     /**
174      * Change the default style of various components.
175      */
176     public static void prepareGUI() {
177         UiUtil.loadFonts();
178         // timer before closing automatically tooltip
179         ToolTipManager.sharedInstance().setDismissDelay(3 * ToolTipManager.sharedInstance().getDismissDelay());
180         UIManager.put(UiUtil.TEXTAREA_FONT, UiUtil.FONT_MONO_NON_ASIAN);  // required for basic text like chunks tab
181         UIManager.put(UiUtil.TEXTPANE_FONT, UIManager.getFont(UiUtil.TEXTAREA_FONT));  // align textpane font
182     }
183 
184     private static void loadFonts() {
185         var graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
186         try (InputStream fontStream = new BufferedInputStream(
187             Objects.requireNonNull(UiUtil.class.getClassLoader().getResourceAsStream("swing/font/UbuntuMono-R-ctrlchar.ttf"))
188         )) {
189             var ubuntuFont = Font.createFont(Font.TRUETYPE_FONT, fontStream);
190             graphicsEnvironment.registerFont(ubuntuFont);
191         } catch (FontFormatException | IOException e) {
192             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Loading Font Ubuntu Mono with control characters failed", e);
193         }
194     }
195 
196     /**
197      * Icons for application window.
198      * @return List of a 16x16 (default) and 32x32 icon (alt-tab, taskbar)
199      */
200     public static List<Image> getIcons() {
201         List<Image> images = new ArrayList<>();
202         // Fix #2154: NoClassDefFoundError on read()
203         try {
204             images.add(UiUtil.APP_ICON.getIcon().getImage());
205             images.add(UiUtil.APP_MIDDLE.getIcon().getImage());
206             images.add(UiUtil.APP_BIG.getIcon().getImage());
207         } catch (NoClassDefFoundError e) {
208             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
209         }
210         return images;
211     }
212     
213     public static void drawPlaceholder(JTextComponent textComponent, Graphics g, String placeholderText) {
214         UiUtil.drawPlaceholder(textComponent, g, placeholderText, 0, g.getFontMetrics().getAscent() + 2);
215     }
216     
217     public static void drawPlaceholder(JTextComponent textComponent, Graphics g, String placeholderText, int x, int y) {
218         int w = textComponent.getWidth();
219         
220         ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
221 
222         var insets = textComponent.getInsets();
223         var fontMetrics = g.getFontMetrics();
224         
225         int c0 = UIManager.getColor("TextArea.background").getRGB();
226         int c1 = UIManager.getColor("TextArea.foreground").getRGB();
227         var m = 0xfefefefe;
228         int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1);
229         
230         g.setColor(new Color(c2, true));
231 
232         var fontNonUbuntu = UiUtil.FONT_NON_MONO_BIG.equals(textComponent.getFont())  // when address bar
233             ? UiUtil.FONT_MONO_ASIAN_BIG.deriveFont(Font.ITALIC)  // bigger font
234             : UiUtil.FONT_MONO_ASIAN.deriveFont(Font.ITALIC);  // fine for address bar, console, textfield
235         g.setFont(
236             I18nViewUtil.isNonUbuntu(I18nUtil.getCurrentLocale())
237             || !ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale()).isLeftToRight()
238             ? fontNonUbuntu
239             : textComponent.getFont().deriveFont(Font.ITALIC)  // same
240         );
241 
242         g.drawString(placeholderText,  (
243             textComponent.getComponentOrientation().isLeftToRight()
244             ? x + insets.left + 2
245             : w - (fontMetrics.stringWidth(placeholderText) + insets.left + 2 + 2*x)  // 2*x magic number for ar address bar margin
246         ), y);
247     }
248     
249     public static void init(JTextComponent component) {
250         component.setCaret(new DefaultCaret() {
251             @Override
252             public void setSelectionVisible(boolean visible) {
253                 super.setSelectionVisible(true);
254             }
255         });
256         component.getActionMap().put(DefaultEditorKit.deletePrevCharAction, new DeletePrevCharAction());
257         component.getActionMap().put(DefaultEditorKit.deleteNextCharAction, new DeleteNextCharAction());
258     }
259 
260     public static void applySyntaxTheme(RSyntaxTextArea textArea) {
261         try {
262             boolean isDark = UIManager.getLookAndFeel().getName().matches(".*(Dark|High Contrast).*");
263             var xmlTheme = String.format("/org/fife/ui/rsyntaxtextarea/themes/%s.xml", isDark ? "dark" : "default");
264             Theme theme = Theme.load(SqlEngine.class.getResourceAsStream(xmlTheme));
265             theme.apply(textArea);
266         } catch (IOException e) {
267             throw new IllegalArgumentException(e);
268         }
269     }
270 
271     public static void applyTheme(String nameTheme) {
272         try {
273             UIManager.setLookAndFeel(StringUtils.isBlank(nameTheme) ? FlatLightFlatIJTheme.class.getName() : nameTheme);
274         } catch (ClassNotFoundException e) {  // when preference is set with a theme name deleted
275             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Theme not found, falling back to default theme");
276             try {
277                 UIManager.setLookAndFeel(FlatLightFlatIJTheme.class.getName());
278             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e2) {
279                 throw new IllegalArgumentException(e);
280             }
281         } catch (InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
282             throw new IllegalArgumentException(e);
283         }
284         FlatLaf.updateUI();  // required
285 
286         // required ATTRIBUTE_ALL without color for compatibility with dark/light mode as text is white/black
287         StyleConstants.setForeground(JTextPaneAppender.ATTRIBUTE_WARN, LogLevelUtil.COLOR_RED);
288         StyleConstants.setForeground(JTextPaneAppender.ATTRIBUTE_INFORM, LogLevelUtil.COLOR_BLU);
289         StyleConstants.setForeground(JTextPaneAppender.ATTRIBUTE_SUCCESS, LogLevelUtil.COLOR_GREEN);
290     }
291 
292     public static GridLayout getColumnLayout(int size) {
293         return new GridLayout((size + 1) / 2, 2);
294     }
295 }