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