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.menubar;
12  
13  import com.jsql.util.GitUtil.ShowOnConsole;
14  import com.jsql.util.I18nUtil;
15  import com.jsql.util.LogLevelUtil;
16  import com.jsql.view.swing.action.ActionSaveTab;
17  import com.jsql.view.swing.action.HotkeyUtil;
18  import com.jsql.view.swing.console.JTextPaneAppender;
19  import com.jsql.view.swing.dialog.DialogAbout;
20  import com.jsql.view.swing.dialog.DialogTranslate;
21  import com.jsql.view.swing.dialog.translate.Language;
22  import com.jsql.view.swing.panel.preferences.PanelExploit;
23  import com.jsql.view.swing.panel.preferences.PanelTampering;
24  import com.jsql.view.swing.sql.SqlEngine;
25  import com.jsql.view.swing.table.PanelTable;
26  import com.jsql.view.swing.text.JPopupTextArea;
27  import com.jsql.view.swing.text.JToolTipI18n;
28  import com.jsql.view.swing.util.I18nViewUtil;
29  import com.jsql.view.swing.util.MediatorHelper;
30  import com.jsql.view.swing.util.ModelSvgIcon;
31  import com.jsql.view.swing.util.UiUtil;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.logging.log4j.LogManager;
34  import org.apache.logging.log4j.Logger;
35  
36  import javax.swing.*;
37  import javax.swing.table.JTableHeader;
38  import javax.swing.table.TableColumnModel;
39  import javax.swing.text.JTextComponent;
40  import javax.swing.text.StyleConstants;
41  import java.awt.*;
42  import java.awt.event.InputEvent;
43  import java.awt.event.KeyEvent;
44  import java.awt.event.MouseAdapter;
45  import java.awt.event.MouseEvent;
46  import java.util.Arrays;
47  import java.util.List;
48  import java.util.Locale;
49  import java.util.stream.Stream;
50  
51  /**
52   * Application main menubar.
53   */
54  public class AppMenubar extends JMenuBar {
55  
56      private static final Logger LOGGER = LogManager.getRootLogger();
57  
58      public static final String MENU_COMMUNITY = "menuCommunity";
59      public static final String MENU_I18N_CONTRIBUTION = "menuI18nContribution";
60      public static final String ITEM_INTO_FRENCH = "itemIntoFrench";
61      public static final String MENU_HELP = "menuHelp";
62      public static final String ITEM_HELP = "itemHelp";
63  
64      private final MenuWindows menuWindows;
65  
66      protected static final List<ModelItemTranslate> ITEMS_TRANSLATE = Stream.of(
67          Language.EN, Language.FR, Language.ES, Language.IT, Language.AR, Language.ZH, Language.RU, Language.TR, Language.KO,
68          Language.SE, Language.FI, Language.CS, Language.PT, Language.PL, Language.ID, Language.NL, Language.RO, Language.DE
69      ).map(ModelItemTranslate::new).toList();
70  
71      private static final List<ModelItemTranslate> ITEMS_TRANSLATE_INTO = Stream.of(
72          Language.FR, Language.ES, Language.SE, Language.FI, Language.TR, Language.CS, Language.RO, Language.IT, Language.PT, Language.AR,
73          Language.PL, Language.RU, Language.ZH, Language.DE, Language.ID, Language.JA, Language.KO, Language.HI, Language.NL, Language.TA
74      ).map(ModelItemTranslate::new).toList();
75  
76      /**
77       * Create a menubar on main frame.
78       */
79      public AppMenubar() {
80          this.add(this.initMenuFile());
81          this.add(this.initMenuEdit());
82          this.add(this.initMenuCommunity());
83          this.menuWindows = new MenuWindows(this);
84          this.add(this.menuWindows);
85          this.add(this.initMenuHelp());
86      }
87  
88      private JMenu initMenuFile() {
89          var menuFile = new JMenu(I18nUtil.valueByKey("MENUBAR_FILE"));
90          I18nViewUtil.addComponentForKey("MENUBAR_FILE", menuFile);
91          menuFile.setMnemonic('F');
92  
93          JMenuItem itemSave = new JMenuItem(new ActionSaveTab());
94          I18nViewUtil.addComponentForKey("MENUBAR_FILE_SAVETABAS", itemSave);
95  
96          JMenuItem itemExit = new JMenuItem(I18nUtil.valueByKey("MENUBAR_FILE_EXIT"), 'x');
97          I18nViewUtil.addComponentForKey("MENUBAR_FILE_EXIT", itemExit);
98          itemExit.addActionListener(actionEvent -> MediatorHelper.frame().dispose());
99  
100         HotkeyUtil.addShortcut(this);
101 
102         menuFile.add(itemSave);
103         menuFile.add(new JSeparator());
104         menuFile.add(itemExit);
105         return menuFile;
106     }
107 
108     private JMenu initMenuEdit() {
109         var menuEdit = new JMenu(I18nUtil.valueByKey("MENUBAR_EDIT"));
110         I18nViewUtil.addComponentForKey("MENUBAR_EDIT", menuEdit);
111         menuEdit.setMnemonic('E');
112 
113         JMenuItem itemCopy = new JMenuItem(I18nUtil.valueByKey("CONTEXT_MENU_COPY"), 'C');
114         I18nViewUtil.addComponentForKey("CONTEXT_MENU_COPY", itemCopy);
115         itemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
116         itemCopy.addActionListener(actionEvent -> {
117             if (MediatorHelper.tabResults().getSelectedComponent() instanceof PanelTable panelTable) {
118                 panelTable.copyTable();
119             } else if (MediatorHelper.tabResults().getSelectedComponent() instanceof JScrollPane jScrollPane) {
120                 ((JTextComponent) jScrollPane.getViewport().getView()).copy();
121             }
122         });
123 
124         JMenuItem itemSelectAll = new JMenuItem(I18nUtil.valueByKey("CONTEXT_MENU_SELECT_ALL"), 'A');
125         I18nViewUtil.addComponentForKey("CONTEXT_MENU_SELECT_ALL", itemSelectAll);
126         itemSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK));
127         itemSelectAll.addActionListener(actionEvent -> {
128             if (MediatorHelper.tabResults().getSelectedComponent() instanceof PanelTable panelTable) {
129                 panelTable.selectTable();
130             } else if (MediatorHelper.tabResults().getSelectedComponent() instanceof JScrollPane jScrollPane) {
131                 // Textarea need focus to select all
132                 jScrollPane.getViewport().getView().requestFocusInWindow();
133                 ((JTextComponent) jScrollPane.getViewport().getView()).selectAll();
134             }
135         });
136 
137         menuEdit.add(itemCopy);
138         menuEdit.add(new JSeparator());
139         menuEdit.add(itemSelectAll);
140         return menuEdit;
141     }
142 
143     private JMenu initMenuCommunity() {
144         var menuCommunity = new JMenu(I18nUtil.valueByKey("MENUBAR_COMMUNITY"));
145         menuCommunity.setMnemonic('C');
146         menuCommunity.setName(AppMenubar.MENU_COMMUNITY);
147         I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY", menuCommunity);
148 
149         JMenu menuI18nContribution = this.initMenuI18nContribution();
150         menuI18nContribution.setName(AppMenubar.MENU_I18N_CONTRIBUTION);
151 
152         JMenuItem itemReportIssue = this.initItemReportIssue();
153         itemReportIssue.setName("itemReportIssue");
154 
155         menuCommunity.add(menuI18nContribution);
156         menuCommunity.add(new JSeparator());
157         menuCommunity.add(itemReportIssue);
158         return menuCommunity;
159     }
160 
161     private JMenu initMenuI18nContribution() {
162         var menuI18nContribution = new JMenu(I18nUtil.valueByKey("MENUBAR_COMMUNITY_HELPTRANSLATE"));
163         I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_HELPTRANSLATE", menuI18nContribution);
164 
165         final var dialogTranslate = new DialogTranslate();  // Render the About dialog behind the scene
166         AppMenubar.ITEMS_TRANSLATE_INTO.forEach(model -> {
167             model.setMenuItem(new JMenuItem(model.getLanguage().getMenuItemLabel(), model.getLanguage().getFlag()));
168             model.getMenuItem().addActionListener(new ActionTranslate(dialogTranslate, model.getLanguage()));
169             menuI18nContribution.add(model.getMenuItem());
170         });
171 
172         var itemIntoOther = new JMenuItem(I18nUtil.valueByKey("MENUBAR_COMMUNITY_ANOTHERLANGUAGE"));
173         I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_ANOTHERLANGUAGE", itemIntoOther);
174 
175         AppMenubar.ITEMS_TRANSLATE_INTO.stream().filter(model -> model.getLanguage() == Language.AR)
176         .forEach(modelItemTranslate -> modelItemTranslate.getMenuItem().setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT));
177         AppMenubar.ITEMS_TRANSLATE_INTO.stream().filter(model -> model.getLanguage() == Language.FR)
178         .forEach(modelItemTranslate -> modelItemTranslate.getMenuItem().setName(AppMenubar.ITEM_INTO_FRENCH));
179 
180         menuI18nContribution.add(new JSeparator());
181         menuI18nContribution.add(itemIntoOther);
182         itemIntoOther.addActionListener(new ActionTranslate(dialogTranslate, Language.OT));
183 
184         return menuI18nContribution;
185     }
186 
187     private JMenuItem initItemReportIssue() {
188         JMenuItem itemReportIssue = new JMenuItem(I18nUtil.valueByKey("MENUBAR_COMMUNITY_REPORTISSUE"), 'R');
189         I18nViewUtil.addComponentForKey("MENUBAR_COMMUNITY_REPORTISSUE", itemReportIssue);
190 
191         String reportBody = """
192             ## What's the expected behavior?
193             
194             ## What's the actual behavior?
195             
196             ## Any other detailed information on the Issue?
197             
198             ## Steps to reproduce the problem
199             
200               1. ...
201               2. ...
202             
203             ## [Community] Request for new feature
204             
205             """;
206         itemReportIssue.addActionListener(actionEvent -> {
207             var panel = new JPanel(new BorderLayout());
208             final JTextArea textarea = new JPopupTextArea(new JTextArea()).getProxy();
209             textarea.setFont(new Font(
210                 UiUtil.FONT_NAME_MONOSPACED,
211                 Font.PLAIN,
212                 UIManager.getDefaults().getFont("TextField.font").getSize()
213             ));
214             textarea.setText(reportBody);
215             panel.add(new JLabel("Describe your bug or issue:"), BorderLayout.NORTH);
216             panel.add(new JScrollPane(textarea));
217             panel.setPreferredSize(new Dimension(500, 350));
218             panel.setMinimumSize(new Dimension(500, 350));
219 
220             textarea.addMouseListener(new MouseAdapter() {
221                 @Override
222                 public void mousePressed(MouseEvent e) {
223                     super.mousePressed(e);
224                     textarea.requestFocusInWindow();
225                 }
226             });
227 
228             int result = JOptionPane.showOptionDialog(
229                 MediatorHelper.frame(),
230                 panel,
231                 "Report an issue or a bug",
232                 JOptionPane.OK_CANCEL_OPTION,
233                 JOptionPane.QUESTION_MESSAGE,
234                 null,
235                 new String[] {
236                     "Send",
237                     I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
238                 },
239                 I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
240             );
241 
242             if (StringUtils.isNotEmpty(textarea.getText()) && result == JOptionPane.YES_OPTION) {
243                 MediatorHelper.model().getMediatorUtils().gitUtil().sendReport(textarea.getText(), ShowOnConsole.YES, "Report");
244             }
245         });
246 
247         return itemReportIssue;
248     }
249 
250     private JMenu initMenuHelp() {
251         var menuHelp = new JMenu(I18nUtil.valueByKey("MENUBAR_HELP"));
252         menuHelp.setMnemonic('H');
253         I18nViewUtil.addComponentForKey("MENUBAR_HELP", menuHelp);
254         menuHelp.setName(AppMenubar.MENU_HELP);
255 
256         JMenuItem itemHelp = new JMenuItem(I18nUtil.valueByKey("MENUBAR_HELP_ABOUT"), 'A');
257         I18nViewUtil.addComponentForKey("MENUBAR_HELP_ABOUT", itemHelp);
258         itemHelp.setName(AppMenubar.ITEM_HELP);
259 
260         JMenuItem itemUpdate = new JMenuItem(I18nUtil.valueByKey("MENUBAR_HELP_UPDATE"), 'U');
261         I18nViewUtil.addComponentForKey("MENUBAR_HELP_UPDATE", itemUpdate);
262 
263         // Render the About dialog behind the scene
264         itemHelp.addActionListener(actionEvent -> {
265             final var dialogAbout = new DialogAbout();
266             if (!dialogAbout.isVisible()) {
267                 dialogAbout.initDialog();
268                 dialogAbout.setVisible(true);  // needed here for button focus
269                 dialogAbout.requestButtonFocus();
270             }
271             dialogAbout.setVisible(true);
272         });
273         itemUpdate.addActionListener(new ActionCheckUpdate());
274 
275         menuHelp.add(itemUpdate);
276         menuHelp.add(new JSeparator());
277         menuHelp.add(itemHelp);
278         return menuHelp;
279     }
280 
281     public static void applyTheme(String nameTheme) {
282         UiUtil.applyTheme(nameTheme);
283 
284         for (String key : I18nViewUtil.keys()) {
285             for (Object component : I18nViewUtil.componentsByKey(key)) {
286                 if (component instanceof JToolTipI18n jToolTipI18n) {
287                     jToolTipI18n.updateUI();  // required
288                 }
289             }
290         }
291 
292         Arrays.asList(
293             UiUtil.DATABASE_BOLD, UiUtil.ADMIN, UiUtil.DOWNLOAD, UiUtil.TERMINAL, UiUtil.UPLOAD, UiUtil.LOCK, UiUtil.TEXTFIELD, UiUtil.BATCH,
294             UiUtil.TABLE_LINEAR, UiUtil.TABLE_BOLD, UiUtil.NETWORK, UiUtil.DATABASE_LINEAR, UiUtil.COG, UiUtil.CUP, UiUtil.CONSOLE, UiUtil.BINARY, UiUtil.CHUNK,
295             UiUtil.ARROW, UiUtil.ARROW_HOVER, UiUtil.ARROW_PRESSED, UiUtil.ARROW_LEFT, UiUtil.ARROW_LEFT_HOVER, UiUtil.ARROW_LEFT_PRESSED,
296             UiUtil.EXPAND, UiUtil.EXPAND_HOVER, UiUtil.EXPAND_PRESSED,
297             UiUtil.HOURGLASS, UiUtil.ARROW_DOWN, UiUtil.ARROW_UP, UiUtil.SQUARE, UiUtil.GLOBE, UiUtil.TICK_GREEN, UiUtil.CROSS_RED,
298             UiUtil.APP_ICON, UiUtil.APP_BIG, UiUtil.APP_MIDDLE
299         ).forEach(ModelSvgIcon::setColorFilter);
300 
301         SqlEngine.applyTheme();
302         PanelTampering.applyTheme();
303         PanelExploit.applyTheme();
304         MediatorHelper.panelConsoles().getTabbedPaneNetworkTab().applyTheme();
305         MediatorHelper.frame().setIconImages(UiUtil.getIcons());
306         MediatorHelper.frame().revalidate();
307 
308         MediatorHelper.model().getMediatorUtils().preferencesUtil().withThemeFlatLafName(nameTheme).persist();
309     }
310     
311     public void switchLocale(Locale newLocale) {
312         Locale oldLocale = I18nUtil.getCurrentLocale();
313         I18nUtil.setCurrentBundle(newLocale);
314 
315         Stream.of(
316             JTextPaneAppender.ATTRIBUTE_WARN,
317             JTextPaneAppender.ATTRIBUTE_INFORM,
318             JTextPaneAppender.ATTRIBUTE_SUCCESS,
319             JTextPaneAppender.ATTRIBUTE_ALL
320         )
321         .forEach(attribute -> {
322             StyleConstants.setFontFamily(attribute, I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_NAME_MONO_ASIAN : UiUtil.FONT_NAME_MONO_NON_ASIAN);
323             StyleConstants.setFontSize(attribute, I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_SIZE_MONO_ASIAN : UiUtil.FONT_SIZE_MONO_NON_ASIAN);
324         });
325 
326         MediatorHelper.managerBruteForce().getResult().setFont(I18nViewUtil.isNonUbuntu(newLocale) ? UiUtil.FONT_MONO_ASIAN : UiUtil.FONT_MONO_NON_ASIAN);
327 
328         this.switchNetworkTable();
329         I18nViewUtil.switchI18nComponents();
330         this.switchOrientation(oldLocale, newLocale);
331         this.switchMenuItems();  // required to restore proper language orientation
332         
333         MediatorHelper.treeDatabase().reloadNodes();
334         MediatorHelper.panelAddressBar().getPanelTrailingAddress().buttonStart.setIcons();
335         int textPosition = ComponentOrientation.getOrientation(newLocale).isLeftToRight()  // leading/trailing not working
336             ? SwingConstants.LEFT
337             : SwingConstants.RIGHT;
338         MediatorHelper.panelAddressBar().getAtomicRadioRequest().setHorizontalTextPosition(textPosition);  // component orientation not working
339         MediatorHelper.panelAddressBar().getAtomicRadioMethod().setHorizontalTextPosition(textPosition);
340         MediatorHelper.panelAddressBar().getAtomicRadioHeader().setHorizontalTextPosition(textPosition);
341 
342         // Fix #92981: IllegalArgumentException on revalidate()
343         // Fix #96185: NullPointerException on revalidate()
344         // Fix #96226: ArrayIndexOutOfBoundsException on revalidate()
345         try {
346             MediatorHelper.frame().revalidate();  // Fix glitches on Linux
347         } catch (NullPointerException | IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
348             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
349         }
350     }
351 
352     private void switchOrientation(Locale oldLocale, Locale newLocale) {
353         var componentOrientation = ComponentOrientation.getOrientation(I18nUtil.getCurrentLocale());
354         MediatorHelper.frame().applyComponentOrientation(componentOrientation);
355         
356         if (!ComponentOrientation.getOrientation(oldLocale).equals(ComponentOrientation.getOrientation(newLocale))) {
357             // not rendered at startup, only on switch, unreliable components width can be 0 at startup
358             // use event windowOpen instead when required
359             AppMenubar.reverse(MediatorHelper.frame().getSplitNS().getSplitEW());
360             AppMenubar.reverse(MediatorHelper.panelConsoles().getNetworkSplitPane());
361         }
362         
363         MediatorHelper.tabResults().setComponentOrientation(ComponentOrientation.getOrientation(newLocale));
364     }
365 
366     private static void reverse(JSplitPane splitPane) {
367         var componentLeft = splitPane.getLeftComponent();
368         var componentRight = splitPane.getRightComponent();
369 
370         // Reset components
371         splitPane.setLeftComponent(null);
372         splitPane.setRightComponent(null);
373         splitPane.setLeftComponent(componentRight);
374         splitPane.setRightComponent(componentLeft);
375 
376         // required as switch to arabic uses reversed location
377         splitPane.setDividerLocation(splitPane.getWidth() - splitPane.getDividerLocation());
378     }
379 
380     private void switchMenuItems() {
381         Stream.concat(AppMenubar.ITEMS_TRANSLATE.stream(), AppMenubar.ITEMS_TRANSLATE_INTO.stream())
382         .forEach(model -> model.getMenuItem().setComponentOrientation(
383             model.getLanguage().isRightToLeft()
384             ? ComponentOrientation.RIGHT_TO_LEFT
385             : ComponentOrientation.LEFT_TO_RIGHT
386         ));
387     }
388 
389     private void switchNetworkTable() {
390         JTableHeader header = MediatorHelper.panelConsoles().getNetworkTable().getTableHeader();
391         TableColumnModel columnModel = header.getColumnModel();
392         columnModel.getColumn(0).setHeaderValue(I18nUtil.valueByKey("NETWORK_TAB_URL_COLUMN"));
393         columnModel.getColumn(1).setHeaderValue(I18nUtil.valueByKey("NETWORK_TAB_SIZE_COLUMN") +" (KB)");
394         columnModel.getColumn(2).setHeaderValue(I18nUtil.valueByKey("SQLENGINE_STRATEGY"));
395         columnModel.getColumn(3).setHeaderValue(I18nUtil.valueByKey("SQLENGINE_METADATA"));
396         header.repaint();
397     }
398     
399     
400     // Getter and setter
401 
402     public JMenu getMenuView() {
403         return this.menuWindows.getMenuView();
404     }
405 
406     public MenuWindows getMenuWindows() {
407         return this.menuWindows;
408     }
409 }