[libbluray-devel] [Git][videolan/libbluray][master] Actionable HAVI component implementation

Petri Hintukainen (@hpi) gitlab at videolan.org
Mon Jun 8 18:31:26 UTC 2026



Petri Hintukainen pushed to branch master at VideoLAN / libbluray


Commits:
db77318f by Shaya Potter at 2026-06-08T17:30:29+00:00
Actionable HAVI component implementation

- - - - -


3 changed files:

- + src/libbluray/bdj/java/org/havi/ui/HActionableHelper.java
- src/libbluray/bdj/java/org/havi/ui/HGraphicButton.java
- src/libbluray/bdj/java/org/havi/ui/HTextButton.java


Changes:

=====================================
src/libbluray/bdj/java/org/havi/ui/HActionableHelper.java
=====================================
@@ -0,0 +1,141 @@
+/*
+ * This file is part of libbluray
+ * Copyright (C) 2026 libbluray project
+ *
+ * This library 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Based on XletView implementation by Martin Sveden.
+ */
+
+package org.havi.ui;
+
+import java.awt.event.KeyEvent;
+
+import org.havi.ui.event.HActionEvent;
+import org.havi.ui.event.HActionListener;
+
+/**
+ * Helper class that implements HActionable functionality.
+ * This encapsulates the action handling logic for use by HGraphicButton, HTextButton, etc.
+ */
+public final class HActionableHelper {
+
+    transient HActionListener hActionListener;
+    private HSound actionSound;
+    private String actionCommand;
+    private HVisible hVisible;
+
+    public HActionableHelper(HVisible hVisible) {
+        this.hVisible = hVisible;
+    }
+
+    /**
+     * Adds an HActionListener to receive action events.
+     */
+    public synchronized void addHActionListener(HActionListener listener) {
+        if (listener == null) {
+            return;
+        }
+        hActionListener = HEventMulticaster.add(hActionListener, listener);
+    }
+
+    /**
+     * Removes an HActionListener.
+     */
+    public synchronized void removeHActionListener(HActionListener listener) {
+        if (listener == null) {
+            return;
+        }
+        hActionListener = HEventMulticaster.remove(hActionListener, listener);
+    }
+
+    /**
+     * Sets the action command string for action events.
+     */
+    public void setActionCommand(String command) {
+        actionCommand = command;
+    }
+
+    /**
+     * Gets the action command string.
+     */
+    public String getActionCommand() {
+        return actionCommand;
+    }
+
+    /**
+     * Sets the sound to play when an action occurs.
+     */
+    public void setActionSound(HSound sound) {
+        actionSound = sound;
+    }
+
+    /**
+     * Gets the action sound.
+     */
+    public HSound getActionSound() {
+        return actionSound;
+    }
+
+    /**
+     * Processes an HActionEvent and returns the new interaction state.
+     * Notifies registered HActionListeners and plays the action sound.
+     *
+     * @param evt The HActionEvent to process
+     * @return The current interaction state (unchanged by action events)
+     */
+    public int processHActionEvent(HActionEvent evt) {
+        // Get current state - action events don't change the state
+        int state = hVisible.getInteractionState();
+
+        // Play action sound
+        if (actionSound != null) {
+            actionSound.play();
+        }
+
+        // Notify action listeners
+        if (hActionListener != null) {
+            hActionListener.actionPerformed(evt);
+        }
+
+        return state;
+    }
+
+    /**
+     * Processes a KeyEvent for action.
+     * Handles ENTER key by firing an HActionEvent.
+     *
+     * @param e The KeyEvent to process
+     * @return true if the event was handled, false otherwise
+     */
+    public boolean processKeyEvent(KeyEvent e) {
+        // Only handle KEY_PRESSED
+        if (e.getID() != KeyEvent.KEY_PRESSED) {
+            return false;
+        }
+
+        // Only handle ENTER key
+        if (e.getKeyCode() != KeyEvent.VK_ENTER) {
+            return false;
+        }
+
+        // Fire action event (cast is safe - this helper is only used by HActionable components)
+        HActionEvent actionEvent = new HActionEvent(
+            (HActionInputPreferred) hVisible, HActionEvent.ACTION_PERFORMED, actionCommand);
+        processHActionEvent(actionEvent);
+
+        return true;
+    }
+}


