diff --git a/src/main/java/io/swtc/SwingCCTVManager.java b/src/main/java/io/swtc/SwingCCTVManager.java index 64091d8..716786d 100644 --- a/src/main/java/io/swtc/SwingCCTVManager.java +++ b/src/main/java/io/swtc/SwingCCTVManager.java @@ -2,26 +2,37 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.WebcamCompositeDriver; +import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; -import com.github.sarxos.webcam.ds.ipcam.IpCamDeviceRegistry; -import com.github.sarxos.webcam.ds.ipcam.IpCamDriver; -import com.github.sarxos.webcam.ds.ipcam.IpCamMode; +import com.github.sarxos.webcam.ds.ipcam.*; import io.swtc.networking.CameraConfig; import io.swtc.networking.CameraSettings; import io.swtc.proccessing.ui.IconSetter; +import io.swtc.proccessing.ui.ShowError; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import java.awt.*; +import java.awt.datatransfer.DataFlavor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.File; +import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URL; import java.util.List; public class SwingCCTVManager { + private static final String TIMEOUT_MS = "1500"; + private static final int REFRESH_INTERVAL = 30000; + private static final String STATUS_ONLINE = "ONLINE"; + private static final String STATUS_OFFLINE = "OFFLINE"; + static { + System.setProperty("ipcam.connection.timeout", TIMEOUT_MS); Webcam.setDriver(new WebcamCompositeDriver() {{ add(new WebcamDefaultDriver()); add(new IpCamDriver()); @@ -33,228 +44,238 @@ public class SwingCCTVManager { private final JTable deviceTable; private final DefaultTableModel tableModel; private final SwingIFrame IFrame; + private boolean isRefreshing = false; public SwingCCTVManager() { - frame = new JFrame("dashboard"); + frame = new JFrame("Dashboard"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(1000, 600); + frame.setSize(1100, 600); frame.setIconImage(IconSetter.getIcon()); this.IFrame = new SwingIFrame(); this.IFrame.show(); - String[] columns = {"Status", "Device Name", "Type", "Resolution", "Address"}; - tableModel = new DefaultTableModel(columns, 0) { + tableModel = new DefaultTableModel(new String[]{"Status", "Device Name", "Type", "Resolution", "Address"}, 0) { @Override - public boolean isCellEditable(int r, int c) { - return false; - } + public boolean isCellEditable(int r, int c) { return false; } }; deviceTable = new JTable(tableModel); - - deviceTable.setRowSelectionAllowed(true); - deviceTable.setFocusable(true); - deviceTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - setupTableAppearance(); - - deviceTable.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - deviceTable.requestFocusInWindow(); - - if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) { - launchSelected(); - } - if (SwingUtilities.isRightMouseButton(e)) { - showContextMenu(e); - } - } - }); - + setupDragAndDrop(); // Initialize DND + setupListeners(); JToolBar toolBar = new JToolBar(); - - Image ogIcon = IconSetter.getIcon(); - Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH); - JLabel iconLabel = new JLabel(new ImageIcon(scaledIcon)); - toolBar.add(iconLabel); - - JLabel textLabel = new JLabel("SWT-CCTV"); - toolBar.add(textLabel); - - toolBar.add(Box.createRigidArea(new Dimension(10, 0))); // 20px horizontal padding - - toolBar.addSeparator(new Dimension(10, 32)); // 10px width, 32px height - - JButton btnAdd = new JButton("Add IP Cam"); - JButton btnLaunch = new JButton("Launch Stream"); - - btnAdd.setPreferredSize(new Dimension(100, 32)); - btnLaunch.setPreferredSize(new Dimension(120, 32)); - - toolBar.add(Box.createRigidArea(new Dimension(5,0))); - toolBar.add(btnAdd); - toolBar.addSeparator(); - toolBar.add(btnLaunch); + setupBrandedToolbar(toolBar); frame.add(toolBar, BorderLayout.SOUTH); frame.add(new JScrollPane(deviceTable), BorderLayout.CENTER); - btnAdd.addActionListener(e -> showAddCameraDialog()); - btnLaunch.addActionListener(e -> launchSelected()); - startAutoRefresh(); refreshTable(); } - private void setupTableAppearance() { - deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); - deviceTable.setRowHeight(30); - - deviceTable.getColumnModel().getColumn(0) - .setCellRenderer(new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent( - JTable t, Object v, boolean s, boolean f, int r, int c) { - - Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c); - setHorizontalAlignment(JLabel.CENTER); - comp.setForeground("ONLINE".equals(v) - ? new Color(0, 150, 0) - : Color.RED); - return comp; - } - }); - } - - private void startAutoRefresh() { - new Timer(5000, e -> refreshTable()).start(); - } - - private void refreshTable() { - int[] selectedRows = deviceTable.getSelectedRows(); - - tableModel.setRowCount(0); - List webcams = Webcam.getWebcams(); - - for (Webcam w : webcams) { - boolean isIp = w.getDevice().getClass().getSimpleName().contains("IpCam"); - String status = w.getDevice() != null ? "ONLINE" : "OFFLINE"; - - tableModel.addRow(new Object[]{ - status, - w.getName(), - isIp ? "IP Stream" : "USB Hardware", - w.getViewSize().width + "x" + w.getViewSize().height, - isIp ? "Network" : "Local" - }); + private void setupBrandedToolbar(JToolBar toolBar) { + Image ogIcon = IconSetter.getIcon(); + if (ogIcon != null) { + Image scaledIcon = ogIcon.getScaledInstance(32, 32, Image.SCALE_SMOOTH); + toolBar.add(new JLabel(new ImageIcon(scaledIcon))); } - for (int r : selectedRows) { - if (r < tableModel.getRowCount()) { - deviceTable.addRowSelectionInterval(r, r); + toolBar.add(new JLabel("SWT-CCTV")); + toolBar.add(Box.createRigidArea(new Dimension(10, 0))); + toolBar.addSeparator(new Dimension(10, 32)); + + JButton btnAdd = new JButton("Add IP Cam"); + JButton btnImport = new JButton("Import Config"); // New Import Button + JButton btnLaunch = new JButton("Launch Stream"); + + btnAdd.setPreferredSize(new Dimension(100, 32)); + btnImport.setPreferredSize(new Dimension(110, 32)); + btnLaunch.setPreferredSize(new Dimension(120, 32)); + + toolBar.add(Box.createRigidArea(new Dimension(5, 0))); + toolBar.add(btnAdd); + toolBar.add(btnImport); + toolBar.addSeparator(); + toolBar.add(btnLaunch); + + btnAdd.addActionListener(e -> showAddCameraDialog()); + btnImport.addActionListener(e -> showImportDialog()); + btnLaunch.addActionListener(e -> launchSelected()); + } + + private void setupDragAndDrop() { + deviceTable.setDragEnabled(true); + deviceTable.setTransferHandler(new TransferHandler() { + @Override + public boolean canImport(TransferSupport support) { + return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor); } + + @Override + public boolean importData(TransferSupport support) { + if (!canImport(support)) return false; + try { + @SuppressWarnings("unchecked") + List files = (List) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); + for (File file : files) { + if (file.getName().endsWith(".json")) { + List configs = CameraSettings.loadFromFile(file); + importCameras(configs); + } + } + return true; + } catch (Exception e) { + return false; + } + } + }); + } + + private void showImportDialog() { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Select Camera Configuration JSON"); + if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + List configs = CameraSettings.loadFromFile(file); + importCameras(configs); } } - private void showContextMenu(MouseEvent e) { - int row = deviceTable.rowAtPoint(e.getPoint()); - if (row >= 0) { - deviceTable.setRowSelectionInterval(row, row); + private void importCameras(List configs) { + if (configs.isEmpty()) { + ShowError.warning(frame, "Import Empty", "No valid camera configurations found in file."); + return; } + for (CameraConfig config : configs) { + try { + if (!IpCamDeviceRegistry.isRegistered(config.getName())) { + IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH); + CameraSettings.save(config); + } + } catch (Exception ignored) {} + } + refreshTable(); + } - JPopupMenu menu = new JPopupMenu(); - JMenuItem ico = new JMenuItem("test"); - JMenuItem launch = new JMenuItem("Launch Live Stream"); - JMenuItem delete = new JMenuItem("Remove Device"); + private void setupTableAppearance() { + deviceTable.setRowHeight(30); + deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); + deviceTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable t, Object v, boolean s, boolean f, int r, int c) { + Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c); + setHorizontalAlignment(JLabel.CENTER); + comp.setForeground(STATUS_ONLINE.equals(v) ? new Color(0, 150, 0) : Color.RED); + return comp; + } + }); + } - launch.addActionListener(al -> launchSelected()); - delete.addActionListener(al -> deleteSelected()); + private synchronized void refreshTable() { + if (isRefreshing) return; + isRefreshing = true; + final int selectedRow = deviceTable.getSelectedRow(); + new SwingWorker() { + @Override + protected Void doInBackground() { + for (WebcamDevice device : new WebcamDefaultDriver().getDevices()) { + Dimension res = (device.getResolutions().length > 0) ? device.getResolutions()[0] : new Dimension(0,0); + publish(new Object[]{STATUS_ONLINE, device.getName(), "USB Hardware", res.width + "x" + res.height, "Local"}); + } + for (IpCamDevice ipDevice : IpCamDeviceRegistry.getIpCameras()) { + publish(probeIpCamera(ipDevice)); + } + return null; + } + @Override + protected void process(List chunks) { + if (!Boolean.TRUE.equals(frame.getRootPane().getClientProperty("cleared"))) { + tableModel.setRowCount(0); + frame.getRootPane().putClientProperty("cleared", true); + } + for (Object[] row : chunks) tableModel.addRow(row); + } + @Override + protected void done() { + frame.getRootPane().putClientProperty("cleared", null); + if (selectedRow != -1 && selectedRow < tableModel.getRowCount()) { + deviceTable.setRowSelectionInterval(selectedRow, selectedRow); + } + isRefreshing = false; + } + }.execute(); + } - menu.add(ico); - menu.add(launch); - menu.addSeparator(); - menu.add(delete); - menu.show(deviceTable, e.getX(), e.getY()); + private Object[] probeIpCamera(IpCamDevice ipDevice) { + String status = STATUS_OFFLINE; String resText = "N/A"; + try { + URL url = ipDevice.getURL(); + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(url.getHost(), url.getPort() != -1 ? url.getPort() : 80), 800); + Dimension d = ipDevice.getResolution(); + if (d != null) { status = STATUS_ONLINE; resText = d.width + "x" + d.height; } + } + } catch (Exception e) { status = STATUS_OFFLINE; } + return new Object[]{status, ipDevice.getName(), "IP Stream", resText, "Network"}; } private void launchSelected() { int row = deviceTable.getSelectedRow(); if (row == -1) return; - String name = (String) tableModel.getValueAt(row, 1); - Webcam.getWebcams().stream() - .filter(w -> w.getName().equals(name)) - .findFirst() - .ifPresent(cam -> - new Thread(() -> IFrame.addCameraInternalFrame(cam)).start() - ); - } - - private void deleteSelected() { - int row = deviceTable.getSelectedRow(); - if (row == -1) return; - - String name = (String) tableModel.getValueAt(row, 1); - if (name.toLowerCase().contains("usb")) return; - - CameraSettings.delete(name); - IpCamDeviceRegistry.unregister(name); - refreshTable(); - } - - private static void loadSavedCameras() { - for (CameraConfig config : CameraSettings.load()) { - try { - IpCamDeviceRegistry.register( - config.getName(), - config.getUrl(), - IpCamMode.PUSH - ); - } catch (MalformedURLException e) { - JOptionPane.showMessageDialog( - null, - "Malformed URL\n" + e.getMessage(), - "Malformed URL", - JOptionPane.ERROR_MESSAGE - ); - } + if (STATUS_OFFLINE.equals(tableModel.getValueAt(row, 0))) { + ShowError.warning(frame, "Device Offline", "Cannot connect to '" + name + "'."); + return; } + new Thread(() -> { + Webcam selected = Webcam.getWebcams().stream().filter(w -> w.getName().equals(name)).findFirst().orElse(null); + if (selected != null) IFrame.addCameraInternalFrame(selected); + }).start(); } private void showAddCameraDialog() { JPanel p = new JPanel(new GridLayout(2, 2, 5, 5)); - JTextField n = new JTextField(); - JTextField u = new JTextField(); - - p.add(new JLabel("Name:")); - p.add(n); - p.add(new JLabel("URL:")); - p.add(u); - - int result = JOptionPane.showConfirmDialog( - frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION - ); - - if (result == JOptionPane.OK_OPTION) { - try { - IpCamDeviceRegistry.register(n.getText(), u.getText(), IpCamMode.PUSH); - CameraSettings.save(new CameraConfig(n.getText(), u.getText())); - refreshTable(); - } catch (Exception ex) { - JOptionPane.showMessageDialog(frame, "Error: " + ex.getMessage()); - } + JTextField n = new JTextField(); JTextField u = new JTextField(); + p.add(new JLabel("Name:")); p.add(n); p.add(new JLabel("URL:")); p.add(u); + if (JOptionPane.showConfirmDialog(frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + importCameras(List.of(new CameraConfig(n.getText(), u.getText()))); } } - public void open() { - frame.setLocationRelativeTo(null); - frame.setVisible(true); + private void setupListeners() { + deviceTable.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + deviceTable.requestFocusInWindow(); + if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) launchSelected(); + if (SwingUtilities.isRightMouseButton(e)) showContextMenu(e); + } + }); } + private void showContextMenu(MouseEvent e) { + int row = deviceTable.rowAtPoint(e.getPoint()); + if (row >= 0) deviceTable.setRowSelectionInterval(row, row); + JPopupMenu menu = new JPopupMenu(); + JMenuItem launch = new JMenuItem("Launch Live Stream"); + launch.addActionListener(al -> launchSelected()); + menu.add(launch); + menu.show(deviceTable, e.getX(), e.getY()); + } + + private static void loadSavedCameras() { + for (CameraConfig config : CameraSettings.load()) { + try { IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH); } catch (Exception ignored) {} + } + } + + private void startAutoRefresh() { new Timer(REFRESH_INTERVAL, e -> refreshTable()).start(); } + + public void open() { frame.setLocationRelativeTo(null); frame.setVisible(true); } + public static void main(String[] args) { SwingUtilities.invokeLater(() -> new SwingCCTVManager().open()); } -} +} \ No newline at end of file diff --git a/src/main/java/io/swtc/networking/CameraConfig.java b/src/main/java/io/swtc/networking/CameraConfig.java index d3814a0..1716f54 100644 --- a/src/main/java/io/swtc/networking/CameraConfig.java +++ b/src/main/java/io/swtc/networking/CameraConfig.java @@ -5,6 +5,7 @@ public class CameraConfig { public String url; // Default constructor for Jackson + public CameraConfig() {} public CameraConfig(String name, String url) { this.name = name; diff --git a/src/main/java/io/swtc/networking/CameraSettings.java b/src/main/java/io/swtc/networking/CameraSettings.java index 15e6268..e8541fa 100644 --- a/src/main/java/io/swtc/networking/CameraSettings.java +++ b/src/main/java/io/swtc/networking/CameraSettings.java @@ -7,24 +7,32 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -/* -* Some JSON stuff for camera config saving -* */ + public class CameraSettings { - private static final File storage_file = new File("network_cameras.json"); - private static final ObjectMapper mapper = new ObjectMapper(); + private static final File STORAGE_FILE = new File("network_cameras.json"); + private static final ObjectMapper MAPPER = new ObjectMapper(); public static List load() { - if (!storage_file.exists() || storage_file.length() == 0) return new ArrayList<>(); + return loadFromFile(STORAGE_FILE); + } + + public static List loadFromFile(File file) { + if (!file.exists() || file.length() == 0) return new ArrayList<>(); try { - return mapper.readValue(storage_file, new TypeReference>() {}); + return MAPPER.readValue(file, new TypeReference>() {}); } catch (IOException e) { + System.err.println("Error reading camera config: " + e.getMessage()); return new ArrayList<>(); } } + /** + * Saves a single camera. Prevents duplicates by name. + */ public static void save(CameraConfig newCam) { List current = load(); + // Prevent duplicate names in the local storage + current.removeIf(cam -> cam.getName().equalsIgnoreCase(newCam.getName())); current.add(newCam); write(current); } @@ -37,7 +45,7 @@ public class CameraSettings { private static void write(List list) { try { - mapper.writerWithDefaultPrettyPrinter().writeValue(storage_file, list); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(STORAGE_FILE, list); } catch (IOException e) { e.printStackTrace(); }