[libbluray-devel] [Git][videolan/libbluray][master] BD-J: HAVI navigable component implementations

Petri Hintukainen (@hpi) gitlab at videolan.org
Mon Jun 8 16:50:30 UTC 2026



Petri Hintukainen pushed to branch master at VideoLAN / libbluray


Commits:
4adb2273 by spotter at 2026-06-08T16:50:21+00:00
BD-J: HAVI navigable component implementations

- - - - -


4 changed files:

- src/libbluray/bdj/java/org/havi/ui/HComponent.java
- src/libbluray/bdj/java/org/havi/ui/HIcon.java
- + src/libbluray/bdj/java/org/havi/ui/HNavigableHelper.java
- src/libbluray/bdj/java/org/havi/ui/HText.java


Changes:

=====================================
src/libbluray/bdj/java/org/havi/ui/HComponent.java
=====================================
@@ -21,7 +21,10 @@
 package org.havi.ui;
 
 import java.awt.Component;
+import java.awt.event.FocusEvent;
+
 import org.dvb.ui.TestOpacity;
+import org.havi.ui.event.HFocusEvent;
 
 import java.awt.BDToolkit;
 
@@ -63,6 +66,18 @@ public abstract class HComponent extends Component implements HMatteLayer, TestO
         return super.isEnabled();
     }
 
+    /**
+     * Override AWT focus processing to create HFocusEvent and delegate to processHFocusEvent
+     * for all HNavigationInputPreferred implementations.
+     */
+    protected void processFocusEvent(FocusEvent e) {
+        if (this instanceof HNavigationInputPreferred) {
+            HFocusEvent hEvent = new HFocusEvent(this, e.getID());
+            ((HNavigationInputPreferred) this).processHFocusEvent(hEvent);
+        }
+        super.processFocusEvent(e);
+    }
+
     private HMatte matte = null;
 
     private static final long serialVersionUID = -4115249517434074428L;


=====================================
src/libbluray/bdj/java/org/havi/ui/HIcon.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,29 +21,42 @@
 package org.havi.ui;
 
 import java.awt.Image;
+import java.awt.event.KeyEvent;
 
 import org.havi.ui.event.HFocusEvent;
 import org.havi.ui.event.HFocusListener;
 
 import org.videolan.BDJXletContext;
