From 88bda6eb4e0340ed50b9534694325e1cb4cab43d Mon Sep 17 00:00:00 2001 From: Joe Wegner Date: Wed, 21 Aug 2019 10:21:29 +0200 Subject: [PATCH 1/3] Add Accessibility checkbox on Preferences panel --- app/src/cc/arduino/view/preferences/Preferences.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java index 736ae826c..3b8f7f74a 100644 --- a/app/src/cc/arduino/view/preferences/Preferences.java +++ b/app/src/cc/arduino/view/preferences/Preferences.java @@ -134,6 +134,7 @@ public class Preferences extends javax.swing.JDialog { externalEditorBox = new javax.swing.JCheckBox(); checkUpdatesBox = new javax.swing.JCheckBox(); saveVerifyUploadBox = new javax.swing.JCheckBox(); + accessibleIDEBox = new javax.swing.JCheckBox(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); scaleSpinner = new javax.swing.JSpinner(); @@ -281,6 +282,9 @@ public class Preferences extends javax.swing.JDialog { saveVerifyUploadBox.setText(tr("Save when verifying or uploading")); checkboxesContainer.add(saveVerifyUploadBox); + accessibleIDEBox.setText(tr("Use accessibility features")); + checkboxesContainer.add(accessibleIDEBox); + jLabel1.setText(tr("Interface scale:")); jLabel2.setText(tr(" (requires restart of Arduino)")); @@ -713,6 +717,7 @@ public class Preferences extends javax.swing.JDialog { private javax.swing.JCheckBox autoScaleCheckBox; private javax.swing.JButton browseButton; private javax.swing.JCheckBox checkUpdatesBox; + private javax.swing.JCheckBox accessibleIDEBox; private javax.swing.JPanel checkboxesContainer; private javax.swing.JComboBox comboLanguage; private javax.swing.JLabel comboLanguageLabel; @@ -828,6 +833,8 @@ public class Preferences extends javax.swing.JDialog { PreferencesData.setBoolean("editor.save_on_verify", saveVerifyUploadBox.isSelected()); + PreferencesData.setBoolean("ide.accessible", accessibleIDEBox.isSelected()); + PreferencesData.set("boardsmanager.additional.urls", additionalBoardsManagerField.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", ",")); PreferencesData.set(Constants.PREF_PROXY_TYPE, proxyTypeButtonGroup.getSelection().getActionCommand()); @@ -902,6 +909,8 @@ public class Preferences extends javax.swing.JDialog { PreferencesData.setBoolean("editor.update_extension", true); } + accessibleIDEBox.setSelected(PreferencesData.getBoolean("ide.accessible")); + saveVerifyUploadBox.setSelected(PreferencesData.getBoolean("editor.save_on_verify")); additionalBoardsManagerField.setText(PreferencesData.get("boardsmanager.additional.urls")); From 2b4c4b57d8a82a40fd509a30a32a7f571cb316ad Mon Sep 17 00:00:00 2001 From: Joe Wegner Date: Wed, 21 Aug 2019 10:22:29 +0200 Subject: [PATCH 2/3] Make UpdateNotification popup accessible When accessible use buttons instead of links in in Updates Available dialog Handle buttons and prevent auto-close for accessible dialog box --- .../contributions/ContributionsSelfCheck.java | 74 ++++++++-- .../cc/arduino/view/NotificationPopup.java | 129 ++++++++++++++++-- 2 files changed, 184 insertions(+), 19 deletions(-) diff --git a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java index 7812f62ad..687323d18 100644 --- a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java +++ b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java @@ -29,32 +29,34 @@ package cc.arduino.contributions; +import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; import cc.arduino.contributions.libraries.LibraryInstaller; import cc.arduino.contributions.libraries.filters.UpdatableLibraryPredicate; import cc.arduino.contributions.packages.ContributionInstaller; import cc.arduino.contributions.packages.filters.UpdatablePlatformPredicate; import cc.arduino.view.NotificationPopup; -import processing.app.Base; -import processing.app.BaseNoGui; -import processing.app.Editor; -import processing.app.I18n; +import org.apache.logging.log4j.LogManager; +import processing.app.*; import javax.swing.*; import javax.swing.event.HyperlinkListener; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; +import java.net.URL; import java.util.TimerTask; import static processing.app.I18n.tr; -public class ContributionsSelfCheck extends TimerTask { +public class ContributionsSelfCheck extends TimerTask implements NotificationPopup.OptionalButtonCallbacks { private final Base base; private final HyperlinkListener hyperlinkListener; private final ContributionInstaller contributionInstaller; private final LibraryInstaller libraryInstaller; private final ProgressListener progressListener; + private final String boardsManagerURL = "http://boardsmanager/DropdownUpdatableCoresItem"; + private final String libraryManagerURL = "http://librarymanager/DropdownUpdatableLibrariesItem"; private volatile boolean cancelled; private volatile NotificationPopup notificationPopup; @@ -81,13 +83,41 @@ public class ContributionsSelfCheck extends TimerTask { return; } - String text; + boolean setAccessible = PreferencesData.getBoolean("ide.accessible"); + final String text; + final String button1Name; + final String button2Name; + String openAnchorBoards = ""; + String closeAnchorBoards = ""; + String openAnchorLibraries = ""; + String closeAnchorLibraries = ""; + + // if accessibility mode and board updates are available set the button name and clear the anchors + if(setAccessible && updatablePlatforms) { + button1Name = tr("Boards"); + openAnchorBoards = ""; + closeAnchorBoards = ""; + } + else { // when not accessibility mode or no boards to update no button is needed + button1Name = null; + } + + // if accessibility mode and libraries updates are available set the button name and clear the anchors + if (setAccessible && updatableLibraries) { + button2Name = tr("Libraries"); + openAnchorLibraries = ""; + closeAnchorLibraries = ""; + } + else { // when not accessibility mode or no libraries to update no button is needed + button2Name = null; + } + if (updatableLibraries && !updatablePlatforms) { - text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), "", ""); + text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), openAnchorLibraries, closeAnchorLibraries); } else if (!updatableLibraries && updatablePlatforms) { - text = I18n.format(tr("Updates available for some of your {0}boards{1}"), "", ""); + text = I18n.format(tr("Updates available for some of your {0}boards{1}"), openAnchorBoards, closeAnchorBoards); } else { - text = I18n.format(tr("Updates available for some of your {0}boards{1} and {2}libraries{3}"), "", "", "", ""); + text = I18n.format(tr("Updates available for some of your {0}libraries{1} and {2}libraries{3}"), openAnchorBoards, closeAnchorBoards, openAnchorLibraries, closeAnchorLibraries); } if (cancelled) { @@ -96,7 +126,13 @@ public class ContributionsSelfCheck extends TimerTask { SwingUtilities.invokeLater(() -> { Editor ed = base.getActiveEditor(); - notificationPopup = new NotificationPopup(ed, hyperlinkListener, text); + boolean accessibleIde = PreferencesData.getBoolean("ide.accessible"); + if (accessibleIde) { + notificationPopup = new NotificationPopup(ed, hyperlinkListener, text, false, this, button1Name, button2Name); + } + else { // if not accessible view leave it the same + notificationPopup = new NotificationPopup(ed, hyperlinkListener, text); + } if (ed.isFocused()) { notificationPopup.begin(); return; @@ -122,6 +158,24 @@ public class ContributionsSelfCheck extends TimerTask { }); } + private void goToManager(String link) { + try { + ((UpdatableBoardsLibsFakeURLsHandler) hyperlinkListener).openBoardLibManager(new URL(link)); + } + catch (Exception e){ + LogManager.getLogger(ContributionsSelfCheck.class).warn("Exception while attempting to go to board manager", e); + } + } + // callback for boards button + public void onOptionalButton1Callback() { + goToManager(boardsManagerURL); + } + + // callback for libraries button + public void onOptionalButton2Callback() { + goToManager(libraryManagerURL); + } + static boolean checkForUpdatablePlatforms() { return BaseNoGui.indexer.getPackages().stream() .flatMap(pack -> pack.getPlatforms().stream()) diff --git a/app/src/cc/arduino/view/NotificationPopup.java b/app/src/cc/arduino/view/NotificationPopup.java index 2334d6e14..69fb3ff9e 100644 --- a/app/src/cc/arduino/view/NotificationPopup.java +++ b/app/src/cc/arduino/view/NotificationPopup.java @@ -36,12 +36,7 @@ import java.awt.FlowLayout; import java.awt.Frame; import java.awt.Image; import java.awt.Point; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; +import java.awt.event.*; import java.util.Timer; import java.util.TimerTask; @@ -55,22 +50,46 @@ import javax.swing.border.LineBorder; import javax.swing.event.HyperlinkListener; import cc.arduino.Constants; +import processing.app.PreferencesData; import processing.app.Theme; -public class NotificationPopup extends JDialog { +import java.awt.event.KeyEvent; +import static processing.app.I18n.tr; + +public class NotificationPopup extends JDialog { private Timer autoCloseTimer = new Timer(false); private boolean autoClose = true; + private OptionalButtonCallbacks optionalButtonCallbacks; + + public interface OptionalButtonCallbacks { + void onOptionalButton1Callback(); + void onOptionalButton2Callback(); + } public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener, String message) { - this(parent, hyperlinkListener, message, true); + this(parent, hyperlinkListener, message, true, null, null, null); } public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener, String message, boolean _autoClose) { + this(parent, hyperlinkListener, message, _autoClose, null, null, null); + } + + public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener, + String message, boolean _autoClose, OptionalButtonCallbacks listener, String button1Name, String button2Name) { super(parent, false); - autoClose = _autoClose; + + if (!PreferencesData.getBoolean("ide.accessible")) { + // often auto-close is too fast for users of screen readers, so don't allow it. + autoClose = _autoClose; + } + else { + autoClose = false; + } + optionalButtonCallbacks = listener; + setLayout(new FlowLayout()); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setUndecorated(true); @@ -90,6 +109,74 @@ public class NotificationPopup extends JDialog { text.addHyperlinkListener(hyperlinkListener); add(text); + if (button1Name != null) { + JButton optionalButton1 = new JButton(tr(button1Name)); + MouseAdapter button1Action = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (optionalButtonCallbacks != null) { + optionalButtonCallbacks.onOptionalButton1Callback(); + } + } + }; + optionalButton1.addMouseListener(button1Action); + + KeyListener button1Key = new KeyListener() { + // Ignore when the key is typed - only act once the key is released + public void keyTyped(KeyEvent e) { + // do nothing here, wait until the key is released + } + + // Ignore when the key is pressed - only act once the key is released + public void keyPressed(KeyEvent e) { + // do nothing here, wait until the key is released + } + + public void keyReleased(KeyEvent e) { + int key = e.getKeyCode(); + if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) { + optionalButtonCallbacks.onOptionalButton1Callback(); + } + } + }; + optionalButton1.addKeyListener(button1Key); + add(optionalButton1); + } + + if (button2Name != null) { + JButton optionalButton2 = new JButton(tr(button2Name)); + MouseAdapter button2Action = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (optionalButtonCallbacks != null) { + optionalButtonCallbacks.onOptionalButton2Callback(); + } + } + }; + optionalButton2.addMouseListener(button2Action); + + KeyListener button2Key = new KeyListener() { + // Ignore when the key is typed - only act once the key is released + public void keyTyped(KeyEvent e) { + // do nothing here, wait until the key is released + } + + // Ignore when the key is pressed - only act once the key is released + public void keyPressed(KeyEvent e) { + // do nothing here, wait until the key is released + } + + public void keyReleased(KeyEvent e) { + int key = e.getKeyCode(); + if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) { + optionalButtonCallbacks.onOptionalButton2Callback(); + } + } + }; + optionalButton2.addKeyListener(button2Key); + add(optionalButton2); + } + Image close = Theme.getThemeImage("close", this, scale(22), scale(22)); JButton closeButton = new JButton(new ImageIcon(close)); closeButton.setBorder(null); @@ -97,6 +184,26 @@ public class NotificationPopup extends JDialog { closeButton.setHideActionText(true); closeButton.setOpaque(false); closeButton.setBackground(new Color(0, 0, 0, 0)); + closeButton.getAccessibleContext().setAccessibleDescription(tr("Close")); + KeyListener closeKey = new KeyListener() { + // Ignore when the key is typed - only act once the key is released + public void keyTyped(KeyEvent e) { + // do nothing here, wait until the key is released + } + + // Ignore when the key is pressed - only act once the key is released + public void keyPressed(KeyEvent e) { + // do nothing here, wait until the key is released + } + + public void keyReleased(KeyEvent e) { + int key = e.getKeyCode(); + if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) { + close(); + } + } + }; + closeButton.addKeyListener(closeKey); add(closeButton); MouseAdapter closeOnClick = new MouseAdapter() { @@ -158,5 +265,9 @@ public class NotificationPopup extends JDialog { }, Constants.NOTIFICATION_POPUP_AUTOCLOSE_DELAY); } setVisible(true); + if (PreferencesData.getBoolean("ide.accessible")) { + requestFocus(); + setModal(true); + } } } From 710667d15ba8964168762a8a49e689e254e42adc Mon Sep 17 00:00:00 2001 From: Joe Wegner Date: Wed, 21 Aug 2019 10:26:41 +0200 Subject: [PATCH 3/3] Remove duplicate code in Preferences --- app/src/cc/arduino/view/preferences/Preferences.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java index 3b8f7f74a..8711ac8b5 100644 --- a/app/src/cc/arduino/view/preferences/Preferences.java +++ b/app/src/cc/arduino/view/preferences/Preferences.java @@ -831,8 +831,6 @@ public class Preferences extends javax.swing.JDialog { PreferencesData.setBoolean("update.check", checkUpdatesBox.isSelected()); - PreferencesData.setBoolean("editor.save_on_verify", saveVerifyUploadBox.isSelected()); - PreferencesData.setBoolean("ide.accessible", accessibleIDEBox.isSelected()); PreferencesData.set("boardsmanager.additional.urls", additionalBoardsManagerField.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", ","));