=====================================
src/libbluray/bdj/java/org/havi/ui/HGraphicButton.java
=====================================
@@ -1,6 +1,7 @@
 /*
  * This file is part of libbluray
  * Copyright (C) 2010  William Hahne
+ * Copyright (C) 2026  libbluray project
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,130 +21,113 @@
 package org.havi.ui;
 
 import java.awt.Image;
+import java.awt.event.KeyEvent;
 
 import org.havi.ui.event.HActionEvent;
 import org.havi.ui.event.HActionListener;
 import org.havi.ui.event.HFocusEvent;
 import org.havi.ui.event.HFocusListener;
 
+import org.videolan.BDJXletContext;
+
 public class HGraphicButton extends HIcon implements HActionable {
+
+    private HActionableHelper actionHelper;
+
     public HGraphicButton() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        super();
+        initAction();
     }
 
     public HGraphicButton(Image image, int x, int y, int width, int height) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        super(image, x, y, width, height);
+        initAction();
     }
 
     public HGraphicButton(Image imageNormal, Image imageFocused,
             Image imageActioned, int x, int y, int width, int height) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        super(imageNormal, imageFocused, x, y, width, height);
+        setGraphicContent(imageActioned, ACTIONED_STATE);
+        initAction();
     }
 
     public HGraphicButton(Image image) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        super(image);
+        initAction();
     }
 
     public HGraphicButton(Image imageNormal, Image imageFocused,
             Image imageActioned) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
-
-    public static void setDefaultLook(HGraphicLook hlook) {
-        DefaultLook = hlook;
-    }
-
-    public static HGraphicLook getDefaultLook() {
-        if (DefaultLook == null)
-            org.videolan.Logger.unimplemented("", "getDefaultLook");
-        return DefaultLook;
-    }
-
-    public void setMove(int keyCode, HNavigable target) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
-
-    public HNavigable getMove(int keyCode) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return null;
-    }
-
-    public void setFocusTraversal(HNavigable up, HNavigable down,
-            HNavigable left, HNavigable right) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
-
-    public boolean isSelected() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return false;
+        this(imageNormal, imageFocused, imageActioned, 0, 0, 0, 0);
     }
 
-    public void setGainFocusSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+    private void initAction() {
+        actionHelper = new HActionableHelper(this);
     }
 
-    public void setLoseFocusSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
-
-    public HSound getGainFocusSound() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return null;
-    }
-
-    public HSound getLoseFocusSound() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return null;
-    }
-
-    public void addHFocusListener(HFocusListener l) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
-
-    public void removeHFocusListener(HFocusListener l) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+    public static void setDefaultLook(HGraphicLook hlook) {
+        BDJXletContext.setXletDefaultLook(PROPERTY_LOOK, hlook);
     }
 
-    public int[] getNavigationKeys() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return null;
+    public static HGraphicLook getDefaultLook() {
+        return (HGraphicLook)BDJXletContext.getXletDefaultLook(PROPERTY_LOOK, DEFAULT_LOOK);
     }
 
-    public void processHFocusEvent(HFocusEvent evt) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-    }
+    // --- HActionable implementation ---
 
     public void addHActionListener(HActionListener l) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        actionHelper.addHActionListener(l);
     }
 
     public void removeHActionListener(HActionListener l) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        actionHelper.removeHActionListener(l);
     }
 
     public void setActionCommand(String command) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        actionHelper.setActionCommand(command);
     }
 
     public void setActionSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        actionHelper.setActionSound(sound);
     }
 
     public HSound getActionSound() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return null;
+        return actionHelper.getActionSound();
     }
 
     public void processHActionEvent(HActionEvent evt) {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
+        int state = getInteractionState();
+        int newState = actionHelper.processHActionEvent(evt);
+
+        if (state != newState) {
+            setInteractionState(newState);
+        }
     }
 
     public String getActionCommand() {
-        org.videolan.Logger.unimplemented(HGraphicButton.class.getName(), "");
-        return "";
+        return actionHelper.getActionCommand();
+    }
+
+    // HNavigable methods are inherited from HIcon
+    // Focus handling is inherited from HIcon
+
+    /**
+     * Process key events for HAVI navigation and action.
+     * Handles ENTER via actionHelper, delegates navigation to parent.
+     */
+    protected void processKeyEvent(KeyEvent e) {
+        // Let actionHelper handle ENTER key
+        if (actionHelper.processKeyEvent(e)) {
+            e.consume();
+            return;
+        }
+
+        // Delegate navigation (arrows) to parent HIcon
+        super.processKeyEvent(e);
     }
 
-    private static HGraphicLook DefaultLook = null;
+    static final Class DEFAULT_LOOK = HGraphicLook.class;
+    private static final String PROPERTY_LOOK = HGraphicButton.class.getName();
 
     private static final long serialVersionUID = 5167775411684840800L;
 }


