From 565a4f3cf3272e33115e9dae694db174965a1cac Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Mon, 19 Jan 2026 13:04:40 +0100 Subject: [PATCH] fixed some functionallity added back some which was removed cause of refactoring ... some new refactoring to make the UI code cleaner Signed-off-by: rattatwinko --- src/main/java/io/swtc/Main.java | 6 +- src/main/java/io/swtc/SwingIFrame.java | 287 ++---------------- .../ui/iframe/CameraInternalFrame.java | 132 ++++++++ .../proccessing/ui/iframe/DesktopPane.java | 66 ++++ .../ui/iframe/EffectsPanelFrame.java | 14 + .../proccessing/ui/iframe/RecordingPane.java | 126 ++++++++ .../proccessing/ui/sections/DNRSection.java | 18 +- .../ui/sections/DetailSection.java | 9 +- .../ui/sections/WhiteBalanceSection.java | 10 +- src/test/java/CameraSettingsTest.java | 98 ------ 10 files changed, 393 insertions(+), 373 deletions(-) create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java delete mode 100644 src/test/java/CameraSettingsTest.java diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java index 84009e5..8733916 100644 --- a/src/main/java/io/swtc/Main.java +++ b/src/main/java/io/swtc/Main.java @@ -9,7 +9,7 @@ public class Main { try { UIManager.setLookAndFeel( - "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" + "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" ); } catch (Exception e) { JOptionPane.showMessageDialog( @@ -21,8 +21,6 @@ public class Main { } // For some reason we need to invoke Later for LaF to work! - SwingUtilities.invokeLater(() -> { - SwingCCTVManager.main(null); - }); + SwingUtilities.invokeLater(() -> SwingCCTVManager.main(null)); } } diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java index ba6b382..bf0e269 100644 --- a/src/main/java/io/swtc/SwingIFrame.java +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -1,29 +1,15 @@ package io.swtc; import com.github.sarxos.webcam.Webcam; -import io.swtc.proccessing.WebcamCaptureLoop; -import io.swtc.proccessing.CameraPanel; - -import javax.imageio.ImageIO; +import io.swtc.proccessing.ui.iframe.*; import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.geom.CubicCurve2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.*; +import java.util.HashMap; +import java.util.Map; public class SwingIFrame { private final JFrame mainFrame; - private final BlenderDesktopPane desktopPane; - private boolean fullscreen = false; - private Rectangle windowedBounds; - private boolean blackbg = false; - private final Color defDesktopBg; - private final Color bgcolor; - + private final DesktopPane desktopPane; private final Map cameraToEffects = new HashMap<>(); public SwingIFrame() { @@ -31,175 +17,48 @@ public class SwingIFrame { mainFrame.setSize(1280, 720); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - bgcolor = Color.decode("#1e1e1e"); - - desktopPane = new BlenderDesktopPane(cameraToEffects); + desktopPane = new DesktopPane(cameraToEffects); desktopPane.setBackground(Color.WHITE); - defDesktopBg = desktopPane.getBackground(); mainFrame.add(desktopPane, BorderLayout.CENTER); - - setupFullscreenToggle(); - setupBlackBg(); } public void addCameraInternalFrame(Webcam webcam) { - JInternalFrame cameraFrame = new JInternalFrame( - webcam.getName(), - true, true, true, true + CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest); + + EffectsPanelFrame effectsFrame = new EffectsPanelFrame( + "Effects - " + webcam.getName(), + cameraFrame.getCameraPanel() ); - CameraPanel cameraPanel = new CameraPanel(); - WebcamCaptureLoop captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) -> - SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) - ); + cameraToEffects.put(cameraFrame, effectsFrame); - JPanel contentPanel = new JPanel(new BorderLayout()); - JTabbedPane tabbedPane = new JTabbedPane(); - - tabbedPane.addTab("View", cameraPanel); - tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam)); - tabbedPane.addTab("Effects", new JPanel()); - - contentPanel.add(tabbedPane, BorderLayout.CENTER); - tabbedPane.setPreferredSize(null); - - // where we show the effectpanel (io.swtc.proccessing.FilterPanel.java) - tabbedPane.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (tabbedPane.getSelectedIndex() == 2) { - tabbedPane.setSelectedIndex(0); - - EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame); - if (effectsFrame != null) { - effectsFrame.setVisible(true); - try { - effectsFrame.setSelected(true); - } catch (java.beans.PropertyVetoException ex) { - ex.printStackTrace(); - } - } - } - } - }); + int offset = desktopPane.getAllFrames().length * 15; + cameraFrame.setLocation(50 + offset, 50 + offset); + effectsFrame.setLocation(700 + offset, 50 + offset); + effectsFrame.setVisible(false); cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() { @Override public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) { - captureLoop.stop(); - EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame); - if (effectsFrame != null) { - effectsFrame.dispose(); - cameraToEffects.remove(cameraFrame); - } + EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame); + if (ef != null) ef.dispose(); + desktopPane.forgetFrame(cameraFrame); } }); - cameraFrame.add(contentPanel); - cameraFrame.setSize(600, 500); - - int offset = desktopPane.getAllFrames().length * 30; - cameraFrame.setLocation(50 + offset, 50 + offset); - - // Pre-create the effects frame but keep it hidden - EffectsPanelFrame effectsFrame = new EffectsPanelFrame( - "Effects - " + webcam.getName(), - cameraPanel, - cameraFrame - ); - effectsFrame.setSize(350, 600); - effectsFrame.setLocation(700 + offset, 50 + offset); - effectsFrame.setVisible(false); // Hidden by default - - cameraToEffects.put(cameraFrame, effectsFrame); - addLinkageListeners(cameraFrame, effectsFrame); - desktopPane.add(cameraFrame); desktopPane.add(effectsFrame); - cameraFrame.setVisible(true); - captureLoop.start(); } - private void addLinkageListeners(JInternalFrame cameraFrame, EffectsPanelFrame effectsFrame) { - java.awt.event.ComponentAdapter linker = new java.awt.event.ComponentAdapter() { - @Override - public void componentMoved(java.awt.event.ComponentEvent e) { - desktopPane.repaint(); - } - @Override - public void componentResized(java.awt.event.ComponentEvent e) { - desktopPane.repaint(); - } - }; - cameraFrame.addComponentListener(linker); - effectsFrame.addComponentListener(linker); - } - - // --- Standard UI Setup Methods --- - - private void setupBlackBg() { - JRootPane root = mainFrame.getRootPane(); - root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("B"), "toggleBlackBg"); - root.getActionMap().put("toggleBlackBg", new AbstractAction() { - @Override public void actionPerformed(ActionEvent e) { setbgblack(); } - }); - } - - private void setupFullscreenToggle() { - JRootPane root = mainFrame.getRootPane(); - root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen"); - root.getActionMap().put("toggleFullscreen", new AbstractAction() { - @Override public void actionPerformed(ActionEvent e) { toggleFullscreen(); } - }); - } - - private void setbgblack() { - if (!blackbg) { - desktopPane.setBackground(bgcolor); - } else { - desktopPane.setBackground(defDesktopBg); - } - blackbg = !blackbg; - } - - private void toggleFullscreen() { - if (!fullscreen) { - windowedBounds = mainFrame.getBounds(); - mainFrame.dispose(); - mainFrame.setUndecorated(true); - mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); - mainFrame.setVisible(true); - } else { - mainFrame.dispose(); - mainFrame.setUndecorated(false); - mainFrame.setExtendedState(JFrame.NORMAL); - mainFrame.setBounds(windowedBounds); - mainFrame.setVisible(true); - } - fullscreen = !fullscreen; - } - - private JPanel createCapturePanel(CameraPanel cameraPanel, Webcam webcam) { - JPanel panel = new JPanel(new BorderLayout(10, 10)); - panel.setBorder(new EmptyBorder(15, 15, 15, 15)); - - JButton takeScreenshot = new JButton("Take Screenshot"); - takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, System.getProperty("user.home"), panel)); - - panel.add(takeScreenshot, BorderLayout.NORTH); - return panel; - } - - private void saveSnapshot(CameraPanel cameraPanel, Webcam webcam, String directory, Component parent) { - BufferedImage img = cameraPanel.getCurrentProcessedImage(); - if (img != null) { + private void handleEffectsRequest(CameraInternalFrame source) { + EffectsPanelFrame effectsFrame = cameraToEffects.get(source); + if (effectsFrame != null) { + effectsFrame.setVisible(true); try { - File file = new File(directory, "snap.png"); - ImageIO.write(img, "PNG", file); - } catch (Exception ex) { - ex.printStackTrace(); - } + effectsFrame.setSelected(true); + effectsFrame.toFront(); + } catch (java.beans.PropertyVetoException ex) { JOptionPane.showMessageDialog(null,"Exception" + ex.getMessage() , "Exception", JOptionPane.ERROR_MESSAGE); } } } @@ -207,100 +66,4 @@ public class SwingIFrame { mainFrame.setLocationRelativeTo(null); mainFrame.setVisible(true); } - - public static void main(String[] args) { - SwingUtilities.invokeLater(() -> { - SwingIFrame dashboard = new SwingIFrame(); - // Example usage: dashboard.addCameraInternalFrame(Webcam.getDefault()); - dashboard.show(); - }); - } - - // --- Inner Classes for Blender Styling --- - - static class BlenderDesktopPane extends JDesktopPane { - private final Map connections; - - public BlenderDesktopPane(Map connections) { - this.connections = connections; - } - - - - @Override - protected void paintChildren(Graphics g) { - super.paintChildren(g); - Graphics2D g2d = (Graphics2D) g.create(); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - for (Map.Entry entry : connections.entrySet()) { - JInternalFrame camera = entry.getKey(); - EffectsPanelFrame effects = entry.getValue(); - - if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) { - drawBezierConnection(g2d, camera, effects); - } - } - g2d.dispose(); - } - - private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) { - Rectangle f = from.getBounds(); - Rectangle t = to.getBounds(); - - int x1 = f.x + f.width; - int y1 = f.y + f.height / 2; - int x2 = t.x; - int y2 = t.y + t.height / 2; - - int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150); - CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2); - - g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - g2d.setColor(new Color(100, 200, 255, 200)); - g2d.draw(curve); - - g2d.fillOval(x1 - 5, y1 - 5, 10, 10); - g2d.fillOval(x2 - 5, y2 - 5, 10, 10); - } - } - - static class EffectsPanelFrame extends JInternalFrame { - - public EffectsPanelFrame(String title, CameraPanel cameraPanel, JInternalFrame cameraFrame) { - super(title, true, true, true, true); - - // Hide instead of dispose so it can be reopened - setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE); - - // Add your FilterPanel - add(new io.swtc.proccessing.FilterPanel(cameraPanel)); - - // Repaint desktop pane when this frame closes so the Bézier disappears - addInternalFrameListener(new InternalFrameAdapter() { - @Override - public void internalFrameClosing(InternalFrameEvent e) { - if (getDesktopPane() != null) { - getDesktopPane().repaint(); - } - } - }); - - // Repaint curves when moving or resizing - addComponentListener(new java.awt.event.ComponentAdapter() { - @Override - public void componentMoved(java.awt.event.ComponentEvent e) { - if (getDesktopPane() != null) - getDesktopPane().repaint(); - } - - @Override - public void componentResized(java.awt.event.ComponentEvent e) { - if (getDesktopPane() != null) - getDesktopPane().repaint(); - } - }); - } - } - } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java new file mode 100644 index 0000000..127789a --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java @@ -0,0 +1,132 @@ +package io.swtc.proccessing.ui.iframe; + +import com.github.sarxos.webcam.Webcam; +import io.swtc.proccessing.WebcamCaptureLoop; +import io.swtc.proccessing.CameraPanel; +import io.swtc.recording.VideoRecorder; +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +public class CameraInternalFrame extends JInternalFrame { + private final WebcamCaptureLoop captureLoop; + private final CameraPanel cameraPanel; + private final VideoRecorder videoRecorder; // Instance of the recorder + private JButton recordBtn; + + public CameraInternalFrame(Webcam webcam, Consumer onOpenEffects) { + super(webcam.getName(), true, true, true, true); + this.cameraPanel = new CameraPanel(); + this.videoRecorder = new VideoRecorder(); // Initialize recorder + + // Initialize capture loop + this.captureLoop = new WebcamCaptureLoop(webcam, img -> + SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) + ); + + setupUI(onOpenEffects); + captureLoop.start(); + } + + private void setupUI(Consumer onOpenEffects) { + JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane.addTab("View", cameraPanel); + tabbedPane.addTab("Capture", new RecordingPane(cameraPanel, videoRecorder)); + tabbedPane.addTab("Effects", new JPanel()); + + tabbedPane.addChangeListener(e -> { + if (tabbedPane.getSelectedIndex() == 2) { + tabbedPane.setSelectedIndex(0); + onOpenEffects.accept(this); + } + }); + + add(tabbedPane); + setSize(600, 500); + } + + private JPanel createCapturePanel() { + JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10)); // Changed to Grid for better button layout + panel.setBorder(new EmptyBorder(15, 15, 15, 15)); + + JButton screenshotBtn = new JButton("Take Screenshot"); + screenshotBtn.addActionListener(e -> saveSnapshot()); + + recordBtn = new JButton("Start Recording"); + recordBtn.addActionListener(e -> toggleRecording()); + + panel.add(screenshotBtn); + panel.add(recordBtn); + + // Wrap in a wrapper to prevent buttons from stretching too much + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.add(panel, BorderLayout.NORTH); + return wrapper; + } + + private void toggleRecording() { + if (!videoRecorder.isRecording()) { + startVideo(); + } else { + stopVideo(); + } + } + + private void startVideo() { + try { + File file = new File(System.getProperty("user.home"), "vid_" + System.currentTimeMillis() + ".mp4"); + videoRecorder.startRecording(cameraPanel, file); + + recordBtn.setText("Stop Recording"); + recordBtn.setForeground(Color.RED); + } catch (IOException ex) { + showError("Failed to start recording", ex); + } + } + + private void stopVideo() { + try { + File savedFile = videoRecorder.stopRecording(); + + recordBtn.setText("Start Recording"); + recordBtn.setForeground(Color.BLACK); + + JOptionPane.showMessageDialog(this, "Video saved to: " + savedFile.getAbsolutePath()); + } catch (IOException ex) { + showError("Failed to stop recording safely", ex); + } + } + + private void saveSnapshot() { + BufferedImage img = cameraPanel.getCurrentProcessedImage(); + if (img != null) { + try { + File file = new File(System.getProperty("user.home"), "snap_" + System.currentTimeMillis() + ".png"); + ImageIO.write(img, "PNG", file); + } catch (Exception ex) { + showError("Snapshot failed", ex); + } + } + } + + private void showError(String title, Exception ex) { + JOptionPane.showMessageDialog(this, title + "\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + + public CameraPanel getCameraPanel() { return cameraPanel; } + + @Override + public void dispose() { + // Safety check: stop recording if the window is closed + if (videoRecorder.isRecording()) { + try { videoRecorder.stopRecording(); } catch (IOException ignored) {} + } + captureLoop.stop(); + super.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java new file mode 100644 index 0000000..f317395 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java @@ -0,0 +1,66 @@ +package io.swtc.proccessing.ui.iframe; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.CubicCurve2D; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class DesktopPane extends JDesktopPane { + private final Map connections; + private final Map connectionColors = new HashMap<>(); + + public DesktopPane(Map connections) { + this.connections = connections; + } + + private Color getConnectionColor(JInternalFrame frame) { + return connectionColors.computeIfAbsent(frame, k -> { + Random rand = new Random(); + return new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256), 200); + }); + } + + public void forgetFrame(JInternalFrame frame) { + connectionColors.remove(frame); + } + + @Override + protected void paintChildren(Graphics g) { + super.paintChildren(g); + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + for (Map.Entry entry : connections.entrySet()) { + JInternalFrame camera = entry.getKey(); + EffectsPanelFrame effects = entry.getValue(); + + if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) { + g2d.setColor(getConnectionColor(camera)); + drawBezierConnection(g2d, camera, effects); + } + } + g2d.dispose(); + } + + private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) { + Rectangle f = from.getBounds(); + Rectangle t = to.getBounds(); + + int x1 = f.x + f.width; + int y1 = f.y + (f.height / 2); + int x2 = t.x; + int y2 = t.y + (t.height / 2); + + int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150); + CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2); + + g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g2d.draw(curve); + + // Terminals + g2d.fillOval(x1 - 5, y1 - 5, 10, 10); + g2d.fillOval(x2 - 5, y2 - 5, 10, 10); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java new file mode 100644 index 0000000..a6533c1 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java @@ -0,0 +1,14 @@ +package io.swtc.proccessing.ui.iframe; + +import io.swtc.proccessing.CameraPanel; +import io.swtc.proccessing.FilterPanel; +import javax.swing.*; + +public class EffectsPanelFrame extends JInternalFrame { + public EffectsPanelFrame(String title, CameraPanel cameraPanel) { + super(title, true, true, true, true); + setDefaultCloseOperation(HIDE_ON_CLOSE); + add(new FilterPanel(cameraPanel)); + setSize(350, 600); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java new file mode 100644 index 0000000..22b7d93 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java @@ -0,0 +1,126 @@ +package io.swtc.proccessing.ui.iframe; + +import io.swtc.proccessing.CameraPanel; +import io.swtc.recording.VideoRecorder; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class RecordingPane extends JPanel { + private final VideoRecorder videoRecorder; + private final CameraPanel cameraPanel; + + private JTextField pathField; + private JButton recordBtn; + private JLabel statusLabel; + private File outputDirectory; + + public RecordingPane(CameraPanel cameraPanel, VideoRecorder videoRecorder) { + this.cameraPanel = cameraPanel; + this.videoRecorder = videoRecorder; + this.outputDirectory = new File(System.getProperty("user.home")); + + setLayout(new GridBagLayout()); + + JPanel contentPanel = new JPanel(); + contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); + contentPanel.setPreferredSize(new Dimension(400, 250)); + + // Add the functional sections + contentPanel.add(createStoragePanel()); + contentPanel.add(Box.createVerticalStrut(15)); + contentPanel.add(createActionPanel()); + contentPanel.add(Box.createVerticalStrut(15)); + contentPanel.add(createStatusPanel()); + + add(contentPanel); + } + + private JPanel createStoragePanel() { + JPanel panel = new JPanel(new BorderLayout(5, 5)); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Storage Settings", TitledBorder.LEFT, TitledBorder.TOP)); + + pathField = new JTextField(outputDirectory.getAbsolutePath()); + pathField.setEditable(false); + + JButton browseBtn = new JButton("Browse..."); + browseBtn.addActionListener(e -> { + JFileChooser chooser = new JFileChooser(outputDirectory); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + outputDirectory = chooser.getSelectedFile(); + pathField.setText(outputDirectory.getAbsolutePath()); + } + }); + + panel.add(pathField, BorderLayout.CENTER); + panel.add(browseBtn, BorderLayout.EAST); + return panel; + } + + private JPanel createActionPanel() { + JPanel panel = new JPanel(new GridLayout(1, 2, 10, 10)); + + recordBtn = new JButton("start recording"); + recordBtn.addActionListener(e -> toggleRecording()); + + JButton snapBtn = new JButton("take snapshot"); + snapBtn.addActionListener(e -> takeSnapshot()); + + panel.add(recordBtn); + panel.add(snapBtn); + return panel; + } + + private JPanel createStatusPanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + statusLabel = new JLabel(""); + statusLabel.setForeground(Color.DARK_GRAY); + panel.add(statusLabel); + return panel; + } + + private void toggleRecording() { + if (!videoRecorder.isRecording()) { + try { + File file = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4"); + videoRecorder.startRecording(cameraPanel, file); + recordBtn.setText("stop recording"); + statusLabel.setText("recording"); + } catch (IOException ex) { + showError("Start Error", ex); + } + } else { + try { + File saved = videoRecorder.stopRecording(); + recordBtn.setText("Start Recording"); + statusLabel.setText("Status: Saved " + saved.getName()); + } catch (IOException ex) { + showError("Stop Error", ex); + } + } + } + + private void takeSnapshot() { + BufferedImage img = cameraPanel.getCurrentProcessedImage(); + if (img != null) { + try { + File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png"); + ImageIO.write(img, "PNG", file); + statusLabel.setText("captured"); + } catch (IOException ex) { + showError("Snapshot Error", ex); + } + } + } + + private void showError(String title, Exception ex) { + JOptionPane.showMessageDialog(this, ex.getMessage(), title, JOptionPane.ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java index ffdc94d..08f73c2 100644 --- a/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java +++ b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java @@ -16,7 +16,6 @@ public class DNRSection extends FilterSection { spatial = addSlider("Spatial Strength", 0, 100, 30, "%"); temporal = addSlider("Temporal Strength", 0, 100, 50, "%"); - // Logic: Disable sliders if DNR is off enabled.addActionListener(e -> { updateEnabledStates(); onUpdate.run(); @@ -25,7 +24,7 @@ public class DNRSection extends FilterSection { spatial.addChangeListener(e -> { if(!spatial.getSlider().getValueIsAdjusting()) onUpdate.run(); }); temporal.addChangeListener(e -> { if(!temporal.getSlider().getValueIsAdjusting()) onUpdate.run(); }); - add(enabled, 0); // Checkbox at top + add(enabled, 0); updateEnabledStates(); } @@ -35,9 +34,18 @@ public class DNRSection extends FilterSection { temporal.getSlider().setEnabled(active); } - public boolean isDnrEnabled() { return enabled.isSelected(); } - public int getSpatial() { return spatial.getValue(); } - public int getTemporal() { return temporal.getValue(); } + @Override + public boolean isEnabled() { + return enabled.isSelected(); + } + + public int getSpatial() { + return enabled.isSelected() ? spatial.getValue() : 0; + } + + public int getTemporal() { + return enabled.isSelected() ? temporal.getValue() : 0; + } public void setState(boolean isEnabled, int s, int t) { enabled.setSelected(isEnabled); diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java index d051b24..98d3f18 100644 --- a/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java +++ b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java @@ -23,8 +23,13 @@ public class DetailSection extends FilterSection { add(edgeEnhance); } - public int getSharpness() { return sharpness.getValue(); } - public boolean isEdgeEnhanceEnabled() { return edgeEnhance.isSelected(); } + public int getSharpness() { + return sharpness.getValue(); + } + + public boolean isEdgeEnhanceEnabled() { + return edgeEnhance != null && edgeEnhance.isSelected(); + } public void setState(int sharpVal, boolean edgeActive) { sharpness.setValue(sharpVal); diff --git a/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java index 10a329a..e0e77c8 100644 --- a/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java +++ b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java @@ -38,8 +38,14 @@ public class WhiteBalanceSection extends FilterSection { balanceBtn.setEnabled(active); } - public boolean isEnabled() { return enabled.isSelected(); } - public int getStrength() { return strength.getValue(); } + @Override + public boolean isEnabled() { + return enabled != null && enabled.isSelected(); + } + + public int getStrength() { + return enabled.isSelected() ? strength.getValue() : 0; + } public void setState(boolean isEnabled, int str) { enabled.setSelected(isEnabled); diff --git a/src/test/java/CameraSettingsTest.java b/src/test/java/CameraSettingsTest.java deleted file mode 100644 index 0abe0ec..0000000 --- a/src/test/java/CameraSettingsTest.java +++ /dev/null @@ -1,98 +0,0 @@ -import io.swtc.networking.CameraConfig; -import io.swtc.networking.CameraSettings; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class CameraSettingsTest { - - // Must match the filename used in CameraSettings.java - private final File TEST_FILE = new File("network_cameras.json"); - - @BeforeEach - @AfterEach - void cleanUp() { - // Ensure we start and end with a clean slate to avoid side effects - if (TEST_FILE.exists()) { - TEST_FILE.delete(); - } - } - - @Test - void testLoadReturnsEmptyListWhenNoFile() { - // If the file doesn't exist, it should return an empty list (not null) - List result = CameraSettings.load(); - - assertNotNull(result, "Load should never return null"); - assertTrue(result.isEmpty(), "Should return empty list if file doesn't exist"); - } - - @Test - void testSaveAndLoad() { - // 1. Create a config (Using your actual constructor) - CameraConfig config = new CameraConfig("FrontDoor", "http://192.168.1.100/mjpeg"); - - // 2. Save it - CameraSettings.save(config); - - // 3. Verify file creation - assertTrue(TEST_FILE.exists(), "File should be created after save"); - - // 4. Load it back - List loaded = CameraSettings.load(); - - // 5. Verify contents - assertEquals(1, loaded.size()); - assertEquals("FrontDoor", loaded.get(0).getName()); - assertEquals("http://192.168.1.100/mjpeg", loaded.get(0).getUrl()); - } - - @Test - void testSaveMultiple() { - // Save two distinct cameras - CameraSettings.save(new CameraConfig("Cam1", "rtsp://10.0.0.1/stream")); - CameraSettings.save(new CameraConfig("Cam2", "rtsp://10.0.0.2/stream")); - - List loaded = CameraSettings.load(); - - assertEquals(2, loaded.size()); - assertEquals("Cam1", loaded.get(0).getName()); - assertEquals("Cam2", loaded.get(1).getName()); - } - - @Test - void testDelete() { - // Setup: Save two cameras - CameraSettings.save(new CameraConfig("Garage", "http://1.1.1.1")); - CameraSettings.save(new CameraConfig("Garden", "http://2.2.2.2")); - - // Action: Delete "Garage" - CameraSettings.delete("Garage"); - - // Verify: Only "Garden" remains - List result = CameraSettings.load(); - assertEquals(1, result.size()); - assertEquals("Garden", result.get(0).getName()); - } - - @Test - void testLoadCorruptFile() throws IOException { - // Manually write broken JSON to the file - try (FileWriter writer = new FileWriter(TEST_FILE)) { - writer.write("{ \"this is broken json\": ... "); - } - - // The code catches IOException and returns empty list - List result = CameraSettings.load(); - - assertNotNull(result); - assertTrue(result.isEmpty(), "Should handle corrupt JSON gracefully by returning empty list"); - } -} \ No newline at end of file