Skip to content

Commit

Permalink
Make UpdateNotification popup accessible
Browse files Browse the repository at this point in the history
When accessible use buttons instead of links in in Updates Available dialog
Handle buttons and prevent auto-close for accessible dialog box
  • Loading branch information
Joe Wegner authored and facchinm committed Aug 21, 2019
1 parent 88bda6e commit 2b4c4b5
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 19 deletions.
74 changes: 64 additions & 10 deletions app/src/cc/arduino/contributions/ContributionsSelfCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -81,13 +83,41 @@ public void run() {
return;
}

String text;
boolean setAccessible = PreferencesData.getBoolean("ide.accessible");
final String text;
final String button1Name;
final String button2Name;
String openAnchorBoards = "<a href=\"" + boardsManagerURL + "\">";
String closeAnchorBoards = "</a>";
String openAnchorLibraries = "<a href=\"" + libraryManagerURL + "\">";
String closeAnchorLibraries = "</a>";

// 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}"), "<a href=\"http://librarymanager/DropdownUpdatableLibrariesItem\">", "</a>");
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}"), "<a href=\"http://boardsmanager/DropdownUpdatableCoresItem\">", "</a>");
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}"), "<a href=\"http://boardsmanager/DropdownUpdatableCoresItem\">", "</a>", "<a href=\"http://librarymanager/DropdownUpdatableLibrariesItem\">", "</a>");
text = I18n.format(tr("Updates available for some of your {0}libraries{1} and {2}libraries{3}"), openAnchorBoards, closeAnchorBoards, openAnchorLibraries, closeAnchorLibraries);
}

if (cancelled) {
Expand All @@ -96,7 +126,13 @@ public void run() {

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;
Expand All @@ -122,6 +158,24 @@ public void windowGainedFocus(WindowEvent evt) {
});
}

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())
Expand Down
129 changes: 120 additions & 9 deletions app/src/cc/arduino/view/NotificationPopup.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,7 @@
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;

Expand All @@ -55,22 +50,46 @@
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);
Expand All @@ -90,13 +109,101 @@ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
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);
closeButton.setBorderPainted(false);
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() {
Expand Down Expand Up @@ -158,5 +265,9 @@ public void run() {
}, Constants.NOTIFICATION_POPUP_AUTOCLOSE_DELAY);
}
setVisible(true);
if (PreferencesData.getBoolean("ide.accessible")) {
requestFocus();
setModal(true);
}
}
}

0 comments on commit 2b4c4b5

Please sign in to comment.