=====================================
src/libbluray/bdj/java/org/havi/ui/HTextButton.java
=====================================
@@ -1,6 +1,7 @@
 /*
  * This file is part of libbluray
  * Copyright (C) 2010  William Hahne
+ * Copyright (C) 2024  libbluray project
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,131 +22,111 @@ package org.havi.ui;
 
 import java.awt.Color;
 import java.awt.Font;
+import java.awt.event.KeyEvent;
+
 import org.havi.ui.event.HActionEvent;
 import org.havi.ui.event.HActionListener;
 import org.havi.ui.event.HFocusEvent;
 import org.havi.ui.event.HFocusListener;
 
+import org.videolan.BDJXletContext;
+
 public class HTextButton extends HText implements HActionable {
+
+    private HActionableHelper actionHelper;
+
     public HTextButton() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        super();
+        initAction();
     }
 
     public HTextButton(String text, int x, int y, int width, int height) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        super(text, x, y, width, height);
+        initAction();
     }
 
     public HTextButton(String text, int x, int y, int width, int height,
             Font font, Color foreground, Color background,
             HTextLayoutManager tlm) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        super(text, x, y, width, height, font, foreground, background, tlm);
+        initAction();
     }
 
     public HTextButton(String text) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        super(text);
+        initAction();
     }
 
     public HTextButton(String text, Font font, Color foreground,
             Color background, HTextLayoutManager tlm) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        super(text, font, foreground, background, tlm);
+        initAction();
     }
 
-    public static void setDefaultLook(HTextLook hlook) {
-        DefaultLook = hlook;
+    private void initAction() {
+        actionHelper = new HActionableHelper(this);
     }
 
-    public static HTextLook getDefaultLook() {
-        if (DefaultLook == null)
-            org.videolan.Logger.unimplemented("", "getDefaultLook");
-        return DefaultLook;
-    }
-
-    public void setMove(int keyCode, HNavigable target) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
-
-    public HNavigable getMove(int keyCode)
-    {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
-    }
-
-    public void setFocusTraversal(HNavigable up, HNavigable down,
-            HNavigable left, HNavigable right) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
-
-    public boolean isSelected() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return false;
-    }
-
-    public void setGainFocusSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
-
-    public void setLoseFocusSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
-
-    public HSound getGainFocusSound() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
-    }
-
-    public HSound getLoseFocusSound() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
-    }
-
-    public void addHFocusListener(HFocusListener l) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
-
-    public void removeHFocusListener(HFocusListener l) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+    public static void setDefaultLook(HTextLook hlook) {
+        BDJXletContext.setXletDefaultLook(PROPERTY_LOOK, hlook);
     }
 
-    public int[] getNavigationKeys() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
+    public static HTextLook getDefaultLook() {
+        return (HTextLook) BDJXletContext.getXletDefaultLook(PROPERTY_LOOK, DEFAULT_LOOK);
     }
 
-    public void processHFocusEvent(HFocusEvent evt) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-    }
+    // --- HActionable implementation ---
 
     public void addHActionListener(HActionListener l) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        actionHelper.addHActionListener(l);
     }
 
     public void removeHActionListener(HActionListener l) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        actionHelper.removeHActionListener(l);
     }
 
     public void setActionCommand(String command) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        actionHelper.setActionCommand(command);
     }
 
     public void setActionSound(HSound sound) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        actionHelper.setActionSound(sound);
     }
 
     public HSound getActionSound() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
+        return actionHelper.getActionSound();
     }
 
     public void processHActionEvent(HActionEvent evt) {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
+        int state = getInteractionState();
+        int newState = actionHelper.processHActionEvent(evt);
+
+        if (state != newState) {
+            setInteractionState(newState);
+        }
     }
 
     public String getActionCommand() {
-        org.videolan.Logger.unimplemented(HTextButton.class.getName(), "");
-        return null;
+        return actionHelper.getActionCommand();
+    }
+
+    // HNavigable methods are inherited from HText
+    // Focus handling is inherited from HText
+
+    /**
+     * Process key events for HAVI navigation and action.
+     * Handles ENTER via actionHelper, delegates navigation to parent.
+     */
+    protected void processKeyEvent(KeyEvent e) {
+        if (actionHelper.processKeyEvent(e)) {
+            e.consume();
+            return;
+        }
+        super.processKeyEvent(e);
     }
 
-    private static HTextLook DefaultLook = null;
+    static final Class DEFAULT_LOOK = HTextLook.class;
+    private static final String PROPERTY_LOOK = HTextButton.class.getName();
 
     private static final long serialVersionUID = 7563558661769889160L;
 }



View it on GitLab: https://code.videolan.org/videolan/libbluray/-/commit/db77318fc4cb4720187983e67b689bdbae033eeb

-- 
View it on GitLab: https://code.videolan.org/videolan/libbluray/-/commit/db77318fc4cb4720187983e67b689bdbae033eeb
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the libbluray-devel mailing list