-import org.videolan.Logger;
 
 public class HIcon extends HStaticIcon implements HNavigable {
+
+    private HNavigableHelper helper;
+
     public HIcon() {
-        org.videolan.Logger.unimplemented(HIcon.class.getName(), "");
+        super();
+        init();
     }
 
     public HIcon(Image image) {
-        org.videolan.Logger.unimplemented(HIcon.class.getName(), "");
+        super(image);
+        init();
     }
 
     public HIcon(Image image, int x, int y, int width, int height) {
-        org.videolan.Logger.unimplemented(HIcon.class.getName(), "");
+        super(image, x, y, width, height);
+        init();
+    }
+
+    public HIcon(Image imageNormal, Image imageFocus, int x, int y, int width, int height) {
+        super(imageNormal, x, y, width, height);
+        setGraphicContent(imageFocus, FOCUSED_STATE);
+        init();
     }
 
-    public HIcon(Image imageNormal, Image imageFocus, int x, int y, int width,
-            int height) {
-        org.videolan.Logger.unimplemented(HIcon.class.getName(), "");
+    private void init() {
+        helper = new HNavigableHelper(this);
+        // Enable key and focus events so processKeyEvent/processFocusEvent are called
+        enableEvents(java.awt.AWTEvent.KEY_EVENT_MASK | java.awt.AWTEvent.FOCUS_EVENT_MASK);
     }
 
     public static void setDefaultLook(HGraphicLook hlook) {
@@ -53,58 +67,79 @@ public class HIcon extends HStaticIcon implements HNavigable {
         return (HGraphicLook)BDJXletContext.getXletDefaultLook(PROPERTY_LOOK, DEFAULT_LOOK);
     }
 
+    // --- HNavigable implementation ---
+
     public void setMove(int keyCode, HNavigable target) {
-        Logger.unimplemented("", "");
+        helper.setMove(keyCode, target);
     }
 
     public HNavigable getMove(int keyCode) {
-        Logger.unimplemented("", "");
-        return null;
+        return helper.getMove(keyCode);
     }
 
     public void setFocusTraversal(HNavigable up, HNavigable down,
             HNavigable left, HNavigable right) {
-        Logger.unimplemented("", "");
+        helper.setFocusTraversal(up, down, left, right);
     }
 
     public boolean isSelected() {
-        Logger.unimplemented("", "");
-        return false;
+        return helper.isSelected();
     }
 
     public void setGainFocusSound(HSound sound) {
-        Logger.unimplemented("", "");
+        helper.setGainFocusSound(sound);
     }
 
     public void setLoseFocusSound(HSound sound) {
-        Logger.unimplemented("", "");
+        helper.setLoseFocusSound(sound);
     }
 
     public HSound getGainFocusSound() {
-        Logger.unimplemented("", "");
-        return null;
+        return helper.getGainFocusSound();
     }
 
     public HSound getLoseFocusSound() {
-        Logger.unimplemented("", "");
-        return null;
+        return helper.getLoseFocusSound();
     }
 
     public void addHFocusListener(HFocusListener l) {
-        Logger.unimplemented("", "");
+        helper.addHFocusListener(l);
     }
 
     public void removeHFocusListener(HFocusListener l) {
-        Logger.unimplemented("", "");
+        helper.removeHFocusListener(l);
     }
 
     public int[] getNavigationKeys() {
-        Logger.unimplemented("", "");
-        return null;
+        return helper.getNavigationKeys();
     }
 
+    // --- Focus event handling ---
+
+    /**
+     * Process HAVI focus events. Updates interaction state and notifies listeners.
+     */
     public void processHFocusEvent(HFocusEvent evt) {
-        Logger.unimplemented("", "");
+        int state = getInteractionState();
+        int newState = helper.processHFocusEvent(evt);
+
+        if (state != newState) {
+            setInteractionState(newState);
+        }
+    }
+
+    /**
+     * Process key events for HAVI navigation.
+     * Delegates to HNavigableHelper for arrow key handling.
+     */
+    protected void processKeyEvent(KeyEvent e) {
+        // Let helper handle navigation keys
+        if (helper.processKeyEvent(e)) {
+            e.consume();
+            return;
+        }
+
+        super.processKeyEvent(e);
     }
 
     static final Class DEFAULT_LOOK = HGraphicLook.class;


=====================================
src/libbluray/bdj/java/org/havi/ui/HNavigableHelper.java
=====================================
@@ -0,0 +1,209 @@
+/*
+ * This file is part of libbluray
+ * 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
+ * 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.Component;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyEvent;
+import java.util.Hashtable;
+
+import org.havi.ui.event.HFocusEvent;
+import org.havi.ui.event.HFocusListener;
+
+/**
+ * Helper class that implements HNavigable functionality.
+ * This encapsulates the navigation logic for use by HIcon, HGraphicButton, etc.
+ */
+public final class HNavigableHelper {
+
+    private Hashtable navTargets = new Hashtable();
+    private HSound gainFocusSound;
+    private HSound loseFocusSound;
+    transient HFocusListener hFocusListener;
+
+    private HVisible hVisible;
+
+    public HNavigableHelper(HVisible hVisible) {
+        this.hVisible = hVisible;
+    }
+
+    /**
+     * Sets a navigation target for the specified key code.
+     * @param keyCode The key code (e.g., KeyEvent.VK_UP)
+     * @param target The HNavigable to transfer focus to, or null to remove
+     */
+    public void setMove(int keyCode, HNavigable target) {
+        Integer code = Integer.valueOf(keyCode);
+        navTargets.remove(code);
+        if (target != null) {
+            navTargets.put(code, target);
+        }
+    }
+
+    /**
+     * Gets the navigation target for the specified key code.
+     * @param keyCode The key code
+     * @return The HNavigable target, or null if not set
+     */
+    public HNavigable getMove(int keyCode) {
+        return (HNavigable) navTargets.get(Integer.valueOf(keyCode));
+    }
+
+    /**
+     * Sets navigation targets for all four arrow keys at once.
+     */
+    public void setFocusTraversal(HNavigable up, HNavigable down, HNavigable left, HNavigable right) {
+        setMove(KeyEvent.VK_UP, up);
+        setMove(KeyEvent.VK_DOWN, down);
+        setMove(KeyEvent.VK_LEFT, left);
+        setMove(KeyEvent.VK_RIGHT, right);
+    }
+
+    /**
+     * Returns true if this component currently has focus.
+     */
+    public boolean isSelected() {
+        return hVisible.hasFocus();
+    }
+
+    public void setGainFocusSound(HSound sound) {
+        gainFocusSound = sound;
+    }
+
+    public void setLoseFocusSound(HSound sound) {
+        loseFocusSound = sound;
+    }
+
+    public HSound getGainFocusSound() {
+        return gainFocusSound;
+    }
+
+    public HSound getLoseFocusSound() {
+        return loseFocusSound;
+    }
+
+    /**
+     * Adds an HFocusListener to receive focus events.
+     */
+    public synchronized void addHFocusListener(HFocusListener listener) {
+        hFocusListener = HEventMulticaster.add(hFocusListener, listener);
+    }
+
+    /**
+     * Removes an HFocusListener.
+     */
+    public synchronized void removeHFocusListener(HFocusListener listener) {
+        hFocusListener = HEventMulticaster.remove(hFocusListener, listener);
+    }
+
+    /**
+     * Returns an array of key codes for which navigation targets are set.
+     */
+    public int[] getNavigationKeys() {
+        if (navTargets.size() == 0) {
+            return null;
+        }
+        int[] keyCodes = new int[navTargets.size()];
+        java.util.Enumeration keys = navTargets.keys();
+        int i = 0;
+        while (keys.hasMoreElements()) {
+            Integer key = (Integer) keys.nextElement();
+            keyCodes[i++] = key.intValue();
+        }
+        return keyCodes;
+    }
+
+    /**
+     * Processes an HFocusEvent and returns the new interaction state.
+     * This is the core method that handles focus gain/loss and focus transfers.
+     *
+     * @param evt The HFocusEvent to process
+     * @return The new interaction state for the HVisible
+     */
+    public int processHFocusEvent(HFocusEvent evt) {
+        int state = hVisible.getInteractionState();
+
+        if (evt.getID() == FocusEvent.FOCUS_GAINED) {
+            // Set focused bit
+            state = state | HState.FOCUSED_STATE_BIT;
+
+            // Play gain focus sound
+            if (gainFocusSound != null) {
+                gainFocusSound.play();
+            }
+
+            // Notify HFocusListeners
+            if (hFocusListener != null) {
+                hFocusListener.focusGained(evt);
+            }
+        }
+        else if (evt.getID() == FocusEvent.FOCUS_LOST) {
+            // Clear focused bit (XOR to toggle off)
+            state = state & (~HState.FOCUSED_STATE_BIT);
+
+            // Play lose focus sound
+            if (loseFocusSound != null) {
+                loseFocusSound.play();
+            }
+
+            // Notify HFocusListeners
+            if (hFocusListener != null) {
+                hFocusListener.focusLost(evt);
+            }
+        }
+        else if (evt.getID() == HFocusEvent.FOCUS_TRANSFER &&
+                 evt.getTransferId() != HFocusEvent.NO_TRANSFER_ID) {
+            // Handle focus transfer to navigation target
+            HNavigable newNav = getMove(evt.getTransferId());
+
+            if (newNav instanceof Component) {
+                ((Component) newNav).requestFocus();
+            }
+        }
+
+        return state;
+    }
+
+    /**
+     * Processes a KeyEvent for navigation.
+     * Looks up navigation targets and transfers focus.
+     *
+     * @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;
+        }
+
+        // Look up navigation target for this key
+        HNavigable target = getMove(e.getKeyCode());
+
+        if (target != null && target instanceof Component) {
+            ((Component) target).requestFocus();
+            return true;
+        }
+
+        return false;
+    }
+}


=====================================
src/libbluray/bdj/java/org/havi/ui/HText.java
=====================================
@@ -2,6 +2,7 @@
  * This file is part of libbluray
  * Copyright (C) 2010  William Hahne
  * Copyright (C) 2013  Petri Hintukainen <phintuka at users.sourceforge.net>
+ * 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
@@ -22,6 +23,7 @@ package org.havi.ui;
 
 import java.awt.Color;
 import java.awt.Font;
+import java.awt.event.KeyEvent;
 
 import org.havi.ui.event.HFocusEvent;
 import org.havi.ui.event.HFocusListener;
@@ -30,6 +32,9 @@ import org.videolan.BDJXletContext;
 import org.videolan.Logger;
 
 public class HText extends HStaticText implements HNavigable {
+
+    private HNavigableHelper helper;
+
     public HText() {
         this(null);
     }
@@ -61,6 +66,8 @@ public class HText extends HStaticText implements HNavigable {
             super.setTextContent(textFocus, DISABLED_FOCUSED_STATE);
             super.setTextContent(textFocus, DISABLED_ACTIONED_FOCUSED_STATE);
         }
+
+        init();
     }
 
     public HText(String text, Font font, Color foreground, Color background,
@@ -88,62 +95,80 @@ public class HText extends HStaticText implements HNavigable {
         setTextLayoutManager(tlm);
     }
 
-    public void setMove(int keyCode, HNavigable target) {
-        logger.unimplemented("setMove");
+    private void init() {
+        helper = new HNavigableHelper(this);
+        // Enable key and focus events so processKeyEvent/processFocusEvent are called
+        enableEvents(java.awt.AWTEvent.KEY_EVENT_MASK | java.awt.AWTEvent.FOCUS_EVENT_MASK);
     }
 
-    public HNavigable getMove(int keyCode) {
-        logger.unimplemented("getMove");
-        return this;
+    // --- HNavigable implementation ---
+
+    public void setMove(int keyCode, HNavigable target) {
+        helper.setMove(keyCode, target);
     }
 
-    public boolean isFocusable() {
-        return true;
+    public HNavigable getMove(int keyCode) {
+        return helper.getMove(keyCode);
     }
 
     public void setFocusTraversal(HNavigable up, HNavigable down,
             HNavigable left, HNavigable right) {
-        logger.unimplemented("setFocusTraversal");
+        helper.setFocusTraversal(up, down, left, right);
     }
 
     public boolean isSelected() {
-        logger.unimplemented("isSelected");
-        return false;
+        return helper.isSelected();
     }
 
     public void setGainFocusSound(HSound sound) {
-        logger.unimplemented("setGainFocusSound");
+        helper.setGainFocusSound(sound);
     }
 
     public void setLoseFocusSound(HSound sound) {
-        logger.unimplemented("setLoseFocusSound");
+        helper.setLoseFocusSound(sound);
     }
 
     public HSound getGainFocusSound() {
-        logger.unimplemented("getGainFocusSound");
-        return null;
+        return helper.getGainFocusSound();
     }
 
     public HSound getLoseFocusSound() {
-        logger.unimplemented("getLoseFocusSound");
-        return null;
+        return helper.getLoseFocusSound();
     }
 
     public void addHFocusListener(HFocusListener l) {
-        logger.unimplemented("addHFocusListener");
+        helper.addHFocusListener(l);
     }
 
     public void removeHFocusListener(HFocusListener l) {
-        logger.unimplemented("removeHFocusListener");
+        helper.removeHFocusListener(l);
     }
 
     public int[] getNavigationKeys() {
-        logger.unimplemented("getNavigationKeys");
-        return null;
+        return helper.getNavigationKeys();
     }
 
+    // --- Focus event handling ---
+
     public void processHFocusEvent(HFocusEvent evt) {
-        logger.unimplemented("processHFocusEvent");
+        int state = getInteractionState();
+        int newState = helper.processHFocusEvent(evt);
+
+        if (state != newState) {
+            setInteractionState(newState);
+        }
+    }
+
+    /**
+     * Process key events for HAVI navigation.
+     * Delegates to HNavigableHelper for arrow key handling.
+     */
+    protected void processKeyEvent(KeyEvent e) {
+        if (helper.processKeyEvent(e)) {
+            e.consume();
+            return;
+        }
+        super.processKeyEvent(e);
     }
 
     public static void setDefaultLook(HTextLook hlook) {



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

-- 
View it on GitLab: https://code.videolan.org/videolan/libbluray/-/commit/4adb2273a72e0cee7d462f3d7df9e1c106159176
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