/*
* Copyright 2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.viewer;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import ccre.channel.CancelOutput;
import ccre.channel.EventCell;
import ccre.cluck.Cluck;
import ccre.supercanvas.DraggableBoxComponent;
import ccre.supercanvas.SuperCanvasPanel;
import ccre.util.LineCollectorOutputStream;
/**
* A webcam viewer component, either controlled locally or across the network by
* robot code.
*
* @author skeggsc
*/
public class WebcamComponent extends DraggableBoxComponent {
private static final int FIXED_WIDTH = 640;
private static final long serialVersionUID = 6555290483696874964L;
private String address = null;
private final StringBuilder addressField = new StringBuilder();
private transient WebcamThread webcam;
private transient BufferedImage image;
private transient volatile String error = "Connecting...";
private boolean flipH, flipV;
private String cluckLink;
private transient CancelOutput cluckCancel;
private static final class CluckEntry {
private final EventCell onChange = new EventCell();
private String address;
public CluckEntry(String linkName) {
Cluck.subscribe(linkName, new LineCollectorOutputStream() {
@Override
protected void collect(String param) {
address = param;
onChange.event();
}
});
// because this happens at runtime
Cluck.getNode().notifyNetworkModified();
}
public CancelOutput connect(WebcamComponent component) {
return onChange.send(() -> {
if (address.equals(component.getAddress())) {
return;
}
component.setAddress(address);
});
}
}
private static final HashMap<String, CluckEntry> ents = new HashMap<>();
private static synchronized CluckEntry getEntry(String name) {
CluckEntry ent = ents.get(name);
if (ent == null) {
ent = new CluckEntry(name);
ents.put(name, ent);
}
return ent;
}
/**
* Create a new WebcamComponent at the specified position.
*
* @param cx the X-coordinate.
* @param cy the Y-coordinate.
*/
public WebcamComponent(int cx, int cy) {
super(cx, cy);
halfWidth = FIXED_WIDTH / 2;
halfHeight = halfWidth * 3 / 4;
}
private synchronized void setImage(BufferedImage image) {
this.image = image;
if (image != null) {
this.halfWidth = FIXED_WIDTH / 2;
this.halfHeight = (int) (((float) image.getHeight() / image.getWidth()) * halfWidth);
}
SuperCanvasPanel panel = this.getPanel();
if (panel != null) {
panel.repaint();
}
}
private synchronized void setCluckLink(String link) {
if (this.cluckCancel != null) {
this.cluckCancel.cancel();
this.cluckCancel = null;
}
this.cluckLink = link;
if (link != null) {
this.cluckCancel = getEntry(link).connect(this);
}
}
private String getAddress() {
return this.webcam == null ? null : this.webcam.getAddress();
}
private synchronized void setAddress(String address) {
if (address != null && address.isEmpty()) {
address = null;
}
this.address = address;
if (this.webcam != null) {
this.webcam.setAddress(address);
}
setImage(null);
}
private void setError(String error) {
if (error == null) {
error = "";
}
this.error = error;
SuperCanvasPanel panel = this.getPanel();
if (panel != null) {
panel.repaint();
}
}
@Override
public synchronized void render(Graphics2D g, int screenWidth, int screenHeight, FontMetrics fontMetrics, int mouseX, int mouseY) {
int x0 = centerX - halfWidth, y0 = centerY - halfHeight;
g.setColor(Color.BLACK);
g.fillRect(x0, y0, halfWidth * 2, halfHeight * 2);
g.setColor(Color.WHITE);
g.drawRect(x0, y0, halfWidth * 2, halfHeight * 2);
BufferedImage image = this.image;
if (image != null) {
g.drawImage(image, x0, y0, x0 + halfWidth * 2, y0 + halfHeight * 2, flipH ? image.getWidth() : 0, flipV ? image.getHeight() : 0, flipH ? 0 : image.getWidth(), flipV ? 0 : image.getHeight(), null);
}
g.drawString(this.address + (this.cluckLink == null ? "" : " :" + this.cluckLink), x0 + 3, y0 + halfHeight * 2 - 3);
if (getPanel().editing == addressField) {
String text = this.addressField.toString() + "|";
g.drawString(text, centerX - g.getFontMetrics().stringWidth(text) / 2, centerY);
}
String text = this.error;
g.drawString(text, centerX - g.getFontMetrics().stringWidth(text) / 2, centerY + g.getFontMetrics().getHeight());
}
@Override
public void onPressedEnter() {
if (getPanel().editing == addressField) {
if (addressField.length() > 0 && addressField.charAt(0) == ':') {
setCluckLink(addressField.substring(1));
} else {
setCluckLink(null);
setAddress(addressField.toString());
}
getPanel().editing = null;
}
}
@Override
public boolean onInteract(int x, int y) {
boolean used = false;
if (Math.abs(x - centerX) >= halfWidth - 10) {
flipH = !flipH;
used = true;
}
if (Math.abs(y - centerY) >= halfHeight - 10) {
flipV = !flipV;
used = true;
}
if (!used) {
if (getPanel().editing == addressField) {
onPressedEnter();
} else {
addressField.setLength(0);
if (cluckLink != null) {
addressField.append(":").append(cluckLink);
} else if (address != null) {
addressField.append(address);
}
getPanel().editing = addressField;
}
}
return true;
}
@Override
public String toString() {
return "camera " + address;
}
@Override
protected synchronized void onChangePanel(SuperCanvasPanel panel) {
if (webcam != null && panel == null) {
if (this.cluckCancel != null) {
this.cluckCancel.cancel();
this.cluckCancel = null;
}
webcam.terminate();
webcam = null;
} else if (webcam == null && panel != null) {
setCluckLink(cluckLink);
webcam = new WebcamThread(this::setImage, this::setError);
webcam.setAddress(address);
}
}
}