Add JavaFXWebcam window

This commit is contained in:
Manfred Karrer 2018-07-13 04:09:40 +02:00
parent fa208157cc
commit 5dec138b4f
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
7 changed files with 251 additions and 119 deletions

View File

@ -49,7 +49,7 @@ import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
@FxmlView
public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
public class AccountView extends ActivatableView<TabPane, Void> {
@FXML
Tab accountSettingsTab;
@ -67,8 +67,8 @@ public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
private EventHandler<KeyEvent> keyEventEventHandler;
@Inject
private AccountView(AccountViewModel model, CachingViewLoader viewLoader, Navigation navigation) {
super(model);
private AccountView(CachingViewLoader viewLoader, Navigation navigation) {
super();
this.viewLoader = viewLoader;
this.navigation = navigation;
}

View File

@ -20,6 +20,7 @@ package bisq.desktop.main.account.content.notifications;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.WebCamWindow;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
@ -55,6 +56,8 @@ public class NotificationsView extends ActivatableView<GridPane, Void> {
private Button wipeOutButton;
private Button devButton;
private ChangeListener<Boolean> useSoundCheckBoxListener, tradeCheckBoxListener;
private WebCamWindow webCamWindow;
private QrCodeReader qrCodeReader;
///////////////////////////////////////////////////////////////////////////////////////////
@ -74,12 +77,26 @@ public class NotificationsView extends ActivatableView<GridPane, Void> {
@Override
public void initialize() {
FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title"));
webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second;
webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"),
Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second;
webCamButton.setDefaultButton(true);
webCamButton.setOnAction((event) -> {
webCamButton.setDisable(true);
new QrCodeWindow(this::applyKeyAndToken);
new WebCamLauncher(webCam -> {
webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height)
.onClose(() -> {
webCamButton.setDisable(false);
qrCodeReader.close();
});
webCamWindow.show();
qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> {
webCamWindow.hide();
webCamButton.setDisable(false);
tokenInputTextField.setText(qrCode);
});
});
});
tokenInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")).second;

View File

@ -0,0 +1,97 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.account.content.notifications;
import bisq.common.UserThread;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import java.awt.image.BufferedImage;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import com.github.sarxos.webcam.Webcam;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javafx.embed.swing.SwingFXUtils;
@Slf4j
// Must not be UI thread
public class QrCodeReader extends Thread {
private Webcam webCam;
private ImageView imageView;
private Consumer<String> resultHandler;
private boolean isRunning;
public QrCodeReader(Webcam webCam, ImageView imageView, Consumer<String> resultHandler) {
this.webCam = webCam;
this.imageView = imageView;
this.resultHandler = resultHandler;
start();
}
@Override
public void run() {
try {
if (!webCam.isOpen())
webCam.open();
isRunning = true;
Result result;
BufferedImage bufferedImage;
while (isRunning) {
bufferedImage = webCam.getImage();
if (bufferedImage != null) {
WritableImage writableImage = SwingFXUtils.toFXImage(bufferedImage, null);
imageView.setImage(writableImage);
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
result = new MultiFormatReader().decode(bitmap);
isRunning = false;
String qrCode = result.getText();
UserThread.execute(() -> resultHandler.accept(qrCode));
} catch (NotFoundException ignore) {
// No qr code in image...
}
}
}
} catch (Throwable t) {
log.error(t.toString());
} finally {
webCam.close();
}
}
public void close() {
isRunning = false;
}
}

View File

@ -1,113 +0,0 @@
package bisq.desktop.main.account.content.notifications;
import bisq.common.UserThread;
import bisq.common.util.Utilities;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javax.swing.JFrame;
public class QrCodeWindow extends JFrame {
private Webcam webcam;
private Executor executor = Utilities.getListeningSingleThreadExecutor("readQR-runner");
private WebcamPanel panel;
private Consumer<String> qrCodeResultHandler;
public QrCodeWindow(Consumer<String> qrCodeResultHandler) {
this.qrCodeResultHandler = qrCodeResultHandler;
setLayout(new FlowLayout());
setTitle("Bisq Notification token"); //TODO
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(WindowEvent windowEvent) {
webcam.close();
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
});
executor.execute(this::doRun);
}
private void doRun() {
try {
webcam = Webcam.getDefault(1000); // one second timeout - the default is too long
Dimension[] sizes = webcam.getViewSizes();
webcam.setViewSize(sizes[sizes.length - 1]);
panel = new WebcamPanel(webcam);
add(panel);
pack();
setLocationRelativeTo(null);
setVisible(true);
} catch (TimeoutException e) {
e.printStackTrace();
}
// check 10 times a second for the QR code
boolean run = true;
while (run) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Result result = null;
BufferedImage image;
if (webcam != null && webcam.isOpen()) {
if ((image = webcam.getImage()) == null)
continue;
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
result = new MultiFormatReader().decode(bitmap);
} catch (NotFoundException ignore) {
// fall thru, it means there is no QR code in image
}
}
if (result != null) {
String qrCode = result.getText();
//boolean sendConfirmation = phone.validatePhoneId(qrCode);
// phone.save();
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
run = false;
UserThread.execute(() -> {
qrCodeResultHandler.accept(qrCode);
});
/* Platform.runLater(
() -> {
// app.updateGUI();
// app.sendConfirmation();
}
);*/
}
}
}
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.account.content.notifications;
import bisq.common.UserThread;
import java.awt.Dimension;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import com.github.sarxos.webcam.Webcam;
@Slf4j
// Must not be UI thread
public class WebCamLauncher extends Thread {
private Consumer<Webcam> resultHandler;
public WebCamLauncher(Consumer<Webcam> resultHandler) {
this.resultHandler = resultHandler;
start();
}
@Override
public void run() {
try {
Webcam webCam = Webcam.getDefault(1000); // one second timeout - the default is too long
Dimension[] sizes = webCam.getViewSizes();
Dimension size = sizes[sizes.length - 1]; // the largest size
webCam.setViewSize(size);
UserThread.execute(() -> resultHandler.accept(webCam));
} catch (TimeoutException e) {
log.error(e.toString());
}
}
}

View File

@ -21,7 +21,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" prefHeight="660.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
xmlns:fx="http://javafx.com/fxml"
fx:controller="bisq.desktop.main.account.settings.AccountSettingsView">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"

View File

@ -0,0 +1,75 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.overlays.windows;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.util.FormBuilder;
import bisq.core.locale.Res;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class WebCamWindow extends Overlay<WebCamWindow> {
@Getter
private ImageView imageView = new ImageView();
public WebCamWindow(double width, double height) {
type = Type.Feedback;
imageView.setFitWidth(width);
imageView.setFitHeight(height);
}
public void show() {
headLine = Res.get("account.notifications.webCamWindow.headline");
createGridPane();
addHeadLine();
addSeparator();
addContent();
addCloseButton();
applyStyles();
display();
}
private void addContent() {
GridPane.setHalignment(headLineLabel, HPos.CENTER);
Label label = FormBuilder.addLabel(gridPane, ++rowIndex, Res.get("account.notifications.waitingForWebCam"));
label.setAlignment(Pos.CENTER);
GridPane.setColumnSpan(label, 2);
GridPane.setHalignment(label, HPos.CENTER);
GridPane.setRowIndex(imageView, rowIndex);
GridPane.setColumnSpan(imageView, 2);
gridPane.getChildren().add(imageView);
}
}