diff --git a/configure.ac b/configure.ac index 2a9d5317f4..72ee6513de 100644 --- a/configure.ac +++ b/configure.ac @@ -233,6 +233,33 @@ AC_ARG_ENABLE([qt-peer], [COMPILE_QT_PEER=no]) AM_CONDITIONAL(CREATE_QT_PEER_LIBRARIES, test "x${COMPILE_QT_PEER}" = xyes) +dnl ----------------------------------------------------------- +dnl Haiku AWT and sound peer (enabled by default on Haiku) +dnl ----------------------------------------------------------- +AC_ARG_ENABLE([haiku-peer], + [AS_HELP_STRING(--enable-haiku-peer,compile the Haiku AWT and sound peer (disabled by --disable-jni) [default=yes on Haiku])], + [case "${enableval}" in + yes) COMPILE_HAIKU_PEER=yes ;; + no) COMPILE_HAIKU_PEER=no ;; + *) COMPILE_HAIKU_PEER=yes ;; + esac], + [case "${host_os}" in + haiku*) COMPILE_HAIKU_PEER=yes ;; + *) COMPILE_HAIKU_PEER=no ;; + esac]) +if test "x${COMPILE_JNI}" != xyes; then + COMPILE_HAIKU_PEER=no +fi +AM_CONDITIONAL(CREATE_HAIKU_PEER_LIBRARIES, test "x${COMPILE_HAIKU_PEER}" = xyes) +if test "x${COMPILE_HAIKU_PEER}" = xyes; then + dnl The Haiku peer is the default toolkit and sound mixer when it is built. + if test "x${enable_default_toolkit}" = x; then + default_toolkit=gnu.java.awt.peer.haiku.HaikuToolkit + fi + HAIKU_MIXER_PROVIDER=gnu.javax.sound.sampled.haiku.HaikuMixerProvider +fi +AC_SUBST(HAIKU_MIXER_PROVIDER) + dnl ----------------------------------------------------------- dnl Plugin (disabled by default) dnl ----------------------------------------------------------- @@ -1142,6 +1169,7 @@ native/jni/gtk-peer/Makefile native/jni/gconf-peer/Makefile native/jni/gstreamer-peer/Makefile native/jni/qt-peer/Makefile +native/jni/haiku-peer/Makefile native/jni/xmlj/Makefile native/jni/midi-alsa/Makefile native/jni/midi-dssi/Makefile diff --git a/gnu/java/awt/peer/haiku/HaikuButtonPeer.java b/gnu/java/awt/peer/haiku/HaikuButtonPeer.java new file mode 100644 index 0000000000..99775ea030 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuButtonPeer.java @@ -0,0 +1,97 @@ +/* HaikuButtonPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingButtonPeer; + +import java.awt.Button; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractButton; + +/** + * A button drawn by Swing. + * + * SwingButtonPeer calls the AWT listeners directly. This peer posts the + * ActionEvent instead, so that AWT 1.0 style action() and handleEvent() + * methods are called too. + */ +public class HaikuButtonPeer + extends SwingButtonPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + HaikuButtonPeer(final Button b) + { + super(b); + AbstractButton jb = (AbstractButton) swingComponent.getJComponent(); + ActionListener[] l = jb.getActionListeners(); + for (int i = 0; i < l.length; i++) + jb.removeActionListener(l[i]); + jb.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + HaikuSwingWrapper.post(b, + new ActionEvent(b, ActionEvent.ACTION_PERFORMED, + b.getActionCommand(), e.getWhen(), + e.getModifiers()), null); + } + }); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuCheckboxMenuItemPeer.java b/gnu/java/awt/peer/haiku/HaikuCheckboxMenuItemPeer.java new file mode 100644 index 0000000000..a23ae3b70c --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuCheckboxMenuItemPeer.java @@ -0,0 +1,86 @@ +/* HaikuCheckboxMenuItemPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.CheckboxMenuItem; +import java.awt.EventQueue; +import java.awt.event.ItemEvent; +import java.awt.peer.CheckboxMenuItemPeer; + +/** + * A checkbox menu item: a BMenuItem that shows a check mark. + */ +public class HaikuCheckboxMenuItemPeer + extends HaikuMenuItemPeer + implements CheckboxMenuItemPeer +{ + + HaikuCheckboxMenuItemPeer(CheckboxMenuItem item) + { + super(item); + setState(item.getState()); + } + + public void setState(boolean state) + { + if (item != 0) + HaikuNative.menuItemSetMarked(item, state); + } + + /** + * Toggles the state and posts an ItemEvent, like other toolkits do. + */ + void activated(int modifiers) + { + EventQueue.invokeLater(new Runnable() + { + public void run() + { + CheckboxMenuItem cb = (CheckboxMenuItem) awtItem; + boolean state = ! cb.getState(); + cb.setState(state); + cb.dispatchEvent(new ItemEvent(cb, ItemEvent.ITEM_STATE_CHANGED, + cb.getLabel(), + state ? ItemEvent.SELECTED + : ItemEvent.DESELECTED)); + } + }); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuCheckboxPeer.java b/gnu/java/awt/peer/haiku/HaikuCheckboxPeer.java new file mode 100644 index 0000000000..04f20d1a97 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuCheckboxPeer.java @@ -0,0 +1,185 @@ +/* HaikuCheckboxPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingCheckboxPeer; + +import java.awt.Checkbox; +import java.awt.CheckboxGroup; +import java.awt.Event; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; + +import javax.swing.AbstractButton; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.JToggleButton; + +/** + * A checkbox drawn by Swing. In a CheckboxGroup it is drawn as a radio + * button and cannot be cleared by a click. + * + * SwingCheckboxPeer calls the AWT listeners directly and has no + * CheckboxGroup support. This peer posts the ItemEvent, and an AWT 1.0 + * ACTION_EVENT for components that use the old event model. + */ +public class HaikuCheckboxPeer + extends SwingCheckboxPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + /** + * A radio button icon. The look and feel icons need a JRadioButton. + */ + private static final Icon RADIO = new Icon() + { + public int getIconWidth() + { + return 13; + } + + public int getIconHeight() + { + return 13; + } + + public void paintIcon(Component c, Graphics g, int x, int y) + { + ButtonModel m = ((AbstractButton) c).getModel(); + boolean on = m.isEnabled(); + g.setColor(m.isArmed() && m.isPressed() ? Color.lightGray + : Color.white); + g.fillOval(x, y, 12, 12); + g.setColor(on ? Color.darkGray : Color.gray); + g.drawOval(x, y, 12, 12); + if (m.isSelected()) + { + g.setColor(on ? Color.black : Color.gray); + g.fillOval(x + 3, y + 3, 7, 7); + } + } + }; + + HaikuCheckboxPeer(final Checkbox cb) + { + super(cb); + final JToggleButton jb = (JToggleButton) swingComponent.getJComponent(); + ItemListener[] l = jb.getItemListeners(); + for (int i = 0; i < l.length; i++) + jb.removeItemListener(l[i]); + jb.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + boolean state = e.getStateChange() == ItemEvent.SELECTED; + if (cb.getCheckboxGroup() != null && ! state) + { + // A radio button stays on until another one is chosen. + setState(true); + return; + } + // Checkbox.setState() does not update the group. + if (cb.getCheckboxGroup() != null) + cb.getCheckboxGroup().setSelectedCheckbox(cb); + else + cb.setState(state); + ItemEvent ie = new ItemEvent(cb, ItemEvent.ITEM_STATE_CHANGED, + cb.getLabel(), + state ? ItemEvent.SELECTED + : ItemEvent.DESELECTED); + Event old = null; + if (cb.getItemListeners().length == 0) + old = new Event(cb, Event.ACTION_EVENT, Boolean.valueOf(state)); + HaikuSwingWrapper.post(cb, ie, old); + } + }); + setCheckboxGroup(cb.getCheckboxGroup()); + } + + public void setCheckboxGroup(CheckboxGroup group) + { + JToggleButton jb = (JToggleButton) swingComponent.getJComponent(); + jb.setIcon(group != null ? RADIO : null); + jb.repaint(); + } + + /** + * Sets the Swing state without item events. The listeners are taken + * off, because SwingCheckboxPeer calls this before this class replaced + * its listener. + */ + public void setState(boolean state) + { + JToggleButton jb = (JToggleButton) swingComponent.getJComponent(); + ItemListener[] l = jb.getItemListeners(); + for (int i = 0; i < l.length; i++) + jb.removeItemListener(l[i]); + try + { + jb.setSelected(state); + } + finally + { + for (int i = 0; i < l.length; i++) + jb.addItemListener(l[i]); + } + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuChoicePeer.java b/gnu/java/awt/peer/haiku/HaikuChoicePeer.java new file mode 100644 index 0000000000..965c5c01e8 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuChoicePeer.java @@ -0,0 +1,211 @@ +/* HaikuChoicePeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Choice; +import java.awt.Event; +import java.awt.Point; +import java.awt.event.ItemEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.peer.ChoicePeer; +import java.util.HashMap; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; + +/** + * A choice. A Swing JComboBox draws the closed choice; the list of items + * is a native BPopUpMenu, so no Swing popup window is needed. + */ +public class HaikuChoicePeer + extends HaikuSwingPeer + implements ChoicePeer +{ + + private static final HashMap choices = + new HashMap(); + + private static int nextId = 1; + + private final int id; + + private final Choice awtChoice; + + private final DefaultComboBoxModel model = new DefaultComboBoxModel(); + + private final JComboBox combo; + + HaikuChoicePeer(Choice choice) + { + awtChoice = choice; + synchronized (choices) + { + id = nextId++; + choices.put(new Integer(id), this); + } + for (int i = 0; i < choice.getItemCount(); i++) + model.addElement(choice.getItem(i)); + combo = new JComboBox(model); + combo.setFocusable(false); + if (choice.getSelectedIndex() >= 0) + combo.setSelectedIndex(choice.getSelectedIndex()); + initWrapped(choice, combo); + } + + static HaikuChoicePeer find(int id) + { + synchronized (choices) + { + return choices.get(new Integer(id)); + } + } + + protected void handleMouseEvent(MouseEvent e) + { + if (e.getID() == MouseEvent.MOUSE_PRESSED + && e.getButton() == MouseEvent.BUTTON1 && awtChoice.isEnabled()) + showPopup(); + } + + protected void handleMouseMotionEvent(MouseEvent e) + { + // The native popup tracks the mouse itself. + } + + protected void handleKeyEvent(KeyEvent e) + { + if (e.getID() != KeyEvent.KEY_PRESSED) + return; + int i = awtChoice.getSelectedIndex(); + switch (e.getKeyCode()) + { + case KeyEvent.VK_UP: + if (i > 0) + selected(i - 1); + break; + case KeyEvent.VK_DOWN: + if (i < awtChoice.getItemCount() - 1) + selected(i + 1); + break; + case KeyEvent.VK_SPACE: + case KeyEvent.VK_ENTER: + showPopup(); + break; + default: + break; + } + } + + private void showPopup() + { + int n = awtChoice.getItemCount(); + if (n == 0) + return; + String[] items = new String[n]; + for (int i = 0; i < n; i++) + items[i] = awtChoice.getItem(i); + Point p = getLocationOnScreen(); + HaikuNative.choicePopup(id, items, awtChoice.getSelectedIndex(), + p.x, p.y + awtChoice.getHeight()); + } + + /** + * Called when the user picked an item. + */ + void selected(final int index) + { + java.awt.EventQueue.invokeLater(new Runnable() + { + public void run() + { + if (index < 0 || index >= awtChoice.getItemCount() + || index == awtChoice.getSelectedIndex()) + return; + awtChoice.select(index); + String item = awtChoice.getItem(index); + ItemEvent e = new ItemEvent(awtChoice, + ItemEvent.ITEM_STATE_CHANGED, item, + ItemEvent.SELECTED); + Event old = null; + if (awtChoice.getItemListeners().length == 0) + old = new Event(awtChoice, Event.ACTION_EVENT, item); + HaikuSwingWrapper.post(awtChoice, e, old); + } + }); + } + + public void add(String item, int index) + { + if (index < 0 || index > model.getSize()) + index = model.getSize(); + model.insertElementAt(item, index); + } + + public void addItem(String item, int index) + { + add(item, index); + } + + public void remove(int index) + { + model.removeElementAt(index); + } + + public void removeAll() + { + model.removeAllElements(); + } + + public void select(int index) + { + if (index >= 0 && index < model.getSize()) + combo.setSelectedIndex(index); + } + + public void dispose() + { + synchronized (choices) + { + choices.remove(new Integer(id)); + } + super.dispose(); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuClipboard.java b/gnu/java/awt/peer/haiku/HaikuClipboard.java new file mode 100644 index 0000000000..f9136a4032 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuClipboard.java @@ -0,0 +1,93 @@ +/* HaikuClipboard.java -- the Haiku system clipboard + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.Transferable; + +/** + * The system clipboard, connected to Haiku's be_clipboard. Text, files, + * pictures and other MIME data are shared with other applications (see + * {@link HaikuTransferable}). Java objects stay inside this application. + */ +final class HaikuClipboard + extends Clipboard +{ + + /** The be_clipboard count when this clipboard last matched it. */ + private long count = -1; + + HaikuClipboard() + { + super("System"); + } + + /** Takes the native clipboard if another application changed it. */ + private void sync() + { + ClipboardOwner oldOwner = null; + Transferable oldContents = null; + synchronized (this) + { + if (HaikuNative.clipboardCount() == count) + return; + count = HaikuNative.clipboardSnapshot(); + oldOwner = owner; + oldContents = contents; + owner = null; + contents = HaikuTransferable.read(HaikuNative.SRC_CLIPBOARD); + } + if (oldOwner != null) + oldOwner.lostOwnership(this, oldContents); + } + + public synchronized void setContents(Transferable t, ClipboardOwner o) + { + super.setContents(t, o); + long msg = HaikuTransferable.toMessage(t, HaikuNative.B_MIME_DATA); + count = HaikuNative.clipboardPut(msg); + } + + /** Also used by getAvailableDataFlavors() and getData(). */ + public Transferable getContents(Object requestor) + { + sync(); + return super.getContents(requestor); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuCursor.java b/gnu/java/awt/peer/haiku/HaikuCursor.java new file mode 100644 index 0000000000..07849525a4 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuCursor.java @@ -0,0 +1,77 @@ +/* HaikuCursor.java -- a custom cursor for the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Cursor; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.image.BufferedImage; + +/** + * A custom cursor made with Toolkit.createCustomCursor(). It holds a + * native BCursor. + */ +final class HaikuCursor + extends Cursor +{ + + /** The native BCursor, or 0 if it could not be made. */ + final long handle; + + HaikuCursor(Image image, Point hotSpot, String name) + { + super(name); + int w = image.getWidth(null); + int h = image.getHeight(null); + long hdl = 0; + if (w > 0 && h > 0) + { + BufferedImage argb = new BufferedImage(w, h, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g = argb.createGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + int[] pixels = argb.getRGB(0, 0, w, h, null, 0, w); + int hx = Math.max(0, Math.min(w - 1, hotSpot.x)); + int hy = Math.max(0, Math.min(h - 1, hotSpot.y)); + hdl = HaikuNative.cursorCreate(pixels, w, h, hx, hy); + } + handle = hdl; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuDialogPeer.java b/gnu/java/awt/peer/haiku/HaikuDialogPeer.java new file mode 100644 index 0000000000..7421b8cf5f --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuDialogPeer.java @@ -0,0 +1,94 @@ +/* HaikuDialogPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import java.awt.Dialog; +import java.awt.peer.DialogPeer; + +/** + * A Dialog backed by a titled BWindow. + * + * GNU Classpath's Dialog.show() only blocks the caller. A shown modal + * dialog gets the modal app feel (it stays in front of the other windows + * of the application), and the event pump drops input to the windows it + * blocks. + */ +public class HaikuDialogPeer + extends HaikuWindowPeer + implements DialogPeer +{ + + HaikuDialogPeer(Dialog d) + { + super(d); + } + + boolean isModal() + { + return ((Dialog) getWindow()).isModal(); + } + + public void show() + { + if (isModal()) + { + setModalShown(true); + HaikuEventPump.get().modalShown(this); + } + super.show(); + } + + public void hide() + { + HaikuEventPump.get().modalHidden(this); + setModalShown(false); + super.hide(); + } + + public void dispose() + { + HaikuEventPump.get().modalHidden(this); + super.dispose(); + } + + public void setTitle(String title) + { + super.setTitle(title); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuDragDrop.java b/gnu/java/awt/peer/haiku/HaikuDragDrop.java new file mode 100644 index 0000000000..59fe93c56b --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuDragDrop.java @@ -0,0 +1,570 @@ +/* HaikuDragDrop.java -- drag and drop for the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Window; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragSourceContext; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetContext; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.InvalidDnDOperationException; +import java.awt.dnd.peer.DragSourceContextPeer; +import java.awt.dnd.peer.DropTargetContextPeer; + +/** + * Drag and drop. All drags, also between Java components, go through + * Haiku's drag system (BView::DragMessage), so Java can drag to and from + * Tracker and other applications. + * + * A Java drag carries "be:jdnd" (a drag id) and "be:jdnd_vm" (a number + * for this VM). When it is dropped on a Java window of the same VM, the + * drop target gets the original Transferable and the drag source learns + * the result. For drops on other applications the result is not known; + * the source is told that the copy succeeded. + */ +final class HaikuDragDrop +{ + + private static final int B_SHIFT_KEY = 0x1; + private static final int B_CONTROL_KEY = 0x4; + private static final int B_OPTION_KEY = 0x40; + + /** Marks the drags of this VM. */ + static final int VM_TOKEN = (int) (System.currentTimeMillis() ^ 0x5a3c96e1); + + // The drag that this VM started, if any. + private static int nextDragId = 1; + private static int dragId; + private static DragSourceContext dragContext; + private static boolean dragFinished = true; + + // The drop target under the mouse. + private static DropTarget target; + private static Component targetComponent; + private static DropContextPeer targetPeer; + + private HaikuDragDrop() + { + } + + // Drag source. + + static DragSourceContextPeer createDragSourceContextPeer() + { + return new SourcePeer(); + } + + private static final class SourcePeer + implements DragSourceContextPeer + { + private Cursor cursor; + + public void startDrag(DragSourceContext context, Cursor c, Image image, + Point offset) + { + cursor = c; + Component comp = context.getComponent(); + Window w = comp instanceof Window ? (Window) comp + : javax.swing.SwingUtilities.getWindowAncestor(comp); + if (w == null || ! (w.getPeer() instanceof HaikuWindowPeer)) + throw new InvalidDnDOperationException("no Haiku window"); + HaikuWindowPeer wp = (HaikuWindowPeer) w.getPeer(); + int id; + synchronized (HaikuDragDrop.class) + { + id = nextDragId++; + dragId = id; + dragContext = context; + dragFinished = false; + } + Transferable t = context.getTransferable(); + boolean files = t.isDataFlavorSupported(DataFlavor.javaFileListFlavor); + long msg = HaikuTransferable.toMessage(t, files + ? HaikuNative.B_SIMPLE_DATA + : HaikuNative.B_MIME_DATA); + HaikuNative.msgAddInt(msg, "be:jdnd", id); + HaikuNative.msgAddInt(msg, "be:jdnd_vm", VM_TOKEN); + HaikuNative.msgAddInt(msg, "be:jdnd_actions", + context.getSourceActions()); + // The drag origin in native window coordinates. + Point p = context.getTrigger().getDragOrigin(); + Point cs = comp.getLocationOnScreen(); + Point ws = w.getLocationOnScreen(); + Insets in = wp.insets(); + int x = p.x + cs.x - ws.x - in.left; + int y = p.y + cs.y - ws.y - in.top; + int[] dims = new int[2]; + int[] argb = image != null ? HaikuTransferable.pixels(image, dims) + : null; + int ox = offset != null ? -offset.x : 0; + int oy = offset != null ? -offset.y : 0; + HaikuNative.dragStart(wp.handle, msg, x - 16, y - 8, 32, 16, argb, + dims[0], dims[1], ox, oy); + } + + public Cursor getCursor() + { + return cursor; + } + + public void setCursor(Cursor c) + { + cursor = c; + } + + public void transferablesFlavorsChanged() + { + // The data was put in the drag message when the drag started. + } + } + + /** + * Called by the event pump when a mouse button is released. If a drag + * of this VM did not end on a Java drop target, it ended elsewhere: + * tell the source after a short time. + */ + static void mouseReleased() + { + final int id; + synchronized (HaikuDragDrop.class) + { + if (dragFinished) + return; + id = dragId; + } + Thread t = new Thread("Haiku drag end") + { + public void run() + { + // Give a drop on a Java window time to arrive. Thread.sleep() + // can return early on Kaffe, so check the clock. + long end = System.currentTimeMillis() + 600; + long now; + while ((now = System.currentTimeMillis()) < end) + { + try + { + Thread.sleep(end - now); + } + catch (InterruptedException e) + { + break; + } + } + DragSourceContext ctx; + synchronized (HaikuDragDrop.class) + { + ctx = dragContext; + } + int actions = ctx != null ? ctx.getSourceActions() : 0; + finishDrag(id, true, (actions & DnDConstants.ACTION_COPY) != 0 + ? DnDConstants.ACTION_COPY : actions); + } + }; + t.setDaemon(true); + t.start(); + } + + /** Tells the drag source how its drag ended (once). */ + static void finishDrag(int id, boolean success, int action) + { + final DragSourceContext ctx; + synchronized (HaikuDragDrop.class) + { + if (dragFinished || id != dragId) + return; + dragFinished = true; + ctx = dragContext; + dragContext = null; + } + if (ctx == null) + return; + final DragSourceDropEvent e = success + ? new DragSourceDropEvent(ctx, action, true) + : new DragSourceDropEvent(ctx); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + ctx.dragDropEnd(e); + } + }); + } + + // Drop target. + + /** The drag source context if src holds a drag of this VM. */ + private static DragSourceContext localContext(int src, int[] idOut) + { + if (HaikuNative.msgInt(src, "be:jdnd_vm", 0) != VM_TOKEN) + return null; + int id = HaikuNative.msgInt(src, "be:jdnd", -1); + synchronized (HaikuDragDrop.class) + { + if (id != dragId || dragFinished) + return null; + idOut[0] = id; + return dragContext; + } + } + + /** The deepest active drop target at window point (x, y). */ + private static Component targetAt(Window w, int x, int y, Point out) + { + Component c = w.findComponentAt(x, y); + while (c != null) + { + DropTarget dt = c.getDropTarget(); + if (dt != null && dt.isActive()) + break; + c = c.getParent(); + } + if (c == null) + return null; + int tx = x, ty = y; + for (Component p = c; p != null && p != w; p = p.getParent()) + { + tx -= p.getX(); + ty -= p.getY(); + } + out.x = tx; + out.y = ty; + return c; + } + + /** The user's drop action: Ctrl copies, else a move if allowed. */ + private static int userAction(int mods, int sourceActions) + { + int a; + if ((mods & B_CONTROL_KEY) != 0 && (mods & B_SHIFT_KEY) != 0) + a = DnDConstants.ACTION_LINK; + else if ((mods & B_CONTROL_KEY) != 0) + a = DnDConstants.ACTION_COPY; + else if ((sourceActions & DnDConstants.ACTION_MOVE) != 0) + a = DnDConstants.ACTION_MOVE; + else + a = DnDConstants.ACTION_COPY; + if ((a & sourceActions) == 0) + { + if ((sourceActions & DnDConstants.ACTION_COPY) != 0) + a = DnDConstants.ACTION_COPY; + else if ((sourceActions & DnDConstants.ACTION_MOVE) != 0) + a = DnDConstants.ACTION_MOVE; + else + a = sourceActions & DnDConstants.ACTION_LINK; + } + return a; + } + + /** + * Called by the event pump for a drag over a window: x, y are window + * coordinates. + */ + static void drag(Window w, int x, int y, int transit, int mods) + { + if (transit == HaikuNative.DRAG_EXIT) + { + exitTarget(); + return; + } + Point p = new Point(); + Component c = targetAt(w, x, y, p); + if (c != targetComponent) + { + exitTarget(); + if (c == null) + return; + int[] id = new int[1]; + DragSourceContext local = localContext(HaikuNative.SRC_DRAG, id); + int actions = local != null ? local.getSourceActions() + : DnDConstants.ACTION_COPY; + Transferable t = local != null ? local.getTransferable() + : HaikuTransferable.read(HaikuNative.SRC_DRAG); + targetComponent = c; + target = c.getDropTarget(); + targetPeer = new DropContextPeer(target, t, local != null, id[0], + actions); + final DropTargetContext dtc = target.getDropTargetContext(); + dtc.addNotify(targetPeer); + final DropTarget dt = target; + final DropTargetDragEvent e = + dragEvent(dtc, p, userAction(mods, actions), actions); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + dt.dragEnter(e); + } + }); + return; + } + if (c == null) + return; + final DropTarget dt = target; + final DropTargetDragEvent e = + dragEvent(dt.getDropTargetContext(), p, + userAction(mods, targetPeer.sourceActions), + targetPeer.sourceActions); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + dt.dragOver(e); + } + }); + } + + private static void exitTarget() + { + if (target == null) + return; + final DropTarget dt = target; + final DropTargetEvent e = new DropTargetEvent(dt.getDropTargetContext()); + target = null; + targetComponent = null; + targetPeer = null; + EventQueue.invokeLater(new Runnable() + { + public void run() + { + dt.dragExit(e); + } + }); + } + + /** Called by the event pump for a drop on a window. */ + static void drop(Window w, int x, int y, int mods) + { + int[] id = new int[1]; + DragSourceContext local = localContext(HaikuNative.SRC_DROP, id); + Point p = new Point(); + Component c = targetAt(w, x, y, p); + target = null; + targetComponent = null; + targetPeer = null; + if (c == null) + { + if (local != null) + finishDrag(id[0], false, DnDConstants.ACTION_NONE); + return; + } + int actions = local != null ? local.getSourceActions() + : DnDConstants.ACTION_COPY; + Transferable t = local != null ? local.getTransferable() + : HaikuTransferable.read(HaikuNative.SRC_DROP); + final DropTarget dt = c.getDropTarget(); + final DropContextPeer peer = new DropContextPeer(dt, t, local != null, + id[0], actions); + final DropTargetContext dtc = dt.getDropTargetContext(); + dtc.addNotify(peer); + final DropTargetDropEvent e = + dropEvent(dtc, p, userAction(mods, actions), actions, local != null); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + dt.drop(e); + peer.dropHandled(); + } + }); + } + + /** + * GNU Classpath 0.98's DropTargetDragEvent and DropTargetDropEvent test + * the source actions wrongly ("~(actions ^ mask) != 0"); only ~mask + * passes. The Classpath patch fixes the test. Without it, the events + * are made with ~mask, and the real value is put into the field. + */ + private static final int BAD_MASK = + ~(DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); + + private static void setActions(Object event, String field, int value) + { + try + { + java.lang.reflect.Field f = event.getClass().getDeclaredField(field); + f.setAccessible(true); + f.setInt(event, value); + } + catch (Exception e) + { + // The event keeps the odd value; the drop still works. + } + } + + private static DropTargetDragEvent dragEvent(DropTargetContext dtc, + Point p, int action, + int sourceActions) + { + try + { + return new DropTargetDragEvent(dtc, p, action, sourceActions); + } + catch (IllegalArgumentException e) + { + DropTargetDragEvent ev = + new DropTargetDragEvent(dtc, p, action, BAD_MASK); + setActions(ev, "srcActions", sourceActions); + return ev; + } + } + + private static DropTargetDropEvent dropEvent(DropTargetContext dtc, + Point p, int action, + int sourceActions, + boolean local) + { + try + { + return new DropTargetDropEvent(dtc, p, action, sourceActions, local); + } + catch (IllegalArgumentException e) + { + DropTargetDropEvent ev = + new DropTargetDropEvent(dtc, p, action, BAD_MASK, local); + setActions(ev, "actions", sourceActions); + return ev; + } + } + + /** The drop target side of one drag or drop. */ + private static final class DropContextPeer + implements DropTargetContextPeer + { + private final DropTarget dropTarget; + private final Transferable transferable; + private final boolean local; + private final int dragId; + final int sourceActions; + private int targetActions = DnDConstants.ACTION_COPY_OR_MOVE; + private int acceptedAction = DnDConstants.ACTION_NONE; + private boolean completed; + + DropContextPeer(DropTarget dt, Transferable t, boolean local, int id, + int sourceActions) + { + dropTarget = dt; + targetActions = dt.getDefaultActions(); + transferable = t; + this.local = local; + dragId = id; + this.sourceActions = sourceActions; + } + + public void setTargetActions(int actions) + { + targetActions = actions; + } + + public int getTargetActions() + { + return targetActions; + } + + public DropTarget getDropTarget() + { + return dropTarget; + } + + public DataFlavor[] getTransferDataFlavors() + { + return transferable != null ? transferable.getTransferDataFlavors() + : new DataFlavor[0]; + } + + public Transferable getTransferable() + { + if (transferable == null) + throw new InvalidDnDOperationException("no data"); + return transferable; + } + + public boolean isTransferableJVMLocal() + { + return local; + } + + public void acceptDrag(int action) + { + // The drag picture does not change. + } + + public void rejectDrag() + { + } + + public void acceptDrop(int action) + { + acceptedAction = action; + } + + public void rejectDrop() + { + acceptedAction = DnDConstants.ACTION_NONE; + } + + public void dropComplete(boolean success) + { + completed = true; + if (local) + finishDrag(dragId, success, success ? acceptedAction + : DnDConstants.ACTION_NONE); + } + + /** After drop(): a listener that never called dropComplete. */ + void dropHandled() + { + if (! completed && local) + finishDrag(dragId, acceptedAction != DnDConstants.ACTION_NONE, + acceptedAction); + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuEventPump.java b/gnu/java/awt/peer/haiku/HaikuEventPump.java new file mode 100644 index 0000000000..da68df176f --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuEventPump.java @@ -0,0 +1,758 @@ +/* HaikuEventPump.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.ComponentReshapeEvent; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.ScrollPane; +import java.awt.TextComponent; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ComponentEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.event.PaintEvent; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Polls native events from libhaikupeer and posts the matching AWT events + * to the system event queue. + */ +final class HaikuEventPump + implements Runnable +{ + + // Haiku modifier bits (InterfaceDefs.h). + private static final int B_SHIFT_KEY = 0x00000001; + private static final int B_COMMAND_KEY = 0x00000002; + private static final int B_CONTROL_KEY = 0x00000004; + private static final int B_OPTION_KEY = 0x00000040; + + // Haiku mouse buttons. + private static final int B_PRIMARY_MOUSE_BUTTON = 0x01; + private static final int B_SECONDARY_MOUSE_BUTTON = 0x02; + private static final int B_TERTIARY_MOUSE_BUTTON = 0x04; + + // Haiku special raw_char values. + private static final int B_BACKSPACE = 0x08; + private static final int B_RETURN = 0x0a; + private static final int B_TAB = 0x09; + private static final int B_ESCAPE = 0x1b; + private static final int B_LEFT_ARROW = 0x1c; + private static final int B_RIGHT_ARROW = 0x1d; + private static final int B_UP_ARROW = 0x1e; + private static final int B_DOWN_ARROW = 0x1f; + private static final int B_INSERT = 0x05; + private static final int B_DELETE = 0x7f; + private static final int B_HOME = 0x01; + private static final int B_END = 0x04; + private static final int B_PAGE_UP = 0x0b; + private static final int B_PAGE_DOWN = 0x0c; + private static final int B_FUNCTION_KEY = 0x10; + + // Haiku "key" codes for function keys. + private static final int B_F1_KEY = 0x02; + private static final int B_F12_KEY = 0x0d; + + /** Trace native events to stderr. */ + private static final boolean DEBUG = + Boolean.getBoolean("gnu.java.awt.peer.haiku.debug"); + + private static HaikuEventPump instance; + + private final HashMap windows = + new HashMap(); + + /** Target and position of the last mouse press, for MOUSE_CLICKED. */ + private Component pressTarget; + private int pressButton; + private boolean dragged; + + /** The click count of the last press. */ + private int pressClicks = 1; + + /** Component under the mouse, for MOUSE_ENTERED / MOUSE_EXITED. */ + private Component hoverTarget; + + /** Window and window coordinates of the last mouse position. */ + private HaikuWindowPeer cursorPeer; + private int cursorX, cursorY; + + static synchronized HaikuEventPump get() + { + if (instance == null) + { + instance = new HaikuEventPump(); + Thread t = new Thread(instance, "Haiku AWT event pump"); + t.setDaemon(true); + t.start(); + } + return instance; + } + + private HaikuEventPump() + { + } + + synchronized void register(HaikuWindowPeer peer) + { + windows.put(new Integer(peer.id), peer); + } + + synchronized void unregister(HaikuWindowPeer peer) + { + windows.remove(new Integer(peer.id)); + } + + private synchronized HaikuWindowPeer lookup(int id) + { + return windows.get(new Integer(id)); + } + + /** Shown modal dialogs, the newest last. */ + private final ArrayList modals = + new ArrayList(); + + synchronized void modalShown(HaikuDialogPeer p) + { + modals.remove(p); + modals.add(p); + } + + synchronized void modalHidden(HaikuDialogPeer p) + { + modals.remove(p); + } + + /** + * The modal dialog that blocks input to w, or null. A modal dialog + * blocks every window except itself and the windows it owns. + */ + private HaikuDialogPeer blocker(Window w) + { + HaikuDialogPeer top; + synchronized (this) + { + if (modals.isEmpty()) + return null; + top = modals.get(modals.size() - 1); + } + Window m = top.getWindow(); + for (Window x = w; x != null; x = x.getOwner()) + if (x == m) + return null; + return top; + } + + /** Event types that are user input to a window. */ + private static boolean isInput(int type) + { + switch (type) + { + case HaikuNative.EV_CLOSE: + case HaikuNative.EV_MOUSE_DOWN: + case HaikuNative.EV_MOUSE_UP: + case HaikuNative.EV_MOUSE_MOVED: + case HaikuNative.EV_MOUSE_ENTER: + case HaikuNative.EV_MOUSE_EXIT: + case HaikuNative.EV_KEY_DOWN: + case HaikuNative.EV_KEY_UP: + case HaikuNative.EV_WHEEL: + case HaikuNative.EV_EDIT: + case HaikuNative.EV_DROP: + case HaikuNative.EV_IM: + return true; + default: + return false; + } + } + + public void run() + { + int[] ev = new int[HaikuNative.EV_INTS]; + while (true) + { + try + { + if (HaikuNative.nextEvent(ev, 100) > 0) + handle(ev); + } + catch (ThreadDeath death) + { + return; + } + catch (Throwable t) + { + System.err.println("Exception in Haiku AWT event pump:"); + t.printStackTrace(); + } + } + } + + private static void post(AWTEvent e) + { + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e); + } + + private void handle(int[] ev) + { + int type = ev[0]; + if (DEBUG) + { + StringBuilder b = new StringBuilder("haiku event:"); + for (int i = 0; i < ev.length; i++) + b.append(' ').append(ev[i]); + System.err.println(b); + } + if (type == HaikuNative.EV_APP_QUIT) + { + ArrayList all; + synchronized (this) + { + all = new ArrayList(windows.values()); + } + for (HaikuWindowPeer p : all) + post(new WindowEvent(p.getWindow(), WindowEvent.WINDOW_CLOSING)); + return; + } + if (type == HaikuNative.EV_MENU) + { + HaikuMenuItemPeer item = HaikuMenuItemPeer.find(ev[2]); + if (item != null) + item.activated(mapModifiers(ev[3])); + return; + } + if (type == HaikuNative.EV_CHOICE) + { + HaikuChoicePeer choice = HaikuChoicePeer.find(ev[1]); + if (choice != null) + choice.selected(ev[2]); + return; + } + + HaikuWindowPeer peer = lookup(ev[1]); + if (peer == null) + return; + Window w = peer.getWindow(); + if (w == null) + return; + Insets in = peer.insets(); + long when = System.currentTimeMillis(); + + if (isInput(type)) + { + HaikuDialogPeer b = blocker(w); + if (b != null) + { + // A click on a blocked window brings the modal dialog forward. + if (type == HaikuNative.EV_MOUSE_DOWN + || type == HaikuNative.EV_CLOSE) + b.toFront(); + return; + } + } + + switch (type) + { + case HaikuNative.EV_FILE_PANEL: + if (peer instanceof HaikuFileDialogPeer) + ((HaikuFileDialogPeer) peer).panelClosed(ev[2] != 0); + break; + + case HaikuNative.EV_FILTER: + if (peer instanceof HaikuFileDialogPeer) + ((HaikuFileDialogPeer) peer).filterRequested(); + break; + + case HaikuNative.EV_STATE: + if (peer instanceof HaikuFramePeer) + ((HaikuFramePeer) peer).nativeStateChanged(ev[2] != 0, ev[3] != 0); + break; + + case HaikuNative.EV_EDIT: + edit(w, ev[2], when); + break; + + case HaikuNative.EV_DRAG: + if (blocker(w) == null || ev[4] == HaikuNative.DRAG_EXIT) + HaikuDragDrop.drag(w, ev[2] + in.left, ev[3] + in.top, ev[4], + ev[5]); + break; + + case HaikuNative.EV_DROP: + HaikuDragDrop.drop(w, ev[2] + in.left, ev[3] + in.top, ev[5]); + break; + + case HaikuNative.EV_IM: + HaikuInputMethods.event(peer, w, ev[2], ev[3] != 0, ev[4], ev[5]); + break; + + case HaikuNative.EV_CLOSE: + post(new WindowEvent(w, WindowEvent.WINDOW_CLOSING)); + break; + + case HaikuNative.EV_RESIZED: + if (peer.nativeResized(ev[2], ev[3])) + { + // Lay out again, then repaint everything at the new size. + reshape(w, peer.getOuterBounds(), true); + post(new ComponentEvent(w, ComponentEvent.COMPONENT_RESIZED)); + } + else + post(new PaintEvent(w, PaintEvent.PAINT, + new Rectangle(0, 0, w.getWidth(), + w.getHeight()))); + break; + + case HaikuNative.EV_MOVED: + if (peer.nativeMoved(ev[2], ev[3])) + { + reshape(w, peer.getOuterBounds(), false); + post(new ComponentEvent(w, ComponentEvent.COMPONENT_MOVED)); + } + break; + + case HaikuNative.EV_ACTIVATED: + if (ev[2] != 0) + { + post(new WindowEvent(w, WindowEvent.WINDOW_ACTIVATED)); + post(new WindowEvent(w, WindowEvent.WINDOW_GAINED_FOCUS)); + } + else + { + post(new WindowEvent(w, WindowEvent.WINDOW_LOST_FOCUS)); + post(new WindowEvent(w, WindowEvent.WINDOW_DEACTIVATED)); + } + break; + + case HaikuNative.EV_MOUSE_DOWN: + mouseDown(w, ev[2] + in.left, ev[3] + in.top, ev[4], ev[5], ev[6], + when); + break; + + case HaikuNative.EV_MOUSE_UP: + mouseUp(w, ev[2] + in.left, ev[3] + in.top, ev[4], ev[5], when); + HaikuDragDrop.mouseReleased(); + break; + + case HaikuNative.EV_MOUSE_MOVED: + mouseMoved(w, ev[2] + in.left, ev[3] + in.top, ev[4], ev[5], when); + synchronized (this) + { + cursorPeer = peer; + cursorX = ev[2] + in.left; + cursorY = ev[3] + in.top; + } + if (ev[4] == 0) + updateCursor(peer, w, ev[2] + in.left, ev[3] + in.top); + break; + + case HaikuNative.EV_MOUSE_ENTER: + break; + + case HaikuNative.EV_MOUSE_EXIT: + if (hoverTarget != null) + { + postMouse(hoverTarget, w, MouseEvent.MOUSE_EXITED, when, + mapModifiers(ev[5]), ev[2] + in.left, ev[3] + in.top, + 0, 0); + hoverTarget = null; + } + break; + + case HaikuNative.EV_WHEEL: + { + int x = ev[2] + in.left; + int y = ev[3] + in.top; + Component target = findTarget(w, x, y); + int[] p = translate(w, target, x, y); + post(new MouseWheelEvent(target, MouseEvent.MOUSE_WHEEL, when, + mapModifiers(ev[5]), p[0], p[1], 0, false, + MouseWheelEvent.WHEEL_UNIT_SCROLL, 3, + ev[4])); + } + break; + + case HaikuNative.EV_KEY_DOWN: + case HaikuNative.EV_KEY_UP: + key(w, type == HaikuNative.EV_KEY_DOWN, ev[2], ev[3], ev[4], ev[5], + when); + break; + + default: + break; + } + } + + /** + * Updates the AWT bounds of w after the user moved or resized the + * native window, without calling back into the peer. + */ + private static void reshape(final Window w, Rectangle r, + final boolean resized) + { + post(new ComponentReshapeEvent(w, r.x, r.y, r.width, r.height)); + if (! resized) + return; + EventQueue.invokeLater(new Runnable() + { + public void run() + { + w.invalidate(); + w.validate(); + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent( + new PaintEvent(w, PaintEvent.PAINT, + new Rectangle(0, 0, w.getWidth(), w.getHeight()))); + } + }); + } + + /** + * The Command-X/C/V/A shortcuts of a Haiku window arrive as edit + * commands, not as keys. Post them as the Ctrl keys that Swing's text + * components use for cut, copy, paste and select all. + */ + private static void edit(Window w, int command, long when) + { + int vk; + switch (command) + { + case HaikuNative.EDIT_CUT: vk = KeyEvent.VK_X; break; + case HaikuNative.EDIT_COPY: vk = KeyEvent.VK_C; break; + case HaikuNative.EDIT_PASTE: vk = KeyEvent.VK_V; break; + case HaikuNative.EDIT_SELECT_ALL: vk = KeyEvent.VK_A; break; + default: return; + } + int m = InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK; + char c = (char) (vk - KeyEvent.VK_A + 1); + post(new KeyEvent(w, KeyEvent.KEY_PRESSED, when, m, vk, c)); + post(new KeyEvent(w, KeyEvent.KEY_RELEASED, when, m, vk, c)); + } + + // Cursors. + + /** + * Shows the cursor of the component at window point (x, y). AWT + * components inherit the cursor of their parent; text components show + * the text cursor unless a cursor was set. + */ + private static void updateCursor(HaikuWindowPeer peer, Window w, int x, + int y) + { + Component c = w.findComponentAt(x, y); + if (c == null) + c = w; + Cursor cursor = c.getCursor(); + if (c instanceof TextComponent && ! c.isCursorSet() + && cursor.getType() == Cursor.DEFAULT_CURSOR) + cursor = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); + peer.showCursor(cursor); + } + + /** + * Shows the cursor again at the last mouse position. Called when a + * component cursor changed. + */ + void refreshCursor() + { + HaikuWindowPeer p; + int x, y; + synchronized (this) + { + p = cursorPeer; + x = cursorX; + y = cursorY; + } + if (p != null && p.getWindow() != null) + updateCursor(p, p.getWindow(), x, y); + } + + // Mouse handling. + + private void mouseDown(Window w, int x, int y, int buttons, int mods, + int clicks, long when) + { + int button = mapButton(buttons); + Component target = findTarget(w, x, y); + pressTarget = target; + pressButton = button; + pressClicks = clicks; + dragged = false; + postMouse(target, w, MouseEvent.MOUSE_PRESSED, when, + mapModifiers(mods) | buttonMask(button), x, y, clicks, button); + } + + private void mouseUp(Window w, int x, int y, int released, int mods, + long when) + { + int button = mapButton(released); + if (button == MouseEvent.NOBUTTON) + button = pressButton; + Component target = pressTarget != null ? pressTarget + : findTarget(w, x, y); + int m = mapModifiers(mods) | buttonMask(button); + postMouse(target, w, MouseEvent.MOUSE_RELEASED, when, m, x, y, + pressClicks, button); + if (! dragged && target == findTarget(w, x, y)) + postMouse(target, w, MouseEvent.MOUSE_CLICKED, when, m, x, y, + pressClicks, button); + pressTarget = null; + } + + private void mouseMoved(Window w, int x, int y, int buttons, int mods, + long when) + { + int m = mapModifiers(mods); + if (pressTarget != null && buttons != 0) + { + dragged = true; + postMouse(pressTarget, w, MouseEvent.MOUSE_DRAGGED, when, + m | buttonMask(pressButton), x, y, 0, 0); + return; + } + Component target = findTarget(w, x, y); + if (target != hoverTarget) + { + if (hoverTarget != null) + postMouse(hoverTarget, w, MouseEvent.MOUSE_EXITED, when, m, x, y, + 0, 0); + postMouse(target, w, MouseEvent.MOUSE_ENTERED, when, m, x, y, 0, 0); + hoverTarget = target; + } + postMouse(target, w, MouseEvent.MOUSE_MOVED, when, m, x, y, 0, 0); + } + + /** Posts a mouse event; x/y are window coordinates. */ + private static void postMouse(Component target, Window w, int id, + long when, int mods, int x, int y, + int clicks, int button) + { + int[] p = translate(w, target, x, y); + post(new MouseEvent(target, id, when, mods, p[0], p[1], clicks, + button == MouseEvent.BUTTON3 + && id == MouseEvent.MOUSE_PRESSED, + button)); + } + + /** Window coordinates to target coordinates. */ + private static int[] translate(Window w, Component target, int x, int y) + { + for (Component c = target; c != null && c != w; c = c.getParent()) + { + x -= c.getX(); + y -= c.getY(); + } + return new int[] { x, y }; + } + + /** + * Finds the deepest visible heavyweight component at (x, y). + * Lightweight components are handled by AWT's LightweightDispatcher. + */ + private static Component findTarget(Component src, int x, int y) + { + // Children of a ScrollPane can extend under its scrollbars. + if (src instanceof ScrollPane + && src.getPeer() instanceof HaikuScrollPanePeer + && ! ((HaikuScrollPanePeer) src.getPeer()).inViewport(x, y)) + return src; + if (src instanceof Container) + { + Container cont = (Container) src; + int n = cont.getComponentCount(); + for (int i = 0; i < n; i++) + { + Component child = cont.getComponent(i); + if (child != null && child.isVisible() + && child.contains(x - child.getX(), y - child.getY())) + { + Component deeper = findTarget(child, x - child.getX(), + y - child.getY()); + if (deeper != null) + return deeper; + } + } + } + if (src instanceof Window) + return src; + return src.isLightweight() ? null : src; + } + + private static int mapButton(int buttons) + { + if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) + return MouseEvent.BUTTON1; + if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) + return MouseEvent.BUTTON3; + if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) + return MouseEvent.BUTTON2; + return MouseEvent.NOBUTTON; + } + + private static int buttonMask(int button) + { + switch (button) + { + case MouseEvent.BUTTON1: + return InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON1_MASK; + case MouseEvent.BUTTON2: + return InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON2_MASK; + case MouseEvent.BUTTON3: + return InputEvent.BUTTON3_DOWN_MASK | InputEvent.BUTTON3_MASK; + default: + return 0; + } + } + + /** + * Maps Haiku modifiers to AWT modifiers. On a PC keyboard Haiku's + * Command key is the Alt key and Option is the Windows key. + */ + static int mapModifiers(int mods) + { + int m = 0; + if ((mods & B_SHIFT_KEY) != 0) + m |= InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK; + if ((mods & B_CONTROL_KEY) != 0) + m |= InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK; + if ((mods & B_COMMAND_KEY) != 0) + m |= InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK; + if ((mods & B_OPTION_KEY) != 0) + m |= InputEvent.META_DOWN_MASK | InputEvent.META_MASK; + return m; + } + + // Keyboard handling. + + private static void key(Window w, boolean down, int rawChar, int unicode, + int mods, int key, long when) + { + int vk = mapKeyCode(rawChar, key); + boolean special = isSpecial(rawChar); + int m = mapModifiers(mods); + if (down) + { + post(new KeyEvent(w, KeyEvent.KEY_PRESSED, when, m, vk, + special ? KeyEvent.CHAR_UNDEFINED : (char) unicode)); + if (! special && unicode != 0 && unicode <= 0xFFFF) + post(new KeyEvent(w, KeyEvent.KEY_TYPED, when, m, + KeyEvent.VK_UNDEFINED, (char) unicode)); + } + else + post(new KeyEvent(w, KeyEvent.KEY_RELEASED, when, m, vk, + special ? KeyEvent.CHAR_UNDEFINED : (char) unicode)); + } + + /** True for keys that produce no KEY_TYPED event. */ + private static boolean isSpecial(int rawChar) + { + switch (rawChar) + { + case B_LEFT_ARROW: + case B_RIGHT_ARROW: + case B_UP_ARROW: + case B_DOWN_ARROW: + case B_INSERT: + case B_HOME: + case B_END: + case B_PAGE_UP: + case B_PAGE_DOWN: + case B_FUNCTION_KEY: + case 0: + return true; + default: + return false; + } + } + + private static int mapKeyCode(int rawChar, int key) + { + switch (rawChar) + { + case B_BACKSPACE: return KeyEvent.VK_BACK_SPACE; + case B_RETURN: return KeyEvent.VK_ENTER; + case B_TAB: return KeyEvent.VK_TAB; + case B_ESCAPE: return KeyEvent.VK_ESCAPE; + case B_LEFT_ARROW: return KeyEvent.VK_LEFT; + case B_RIGHT_ARROW: return KeyEvent.VK_RIGHT; + case B_UP_ARROW: return KeyEvent.VK_UP; + case B_DOWN_ARROW: return KeyEvent.VK_DOWN; + case B_INSERT: return KeyEvent.VK_INSERT; + case B_DELETE: return KeyEvent.VK_DELETE; + case B_HOME: return KeyEvent.VK_HOME; + case B_END: return KeyEvent.VK_END; + case B_PAGE_UP: return KeyEvent.VK_PAGE_UP; + case B_PAGE_DOWN: return KeyEvent.VK_PAGE_DOWN; + case ' ': return KeyEvent.VK_SPACE; + case '`': return KeyEvent.VK_BACK_QUOTE; + case '\'': return KeyEvent.VK_QUOTE; + case B_FUNCTION_KEY: + if (key >= B_F1_KEY && key <= B_F12_KEY) + return KeyEvent.VK_F1 + (key - B_F1_KEY); + return KeyEvent.VK_UNDEFINED; + default: + break; + } + if (rawChar >= 'a' && rawChar <= 'z') + return rawChar - 'a' + KeyEvent.VK_A; + if (rawChar >= 'A' && rawChar <= 'Z') + return rawChar; + if (rawChar >= '0' && rawChar <= '9') + return rawChar; + switch (rawChar) + { + case ',': case '-': case '.': case '/': case ';': case '=': + case '[': case '\\': case ']': + return rawChar; // AWT VK codes equal ASCII for these. + default: + return KeyEvent.VK_UNDEFINED; + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuFileDialogPeer.java b/gnu/java/awt/peer/haiku/HaikuFileDialogPeer.java new file mode 100644 index 0000000000..20f2b4802a --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuFileDialogPeer.java @@ -0,0 +1,205 @@ +/* HaikuFileDialogPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.EventQueue; +import java.awt.FileDialog; +import java.awt.peer.FileDialogPeer; +import java.io.File; +import java.io.FilenameFilter; + +/** + * A file dialog shown as a native BFilePanel. + * + * The dialog's own window is never shown. FileDialog.show() blocks in + * Dialog.show() until the panel closes; then {@link #panelClosed} stores + * the result and hides the dialog, which makes show() return. + * + * A FilenameFilter is supported: the panel asks for each directory it + * shows (EV_FILTER), and {@link #filterRequested} answers for every name in + * it. Folders are always shown. + */ +public class HaikuFileDialogPeer + extends HaikuDialogPeer + implements FileDialogPeer +{ + + HaikuFileDialogPeer(FileDialog fd) + { + super(fd); + } + + /** The filter of the open panel, or null. */ + private volatile FilenameFilter activeFilter; + + public void show() + { + FileDialog fd = (FileDialog) getWindow(); + FilenameFilter filter = fd.getFilenameFilter(); + String file = fd.getFile(); + // A file name such as "*.txt" or "*.gif;*.jpg" is used as a filter. + if (file != null && file.indexOf('*') >= 0) + { + if (filter == null) + filter = new GlobFilter(file); + file = null; + } + activeFilter = filter; + HaikuNative.filePanelShow(id, fd.getMode() == FileDialog.SAVE, + fd.getTitle(), fd.getDirectory(), file, + filter != null); + } + + /** + * Called by the event pump when the panel shows a directory that it has + * no answers for. Runs the FilenameFilter on every name in it. + */ + void filterRequested() + { + String dir; + while ((dir = HaikuNative.filterNext(id)) != null) + { + File d = new File(dir); + String[] names = d.list(); + if (names == null) + names = new String[0]; + boolean[] accepted = new boolean[names.length]; + FilenameFilter filter = activeFilter; + for (int i = 0; i < names.length; i++) + { + try + { + accepted[i] = filter == null || filter.accept(d, names[i]); + } + catch (RuntimeException e) + { + accepted[i] = true; + } + } + HaikuNative.filterAnswer(id, dir, names, accepted); + } + } + + /** Matches names against patterns such as "*.txt;*.java". */ + private static final class GlobFilter + implements FilenameFilter + { + private final java.util.regex.Pattern pattern; + + GlobFilter(String globs) + { + StringBuilder re = new StringBuilder(); + java.util.StringTokenizer t = new java.util.StringTokenizer(globs, + ";,"); + while (t.hasMoreTokens()) + { + String g = t.nextToken().trim(); + if (re.length() > 0) + re.append('|'); + for (int i = 0; i < g.length(); i++) + { + char c = g.charAt(i); + if (c == '*') + re.append(".*"); + else if (c == '?') + re.append('.'); + else if (Character.isLetterOrDigit(c)) + re.append(c); + else + re.append('\\').append(c); + } + } + pattern = java.util.regex.Pattern.compile(re.toString(), + java.util.regex.Pattern.CASE_INSENSITIVE); + } + + public boolean accept(File dir, String name) + { + return pattern.matcher(name).matches(); + } + } + + public void hide() + { + // The panel hides itself. + } + + /** + * Called by the event pump when the panel closed. + */ + void panelClosed(boolean chosen) + { + final String path = chosen ? HaikuNative.fileResult(id) : null; + final FileDialog fd = (FileDialog) getWindow(); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + if (path != null) + { + File f = new File(path); + String dir = f.getParent(); + if (dir != null && ! dir.endsWith(File.separator)) + dir += File.separator; + fd.setDirectory(dir); + fd.setFile(f.getName()); + } + else + fd.setFile(null); + fd.setVisible(false); + } + }); + } + + public void setFile(String file) + { + // Used the next time the panel opens. + } + + public void setDirectory(String dir) + { + // Used the next time the panel opens. + } + + public void setFilenameFilter(FilenameFilter filter) + { + // Used the next time the panel opens. + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuFontMetrics.java b/gnu/java/awt/peer/haiku/HaikuFontMetrics.java new file mode 100644 index 0000000000..f31f46d77f --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuFontMetrics.java @@ -0,0 +1,143 @@ +/* HaikuFontMetrics.java -- font metrics from Haiku's BFont + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.font.LineMetrics; +import java.awt.geom.Rectangle2D; + +/** + * FontMetrics of a {@link HaikuFontPeer}. Widths are the BFont advances + * that the app_server uses to draw the text. + */ +final class HaikuFontMetrics + extends FontMetrics +{ + + private final HaikuFontPeer peer; + + private final HaikuFontPeer.Metrics m; + + HaikuFontMetrics(Font font, HaikuFontPeer peer) + { + super(font); + this.peer = peer; + m = peer.metrics(font.getSize2D()); + } + + public int getAscent() + { + return (int) Math.ceil(m.ascent); + } + + public int getDescent() + { + return (int) Math.ceil(m.descent); + } + + public int getLeading() + { + return Math.round(m.leading); + } + + public int getMaxAscent() + { + return getAscent(); + } + + public int getMaxDescent() + { + return getDescent(); + } + + public int getMaxAdvance() + { + return (int) Math.ceil(m.maxAdvance); + } + + public int charWidth(char c) + { + if (c < 256) + return Math.round(m.latin[c]); + return Math.round(peer.width(String.valueOf(c), font.getSize2D())); + } + + public int charWidth(int codePoint) + { + if (codePoint < 0x10000) + return charWidth((char) codePoint); + return Math.round(peer.width(new String(Character.toChars(codePoint)), + font.getSize2D())); + } + + public int stringWidth(String s) + { + return Math.round(peer.width(s, font.getSize2D())); + } + + public int charsWidth(char[] data, int off, int len) + { + return stringWidth(new String(data, off, len)); + } + + public int[] getWidths() + { + int[] w = new int[256]; + for (int i = 0; i < 256; i++) + w[i] = Math.round(m.latin[i]); + return w; + } + + public LineMetrics getLineMetrics(String s, Graphics g) + { + return new HaikuFontPeer.HaikuLineMetrics(m, s.length()); + } + + public Rectangle2D getStringBounds(String s, Graphics g) + { + return new Rectangle2D.Float(0, -m.ascent, peer.width(s, font.getSize2D()), + m.ascent + m.descent + m.leading); + } + + public boolean hasUniformLineMetrics() + { + return true; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuFontPeer.java b/gnu/java/awt/peer/haiku/HaikuFontPeer.java new file mode 100644 index 0000000000..2d01c9dcac --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuFontPeer.java @@ -0,0 +1,404 @@ +/* HaikuFontPeer.java -- Java fonts backed by Haiku's BFont + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.ClasspathFontPeer; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.font.LineMetrics; +import java.awt.geom.Rectangle2D; +import java.text.CharacterIterator; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * A font backed by a Haiku BFont. Haiku measures the text (BFont + * escapements) and the app_server draws it, so Java text looks like the + * rest of Haiku: hinted, anti-aliased, and with Haiku's font fallback + * for scripts that the font lacks. + * + * The Java logical fonts map to Haiku's fonts: Dialog and SansSerif to + * the plain font, Monospaced and DialogInput to the fixed font, Serif to + * a serif family. Other names are Haiku font families. + */ +public class HaikuFontPeer + extends ClasspathFontPeer +{ + + // Face flags (Font.h). + private static final int B_ITALIC_FACE = 0x01; + private static final int B_BOLD_FACE = 0x20; + private static final int B_REGULAR_FACE = 0x40; + + private static final String[] SERIF_FAMILIES = { + "Noto Serif", "DejaVu Serif", "Liberation Serif", "Noto Serif Display" + }; + + /** The native BFont. */ + final long handle; + + /** The Haiku family name. */ + final String haikuFamily; + + private final HashMap metrics = new HashMap(); + + private static String[] families; + + HaikuFontPeer(String name, Map attrs) + { + super(name, attrs); + HaikuNative.ensureInitialized(); + String fam = resolveFamily(name); + int face = 0; + if ((style & Font.BOLD) != 0) + face |= B_BOLD_FACE; + if ((style & Font.ITALIC) != 0) + face |= B_ITALIC_FACE; + if (face == 0) + face = B_REGULAR_FACE; + long h = HaikuNative.fontOpen(fam, face); + if (h == 0) + { + fam = ""; + h = HaikuNative.fontOpen("", face); + } + handle = h; + haikuFamily = fam.length() > 0 ? fam : HaikuNative.fontFamilies(1)[0]; + } + + /** The installed Haiku font families. */ + static synchronized String[] haikuFamilies() + { + if (families == null) + { + HaikuNative.ensureInitialized(); + families = HaikuNative.fontFamilies(0); + } + return families; + } + + private static String findFamily(String name) + { + String[] f = haikuFamilies(); + for (int i = 0; i < f.length; i++) + if (f[i].equalsIgnoreCase(name)) + return f[i]; + return null; + } + + /** The Haiku family for a Java font name; "" is the plain font. */ + private static String resolveFamily(String name) + { + String n = name == null ? "" : name.toLowerCase(Locale.ENGLISH); + if (n.equals("dialog") || n.equals("sansserif") || n.equals("default") + || n.equals("helvetica") || n.equals("arial") || n.length() == 0) + return ""; + if (n.equals("monospaced") || n.equals("dialoginput") + || n.equals("courier") || n.equals("courier new")) + return HaikuNative.fontFamilies(2)[0]; + if (n.equals("serif") || n.equals("timesroman") || n.equals("times") + || n.equals("times new roman")) + { + for (int i = 0; i < SERIF_FAMILIES.length; i++) + if (findFamily(SERIF_FAMILIES[i]) != null) + return SERIF_FAMILIES[i]; + return ""; + } + String f = findFamily(name); + return f != null ? f : ""; + } + + /** Metrics at one size: ascent, descent, leading, max advance. */ + static final class Metrics + { + final float ascent, descent, leading, maxAdvance; + /** Advances of the characters 0..255. */ + final float[] latin = new float[256]; + + Metrics(long font, float size) + { + float[] m = new float[4]; + HaikuNative.fontMetrics(font, size, m); + ascent = m[0]; + descent = m[1]; + leading = m[2]; + maxAdvance = m[3]; + char[] cs = new char[256]; + for (int i = 0; i < 256; i++) + cs[i] = i < 32 || (i >= 0x7f && i < 0xa0) ? ' ' : (char) i; + HaikuNative.fontAdvances(font, size, new String(cs), latin); + for (int i = 0; i < 256; i++) + if (i < 32 || (i >= 0x7f && i < 0xa0)) + latin[i] = 0; + } + } + + Metrics metrics(float size) + { + synchronized (metrics) + { + Float key = new Float(size); + Metrics m = metrics.get(key); + if (m == null) + { + m = new Metrics(handle, size); + metrics.put(key, m); + } + return m; + } + } + + /** The advance of each char of text. */ + float[] advances(String text, float size) + { + int n = text.length(); + float[] a = new float[n]; + boolean latin = true; + for (int i = 0; i < n && latin; i++) + latin = text.charAt(i) < 256; + if (latin) + { + Metrics m = metrics(size); + for (int i = 0; i < n; i++) + a[i] = m.latin[text.charAt(i)]; + } + else if (n > 0) + HaikuNative.fontAdvances(handle, size, text, a); + return a; + } + + float width(String text, float size) + { + float[] a = advances(text, size); + float w = 0; + for (int i = 0; i < a.length; i++) + w += a[i]; + return w; + } + + static String text(CharacterIterator ci, int start, int limit) + { + StringBuilder b = new StringBuilder(Math.max(0, limit - start)); + for (char c = ci.setIndex(start); ci.getIndex() < limit; c = ci.next()) + b.append(c); + return b.toString(); + } + + // ClasspathFontPeer. + + public boolean canDisplay(Font font, int c) + { + boolean[] has = new boolean[1]; + HaikuNative.fontHasGlyphs(handle, String.valueOf((char) c), has); + return has[0]; + } + + public int canDisplayUpTo(Font font, CharacterIterator ci, int start, + int limit) + { + String t = text(ci, start, limit); + boolean[] has = new boolean[t.length()]; + HaikuNative.fontHasGlyphs(handle, t, has); + for (int i = 0; i < has.length; i++) + if (! has[i]) + return start + i; + return -1; + } + + public String getSubFamilyName(Font font, Locale locale) + { + if ((style & Font.BOLD) != 0 && (style & Font.ITALIC) != 0) + return "Bold Italic"; + if ((style & Font.BOLD) != 0) + return "Bold"; + if ((style & Font.ITALIC) != 0) + return "Italic"; + return "Regular"; + } + + public String getPostScriptName(Font font) + { + return haikuFamily.replace(" ", "") + "-" + + getSubFamilyName(font, null).replace(" ", ""); + } + + public int getNumGlyphs(Font font) + { + return 0x10000; + } + + public int getMissingGlyphCode(Font font) + { + return 0; + } + + public byte getBaselineFor(Font font, char c) + { + return Font.ROMAN_BASELINE; + } + + public String getGlyphName(Font font, int glyphIndex) + { + String hex = Integer.toHexString(glyphIndex).toUpperCase(); + while (hex.length() < 4) + hex = "0" + hex; + return "uni" + hex; + } + + public GlyphVector createGlyphVector(Font font, FontRenderContext frc, + CharacterIterator ci) + { + return new HaikuGlyphVector(font, this, frc, + text(ci, ci.getBeginIndex(), ci.getEndIndex())); + } + + /** Glyph codes are the characters. */ + public GlyphVector createGlyphVector(Font font, FontRenderContext frc, + int[] glyphCodes) + { + char[] cs = new char[glyphCodes.length]; + for (int i = 0; i < cs.length; i++) + cs[i] = (char) glyphCodes[i]; + return new HaikuGlyphVector(font, this, frc, new String(cs)); + } + + public GlyphVector layoutGlyphVector(Font font, FontRenderContext frc, + char[] chars, int start, int limit, + int flags) + { + return new HaikuGlyphVector(font, this, frc, + new String(chars, start, limit - start)); + } + + public FontMetrics getFontMetrics(Font font) + { + return new HaikuFontMetrics(font, this); + } + + public boolean hasUniformLineMetrics(Font font) + { + return true; + } + + public LineMetrics getLineMetrics(Font font, CharacterIterator ci, + int begin, int limit, + FontRenderContext frc) + { + return new HaikuLineMetrics(metrics(font.getSize2D()), limit - begin); + } + + public Rectangle2D getMaxCharBounds(Font font, FontRenderContext frc) + { + Metrics m = metrics(font.getSize2D()); + return new Rectangle2D.Float(0, -m.ascent, m.maxAdvance, + m.ascent + m.descent + m.leading); + } + + /** Line metrics from the BFont height. */ + static final class HaikuLineMetrics + extends LineMetrics + { + private final Metrics m; + private final int chars; + + HaikuLineMetrics(Metrics m, int chars) + { + this.m = m; + this.chars = chars; + } + + public float getAscent() + { + return m.ascent; + } + + public int getBaselineIndex() + { + return Font.ROMAN_BASELINE; + } + + public float[] getBaselineOffsets() + { + return new float[] { 0, (m.descent - m.ascent) / 2, -m.ascent }; + } + + public float getDescent() + { + return m.descent; + } + + public float getHeight() + { + return m.ascent + m.descent + m.leading; + } + + public float getLeading() + { + return m.leading; + } + + public int getNumChars() + { + return chars; + } + + public float getStrikethroughOffset() + { + return -m.ascent / 3; + } + + public float getStrikethroughThickness() + { + return Math.max(1, m.ascent / 12); + } + + public float getUnderlineOffset() + { + return Math.max(1, m.descent / 2); + } + + public float getUnderlineThickness() + { + return Math.max(1, m.ascent / 12); + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuFramePeer.java b/gnu/java/awt/peer/haiku/HaikuFramePeer.java new file mode 100644 index 0000000000..11f094633c --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuFramePeer.java @@ -0,0 +1,170 @@ +/* HaikuFramePeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import java.awt.Frame; +import java.awt.Image; +import java.awt.MenuBar; +import java.awt.Rectangle; +import java.awt.event.WindowEvent; +import java.awt.peer.FramePeer; + +/** + * A Frame backed by a titled BWindow. + */ +public class HaikuFramePeer + extends HaikuWindowPeer + implements FramePeer +{ + + HaikuFramePeer(Frame f) + { + super(f); + MenuBar mb = f.getMenuBar(); + if (mb != null) + setMenuBar(mb); + if (f.getMaximizedBounds() != null) + setMaximizedBounds(f.getMaximizedBounds()); + } + + public void setIconImage(Image image) + { + // Haiku windows have no per-window icon. + } + + /** + * Puts a native BMenuBar at the top of the window. The menu bar is part + * of the insets, as in other toolkits; the outer bounds stay the same. + */ + public void setMenuBar(MenuBar mb) + { + long bar = 0; + if (mb != null && mb.getPeer() instanceof HaikuMenuBarPeer) + bar = ((HaikuMenuBarPeer) mb.getPeer()).bar; + HaikuNative.setMenuBar(handle, bar); + insetsChanged(); + } + + public void setTitle(String title) + { + super.setTitle(title); + } + + /** The AWT state of the native window. */ + public int getState() + { + boolean[] st = new boolean[2]; + HaikuNative.getFrameState(handle, st); + return toAWTState(st[0], st[1]); + } + + private static int toAWTState(boolean minimized, boolean zoomed) + { + int state = Frame.NORMAL; + if (minimized) + state |= Frame.ICONIFIED; + if (zoomed) + state |= Frame.MAXIMIZED_BOTH; + return state; + } + + /** + * Minimizes, restores or zooms the window. Haiku has one zoomed state, + * so MAXIMIZED_HORIZ and MAXIMIZED_VERT zoom the window too. + */ + public void setState(int state) + { + // The native window reports the change with EV_STATE, which posts + // the window events. + HaikuNative.setFrameState(handle, (state & Frame.ICONIFIED) != 0, + (state & Frame.MAXIMIZED_BOTH) != 0); + } + + /** The state last reported to AWT. */ + private int lastState = Frame.NORMAL; + + /** + * Called by the event pump when the window was minimized, restored or + * zoomed. Posts the matching window events. + */ + void nativeStateChanged(boolean minimized, boolean zoomed) + { + int state = toAWTState(minimized, zoomed); + int old; + synchronized (this) + { + old = lastState; + if (state == old) + return; + lastState = state; + } + Frame f = (Frame) getWindow(); + java.awt.EventQueue q = java.awt.Toolkit.getDefaultToolkit() + .getSystemEventQueue(); + if ((state & Frame.ICONIFIED) != (old & Frame.ICONIFIED)) + q.postEvent(new WindowEvent(f, (state & Frame.ICONIFIED) != 0 + ? WindowEvent.WINDOW_ICONIFIED + : WindowEvent.WINDOW_DEICONIFIED)); + q.postEvent(new WindowEvent(f, WindowEvent.WINDOW_STATE_CHANGED, old, + state)); + } + + public void setMaximizedBounds(Rectangle r) + { + if (r == null) + HaikuNative.setZoomLimits(handle, 0, 0); + else + HaikuNative.setZoomLimits(handle, r.width, r.height); + } + + public boolean isRestackSupported() + { + return false; + } + + public void setBoundsPrivate(int x, int y, int width, int height) + { + reshape(x, y, width, height); + } + + public Rectangle getBoundsPrivate() + { + return getOuterBounds(); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuGlyphVector.java b/gnu/java/awt/peer/haiku/HaikuGlyphVector.java new file mode 100644 index 0000000000..99731e0867 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGlyphVector.java @@ -0,0 +1,282 @@ +/* HaikuGlyphVector.java -- a glyph vector of a Haiku font + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Font; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphJustificationInfo; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * A glyph vector of a {@link HaikuFontPeer}. The glyph codes are the + * characters; the positions come from the BFont advances. + * + * HaikuGraphics2D draws a glyph vector whose positions were not changed + * by asking the app_server to draw its text. Outlines (for shapes, other + * transforms and paints) come from BFont::GetGlyphShapes. + */ +final class HaikuGlyphVector + extends GlyphVector +{ + + final Font font; + + final HaikuFontPeer peer; + + final String text; + + private final FontRenderContext frc; + + /** x, y of each glyph and the end position: 2 * (n + 1) values. */ + private final float[] positions; + + /** True after setGlyphPosition() or setGlyphTransform(). */ + boolean customLayout; + + private Shape outline; + + HaikuGlyphVector(Font font, HaikuFontPeer peer, FontRenderContext frc, + String text) + { + this.font = font; + this.peer = peer; + this.frc = frc; + this.text = text; + positions = new float[2 * (text.length() + 1)]; + performDefaultLayout(); + } + + public void performDefaultLayout() + { + float[] adv = peer.advances(text, font.getSize2D()); + float x = 0; + for (int i = 0; i < adv.length; i++) + { + positions[2 * i] = x; + positions[2 * i + 1] = 0; + x += adv[i]; + } + positions[2 * adv.length] = x; + positions[2 * adv.length + 1] = 0; + customLayout = false; + outline = null; + } + + public boolean equals(GlyphVector gv) + { + if (! (gv instanceof HaikuGlyphVector)) + return false; + HaikuGlyphVector o = (HaikuGlyphVector) gv; + return o.font.equals(font) && o.text.equals(text) + && java.util.Arrays.equals(o.positions, positions); + } + + public Font getFont() + { + return font; + } + + public FontRenderContext getFontRenderContext() + { + return frc; + } + + public int getNumGlyphs() + { + return text.length(); + } + + public int getGlyphCode(int i) + { + return text.charAt(i); + } + + public int[] getGlyphCodes(int begin, int count, int[] codes) + { + if (codes == null) + codes = new int[count]; + for (int i = 0; i < count; i++) + codes[i] = text.charAt(begin + i); + return codes; + } + + public Point2D getGlyphPosition(int i) + { + return new Point2D.Float(positions[2 * i], positions[2 * i + 1]); + } + + public float[] getGlyphPositions(int begin, int count, float[] out) + { + if (out == null) + out = new float[2 * count]; + System.arraycopy(positions, 2 * begin, out, 0, 2 * count); + return out; + } + + public void setGlyphPosition(int i, Point2D p) + { + positions[2 * i] = (float) p.getX(); + positions[2 * i + 1] = (float) p.getY(); + customLayout = true; + outline = null; + } + + public AffineTransform getGlyphTransform(int i) + { + return null; + } + + public void setGlyphTransform(int i, AffineTransform t) + { + // Glyphs are not transformed one by one. + } + + public GlyphJustificationInfo getGlyphJustificationInfo(int i) + { + return null; + } + + private float advance(int i) + { + return positions[2 * (i + 1)] - positions[2 * i]; + } + + public Rectangle2D getLogicalBounds() + { + HaikuFontPeer.Metrics m = peer.metrics(font.getSize2D()); + return new Rectangle2D.Float(0, -m.ascent, positions[2 * text.length()], + m.ascent + m.descent + m.leading); + } + + public Rectangle2D getVisualBounds() + { + Rectangle2D r = getOutline().getBounds2D(); + if (r.isEmpty()) + return getLogicalBounds(); + return r; + } + + public Shape getGlyphLogicalBounds(int i) + { + HaikuFontPeer.Metrics m = peer.metrics(font.getSize2D()); + return new Rectangle2D.Float(positions[2 * i], + positions[2 * i + 1] - m.ascent, advance(i), + m.ascent + m.descent + m.leading); + } + + public Shape getGlyphVisualBounds(int i) + { + return getGlyphOutline(i).getBounds2D(); + } + + public GlyphMetrics getGlyphMetrics(int i) + { + Rectangle2D b = getGlyphOutline(i).getBounds2D(); + b.setRect(b.getX() - positions[2 * i], b.getY() - positions[2 * i + 1], + b.getWidth(), b.getHeight()); + char c = text.charAt(i); + byte type = Character.isWhitespace(c) ? GlyphMetrics.WHITESPACE + : GlyphMetrics.STANDARD; + return new GlyphMetrics(advance(i), b, type); + } + + /** The outline of all glyphs at their positions. */ + public Shape getOutline() + { + if (outline == null) + outline = path(text, 0, text.length()); + return outline; + } + + public Shape getOutline(float x, float y) + { + GeneralPath p = new GeneralPath(getOutline()); + p.transform(AffineTransform.getTranslateInstance(x, y)); + return p; + } + + public Shape getGlyphOutline(int i) + { + return path(text, i, i + 1); + } + + /** The outline of glyphs begin..end at their positions. */ + private Shape path(String t, int begin, int end) + { + String part = t.substring(begin, end); + float[] xs = new float[part.length()]; + for (int i = 0; i < xs.length; i++) + xs[i] = positions[2 * (begin + i)]; + float[] p = HaikuNative.fontOutline(peer.handle, font.getSize2D(), part, + xs); + GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO); + int k = 0; + while (k < p.length) + { + switch ((int) p[k++]) + { + case 0: + path.moveTo(p[k], p[k + 1]); + k += 2; + break; + case 1: + path.lineTo(p[k], p[k + 1]); + k += 2; + break; + case 2: + path.curveTo(p[k], p[k + 1], p[k + 2], p[k + 3], p[k + 4], + p[k + 5]); + k += 6; + break; + default: + path.closePath(); + break; + } + } + AffineTransform ft = font.getTransform(); + if (ft != null && ! ft.isIdentity()) + path.transform(ft); + return path; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuGraphics2D.java b/gnu/java/awt/peer/haiku/HaikuGraphics2D.java new file mode 100644 index 0000000000..b28d6ced2b --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGraphics2D.java @@ -0,0 +1,1024 @@ +/* HaikuGraphics2D.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.java2d.AbstractGraphics2D; +import gnu.java.awt.java2d.ScanlineCoverage; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Paint; +import java.awt.PaintContext; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.ImageObserver; +import java.awt.image.Raster; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; + +/** + * A Graphics2D that renders into a BufferedImage in pure Java. + * + * When the image is the backing store of a {@link HaikuWindowPeer}, the + * area that was drawn is copied to the native window when the graphics + * is disposed (or {@link #flush()} is called). + * + * Images of type INT_RGB / INT_ARGB / INT_ARGB_PRE get fast paths for + * rectangle fills, image copies and copyArea; everything else goes + * through AbstractGraphics2D's generic rasterizer. + */ +public class HaikuGraphics2D + extends AbstractGraphics2D +{ + + private final BufferedImage image; + + private final WritableRaster raster; + + private final ColorModel colorModel; + + /** The window to flush to, or null for an off-screen image. */ + private final HaikuWindowPeer target; + + /** Direct pixel access, or null if the image has no simple int layout. */ + private int[] pixels; + + private int scan; + + /** True if the pixels carry an alpha channel. */ + private boolean hasAlpha; + + /** Area that was drawn since the last flush, in device coordinates. */ + private Rectangle dirty; + + private boolean disposed; + + /** The XOR colour, or null in paint mode. */ + private Color xorColor; + + HaikuGraphics2D(BufferedImage img, HaikuWindowPeer target) + { + super(); + image = img; + raster = img.getRaster(); + colorModel = img.getColorModel(); + this.target = target; + int type = img.getType(); + DataBuffer db = raster.getDataBuffer(); + if ((type == BufferedImage.TYPE_INT_RGB + || type == BufferedImage.TYPE_INT_ARGB + || type == BufferedImage.TYPE_INT_ARGB_PRE) + && db instanceof DataBufferInt + && db.getNumBanks() == 1 && db.getOffset() == 0 + && raster.getSampleModel() instanceof SinglePixelPackedSampleModel + && raster.getMinX() == 0 && raster.getMinY() == 0 + && raster.getSampleModelTranslateX() == 0 + && raster.getSampleModelTranslateY() == 0) + { + pixels = ((DataBufferInt) db).getData(); + scan = ((SinglePixelPackedSampleModel) raster.getSampleModel()) + .getScanlineStride(); + hasAlpha = type != BufferedImage.TYPE_INT_RGB; + } + init(); + } + + BufferedImage getImage() + { + return image; + } + + protected ColorModel getColorModel() + { + return colorModel; + } + + protected WritableRaster getDestinationRaster() + { + return raster; + } + + protected Rectangle getDeviceBounds() + { + return new Rectangle(0, 0, raster.getWidth(), raster.getHeight()); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration(); + } + + public void renderScanline(int y, ScanlineCoverage c) + { + if (y < 0 || y >= raster.getHeight()) + return; + Paint p = getPaint(); + if (pixels != null && p instanceof Color && isPlainSrcOver()) + { + // Blend the colour with the coverage directly. The generic code + // in AbstractGraphics2D needs an alpha raster in the paint + // context, which opaque colours do not have. + Color col = (Color) p; + int rgb = col.getRGB() & 0xFFFFFF; + int alpha = col.getAlpha(); + int xor = xorBits(col); + int maxCoverage = c.getMaxCoverage(); + int width = raster.getWidth(); + int off = y * scan; + int minX = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + ScanlineCoverage.Iterator iter = c.iterate(); + while (iter.hasNext()) + { + ScanlineCoverage.Range range = iter.next(); + int coverage = range.getCoverage(); + if (coverage <= 0) + continue; + int x0 = Math.max(range.getXPos(), 0); + int x1 = Math.min(range.getXPosEnd(), width); + if (x0 >= x1) + continue; + int a = coverage >= maxCoverage ? alpha + : alpha * coverage / maxCoverage; + if (xor != 0) + { + // XOR has no partial coverage; use the pixels that are + // at least half covered. + if (2 * coverage >= maxCoverage) + for (int x = x0; x < x1; x++) + pixels[off + x] ^= xor; + } + else if (a >= 255) + java.util.Arrays.fill(pixels, off + x0, off + x1, + 0xFF000000 | rgb); + else if (a > 0) + { + int s = (a << 24) | rgb; + for (int x = x0; x < x1; x++) + pixels[off + x] = blend(s, pixels[off + x]); + } + minX = Math.min(minX, x0); + maxX = Math.max(maxX, x1); + } + if (minX < maxX) + markDirty(minX, y, maxX - minX, 1); + return; + } + if (pixels != null && isPlainSrcOver()) + { + renderPaintScanline(y, c); + return; + } + super.renderScanline(y, c); + } + + /** + * Renders one scanline with an arbitrary Paint (gradients, textures) + * by asking its PaintContext for the source pixels. + */ + private void renderPaintScanline(int y, ScanlineCoverage c) + { + int width = raster.getWidth(); + int x0 = Math.max(c.getMinX(), 0); + int x1 = Math.min(c.getMaxX(), width); + if (x0 >= x1) + return; + PaintContext pCtx = getPaintContext(); + Raster src = pCtx.getRaster(x0, y, x1 - x0, 1); + ColorModel cm = pCtx.getColorModel(); + int maxCoverage = c.getMaxCoverage(); + int off = y * scan; + int sx0 = src.getMinX(); + int sy0 = src.getMinY(); + Object data = null; + ScanlineCoverage.Iterator iter = c.iterate(); + while (iter.hasNext()) + { + ScanlineCoverage.Range range = iter.next(); + int coverage = range.getCoverage(); + if (coverage <= 0) + continue; + int rx0 = Math.max(range.getXPos(), x0); + int rx1 = Math.min(range.getXPosEnd(), x1); + for (int x = rx0; x < rx1; x++) + { + data = src.getDataElements(sx0 + x - x0, sy0, data); + int argb = cm.getRGB(data); + int a = argb >>> 24; + if (coverage < maxCoverage) + a = a * coverage / maxCoverage; + pixels[off + x] = blend((a << 24) | (argb & 0xFFFFFF), + pixels[off + x]); + } + } + markDirty(x0, y, x1 - x0, 1); + } + + /** Cached paint context for renderPaintScanline(). */ + private PaintContext paintCtx; + private Paint paintCtxPaint; + private java.awt.geom.AffineTransform paintCtxTransform; + + private PaintContext getPaintContext() + { + Paint p = getPaint(); + if (paintCtx == null || paintCtxPaint != p + || ! transform.equals(paintCtxTransform)) + { + paintCtx = p.createContext(colorModel, getDeviceBounds(), + getDeviceBounds(), transform, + getRenderingHints()); + paintCtxPaint = p; + paintCtxTransform = new java.awt.geom.AffineTransform(transform); + } + return paintCtx; + } + + protected void updateRaster(Raster r, int x, int y, int w, int h) + { + markDirty(x, y, w, h); + } + + private void markDirty(int x, int y, int w, int h) + { + if (w <= 0 || h <= 0) + return; + if (dirty == null) + dirty = new Rectangle(x, y, w, h); + else + dirty.add(new Rectangle(x, y, w, h)); + } + + /** + * Returns the current clip in device coordinates, intersected with the + * device bounds. Only valid when the transform is a pure translation. + */ + private Rectangle deviceClip() + { + Rectangle bounds = getDeviceBounds(); + Rectangle c = getClipBounds(); + if (c == null) + return bounds; + c.translate((int) transform.getTranslateX(), + (int) transform.getTranslateY()); + return c.intersection(bounds); + } + + private boolean isSimpleTransform() + { + int t = transform.getType(); + return t == java.awt.geom.AffineTransform.TYPE_IDENTITY + || t == java.awt.geom.AffineTransform.TYPE_TRANSLATION; + } + + private boolean isPlainSrcOver() + { + return getComposite() == AlphaComposite.SrcOver; + } + + /** + * The bits to XOR into a pixel for colour c, or 0 in paint mode. + * The result is never 0 in XOR mode for a colour that differs from the + * XOR colour; drawing in the XOR colour itself changes nothing anyway. + */ + private int xorBits(Color c) + { + if (xorColor == null || c == null) + return 0; + return (c.getRGB() ^ xorColor.getRGB()) & 0xFFFFFF; + } + + public void setPaintMode() + { + xorColor = null; + } + + public void setXORMode(Color c) + { + xorColor = c; + } + + // Native drawing (HaikuRaster). The native code handles solid colours + // and linear gradients with a rectangular clip and the SrcOver rule; + // everything else uses the Java code of AbstractGraphics2D. + + /** -Dgnu.java.awt.peer.haiku.javaDrawing=true turns the native code off. */ + private static final boolean NATIVE = + ! Boolean.getBoolean("gnu.java.awt.peer.haiku.javaDrawing"); + + /** The SrcOver extra alpha (0..255), or -1 for other composites. */ + private int srcOverAlpha() + { + if (! NATIVE) + return -1; + java.awt.Composite c = getComposite(); + if (! (c instanceof AlphaComposite) + || ((AlphaComposite) c).getRule() != AlphaComposite.SRC_OVER) + return -1; + return Math.round(((AlphaComposite) c).getAlpha() * 255); + } + + /** + * The device clip as {x, y, w, h}, or null if the clip is not a + * rectangle in device space. + */ + private int[] nativeClip() + { + Rectangle bounds = getDeviceBounds(); + java.awt.Shape c = getClip(); + Rectangle r = bounds; + if (c != null) + { + if (c instanceof java.awt.geom.Area + && ((java.awt.geom.Area) c).isRectangular()) + c = c.getBounds2D(); + if (! (c instanceof java.awt.geom.Rectangle2D) + || transform.getShearX() != 0 || transform.getShearY() != 0) + return null; + java.awt.geom.Rectangle2D d = + transform.createTransformedShape(c).getBounds2D(); + int x0 = (int) Math.round(d.getMinX()); + int y0 = (int) Math.round(d.getMinY()); + int x1 = (int) Math.round(d.getMaxX()); + int y1 = (int) Math.round(d.getMaxY()); + r = new Rectangle(x0, y0, x1 - x0, y1 - y0).intersection(bounds); + } + if (r.width <= 0 || r.height <= 0) + return new int[4]; + return new int[] { r.x, r.y, r.width, r.height }; + } + + /** A colour with the composite's extra alpha applied, as ARGB. */ + private static int argb(Color c, int extraAlpha) + { + int rgb = c.getRGB(); + if (extraAlpha >= 255) + return rgb; + int a = ((rgb >>> 24) * extraAlpha + 127) / 255; + return (a << 24) | (rgb & 0xFFFFFF); + } + + /** Fills a device rectangle with a color, honouring the clip. */ + private boolean fastFill(int x, int y, int w, int h, Color c) + { + return fastFill(x, y, w, h, c, xorBits(c)); + } + + private boolean fastFill(int x, int y, int w, int h, Color c, int xor) + { + int ea = srcOverAlpha(); + if (pixels == null || c == null || ea < 0) + return false; + int[] clip = nativeClip(); + if (clip == null) + return false; + HaikuRaster.fillRect(pixels, scan, hasAlpha, clip, x, y, w, h, + argb(c, ea), xor); + markDirty(Math.max(x, clip[0]), Math.max(y, clip[1]), + Math.min(x + w, clip[0] + clip[2]) - Math.max(x, clip[0]), + Math.min(y + h, clip[1] + clip[3]) - Math.max(y, clip[1])); + return true; + } + + /** Marks the device bounds of a shape, clipped, as dirty. */ + private void markShape(java.awt.geom.Rectangle2D b, int[] clip, int grow) + { + int x0 = Math.max((int) Math.floor(b.getMinX()) - grow, clip[0]); + int y0 = Math.max((int) Math.floor(b.getMinY()) - grow, clip[1]); + int x1 = Math.min((int) Math.ceil(b.getMaxX()) + grow + 1, + clip[0] + clip[2]); + int y1 = Math.min((int) Math.ceil(b.getMaxY()) + grow + 1, + clip[1] + clip[3]); + markDirty(x0, y0, x1 - x0, y1 - y0); + } + + /** + * Fills a shape with the native code: colours and linear gradients, + * SrcOver, rectangular clip. + * + * @return false if the Java code must draw it + */ + private boolean nativeFill(java.awt.Shape s, boolean aa) + { + int ea = srcOverAlpha(); + Paint p = getPaint(); + if (pixels == null || ea < 0 + || ! (p instanceof Color || p instanceof java.awt.GradientPaint)) + return false; + int[] clip = nativeClip(); + if (clip == null) + return false; + if (clip[2] == 0) + return true; + boolean[] evenOdd = new boolean[1]; + float[] path = HaikuRaster.path(s, transform, evenOdd); + if (path.length == 0) + return true; + if (p instanceof Color) + { + int xor = xorBits((Color) p); + HaikuRaster.fillPath(pixels, scan, hasAlpha, clip, path, path.length, + evenOdd[0], aa && xor == 0, + argb((Color) p, ea), xor); + } + else + { + java.awt.GradientPaint gp = (java.awt.GradientPaint) p; + java.awt.geom.AffineTransform inv; + try + { + inv = transform.createInverse(); + } + catch (java.awt.geom.NoninvertibleTransformException e) + { + return true; + } + double[] m = new double[6]; + inv.getMatrix(m); + double[] g = { gp.getPoint1().getX(), gp.getPoint1().getY(), + gp.getPoint2().getX(), gp.getPoint2().getY(), + m[0], m[1], m[2], m[3], m[4], m[5] }; + HaikuRaster.fillPathGradient(pixels, scan, hasAlpha, clip, path, + path.length, evenOdd[0], aa, g, + argb(gp.getColor1(), ea), + argb(gp.getColor2(), ea), + gp.isCyclic()); + } + markShape(transform.createTransformedShape(s).getBounds2D(), clip, 1); + return true; + } + + protected void fillShape(java.awt.Shape s, boolean isFont) + { + boolean aa; + if (isFont) + { + Object v = getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING); + aa = v != RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; + } + else + aa = getRenderingHint(RenderingHints.KEY_ANTIALIASING) + == RenderingHints.VALUE_ANTIALIAS_ON; + if (! nativeFill(s, aa)) + super.fillShape(s, isFont); + } + + /** + * Thin lines (width up to 1, no dashes, no anti-aliasing, a colour) are + * drawn as 1-pixel lines, as in the JDK; the generic code fills a + * stroked outline. Other strokes are filled natively by fillShape(). + */ + public void draw(java.awt.Shape s) + { + java.awt.Stroke st = getStroke(); + Paint p = getPaint(); + int ea = srcOverAlpha(); + if (pixels != null && ea >= 0 && p instanceof Color + && st instanceof java.awt.BasicStroke + && ((java.awt.BasicStroke) st).getLineWidth() + * Math.max(Math.abs(transform.getScaleX()), + Math.abs(transform.getScaleY())) <= 1 + && ((java.awt.BasicStroke) st).getDashArray() == null + && getRenderingHint(RenderingHints.KEY_ANTIALIASING) + != RenderingHints.VALUE_ANTIALIAS_ON) + { + int[] clip = nativeClip(); + if (clip != null) + { + if (clip[2] > 0) + { + boolean[] evenOdd = new boolean[1]; + float[] path = HaikuRaster.path(s, transform, evenOdd); + HaikuRaster.drawPathThin(pixels, scan, hasAlpha, clip, path, + path.length, argb((Color) p, ea), + xorBits((Color) p)); + markShape(transform.createTransformedShape(s).getBounds2D(), + clip, 1); + } + return; + } + } + super.draw(s); + } + + /** + * Draws a 1-pixel line. Called with device coordinates; the default + * implementation would apply the transform a second time. + */ + protected void rawDrawLine(int x0, int y0, int x1, int y1) + { + Paint p = getPaint(); + int ea = srcOverAlpha(); + int[] clip; + if (pixels != null && p instanceof Color && ea >= 0 + && (clip = nativeClip()) != null) + { + HaikuRaster.drawLine(pixels, scan, hasAlpha, clip, x0, y0, x1, y1, + argb((Color) p, ea), xorBits((Color) p)); + markDirty(Math.min(x0, x1), Math.min(y0, y1), + Math.abs(x1 - x0) + 1, Math.abs(y1 - y0) + 1); + return; + } + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + super.draw(new java.awt.geom.Line2D.Float(x0 - tx, y0 - ty, x1 - tx, + y1 - ty)); + } + + /** + * Draws a rectangle outline, (w + 1) x (h + 1) pixels. Called with + * device coordinates. + */ + protected void rawDrawRect(int x, int y, int w, int h) + { + if (w < 0 || h < 0) + return; + // No pixel is drawn twice, so XOR mode works. + rawDrawLine(x, y, x + w, y); + if (h > 0) + rawDrawLine(x, y + h, x + w, y + h); + if (h > 1) + { + rawDrawLine(x, y + 1, x, y + h - 1); + if (w > 0) + rawDrawLine(x + w, y + 1, x + w, y + h - 1); + } + } + + protected void rawFillRect(int x, int y, int w, int h) + { + Paint p = getPaint(); + if (! (p instanceof Color && fastFill(x, y, w, h, (Color) p))) + { + // Device coordinates; fill() applies the transform itself. + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + fill(new Rectangle(x - tx, y - ty, w, h)); + } + } + + public void clearRect(int x, int y, int width, int height) + { + Color bg = getBackground(); + if (bg == null) + bg = Color.WHITE; + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + if (! fastFill(x + tx, y + ty, width, height, bg, 0)) + { + Paint saved = getPaint(); + setPaint(bg); + fillRect(x, y, width, height); + setPaint(saved); + } + } + + /** + * Draws the source rectangle (sx, sy, sw, sh) of an image through + * imageToUser (image space to user space) with the native code. + * + * @return false if the Java code must draw it + */ + private boolean nativeImage(Image img, java.awt.geom.AffineTransform + imageToUser, int sx, int sy, int sw, int sh, + Color bg) + { + int ea = srcOverAlpha(); + if (pixels == null || ea < 0) + return false; + HaikuRaster.Source src = HaikuRaster.source(img); + if (src == null) + return false; + int[] clip = nativeClip(); + if (clip == null) + return false; + sx = Math.max(sx, 0); + sy = Math.max(sy, 0); + sw = Math.min(sw, src.w - sx); + sh = Math.min(sh, src.h - sy); + if (clip[2] == 0 || sw <= 0 || sh <= 0) + return true; + java.awt.geom.AffineTransform t = new java.awt.geom.AffineTransform(transform); + t.concatenate(imageToUser); + t.translate(sx, sy); + java.awt.geom.Rectangle2D area = new java.awt.geom.Rectangle2D.Double(0, 0, sw, sh); + if (bg != null) + { + // The background fills the image area, then the image goes on top. + Paint saved = getPaint(); + setPaint(bg); + java.awt.geom.AffineTransform ut = new java.awt.geom.AffineTransform(imageToUser); + ut.translate(sx, sy); + fill(ut.createTransformedShape(area)); + setPaint(saved); + } + java.awt.geom.AffineTransform inv; + try + { + inv = t.createInverse(); + } + catch (java.awt.geom.NoninvertibleTransformException e) + { + return true; + } + double[] m = new double[6]; + inv.getMatrix(m); + Rectangle box = t.createTransformedShape(area).getBounds(); + Object interp = getRenderingHint(RenderingHints.KEY_INTERPOLATION); + boolean bilinear = interp != null + && interp != RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; + HaikuRaster.drawImage(pixels, scan, hasAlpha, clip, src.data, src.scan, + src.alpha, src.x + sx, src.y + sy, sw, sh, m, + new int[] { box.x, box.y, box.width, box.height }, + bilinear, ea); + markShape(box, clip, 0); + return true; + } + + /** + * The Java fallback for nativeImage(): background, then the image + * through AbstractGraphics2D's transform path. The other drawImage() + * overloads of AbstractGraphics2D call back into this class, which + * would recurse. + */ + private boolean javaImage(Image img, java.awt.geom.AffineTransform + imageToUser, int sx, int sy, int sw, int sh, + Color bg, ImageObserver obs) + { + int iw = img.getWidth(obs), ih = img.getHeight(obs); + if (iw <= 0 || ih <= 0) + return false; + sx = Math.max(sx, 0); + sy = Math.max(sy, 0); + sw = Math.min(sw, iw - sx); + sh = Math.min(sh, ih - sy); + if (sw <= 0 || sh <= 0) + return true; + java.awt.geom.AffineTransform at = new java.awt.geom.AffineTransform(imageToUser); + at.translate(sx, sy); + if (bg != null) + { + Paint saved = getPaint(); + setPaint(bg); + fill(at.createTransformedShape(new Rectangle(0, 0, sw, sh))); + setPaint(saved); + } + if ((sx != 0 || sy != 0 || sw != iw || sh != ih) + && img instanceof BufferedImage) + img = ((BufferedImage) img).getSubimage(sx, sy, sw, sh); + return super.drawImage(img, at, obs); + } + + protected boolean rawDrawImage(Image img, int x, int y, ImageObserver obs) + { + img = unwrap(img); + // Device coordinates: undo the translation that nativeImage() adds. + java.awt.geom.AffineTransform at = java.awt.geom.AffineTransform + .getTranslateInstance(x - transform.getTranslateX(), + y - transform.getTranslateY()); + if (nativeImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + null)) + return true; + return javaImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + null, obs); + } + + /** + * Returns a RenderedImage for images that AbstractGraphics2D cannot draw + * itself (it silently ignores anything that is not a RenderedImage). + */ + private static Image unwrap(Image img) + { + if (img instanceof gnu.java.awt.image.AsyncImage) + img = ((gnu.java.awt.image.AsyncImage) img).getRealImage(); + if (img instanceof HaikuVolatileImage) + img = ((HaikuVolatileImage) img).getSnapshot(); + return img; + } + + public boolean drawImage(Image img, java.awt.geom.AffineTransform xform, + ImageObserver obs) + { + img = unwrap(img); + if (xform == null) + xform = new java.awt.geom.AffineTransform(); + if (nativeImage(img, xform, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + null)) + return true; + return super.drawImage(img, xform, obs); + } + + public boolean drawImage(Image img, int x, int y, ImageObserver obs) + { + img = unwrap(img); + java.awt.geom.AffineTransform at = + java.awt.geom.AffineTransform.getTranslateInstance(x, y); + if (nativeImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + null)) + return true; + return javaImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + null, obs); + } + + // The generic AbstractGraphics2D code draws scaled images and images + // with a background colour through an ImagePaint, one pixel at a time; + // these overloads use the native code. + + public boolean drawImage(Image img, int x, int y, int w, int h, + ImageObserver obs) + { + return drawImage(img, x, y, w, h, null, obs); + } + + public boolean drawImage(Image img, int x, int y, Color bg, + ImageObserver obs) + { + img = unwrap(img); + java.awt.geom.AffineTransform at = + java.awt.geom.AffineTransform.getTranslateInstance(x, y); + if (nativeImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + bg)) + return true; + return javaImage(img, at, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, + bg, obs); + } + + public boolean drawImage(Image img, int x, int y, int w, int h, Color bg, + ImageObserver obs) + { + img = unwrap(img); + int iw = img.getWidth(obs), ih = img.getHeight(obs); + if (w <= 0 || h <= 0) + return true; + if (iw <= 0 || ih <= 0) + return false; + return drawImage(img, x, y, x + w, y + h, 0, 0, iw, ih, bg, obs); + } + + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver obs) + { + return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, obs); + } + + /** Draws (sx1, sy1)-(sx2, sy2) of the image scaled to (dx1, dy1)-(dx2, dy2). */ + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, Color bg, + ImageObserver obs) + { + img = unwrap(img); + if (dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2) + return true; + // Image space to user space: the source corner maps to (dx1, dy1), + // and flipped rectangles flip the image. + int sx = Math.min(sx1, sx2), sy = Math.min(sy1, sy2); + int sw = Math.abs(sx2 - sx1), sh = Math.abs(sy2 - sy1); + double scaleX = (double) (dx2 - dx1) / (sx2 - sx1); + double scaleY = (double) (dy2 - dy1) / (sy2 - sy1); + java.awt.geom.AffineTransform at = new java.awt.geom.AffineTransform( + scaleX, 0, 0, scaleY, dx1 - sx1 * scaleX, dy1 - sy1 * scaleY); + if (nativeImage(img, at, sx, sy, sw, sh, bg)) + return true; + return javaImage(img, at, sx, sy, sw, sh, bg, obs); + } + + /** Non-premultiplied ARGB src over an opaque-or-ARGB destination. */ + private static int blend(int s, int d) + { + int a = s >>> 24; + if (a == 255) + return s; + if (a == 0) + return d; + int na = 255 - a; + int r = (((s >> 16) & 0xFF) * a + ((d >> 16) & 0xFF) * na) / 255; + int g = (((s >> 8) & 0xFF) * a + ((d >> 8) & 0xFF) * na) / 255; + int b = ((s & 0xFF) * a + (d & 0xFF) * na) / 255; + int da = d >>> 24; + int oa = a + da * na / 255; + return (oa << 24) | (r << 16) | (g << 8) | b; + } + + public void copyArea(int x, int y, int width, int height, int dx, int dy) + { + if (! isSimpleTransform()) + return; + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + Rectangle bounds = getDeviceBounds(); + Rectangle src = new Rectangle(x + tx, y + ty, width, height) + .intersection(bounds); + Rectangle dst = new Rectangle(src.x + dx, src.y + dy, src.width, + src.height).intersection(deviceClip()); + if (dst.width <= 0 || dst.height <= 0) + return; + if (pixels != null && NATIVE) + HaikuRaster.copyArea(pixels, scan, dst.x - dx, dst.y - dy, dst.width, + dst.height, dx, dy); + else + { + Object data = raster.getDataElements(dst.x - dx, dst.y - dy, + dst.width, dst.height, null); + raster.setDataElements(dst.x, dst.y, dst.width, dst.height, data); + } + markDirty(dst.x, dst.y, dst.width, dst.height); + } + + // Text: Haiku fonts are drawn by the app_server. + + /** A rendered string: coverage, size, baseline and origin. */ + private static final class TextMask + { + final byte[] cover; + final int w, h, baseline, originX; + + TextMask(byte[] cover, int[] dims) + { + this.cover = cover; + w = dims[0]; + h = dims[1]; + baseline = dims[2]; + originX = dims[3]; + } + } + + /** Recently drawn strings; Swing draws the same labels again and again. */ + private static final java.util.LinkedHashMap textCache = + new java.util.LinkedHashMap(256, 0.75f, true) + { + protected boolean removeEldestEntry(java.util.Map.Entry e) + { + return size() > 500; + } + }; + + private static TextMask renderText(HaikuFontPeer peer, float size, + String text) + { + String key = peer.handle + "/" + size + "/" + text; + synchronized (textCache) + { + TextMask m = textCache.get(key); + if (m != null) + return m; + } + int[] dims = new int[4]; + byte[] cover = HaikuNative.fontRender(peer.handle, size, text, dims); + if (cover == null) + return null; + TextMask m = new TextMask(cover, dims); + synchronized (textCache) + { + textCache.put(key, m); + } + return m; + } + + /** + * Draws glyph vectors of Haiku fonts with the app_server, at the scale + * of the transform. Rotated or sheared text, transformed fonts, paints + * that are not colours and moved glyphs are filled as outlines. + */ + public void drawGlyphVector(java.awt.font.GlyphVector gv, float x, float y) + { + if (gv instanceof HaikuGlyphVector && pixels != null + && getPaint() instanceof Color) + { + HaikuGlyphVector hgv = (HaikuGlyphVector) gv; + java.awt.geom.AffineTransform t = getTransform(); + java.awt.geom.AffineTransform ft = hgv.font.getTransform(); + double sx = t.getScaleX(); + double sy = t.getScaleY(); + if (! hgv.customLayout && t.getShearX() == 0 && t.getShearY() == 0 + && sx > 0 && Math.abs(sx - sy) < 1e-6 + && (ft == null || ft.isIdentity())) + { + if (hgv.text.length() == 0) + return; + TextMask m = renderText(hgv.peer, + (float) (hgv.font.getSize2D() * sy), + hgv.text); + if (m != null) + { + int dx = (int) Math.round(t.getTranslateX() + sx * x) + - m.originX; + int dy = (int) Math.round(t.getTranslateY() + sy * y) + - m.baseline; + blendCoverage(m.cover, m.w, m.h, dx, dy); + return; + } + } + } + // AbstractGraphics2D translates before filling, which also moves + // gradient and texture paints; fill the outline in place instead. + fillShape(gv.getOutline(x, y), true); + } + + /** Blends the current colour through a coverage mask at (dx, dy). */ + private void blendCoverage(byte[] cover, int w, int h, int dx, int dy) + { + Color c = (Color) getPaint(); + int rgb = c.getRGB(); + int ca = c.getAlpha(); + int cr = (rgb >> 16) & 0xff, cg = (rgb >> 8) & 0xff, cb = rgb & 0xff; + Rectangle area = new Rectangle(0, 0, image.getWidth(), image.getHeight()); + java.awt.Shape clip = getClip(); + if (clip != null) + area = area.intersection(getTransform().createTransformedShape(clip) + .getBounds()); + area = area.intersection(new Rectangle(dx, dy, w, h)); + if (area.isEmpty()) + return; + for (int yy = area.y; yy < area.y + area.height; yy++) + for (int xx = area.x; xx < area.x + area.width; xx++) + { + int a = (cover[(yy - dy) * w + (xx - dx)] & 0xff) * ca / 255; + if (a == 0) + continue; + int k = yy * scan + xx; + int d = pixels[k]; + int r = (cr * a + ((d >> 16) & 0xff) * (255 - a)) / 255; + int g = (cg * a + ((d >> 8) & 0xff) * (255 - a)) / 255; + int b = (cb * a + (d & 0xff) * (255 - a)) / 255; + int al = hasAlpha ? a + (((d >>> 24) * (255 - a)) / 255) : 0xff; + pixels[k] = (al << 24) | (r << 16) | (g << 8) | b; + } + markDirty(area.x, area.y, area.width, area.height); + } + + public Graphics create() + { + HaikuGraphics2D copy = (HaikuGraphics2D) super.create(); + copy.dirty = null; + copy.disposed = false; + return copy; + } + + /** + * Copies the area drawn so far to the native window. + */ + public void flush() + { + if (target != null && dirty != null) + { + Rectangle d = dirty; + dirty = null; + target.flushBacking(image, d.x, d.y, d.width, d.height); + } + } + + public void dispose() + { + if (! disposed) + { + disposed = true; + flush(); + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuGraphicsConfiguration.java b/gnu/java/awt/peer/haiku/HaikuGraphicsConfiguration.java new file mode 100644 index 0000000000..a59f0bf391 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGraphicsConfiguration.java @@ -0,0 +1,130 @@ +/* HaikuGraphicsConfiguration.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.VolatileImage; + +/** + * The single 32-bit configuration of the Haiku screen. + */ +class HaikuGraphicsConfiguration + extends GraphicsConfiguration +{ + + private final HaikuGraphicsDevice device; + + private int width, height, dpi; + + HaikuGraphicsConfiguration(HaikuGraphicsDevice dev) + { + device = dev; + int[] info = new int[3]; + HaikuNative.ensureInitialized(); + HaikuNative.screenInfo(info); + width = info[0]; + height = info[1]; + dpi = info[2]; + } + + int getResolution() + { + return dpi; + } + + public GraphicsDevice getDevice() + { + return device; + } + + public BufferedImage createCompatibleImage(int w, int h) + { + return new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + } + + public BufferedImage createCompatibleImage(int w, int h, int transparency) + { + if (transparency == Transparency.OPAQUE) + return createCompatibleImage(w, h); + return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + } + + public VolatileImage createCompatibleVolatileImage(int w, int h) + { + return new HaikuVolatileImage(w, h); + } + + public VolatileImage createCompatibleVolatileImage(int w, int h, + int transparency) + { + return new HaikuVolatileImage(w, h); + } + + public ColorModel getColorModel() + { + return ColorModel.getRGBdefault(); + } + + public ColorModel getColorModel(int transparency) + { + return ColorModel.getRGBdefault(); + } + + public AffineTransform getDefaultTransform() + { + return new AffineTransform(); + } + + public AffineTransform getNormalizingTransform() + { + double s = dpi / 72.0; + return AffineTransform.getScaleInstance(s, s); + } + + public Rectangle getBounds() + { + return new Rectangle(0, 0, width, height); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuGraphicsDevice.java b/gnu/java/awt/peer/haiku/HaikuGraphicsDevice.java new file mode 100644 index 0000000000..b1ed538c7d --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGraphicsDevice.java @@ -0,0 +1,78 @@ +/* HaikuGraphicsDevice.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; + +/** + * The main Haiku screen. + */ +class HaikuGraphicsDevice + extends GraphicsDevice +{ + + private HaikuGraphicsConfiguration config; + + HaikuGraphicsDevice() + { + } + + public int getType() + { + return TYPE_RASTER_SCREEN; + } + + public String getIDstring() + { + return "Haiku screen 0"; + } + + public GraphicsConfiguration[] getConfigurations() + { + return new GraphicsConfiguration[] { getDefaultConfiguration() }; + } + + public synchronized GraphicsConfiguration getDefaultConfiguration() + { + if (config == null) + config = new HaikuGraphicsConfiguration(this); + return config; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuGraphicsEnvironment.java b/gnu/java/awt/peer/haiku/HaikuGraphicsEnvironment.java new file mode 100644 index 0000000000..d5287bbfa6 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGraphicsEnvironment.java @@ -0,0 +1,104 @@ +/* HaikuGraphicsEnvironment.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.util.Locale; + +/** + * The Haiku graphics environment: one screen, Java-rendered images. + */ +public class HaikuGraphicsEnvironment + extends GraphicsEnvironment +{ + + private HaikuGraphicsDevice device; + + public HaikuGraphicsEnvironment() + { + } + + public GraphicsDevice[] getScreenDevices() + { + return new GraphicsDevice[] { getDefaultScreenDevice() }; + } + + public synchronized GraphicsDevice getDefaultScreenDevice() + { + if (device == null) + device = new HaikuGraphicsDevice(); + return device; + } + + public Graphics2D createGraphics(BufferedImage image) + { + return new HaikuGraphics2D(image, null); + } + + public Font[] getAllFonts() + { + String[] names = getAvailableFontFamilyNames(); + Font[] fonts = new Font[names.length]; + for (int i = 0; i < names.length; i++) + fonts[i] = new Font(names[i], Font.PLAIN, 1); + return fonts; + } + + public String[] getAvailableFontFamilyNames() + { + return getAvailableFontFamilyNames(Locale.getDefault()); + } + + public String[] getAvailableFontFamilyNames(Locale l) + { + // The Java logical fonts, then Haiku's font families. + String[] logical = { "Dialog", "DialogInput", "Monospaced", "SansSerif", + "Serif" }; + String[] haiku = HaikuFontPeer.haikuFamilies(); + String[] all = new String[logical.length + haiku.length]; + System.arraycopy(logical, 0, all, 0, logical.length); + System.arraycopy(haiku, 0, all, logical.length, haiku.length); + return all; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuInputMethods.java b/gnu/java/awt/peer/haiku/HaikuInputMethods.java new file mode 100644 index 0000000000..15626cb6b2 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuInputMethods.java @@ -0,0 +1,238 @@ +/* HaikuInputMethods.java -- Haiku input methods for AWT + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.KeyboardFocusManager; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.InputMethodEvent; +import java.awt.event.KeyEvent; +import java.awt.font.TextAttribute; +import java.awt.font.TextHitInfo; +import java.awt.im.InputMethodHighlight; +import java.awt.im.InputMethodRequests; +import java.text.AttributedString; + +import javax.swing.text.JTextComponent; + +/** + * Connects Haiku input methods (B_INPUT_METHOD_EVENT) to AWT. + * + * Components with InputMethodRequests get InputMethodEvents with the + * composed text inline. Other components (all of GNU Classpath's Swing + * text components) get the confirmed text as KEY_TYPED events, and the + * composed text is shown in a small window below the caret. + */ +final class HaikuInputMethods +{ + + private HaikuInputMethods() + { + } + + private static void post(java.awt.AWTEvent e) + { + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e); + } + + private static Component focusOwner(Window w) + { + Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getFocusOwner(); + return c != null ? c : w; + } + + /** + * The caret of a component in screen coordinates. For text components + * this is the caret rectangle; else the bottom left corner. + */ + static Rectangle caret(Component c) + { + JTextComponent text = null; + if (c instanceof JTextComponent) + text = (JTextComponent) c; + else if (c.getPeer() instanceof HaikuTextFieldPeer) + text = ((HaikuTextFieldPeer) c.getPeer()).textComponent(); + Point origin; + try + { + origin = c.getLocationOnScreen(); + } + catch (Exception e) + { + return new Rectangle(0, 0, 1, 16); + } + if (text != null) + { + try + { + Rectangle r = text.modelToView(text.getCaretPosition()); + if (r != null) + { + // An AWT text field draws its Swing field at its own origin. + return new Rectangle(origin.x + r.x, origin.y + r.y, + Math.max(r.width, 1), r.height); + } + } + catch (Exception e) + { + // Use the component corner. + } + } + int h = 16; + if (c.getFont() != null) + { + FontMetrics fm = c.getFontMetrics(c.getFont()); + if (fm != null) + h = fm.getHeight(); + } + return new Rectangle(origin.x + 2, origin.y + Math.max(0, c.getHeight() - h), + 1, h); + } + + /** Called by the event pump for EV_IM. */ + static void event(HaikuWindowPeer peer, Window w, int opcode, + boolean confirmed, int selStart, int selEnd) + { + Component c = focusOwner(w); + InputMethodRequests req = c.getInputMethodRequests(); + switch (opcode) + { + case HaikuNative.B_INPUT_METHOD_CHANGED: + changed(peer, w, c, req, HaikuNative.imText(peer.id), confirmed, + selStart, selEnd); + break; + case HaikuNative.B_INPUT_METHOD_LOCATION_REQUEST: + locationRequest(peer, c, req, HaikuNative.imText(peer.id)); + break; + case HaikuNative.B_INPUT_METHOD_STOPPED: + HaikuNative.imShowComposition(null, 0, 0, 0, 0); + if (req != null) + post(new InputMethodEvent(c, InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + new AttributedString("").getIterator(), + 0, null, null)); + break; + default: + break; + } + } + + private static void changed(HaikuWindowPeer peer, Window w, Component c, + InputMethodRequests req, String text, + boolean confirmed, int selStart, int selEnd) + { + if (confirmed) + { + HaikuNative.imShowComposition(null, 0, 0, 0, 0); + if (req != null) + { + post(new InputMethodEvent(c, InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + new AttributedString(text).getIterator(), + text.length(), null, null)); + return; + } + long when = System.currentTimeMillis(); + for (int i = 0; i < text.length(); i++) + post(new KeyEvent(w, KeyEvent.KEY_TYPED, when, 0, + KeyEvent.VK_UNDEFINED, text.charAt(i))); + return; + } + if (req != null) + { + AttributedString as = new AttributedString(text); + if (text.length() > 0) + { + as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, + InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT); + if (selEnd > selStart) + as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, + InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT, + selStart, selEnd); + } + post(new InputMethodEvent(c, InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + as.getIterator(), 0, + TextHitInfo.leading(selEnd), null)); + return; + } + Rectangle r = caret(c); + HaikuNative.imShowComposition(text, r.x, r.y + r.height + 2, selStart, + selEnd); + } + + /** Tells the input method where each composed character is. */ + private static void locationRequest(HaikuWindowPeer peer, Component c, + InputMethodRequests req, String text) + { + int n = Math.max(1, text.length()); + int[] xs = new int[n]; + int[] ys = new int[n]; + float height; + if (req != null) + { + Rectangle r = null; + for (int i = 0; i < n; i++) + { + r = req.getTextLocation(TextHitInfo.leading(i)); + xs[i] = r.x; + ys[i] = r.y; + } + height = r != null ? r.height : 16; + } + else + { + // The composed text is in the composition window below the caret. + Rectangle r = caret(c); + FontMetrics fm = c.getFont() != null + ? c.getFontMetrics(c.getFont()) : null; + int x = r.x; + for (int i = 0; i < n; i++) + { + xs[i] = x + 4; + ys[i] = r.y + r.height + 2; + x += fm != null && i < text.length() ? fm.charWidth(text.charAt(i)) + : 8; + } + height = r.height; + } + HaikuNative.imLocationReply(peer.handle, xs, ys, height); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuLabelPeer.java b/gnu/java/awt/peer/haiku/HaikuLabelPeer.java new file mode 100644 index 0000000000..456acf1ff2 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuLabelPeer.java @@ -0,0 +1,109 @@ +/* HaikuLabelPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingLabelPeer; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Label; + +/** + * A label drawn by Swing. An empty AWT label keeps the height of one + * line of text, as in other toolkits; an empty JLabel has no height. + */ +public class HaikuLabelPeer + extends SwingLabelPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + HaikuLabelPeer(Label label) + { + super(label); + } + + private Dimension atLeastOneLine(Dimension d) + { + java.awt.Font f = awtComponent.getFont(); + if (f == null || awtComponent.getParent() == null) + return d; + FontMetrics fm = getFontMetrics(f); + if (fm != null) + d.height = Math.max(d.height, fm.getHeight()); + return d; + } + + public Dimension getPreferredSize() + { + return atLeastOneLine(super.getPreferredSize()); + } + + public Dimension preferredSize() + { + return atLeastOneLine(super.preferredSize()); + } + + public Dimension getMinimumSize() + { + return atLeastOneLine(super.getMinimumSize()); + } + + public Dimension minimumSize() + { + return atLeastOneLine(super.minimumSize()); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuListPeer.java b/gnu/java/awt/peer/haiku/HaikuListPeer.java new file mode 100644 index 0000000000..1d56e5a406 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuListPeer.java @@ -0,0 +1,307 @@ +/* HaikuListPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Dimension; +import java.awt.Event; +import java.awt.FontMetrics; +import java.awt.Insets; +import java.awt.List; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.peer.ListPeer; +import java.util.Arrays; + +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +/** + * A list drawn by a Swing JList in a JScrollPane. + */ +public class HaikuListPeer + extends HaikuSwingPeer + implements ListPeer +{ + + private class KeyList + extends JList + implements HaikuSwingWrapper.KeyTarget + { + KeyList() + { + super(model); + } + + public void routeKey(KeyEvent e) + { + processKeyEvent(new KeyEvent(this, e.getID(), e.getWhen(), + e.getModifiersEx(), e.getKeyCode(), + e.getKeyChar(), e.getKeyLocation())); + } + } + + private final List awtList; + + private final DefaultListModel model = new DefaultListModel(); + + private final KeyList jList; + + private final JScrollPane scroll; + + /** True while AWT changes the selection; do not echo it back. */ + private boolean setting; + + /** The selection last reported to AWT. */ + private int[] reported = new int[0]; + + HaikuListPeer(final List list) + { + awtList = list; + jList = new KeyList(); + String[] items = list.getItems(); + for (int i = 0; i < items.length; i++) + model.addElement(items[i]); + setMultipleMode(list.isMultipleMode()); + int[] sel = list.getSelectedIndexes(); + setting = true; + jList.setSelectedIndices(sel); + setting = false; + reported = sel; + jList.addListSelectionListener(new ListSelectionListener() + { + public void valueChanged(ListSelectionEvent e) + { + if (! setting && ! e.getValueIsAdjusting()) + selectionChanged(); + } + }); + jList.addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + if (e.getClickCount() == 2) + { + int i = jList.locationToIndex(e.getPoint()); + if (i >= 0) + HaikuSwingWrapper.post(list, + new ActionEvent(list, ActionEvent.ACTION_PERFORMED, + list.getItem(i)), null); + } + } + }); + scroll = new JScrollPane(jList); + // The backing store mode needs createImage() on a lightweight + // component, which has no peer to make the image. + scroll.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); + initWrapped(list, scroll); + wrapper.keyTarget = jList; + } + + /** Posts item events for every index that changed. */ + private void selectionChanged() + { + int[] now = jList.getSelectedIndices(); + int[] before = reported; + reported = now; + boolean old = awtList.getItemListeners().length == 0; + for (int i = 0; i < now.length; i++) + if (! contains(before, now[i])) + post(now[i], true, old); + for (int i = 0; i < before.length; i++) + if (! contains(now, before[i])) + post(before[i], false, old); + } + + private void post(int index, boolean selected, boolean old) + { + ItemEvent e = new ItemEvent(awtList, ItemEvent.ITEM_STATE_CHANGED, + new Integer(index), + selected ? ItemEvent.SELECTED + : ItemEvent.DESELECTED); + Event oe = null; + if (old) + oe = new Event(awtList, selected ? Event.LIST_SELECT + : Event.LIST_DESELECT, + new Integer(index)); + HaikuSwingWrapper.post(awtList, e, oe); + } + + private static boolean contains(int[] a, int v) + { + for (int i = 0; i < a.length; i++) + if (a[i] == v) + return true; + return false; + } + + private void setSelection(int[] sel) + { + setting = true; + try + { + jList.setSelectedIndices(sel); + reported = jList.getSelectedIndices(); + } + finally + { + setting = false; + } + } + + public void add(String item, int index) + { + if (index < 0 || index > model.size()) + index = model.size(); + model.add(index, item); + } + + public void addItem(String item, int index) + { + add(item, index); + } + + public void clear() + { + removeAll(); + } + + public void delItems(int start, int end) + { + model.removeRange(start, end); + reported = jList.getSelectedIndices(); + } + + public void deselect(int index) + { + setting = true; + try + { + jList.removeSelectionInterval(index, index); + reported = jList.getSelectedIndices(); + } + finally + { + setting = false; + } + } + + public int[] getSelectedIndexes() + { + return jList.getSelectedIndices(); + } + + public void makeVisible(int index) + { + jList.ensureIndexIsVisible(index); + } + + public void removeAll() + { + model.clear(); + reported = new int[0]; + } + + public void select(int index) + { + if (jList.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) + setSelection(new int[] { index }); + else + { + setting = true; + try + { + jList.addSelectionInterval(index, index); + reported = jList.getSelectedIndices(); + } + finally + { + setting = false; + } + } + } + + public void setMultipleMode(boolean multi) + { + jList.setSelectionMode(multi + ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + : ListSelectionModel.SINGLE_SELECTION); + } + + public void setMultipleSelections(boolean multi) + { + setMultipleMode(multi); + } + + public Dimension getPreferredSize(int rows) + { + return preferredSize(rows); + } + + public Dimension getMinimumSize(int rows) + { + return minimumSize(rows); + } + + public Dimension preferredSize(int rows) + { + FontMetrics fm = jList.getFontMetrics(jList.getFont()); + int rowH = fm.getHeight(); + int w = 0; + for (int i = 0; i < model.size(); i++) + w = Math.max(w, fm.stringWidth(String.valueOf(model.get(i)))); + Insets in = scroll.getInsets(); + int sb = scroll.getVerticalScrollBar().getPreferredSize().width; + return new Dimension(Math.max(w, 4 * rowH) + 8 + sb + in.left + in.right, + rows * rowH + 4 + in.top + in.bottom); + } + + public Dimension minimumSize(int rows) + { + return preferredSize(rows); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuMenuBarPeer.java b/gnu/java/awt/peer/haiku/HaikuMenuBarPeer.java new file mode 100644 index 0000000000..c565c43d86 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuMenuBarPeer.java @@ -0,0 +1,101 @@ +/* HaikuMenuBarPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Font; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.peer.MenuBarPeer; + +/** + * A menu bar backed by a native BMenuBar. The frame peer puts it at the + * top of the native window (see {@link HaikuFramePeer#setMenuBar}). + */ +public class HaikuMenuBarPeer + implements MenuBarPeer +{ + + final MenuBar awtMenuBar; + + /** The native BMenuBar. */ + long bar; + + HaikuMenuBarPeer(MenuBar mb) + { + awtMenuBar = mb; + bar = HaikuNative.menuCreate(HaikuNative.MENU_BAR, null); + } + + public void addMenu(Menu m) + { + int index = -1; + for (int i = 0; i < awtMenuBar.getMenuCount(); i++) + if (awtMenuBar.getMenu(i) == m) + index = i; + HaikuMenuPeer.addTo(bar, m, index); + } + + public void addHelpMenu(Menu menu) + { + // MenuBar.setHelpMenu() also keeps the menu in the menu list. + if (menu.getPeer() instanceof HaikuMenuPeer) + HaikuNative.menuAddSubmenu(bar, ((HaikuMenuPeer) menu.getPeer()).menu, + -1); + } + + public void delMenu(int index) + { + HaikuNative.menuRemoveItem(bar, index); + } + + public void setFont(Font font) + { + // Menus use the system menu font. + } + + public void dispose() + { + if (bar != 0) + { + HaikuNative.menuDispose(bar); + bar = 0; + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuMenuItemPeer.java b/gnu/java/awt/peer/haiku/HaikuMenuItemPeer.java new file mode 100644 index 0000000000..c7e709f2c1 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuMenuItemPeer.java @@ -0,0 +1,166 @@ +/* HaikuMenuItemPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.CheckboxMenuItem; +import java.awt.EventQueue; +import java.awt.Font; +import java.awt.MenuItem; +import java.awt.MenuShortcut; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.peer.MenuItemPeer; +import java.util.HashMap; + +/** + * A menu item backed by a native BMenuItem. + * + * Each item has an id. When the user selects the item, the native side + * sends the id back as an EV_MENU event, and {@link #activated(int)} posts + * the AWT event. + */ +public class HaikuMenuItemPeer + implements MenuItemPeer +{ + + private static final HashMap items = + new HashMap(); + + private static int nextId = 1; + + final MenuItem awtItem; + + final int id; + + /** The native BMenuItem, or 0 for a menu (see HaikuMenuPeer). */ + long item; + + HaikuMenuItemPeer(MenuItem awtItem) + { + this.awtItem = awtItem; + synchronized (items) + { + id = nextId++; + items.put(new Integer(id), this); + } + if (! (this instanceof HaikuMenuPeer)) + { + String label = awtItem.getLabel(); + boolean separator = "-".equals(label); + int key = 0; + int mods = 0; + MenuShortcut s = awtItem.getShortcut(); + if (s != null && ! separator) + { + key = Character.toLowerCase((char) s.getKey()); + mods = HaikuNative.B_COMMAND_KEY; + if (s.usesShiftModifier()) + mods |= HaikuNative.B_SHIFT_KEY; + } + item = HaikuNative.menuItemCreate(id, label, key, mods, separator); + if (! awtItem.isEnabled()) + setEnabled(false); + } + } + + static HaikuMenuItemPeer find(int id) + { + synchronized (items) + { + return items.get(new Integer(id)); + } + } + + /** + * Called by the event pump when the user selected this item. + */ + void activated(int modifiers) + { + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent( + new ActionEvent(awtItem, ActionEvent.ACTION_PERFORMED, + awtItem.getActionCommand(), + System.currentTimeMillis(), modifiers)); + } + + public void disable() + { + setEnabled(false); + } + + public void enable() + { + setEnabled(true); + } + + public void setEnabled(boolean enabled) + { + if (item != 0) + HaikuNative.menuItemSetEnabled(item, enabled); + } + + public void setLabel(String text) + { + if (item != 0) + HaikuNative.menuItemSetLabel(item, text); + } + + public void setFont(Font font) + { + // Menus use the system menu font. + } + + public void dispose() + { + synchronized (items) + { + items.remove(new Integer(id)); + } + disposeNative(); + } + + void disposeNative() + { + if (item != 0) + { + HaikuNative.menuItemDispose(item); + item = 0; + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuMenuPeer.java b/gnu/java/awt/peer/haiku/HaikuMenuPeer.java new file mode 100644 index 0000000000..d96f7c095c --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuMenuPeer.java @@ -0,0 +1,124 @@ +/* HaikuMenuPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Menu; +import java.awt.MenuItem; +import java.awt.peer.MenuPeer; + +/** + * A menu backed by a native BMenu. Its items are added by AWT through + * {@link #addItem(MenuItem)} (see Menu.addNotify()). + */ +public class HaikuMenuPeer + extends HaikuMenuItemPeer + implements MenuPeer +{ + + /** The native BMenu. */ + long menu; + + HaikuMenuPeer(Menu awtMenu) + { + this(awtMenu, HaikuNative.MENU); + } + + HaikuMenuPeer(Menu awtMenu, int kind) + { + super(awtMenu); + menu = HaikuNative.menuCreate(kind, awtMenu.getLabel()); + if (! awtMenu.isEnabled()) + setEnabled(false); + } + + /** + * Adds the native object of an item (or submenu) to a native menu. + */ + static void addTo(long parent, MenuItem mi, int index) + { + Object p = mi.getPeer(); + if (p instanceof HaikuMenuPeer) + HaikuNative.menuAddSubmenu(parent, ((HaikuMenuPeer) p).menu, index); + else if (p instanceof HaikuMenuItemPeer) + HaikuNative.menuAddItem(parent, ((HaikuMenuItemPeer) p).item, index); + } + + public void addItem(MenuItem item) + { + // AWT adds items in order, but Menu.insert() deletes the tail and + // adds it again; the index keeps the native order the same. + Menu m = (Menu) awtItem; + int index = -1; + for (int i = 0; i < m.getItemCount(); i++) + if (m.getItem(i) == item) + index = i; + addTo(menu, item, index); + } + + public void addSeparator() + { + HaikuNative.menuAddSeparator(menu); + } + + public void delItem(int index) + { + HaikuNative.menuRemoveItem(menu, index); + } + + public void setEnabled(boolean enabled) + { + if (menu != 0) + HaikuNative.menuSetEnabled(menu, enabled); + } + + public void setLabel(String text) + { + HaikuNative.menuSetLabel(menu, text); + } + + void disposeNative() + { + if (menu != 0) + { + HaikuNative.menuDispose(menu); + menu = 0; + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuNative.java b/gnu/java/awt/peer/haiku/HaikuNative.java new file mode 100644 index 0000000000..149b5ff83d --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuNative.java @@ -0,0 +1,364 @@ +/* HaikuNative.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +/** + * Native entry points of the Haiku AWT peer (libhaikupeer). + * + * The native side runs a BApplication on its own native thread. Native + * threads never call into Java; Java polls events with + * {@link #nextEvent(int[], int)} from the {@link HaikuEventPump} thread. + */ +final class HaikuNative +{ + // Event types. Keep in sync with gnu_java_awt_peer_haiku_HaikuNative.cpp. + static final int EV_CLOSE = 1; + static final int EV_RESIZED = 2; + static final int EV_MOVED = 3; + static final int EV_MOUSE_DOWN = 4; + static final int EV_MOUSE_UP = 5; + static final int EV_MOUSE_MOVED = 6; + static final int EV_MOUSE_ENTER = 7; + static final int EV_MOUSE_EXIT = 8; + static final int EV_KEY_DOWN = 9; + static final int EV_KEY_UP = 10; + static final int EV_ACTIVATED = 11; + static final int EV_WHEEL = 12; + static final int EV_APP_QUIT = 13; + static final int EV_MENU = 14; + static final int EV_CHOICE = 15; + static final int EV_FILE_PANEL = 16; + static final int EV_STATE = 17; + static final int EV_EDIT = 18; + static final int EV_FILTER = 19; + static final int EV_DRAG = 20; + static final int EV_DROP = 21; + static final int EV_IM = 22; + + // Transits of EV_DRAG. + static final int DRAG_ENTER = 1; + static final int DRAG_OVER = 2; + static final int DRAG_EXIT = 3; + + // Message sources for msgTypes() and the other msg readers. + static final int SRC_CLIPBOARD = 0; + static final int SRC_DRAG = 1; + static final int SRC_DROP = 2; + + // Message codes (AppDefs.h). + static final int B_SIMPLE_DATA = 0x44415441; // 'DATA' + static final int B_MIME_DATA = 0x4d494d45; // 'MIME' + + // Input method opcodes (Input.h). + static final int B_INPUT_METHOD_STARTED = 0; + static final int B_INPUT_METHOD_STOPPED = 1; + static final int B_INPUT_METHOD_CHANGED = 2; + static final int B_INPUT_METHOD_LOCATION_REQUEST = 3; + + // Window feels (Window.h). + static final int B_NORMAL_WINDOW_FEEL = 0; + static final int B_MODAL_APP_WINDOW_FEEL = 1; + static final int B_FLOATING_ALL_WINDOW_FEEL = 6; + + // Commands of EV_EDIT (the Command-X/C/V/A shortcuts of a window). + static final int EDIT_CUT = 1; + static final int EDIT_COPY = 2; + static final int EDIT_PASTE = 3; + static final int EDIT_SELECT_ALL = 4; + + // Menu kinds for menuCreate(). + static final int MENU_BAR = 0; + static final int MENU = 1; + static final int MENU_POPUP = 2; + + // Haiku modifier bits (InterfaceDefs.h). + static final int B_SHIFT_KEY = 0x1; + static final int B_COMMAND_KEY = 0x2; + + /** Number of ints in one event record. */ + static final int EV_INTS = 8; + + // Window kinds for createWindow(). + static final int KIND_FRAME = 0; + static final int KIND_DIALOG = 1; + static final int KIND_WINDOW = 2; + + private static boolean initialized; + + private HaikuNative() + { + } + + /** + * Loads libhaikupeer and starts the BApplication thread. + */ + static synchronized void ensureInitialized() + { + if (initialized) + return; + String explicit = System.getProperty("gnu.java.awt.peer.haiku.library"); + if (explicit != null) + System.load(explicit); + else + System.loadLibrary("haikupeer"); + if (! init()) + throw new java.awt.AWTError("Cannot start the Haiku BApplication"); + initialized = true; + } + + private static native boolean init(); + + /** out = width, height, dpi. */ + static native void screenInfo(int[] out); + + static native long createWindow(int id, int kind, String title, + int x, int y, int w, int h); + + /** out = left, top, right, bottom. */ + static native void getInsets(long window, int[] out); + + static native void setTitle(long window, String title); + + static native void setVisible(long window, boolean visible); + + /** Bounds of the client area in screen coordinates. */ + static native void setBounds(long window, int x, int y, int w, int h); + + static native void setResizable(long window, boolean resizable); + + static native void toFront(long window); + + static native void toBack(long window); + + static native void dispose(long window); + + static native void blit(long window, int[] pixels, int imgW, int imgH, + int x, int y, int w, int h); + + static native int nextEvent(int[] out, int timeoutMs); + + static native void beep(); + + // Menus. Handles are native BMenu / BMenuItem pointers. + + static native long menuCreate(int kind, String label); + + static native long menuItemCreate(int id, String label, int shortcut, + int modifiers, boolean separator); + + static native void menuAddItem(long menu, long item, int index); + + static native void menuAddSubmenu(long menu, long submenu, int index); + + static native void menuAddSeparator(long menu); + + static native void menuRemoveItem(long menu, int index); + + static native void menuItemSetLabel(long item, String label); + + static native void menuItemSetEnabled(long item, boolean enabled); + + static native void menuItemSetMarked(long item, boolean marked); + + static native void menuSetLabel(long menu, String label); + + static native void menuSetEnabled(long menu, boolean enabled); + + static native void menuItemDispose(long item); + + static native void menuDispose(long menu); + + /** Attaches a menu bar (0 detaches) and returns its height. */ + static native int setMenuBar(long window, long menuBar); + + static native void popupShow(long menu, int x, int y); + + static native void choicePopup(int id, String[] items, int selected, + int x, int y); + + // File panels. + + /** filtered: ask Java through EV_FILTER which files to show. */ + static native void filePanelShow(int id, boolean save, String title, + String dir, String file, + boolean filtered); + + static native String fileResult(int id); + + /** The next directory a file panel wants filtered, or null. */ + static native String filterNext(int id); + + static native void filterAnswer(int id, String dir, String[] names, + boolean[] accepted); + + // Frame state. + + static native void setFrameState(long window, boolean minimized, + boolean zoomed); + + /** out = minimized, zoomed. */ + static native void getFrameState(long window, boolean[] out); + + /** Largest outer size of a zoomed window; 0 for no limit. */ + static native void setZoomLimits(long window, int w, int h); + + // Cursors. + + /** type is a java.awt.Cursor type; custom is 0 or from cursorCreate. */ + static native void setCursor(long window, int type, long custom); + + /** Makes a cursor from ARGB pixels. Returns 0 on failure. */ + static native long cursorCreate(int[] pixels, int w, int h, int hotX, + int hotY); + + static native void setFeel(long window, int feel); + + // Messages for the clipboard and drag and drop. + + static native long msgNew(int what); + + static native void msgDelete(long msg); + + static native void msgAddData(long msg, String mime, byte[] data); + + static native void msgAddInt(long msg, String name, int value); + + /** Adds a picture as an archived bitmap and as PNG. */ + static native void msgAddImage(long msg, int[] argb, int w, int h); + + /** Adds files as entry_refs ("refs"). */ + static native void msgAddFiles(long msg, String[] paths); + + /** Changes each time any application writes to the clipboard. */ + static native long clipboardCount(); + + /** Replaces the clipboard with msg (deleted). Returns the new count. */ + static native long clipboardPut(long msg); + + /** Copies the clipboard into SRC_CLIPBOARD. Returns the count. */ + static native long clipboardSnapshot(); + + static native int msgGeneration(int src); + + /** MIME types, "refs" and archived bitmap names in a source. */ + static native String[] msgTypes(int src); + + static native byte[] msgData(int src, String mime); + + /** The picture in a source as ARGB pixels; dims = width, height. */ + static native int[] msgImage(int src, int[] dims); + + static native String[] msgFiles(int src); + + static native int msgInt(int src, String name, int def); + + /** + * Drags msg (deleted) from a window: with the picture (mouse at offX, + * offY in it) or else the rectangle (x, y, w, h). + */ + static native void dragStart(long window, long msg, int x, int y, int w, + int h, int[] argb, int iw, int ih, int offX, + int offY); + + // Printing. + + /** Returns 0 if the user cancelled a dialog. */ + static native long printCreate(String name, boolean pageDialog, + boolean jobDialog); + + /** page: the page setup dialog, else the print dialog. */ + static native boolean printDialog(long job, boolean page); + + /** + * out = paper l, t, r, b, printable l, t, r, b (points), first page, + * last page, x dpi, y dpi. + */ + static native void printInfo(long job, float[] out); + + static native boolean printBegin(long job); + + /** One page of w x h ARGB pixels that fill the printable area. */ + static native boolean printPage(long job, int[] argb, int w, int h); + + static native void printEnd(long job, boolean commit); + + // Input methods. + + static native String imText(int windowId); + + static native void imLocationReply(long window, int[] xs, int[] ys, + float height); + + // Fonts (BFont). Sizes are pixels (points at 72 dpi). + + /** Opens a font ("" = the plain font); 0 if the family is unknown. */ + static native long fontOpen(String family, int face); + + /** which: 0 all families, 1 the plain font's, 2 the fixed font's. */ + static native String[] fontFamilies(int which); + + /** out = ascent, descent, leading, max advance. */ + static native void fontMetrics(long font, float size, float[] out); + + /** The advance of each UTF-16 unit of text. */ + static native void fontAdvances(long font, float size, String text, + float[] out); + + static native float fontWidth(long font, float size, String text); + + static native void fontHasGlyphs(long font, String text, boolean[] out); + + /** + * Draws text; returns coverage (0..255) per pixel. dims = width, + * height, baseline y, origin x. + */ + static native byte[] fontRender(long font, float size, String text, + int[] dims); + + /** + * The outline of text: 0 x y = move, 1 x y = line, + * 2 x1 y1 x2 y2 x3 y3 = cubic, 3 = close. xs: x of each unit, or null. + */ + static native float[] fontOutline(long font, float size, String text, + float[] xs); + + /** Shows composing text near (x, y) on the screen; null hides it. */ + static native void imShowComposition(String text, int x, int y, + int selStart, int selEnd); +} diff --git a/gnu/java/awt/peer/haiku/HaikuPanelPeer.java b/gnu/java/awt/peer/haiku/HaikuPanelPeer.java new file mode 100644 index 0000000000..e6f117f704 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuPanelPeer.java @@ -0,0 +1,106 @@ +/* HaikuPanelPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingPanelPeer; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Panel; +import java.awt.Rectangle; + +/** + * A panel. Like a native panel, it clears its background before AWT + * paints it; SwingPanelPeer does not. + */ +public class HaikuPanelPeer + extends SwingPanelPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + HaikuPanelPeer(Panel panel) + { + super(panel); + } + + protected void peerPaintComponent(Graphics g) + { + clearBackground(awtComponent, g); + // Component.update() clears with the graphics background colour; the + // double buffer image's graphics would use white. + Color bg = awtComponent.getBackground(); + if (bg != null && g instanceof java.awt.Graphics2D) + ((java.awt.Graphics2D) g).setBackground(bg); + // paint() gets a copy of g; it must start with the component's + // foreground colour and font, not the background colour. + g.setColor(awtComponent.getForeground()); + if (awtComponent.getFont() != null) + g.setFont(awtComponent.getFont()); + } + + static void clearBackground(java.awt.Component c, Graphics g) + { + Color bg = c.getBackground(); + if (bg == null) + bg = HaikuWindowPeer.DEFAULT_BACKGROUND; + Rectangle clip = g.getClipBounds(); + if (clip == null) + clip = new Rectangle(0, 0, c.getWidth(), c.getHeight()); + g.setColor(bg); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuPopupMenuPeer.java b/gnu/java/awt/peer/haiku/HaikuPopupMenuPeer.java new file mode 100644 index 0000000000..a576d8b1e5 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuPopupMenuPeer.java @@ -0,0 +1,73 @@ +/* HaikuPopupMenuPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Component; +import java.awt.Event; +import java.awt.Point; +import java.awt.PopupMenu; +import java.awt.peer.PopupMenuPeer; + +/** + * A popup menu backed by a native BPopUpMenu. It is shown without + * blocking; a selection comes back as an EV_MENU event. + */ +public class HaikuPopupMenuPeer + extends HaikuMenuPeer + implements PopupMenuPeer +{ + + HaikuPopupMenuPeer(PopupMenu menu) + { + super(menu, HaikuNative.MENU_POPUP); + } + + public void show(Component origin, int x, int y) + { + Point p = origin.getLocationOnScreen(); + HaikuNative.popupShow(menu, p.x + x, p.y + y); + } + + public void show(Event e) + { + if (e.target instanceof Component) + show((Component) e.target, e.x, e.y); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuPrintJob.java b/gnu/java/awt/peer/haiku/HaikuPrintJob.java new file mode 100644 index 0000000000..71de7b6c07 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuPrintJob.java @@ -0,0 +1,214 @@ +/* HaikuPrintJob.java -- AWT 1.1 printing with BPrintJob + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.PrintGraphics; +import java.awt.PrintJob; +import java.awt.image.BufferedImage; + +/** + * An AWT print job (Toolkit.getPrintJob()). The page setup and print + * dialogs are Haiku's. Each page is drawn in Java into a picture of the + * printable area and then spooled with BPrintJob. Page coordinates are + * points (1/72 inch) from the top left of the printable area. + */ +final class HaikuPrintJob + extends PrintJob +{ + + /** The most dots per inch used for page pictures. */ + static final int MAX_DPI = 150; + + private long job; + + /** Printable area in points. */ + private final int width, height; + + /** Picture dots per point. */ + private final double scale; + + private PageGraphics page; + + private boolean begun; + + private HaikuPrintJob(long job) + { + this.job = job; + float[] info = new float[12]; + HaikuNative.printInfo(job, info); + width = Math.max(1, Math.round(info[6] - info[4])); + height = Math.max(1, Math.round(info[7] - info[5])); + scale = pictureScale(info[10], info[11]); + } + + /** Shows the dialogs; null if the user cancelled. */ + static HaikuPrintJob create(String title) + { + HaikuNative.ensureInitialized(); + long job = HaikuNative.printCreate(title, true, true); + return job != 0 ? new HaikuPrintJob(job) : null; + } + + static double pictureScale(float xdpi, float ydpi) + { + float dpi = Math.min(Math.max(xdpi, ydpi), MAX_DPI); + return Math.max(dpi, 72) / 72.0; + } + + /** A white page picture for an area of w x h points. */ + static BufferedImage newPage(int w, int h, double scale) + { + BufferedImage img = new BufferedImage((int) Math.ceil(w * scale), + (int) Math.ceil(h * scale), + BufferedImage.TYPE_INT_RGB); + Graphics2D g = img.createGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, img.getWidth(), img.getHeight()); + g.dispose(); + return img; + } + + /** Spools a page picture. */ + static boolean spool(long job, BufferedImage img) + { + int w = img.getWidth(); + int h = img.getHeight(); + return HaikuNative.printPage(job, img.getRGB(0, 0, w, h, null, 0, w), + w, h); + } + + /** Starts a new page; the graphics finish the page when disposed. */ + public synchronized Graphics getGraphics() + { + if (job == 0) + return null; + if (page != null) + page.dispose(); + if (! begun) + { + begun = true; + HaikuNative.printBegin(job); + } + BufferedImage img = newPage(width, height, scale); + page = new PageGraphics(img, this); + page.scale(scale, scale); + page.setColor(Color.black); + page.setBackground(Color.white); + return page; + } + + synchronized void pageDone(PageGraphics g) + { + if (g != page || job == 0) + return; + page = null; + spool(job, g.image()); + } + + public Dimension getPageDimension() + { + return new Dimension(width, height); + } + + public int getPageResolution() + { + return 72; + } + + public boolean lastPageFirst() + { + return false; + } + + /** Prints the pages. */ + public synchronized void end() + { + if (job == 0) + return; + if (page != null) + page.dispose(); + long j = job; + job = 0; + HaikuNative.printEnd(j, begun); + } + + /** The graphics of one page. */ + static final class PageGraphics + extends HaikuGraphics2D + implements PrintGraphics + { + private final HaikuPrintJob printJob; + private final BufferedImage img; + private boolean root = true; + + PageGraphics(BufferedImage img, HaikuPrintJob job) + { + super(img, null); + this.img = img; + printJob = job; + } + + BufferedImage image() + { + return img; + } + + public PrintJob getPrintJob() + { + return printJob; + } + + public Graphics create() + { + PageGraphics copy = (PageGraphics) super.create(); + copy.root = false; + return copy; + } + + public void dispose() + { + super.dispose(); + if (root) + printJob.pageDone(this); + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuPrinterJob.java b/gnu/java/awt/peer/haiku/HaikuPrinterJob.java new file mode 100644 index 0000000000..b104930130 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuPrinterJob.java @@ -0,0 +1,242 @@ +/* HaikuPrinterJob.java -- Java 2 printing with BPrintJob + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Pageable; +import java.awt.print.Paper; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +/** + * A java.awt.print.PrinterJob that prints with Haiku's print server. + * PrinterJob.getPrinterJob() returns it when the system property + * java.awt.printerjob names this class (the Haiku toolkit sets it). + */ +public class HaikuPrinterJob + extends PrinterJob +{ + + private long job; + + private Printable printable; + + private PageFormat format; + + private Pageable pageable; + + private String jobName = "Java"; + + private int copies = 1; + + private volatile boolean cancelled; + + public HaikuPrinterJob() + { + HaikuNative.ensureInitialized(); + } + + public int getCopies() + { + return copies; + } + + public void setCopies(int copies) + { + this.copies = copies; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String name) + { + jobName = name; + } + + public String getUserName() + { + return System.getProperty("user.name"); + } + + public void cancel() + { + cancelled = true; + } + + public boolean isCancelled() + { + return cancelled; + } + + /** The paper of the print job, or US Letter before a dialog. */ + public PageFormat defaultPage(PageFormat pf) + { + PageFormat result = (PageFormat) pf.clone(); + Paper paper = new Paper(); + if (job != 0) + { + float[] i = new float[12]; + HaikuNative.printInfo(job, i); + paper.setSize(i[2] - i[0], i[3] - i[1]); + paper.setImageableArea(i[4] - i[0], i[5] - i[1], i[6] - i[4], + i[7] - i[5]); + } + result.setPaper(paper); + return result; + } + + public PageFormat pageDialog(PageFormat pf) + { + boolean ok; + if (job == 0) + { + job = HaikuNative.printCreate(jobName, true, false); + ok = job != 0; + } + else + ok = HaikuNative.printDialog(job, true); + return ok ? defaultPage(pf) : pf; + } + + public boolean printDialog() + { + if (job == 0) + { + job = HaikuNative.printCreate(jobName, false, true); + return job != 0; + } + return HaikuNative.printDialog(job, false); + } + + public void setPageable(Pageable p) + { + pageable = p; + printable = null; + } + + public void setPrintable(Printable p) + { + setPrintable(p, defaultPage()); + } + + public void setPrintable(Printable p, PageFormat pf) + { + printable = p; + format = pf; + pageable = null; + } + + public PageFormat validatePage(PageFormat pf) + { + return (PageFormat) pf.clone(); + } + + /** + * Prints all pages (or the range chosen in the print dialog). Without + * a dialog before, the print server's settings are used. + */ + public void print() + throws PrinterException + { + if (printable == null && pageable == null) + throw new PrinterException("nothing to print"); + if (job == 0) + job = HaikuNative.printCreate(jobName, false, false); + if (job == 0) + throw new PrinterException("cannot start a print job"); + cancelled = false; + float[] info = new float[12]; + HaikuNative.printInfo(job, info); + int first = Math.max(1, (int) info[8]); + int last = (int) info[9]; + if (last < first || last > 9999) + last = Integer.MAX_VALUE; + int w = Math.max(1, Math.round(info[6] - info[4])); + int h = Math.max(1, Math.round(info[7] - info[5])); + double scale = HaikuPrintJob.pictureScale(info[10], info[11]); + boolean ok = HaikuNative.printBegin(job); + if (! ok) + { + HaikuNative.printEnd(job, false); + job = 0; + throw new PrinterException("the print server refused the job"); + } + try + { + int count = pageable != null ? pageable.getNumberOfPages() + : Integer.MAX_VALUE; + for (int index = 0; ok && ! cancelled && index < count + && index + 1 <= last; index++) + { + Printable p = pageable != null ? pageable.getPrintable(index) + : printable; + PageFormat pf = pageable != null ? pageable.getPageFormat(index) + : format; + if (pf == null) + pf = defaultPage(); + BufferedImage img = HaikuPrintJob.newPage(w, h, scale); + Graphics2D g = img.createGraphics(); + g.scale(scale, scale); + // Page coordinates start at the paper corner. + g.translate(-(info[4] - info[0]), -(info[5] - info[1])); + g.setColor(java.awt.Color.black); + g.setBackground(java.awt.Color.white); + int r = p.print(g, pf, index); + g.dispose(); + if (r == Printable.NO_SUCH_PAGE) + break; + if (index + 1 >= first) + ok = HaikuPrintJob.spool(job, img); + } + } + finally + { + long j = job; + job = 0; + HaikuNative.printEnd(j, ok && ! cancelled); + } + if (! ok) + throw new PrinterException("printing failed"); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuRaster.java b/gnu/java/awt/peer/haiku/HaikuRaster.java new file mode 100644 index 0000000000..8f814d9f61 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuRaster.java @@ -0,0 +1,192 @@ +/* HaikuRaster.java -- native pixel loops for HaikuGraphics2D + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Image; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.awt.image.SinglePixelPackedSampleModel; + +/** + * Native pixel loops for {@link HaikuGraphics2D}. On Kaffe's interpreter + * per-pixel Java code is very slow; these functions work directly on the + * int[] pixels (0x00RRGGBB, or non-premultiplied 0xAARRGGBB when alpha + * is true). clip is a device rectangle {x, y, w, h}. + */ +final class HaikuRaster +{ + + static + { + HaikuNative.ensureInitialized(); + } + + private HaikuRaster() + { + } + + static native void fillRect(int[] dst, int scan, boolean alpha, int[] clip, + int x, int y, int w, int h, int argb, + int xorBits); + + static native void drawLine(int[] dst, int scan, boolean alpha, int[] clip, + int x0, int y0, int x1, int y1, int argb, + int xorBits); + + /** path: see {@link #path}. */ + static native void drawPathThin(int[] dst, int scan, boolean alpha, + int[] clip, float[] path, int n, int argb, + int xorBits); + + static native void fillPath(int[] dst, int scan, boolean alpha, int[] clip, + float[] path, int n, boolean evenOdd, + boolean aa, int argb, int xorBits); + + /** g = x1, y1, x2, y2 (user space), then the inverse transform (6). */ + static native void fillPathGradient(int[] dst, int scan, boolean alpha, + int[] clip, float[] path, int n, + boolean evenOdd, boolean aa, + double[] g, int c1, int c2, + boolean cyclic); + + /** + * Draws the source rectangle (sx, sy, sw, sh) of src through a + * transform. inverse maps device points to source points (relative to + * sx, sy); box is the device area {x, y, w, h} to visit. + */ + static native void drawImage(int[] dst, int scan, boolean alpha, + int[] clip, int[] src, int sscan, + boolean srcAlpha, int sx, int sy, int sw, + int sh, double[] inverse, int[] box, + boolean bilinear, int extraAlpha); + + static native void copyArea(int[] dst, int scan, int x, int y, int w, + int h, int dx, int dy); + + /** + * A shape in device space as a flat path: 0 x y = move, 1 x y = line, + * 2 x1 y1 x2 y2 = quad, 3 x1 y1 x2 y2 x3 y3 = cubic, 4 = close. The + * winding rule goes to evenOdd[0]. + */ + static float[] path(Shape s, AffineTransform t, + boolean[] evenOdd) + { + PathIterator pi = s.getPathIterator(t); + evenOdd[0] = pi.getWindingRule() == PathIterator.WIND_EVEN_ODD; + float[] out = new float[64]; + float[] c = new float[6]; + int n = 0; + while (! pi.isDone()) + { + int type = pi.currentSegment(c); + int pts = type == PathIterator.SEG_MOVETO + || type == PathIterator.SEG_LINETO ? 1 + : type == PathIterator.SEG_QUADTO ? 2 + : type == PathIterator.SEG_CUBICTO ? 3 : 0; + if (n + 1 + 2 * pts > out.length) + { + float[] bigger = new float[out.length * 2]; + System.arraycopy(out, 0, bigger, 0, n); + out = bigger; + } + out[n++] = type == PathIterator.SEG_MOVETO ? 0 + : type == PathIterator.SEG_LINETO ? 1 + : type == PathIterator.SEG_QUADTO ? 2 + : type == PathIterator.SEG_CUBICTO ? 3 : 4; + for (int i = 0; i < 2 * pts; i++) + out[n++] = c[i]; + pi.next(); + } + if (n != out.length) + { + float[] exact = new float[n]; + System.arraycopy(out, 0, exact, 0, n); + out = exact; + } + return out; + } + + /** The int pixels of an image: data, scan, offset, size, alpha. */ + static final class Source + { + int[] data; + int scan, x, y, w, h; + boolean alpha; + } + + /** + * The pixels of an image as 0x(AA)RRGGBB ints without copying when the + * image is INT_RGB or INT_ARGB (also a sub-image); other images are + * converted. Returns null for images that are not BufferedImages. + */ + static Source source(Image img) + { + if (! (img instanceof BufferedImage)) + return null; + BufferedImage b = (BufferedImage) img; + int type = b.getType(); + if (type != BufferedImage.TYPE_INT_RGB + && type != BufferedImage.TYPE_INT_ARGB) + { + b = HaikuToolkit.toDirect(b); + type = b.getType(); + } + Raster r = b.getRaster(); + DataBuffer db = r.getDataBuffer(); + if (! (db instanceof DataBufferInt) || db.getNumBanks() != 1 + || ! (r.getSampleModel() instanceof SinglePixelPackedSampleModel)) + return null; + SinglePixelPackedSampleModel sm = + (SinglePixelPackedSampleModel) r.getSampleModel(); + Source s = new Source(); + s.data = ((DataBufferInt) db).getData(); + s.scan = sm.getScanlineStride(); + int off = db.getOffset(); + s.x = r.getMinX() - r.getSampleModelTranslateX() + off % s.scan; + s.y = r.getMinY() - r.getSampleModelTranslateY() + off / s.scan; + s.w = b.getWidth(); + s.h = b.getHeight(); + s.alpha = type == BufferedImage.TYPE_INT_ARGB; + return s; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuScrollPanePeer.java b/gnu/java/awt/peer/haiku/HaikuScrollPanePeer.java new file mode 100644 index 0000000000..4d352e6d0d --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuScrollPanePeer.java @@ -0,0 +1,335 @@ +/* HaikuScrollPanePeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingContainerPeer; + +import java.awt.Adjustable; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.ScrollPane; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.awt.event.MouseEvent; +import java.awt.peer.ScrollPanePeer; + +import javax.swing.JScrollBar; + +/** + * A scroll pane with Swing scroll bars at its right and bottom edges. + * + * ScrollPane.layout() puts the child at (0, 0) and asks the peer to + * scroll. This peer scrolls by moving the child to (-x, -y), and clips + * the child to the viewport when it paints. + */ +public class HaikuScrollPanePeer + extends SwingContainerPeer + implements ScrollPanePeer, java.awt.dnd.peer.DropTargetPeer +{ + + private final ScrollPane sp; + + private final HaikuSwingWrapper wrapper; + + private final JScrollBar hbar; + + private final JScrollBar vbar; + + private int scrollX, scrollY; + + private int childW, childH; + + /** True while the peer sets the scroll bars; do not echo them back. */ + private boolean setting; + + HaikuScrollPanePeer(ScrollPane pane) + { + super(pane); + sp = pane; + hbar = new JScrollBar(JScrollBar.HORIZONTAL); + vbar = new JScrollBar(JScrollBar.VERTICAL); + wrapper = new HaikuSwingWrapper(this, pane, null); + wrapper.setLayout(null); + wrapper.add(hbar); + wrapper.add(vbar); + HaikuSwingPeer.noFocus(wrapper); + AdjustmentListener l = new AdjustmentListener() + { + public void adjustmentValueChanged(AdjustmentEvent e) + { + if (setting) + return; + final int x = hbar.getValue(); + final int y = vbar.getValue(); + EventQueue.invokeLater(new Runnable() + { + public void run() + { + if (sp.getComponentCount() > 0) + sp.setScrollPosition(x, y); + } + }); + } + }; + // AWT's default unit increment of one pixel is too small for arrow + // clicks; native toolkits use a larger step too. + hbar.setUnitIncrement(10); + vbar.setUnitIncrement(10); + hbar.addAdjustmentListener(l); + vbar.addAdjustmentListener(l); + init(pane, wrapper); + } + + /** True if (x, y), in scroll pane coordinates, is in the viewport. */ + boolean inViewport(int x, int y) + { + Dimension vp = sp.getViewportSize(); + return x >= 0 && y >= 0 && x < vp.width && y < vp.height; + } + + /** Places and sets up the scroll bars for the current sizes. */ + private void layoutBars() + { + int w = sp.getWidth(); + int h = sp.getHeight(); + Dimension vp = sp.getViewportSize(); + boolean needV = vp.width < w; + boolean needH = vp.height < h; + setting = true; + try + { + vbar.setVisible(needV); + hbar.setVisible(needH); + if (needV) + { + vbar.setBounds(vp.width, 0, w - vp.width, vp.height); + vbar.validate(); + } + if (needH) + { + hbar.setBounds(0, vp.height, vp.width, h - vp.height); + hbar.validate(); + } + hbar.setValues(scrollX, vp.width, 0, Math.max(childW, vp.width)); + vbar.setValues(scrollY, vp.height, 0, Math.max(childH, vp.height)); + hbar.setBlockIncrement(Math.max(vp.width - 10, 1)); + vbar.setBlockIncrement(Math.max(vp.height - 10, 1)); + } + finally + { + setting = false; + } + } + + public void reshape(int x, int y, int width, int height) + { + super.reshape(x, y, width, height); + layoutBars(); + } + + public Dimension getPreferredSize() + { + return new Dimension(100, 100); + } + + public Dimension getMinimumSize() + { + return new Dimension(getVScrollbarWidth() + 10, + getHScrollbarHeight() + 10); + } + + public int getHScrollbarHeight() + { + return hbar.getPreferredSize().height; + } + + public int getVScrollbarWidth() + { + return vbar.getPreferredSize().width; + } + + public void setScrollPosition(int x, int y) + { + scrollX = x; + scrollY = y; + layoutBars(); + // ScrollPane.layout() moves the child back to (0, 0) after this call, + // so move it later. + EventQueue.invokeLater(new Runnable() + { + public void run() + { + if (sp.getComponentCount() == 0) + return; + Component child = sp.getComponent(0); + if (child.getX() != -scrollX || child.getY() != -scrollY) + { + child.setLocation(-scrollX, -scrollY); + sp.repaint(); + } + } + }); + } + + public void childResized(int width, int height) + { + childW = width; + childH = height; + layoutBars(); + } + + public void setUnitIncrement(Adjustable item, int inc) + { + if (inc <= 1) + return; + if (item.getOrientation() == Adjustable.HORIZONTAL) + hbar.setUnitIncrement(inc); + else + vbar.setUnitIncrement(inc); + } + + public void setValue(Adjustable item, int value) + { + if (item.getOrientation() == Adjustable.HORIZONTAL) + { + if (value != scrollX) + setScrollPosition(value, scrollY); + } + else if (value != scrollY) + setScrollPosition(scrollX, value); + } + + /** + * Paints the child clipped to the viewport, then the scroll bars. + */ + private static void paintBar(Graphics g, JScrollBar bar) + { + if (! bar.isVisible()) + return; + Graphics cg = g.create(bar.getX(), bar.getY(), bar.getWidth(), + bar.getHeight()); + try + { + bar.paint(cg); + } + finally + { + cg.dispose(); + } + } + + protected void peerPaint(Graphics g, boolean update) + { + Dimension vp = sp.getViewportSize(); + Graphics g2 = g.create(); + try + { + g2.clipRect(0, 0, vp.width, vp.height); + peerPaintChildren(g2); + } + finally + { + g2.dispose(); + } + int w = sp.getWidth(); + int h = sp.getHeight(); + if (vp.width < w && vp.height < h) + { + Color bg = sp.getBackground(); + g.setColor(bg != null ? bg : Color.lightGray); + g.fillRect(vp.width, vp.height, w - vp.width, h - vp.height); + } + paintBar(g, vbar); + paintBar(g, hbar); + } + + /** True while a mouse button that was pressed on a scroll bar is held. */ + private boolean barGrab; + + protected void handleMouseEvent(MouseEvent e) + { + if (e.getID() == MouseEvent.MOUSE_PRESSED) + barGrab = ! inViewport(e.getX(), e.getY()); + boolean bars; + switch (e.getID()) + { + case MouseEvent.MOUSE_RELEASED: + case MouseEvent.MOUSE_CLICKED: + case MouseEvent.MOUSE_PRESSED: + bars = barGrab; + break; + default: + bars = ! inViewport(e.getX(), e.getY()); + } + if (bars) + wrapper.handleMouseEvent(e); + else + super.handleMouseEvent(e); + } + + protected void handleMouseMotionEvent(MouseEvent e) + { + if (e.getID() == MouseEvent.MOUSE_DRAGGED ? barGrab + : ! inViewport(e.getX(), e.getY())) + wrapper.handleMouseMotionEvent(e); + else + super.handleMouseMotionEvent(e); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuScrollbarPeer.java b/gnu/java/awt/peer/haiku/HaikuScrollbarPeer.java new file mode 100644 index 0000000000..6c025b7ca3 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuScrollbarPeer.java @@ -0,0 +1,130 @@ +/* HaikuScrollbarPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import java.awt.Event; +import java.awt.Scrollbar; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.awt.peer.ScrollbarPeer; + +import javax.swing.JScrollBar; + +/** + * A scroll bar drawn by a Swing JScrollBar. + */ +public class HaikuScrollbarPeer + extends HaikuSwingPeer + implements ScrollbarPeer +{ + + private final JScrollBar bar; + + /** True while AWT sets the values; do not echo them back. */ + private boolean setting; + + HaikuScrollbarPeer(final Scrollbar sb) + { + bar = new JScrollBar(sb.getOrientation() == Scrollbar.HORIZONTAL + ? JScrollBar.HORIZONTAL : JScrollBar.VERTICAL); + setValues(sb.getValue(), sb.getVisibleAmount(), sb.getMinimum(), + sb.getMaximum()); + setLineIncrement(sb.getUnitIncrement()); + setPageIncrement(sb.getBlockIncrement()); + bar.addAdjustmentListener(new AdjustmentListener() + { + public void adjustmentValueChanged(AdjustmentEvent e) + { + if (setting) + return; + int type = e.getAdjustmentType(); + int value = e.getValue(); + AdjustmentEvent ae = + new AdjustmentEvent(sb, AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, + type, value, e.getValueIsAdjusting()); + Event old = null; + if (sb.getAdjustmentListeners().length == 0) + old = new Event(sb, oldType(type), new Integer(value)); + HaikuSwingWrapper.post(sb, ae, old); + } + }); + initWrapped(sb, bar); + } + + static int oldType(int type) + { + switch (type) + { + case AdjustmentEvent.UNIT_DECREMENT: + return Event.SCROLL_LINE_UP; + case AdjustmentEvent.UNIT_INCREMENT: + return Event.SCROLL_LINE_DOWN; + case AdjustmentEvent.BLOCK_DECREMENT: + return Event.SCROLL_PAGE_UP; + case AdjustmentEvent.BLOCK_INCREMENT: + return Event.SCROLL_PAGE_DOWN; + default: + return Event.SCROLL_ABSOLUTE; + } + } + + public void setLineIncrement(int inc) + { + bar.setUnitIncrement(inc); + } + + public void setPageIncrement(int inc) + { + bar.setBlockIncrement(inc); + } + + public void setValues(int value, int visible, int min, int max) + { + setting = true; + try + { + bar.setValues(value, visible, min, max); + } + finally + { + setting = false; + } + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuSwingPeer.java b/gnu/java/awt/peer/haiku/HaikuSwingPeer.java new file mode 100644 index 0000000000..91a59d92af --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuSwingPeer.java @@ -0,0 +1,126 @@ +/* HaikuSwingPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingComponentPeer; + +import java.awt.Component; + +import javax.swing.JComponent; + +/** + * Base for Swing based peers whose Swing widget is held in a + * {@link HaikuSwingWrapper}. + */ +class HaikuSwingPeer + extends SwingComponentPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + HaikuSwingWrapper wrapper; + + void initWrapped(Component awt, JComponent inner) + { + wrapper = new HaikuSwingWrapper(this, awt, inner); + noFocus(wrapper); + init(awt, wrapper); + if (awt.getFont() != null) + setFont(awt.getFont()); + if (awt.getBackground() != null) + setBackground(awt.getBackground()); + if (awt.getForeground() != null) + setForeground(awt.getForeground()); + } + + /** + * Makes the Swing components unable to take the focus. The AWT + * component has the focus and the wrapper sends key events on. AWT + * focus requests from a Swing component would look for a Window in a + * tree that ends at the wrapper. + */ + static void noFocus(java.awt.Container c) + { + c.setFocusable(false); + for (Component child : c.getComponents()) + if (child instanceof java.awt.Container) + noFocus((java.awt.Container) child); + else + child.setFocusable(false); + } + + public void reshape(int x, int y, int width, int height) + { + super.reshape(x, y, width, height); + if (wrapper != null) + wrapper.validate(); + } + + public void setFont(java.awt.Font font) + { + super.setFont(font); + if (wrapper != null) + for (Component c : wrapper.getComponents()) + c.setFont(font); + } + + public void setBackground(java.awt.Color color) + { + // Keep the look and feel colours of the inner widgets; only the + // wrapper takes the AWT background. + super.setBackground(color); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuSwingWrapper.java b/gnu/java/awt/peer/haiku/HaikuSwingWrapper.java new file mode 100644 index 0000000000..7160ceaab7 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuSwingWrapper.java @@ -0,0 +1,254 @@ +/* HaikuSwingWrapper.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingComponent; +import gnu.java.awt.peer.swing.SwingComponentPeer; + +import java.awt.AWTEvent; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Event; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Point; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/** + * Holds the Swing widgets of a Swing based peer. + * + * The Classpath Swing peers send all mouse events to the top Swing + * component. Widgets made of several Swing components (a scroll bar with + * arrow buttons, a list in a scroll pane) need the events at the deepest + * component, so this wrapper finds it. While a button is held, the + * component that got the press gets all events, as in AWT. + */ +class HaikuSwingWrapper + extends JPanel + implements SwingComponent +{ + + /** Swing components that take key events from the wrapper. */ + interface KeyTarget + { + void routeKey(KeyEvent e); + } + + private final SwingComponentPeer peer; + + private final Component awt; + + /** The component that gets key events, or null. */ + KeyTarget keyTarget; + + /** The component that got the last mouse press. */ + private Component grab; + + /** The component under the mouse. */ + private Component hover; + + HaikuSwingWrapper(SwingComponentPeer peer, Component awt, JComponent inner) + { + super(new BorderLayout()); + this.peer = peer; + this.awt = awt; + setOpaque(false); + setDoubleBuffered(false); + if (inner != null) + add(inner, BorderLayout.CENTER); + if (inner instanceof KeyTarget) + keyTarget = (KeyTarget) inner; + } + + public JComponent getJComponent() + { + return this; + } + + public void handleMouseEvent(MouseEvent ev) + { + route(ev); + } + + public void handleMouseMotionEvent(MouseEvent ev) + { + route(ev); + } + + public void handleKeyEvent(KeyEvent ev) + { + if (keyTarget != null) + keyTarget.routeKey(ev); + } + + public void handleFocusEvent(FocusEvent ev) + { + // Nothing to do; the AWT component has the focus. + } + + /** + * Sends a mouse event (in wrapper coordinates) to the right Swing + * component. + */ + private void route(MouseEvent ev) + { + int id = ev.getID(); + int x = ev.getX(); + int y = ev.getY(); + Component target; + switch (id) + { + case MouseEvent.MOUSE_PRESSED: + target = deepest(x, y); + grab = target; + break; + case MouseEvent.MOUSE_DRAGGED: + case MouseEvent.MOUSE_RELEASED: + case MouseEvent.MOUSE_CLICKED: + target = grab != null ? grab : deepest(x, y); + break; + case MouseEvent.MOUSE_EXITED: + if (hover != null) + send(hover, ev, MouseEvent.MOUSE_EXITED); + hover = null; + return; + case MouseEvent.MOUSE_ENTERED: + return; + default: + target = deepest(x, y); + break; + } + if (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_DRAGGED) + { + Component under = deepest(x, y); + if (under != hover) + { + if (hover != null) + send(hover, ev, MouseEvent.MOUSE_EXITED); + send(under, ev, MouseEvent.MOUSE_ENTERED); + hover = under; + } + } + send(target, ev, id); + } + + private Component deepest(int x, int y) + { + Component c = SwingUtilities.getDeepestComponentAt(this, x, y); + return c != null ? c : this; + } + + private void send(Component target, MouseEvent ev, int id) + { + int x = ev.getX(); + int y = ev.getY(); + for (Component c = target; c != null && c != this; c = c.getParent()) + { + x -= c.getX(); + y -= c.getY(); + } + MouseEvent e = new MouseEvent(target, id, ev.getWhen(), + ev.getModifiersEx(), + x, y, ev.getClickCount(), + ev.isPopupTrigger(), ev.getButton()); + if (target == this) + { + if (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_DRAGGED) + processMouseMotionEvent(e); + else + processMouseEvent(e); + } + else + target.dispatchEvent(e); + } + + // The wrapper is not in the AWT tree; take these from the peer. + + public Point getLocationOnScreen() + { + return peer.getLocationOnScreen(); + } + + public boolean isShowing() + { + return awt != null && awt.isShowing(); + } + + public Image createImage(int w, int h) + { + return peer.createImage(w, h); + } + + public Graphics getGraphics() + { + return peer.getGraphics(); + } + + public Container getParent() + { + return awt != null ? awt.getParent() : null; + } + + /** + * Posts an AWT event for c, and an AWT 1.0 event when c has no + * listener of the new kind. Classpath does not translate item and + * adjustment events to the old model. + */ + static void post(final Component c, final AWTEvent e, final Event old) + { + EventQueue.invokeLater(new Runnable() + { + public void run() + { + c.dispatchEvent(e); + if (old != null) + c.postEvent(old); + } + }); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuTextFieldPeer.java b/gnu/java/awt/peer/haiku/HaikuTextFieldPeer.java new file mode 100644 index 0000000000..9b2b191a0c --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuTextFieldPeer.java @@ -0,0 +1,107 @@ +/* HaikuTextFieldPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.swing.SwingTextFieldPeer; + +import java.awt.TextField; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JTextField; + +/** + * A text field drawn by Swing. This peer adds the ActionEvent for the + * Enter key, which SwingTextFieldPeer does not send. + */ +public class HaikuTextFieldPeer + extends SwingTextFieldPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + HaikuTextFieldPeer(final TextField tf) + { + super(tf); + JTextField jtf = (JTextField) swingComponent.getJComponent(); + jtf.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + HaikuSwingWrapper.post(tf, + new ActionEvent(tf, ActionEvent.ACTION_PERFORMED, tf.getText(), + e.getWhen(), e.getModifiers()), null); + } + }); + } + + /** + * Focus events come from the AWT TextField, but Swing's text UI expects + * its JTextField as the source (it throws a ClassCastException). + */ + protected void handleFocusEvent(java.awt.event.FocusEvent e) + { + super.handleFocusEvent(new java.awt.event.FocusEvent(textComponent(), + e.getID(), + e.isTemporary())); + } + + /** The Swing text field that draws this TextField. */ + javax.swing.text.JTextComponent textComponent() + { + return (javax.swing.text.JTextComponent) swingComponent.getJComponent(); + } + + /** Shows the new cursor if the mouse is over this component. */ + public void setCursor(java.awt.Cursor cursor) + { + super.setCursor(cursor); + HaikuEventPump.get().refreshCursor(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuToolkit.java b/gnu/java/awt/peer/haiku/HaikuToolkit.java new file mode 100644 index 0000000000..49a4a4200c --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuToolkit.java @@ -0,0 +1,631 @@ +/* HaikuToolkit.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import gnu.classpath.SystemProperties; +import gnu.java.awt.ClasspathToolkit; +import gnu.java.awt.EmbeddedWindow; +import gnu.java.awt.font.OpenTypeFontPeer; +import gnu.java.awt.image.ImageConverter; +import gnu.java.awt.peer.ClasspathFontPeer; +import gnu.java.awt.peer.EmbeddedWindowPeer; +import gnu.java.awt.peer.swing.SwingCanvasPeer; +import gnu.java.awt.peer.swing.SwingTextAreaPeer; + +import java.awt.AWTException; +import java.awt.Button; +import java.awt.Canvas; +import java.awt.Checkbox; +import java.awt.CheckboxMenuItem; +import java.awt.Choice; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.FileDialog; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Frame; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.Point; +import java.awt.PopupMenu; +import java.awt.PrintJob; +import java.awt.ScrollPane; +import java.awt.Scrollbar; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Window; +import java.awt.Dialog.ModalExclusionType; +import java.awt.Dialog.ModalityType; +import java.awt.datatransfer.Clipboard; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.peer.DragSourceContextPeer; +import java.awt.im.InputMethodHighlight; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.awt.peer.ButtonPeer; +import java.awt.peer.CanvasPeer; +import java.awt.peer.CheckboxMenuItemPeer; +import java.awt.peer.CheckboxPeer; +import java.awt.peer.ChoicePeer; +import java.awt.peer.DialogPeer; +import java.awt.peer.FileDialogPeer; +import java.awt.peer.FontPeer; +import java.awt.peer.FramePeer; +import java.awt.peer.LabelPeer; +import java.awt.peer.ListPeer; +import java.awt.peer.MenuBarPeer; +import java.awt.peer.MenuItemPeer; +import java.awt.peer.MenuPeer; +import java.awt.peer.PanelPeer; +import java.awt.peer.PopupMenuPeer; +import java.awt.peer.RobotPeer; +import java.awt.peer.ScrollPanePeer; +import java.awt.peer.ScrollbarPeer; +import java.awt.peer.TextAreaPeer; +import java.awt.peer.TextFieldPeer; +import java.awt.peer.WindowPeer; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.imageio.ImageIO; + +/** + * AWT toolkit for Haiku. + * + * Top-level windows are native BWindows (see {@link HaikuWindowPeer}); + * widgets inside them use the Swing based peers from + * gnu.java.awt.peer.swing. All drawing is done in Java. + * + * Select it with -Dawt.toolkit=gnu.java.awt.peer.haiku.HaikuToolkit. + */ +public class HaikuToolkit + extends ClasspathToolkit +{ + + private EventQueue eventQueue; + + private ColorModel colorModel; + + private Clipboard clipboard; + + private final HashMap imageCache = new HashMap(); + + private final HashMap fontCache = + new HashMap(); + + private static final boolean DEBUG = + Boolean.getBoolean("gnu.java.awt.peer.haiku.debug"); + + public HaikuToolkit() + { + SystemProperties.setProperty("java.awt.graphicsenv", + "gnu.java.awt.peer.haiku.HaikuGraphicsEnvironment"); + // PrinterJob.getPrinterJob() uses this class, as in the JDK. + if (SystemProperties.getProperty("java.awt.printerjob") == null) + SystemProperties.setProperty("java.awt.printerjob", + "gnu.java.awt.peer.haiku.HaikuPrinterJob"); + HaikuNative.ensureInitialized(); + } + + public GraphicsEnvironment getLocalGraphicsEnvironment() + { + return new HaikuGraphicsEnvironment(); + } + + private static HaikuGraphicsConfiguration getConfig() + { + return (HaikuGraphicsConfiguration) + GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration(); + } + + public ClasspathFontPeer getClasspathFontPeer(String name, Map attrs) + { + String canonical = OpenTypeFontPeer.encodeFont(name, attrs); + synchronized (fontCache) + { + ClasspathFontPeer font = fontCache.get(canonical); + if (font == null) + { + font = new HaikuFontPeer(name, attrs); + fontCache.put(canonical, font); + } + return font; + } + } + + public Font createFont(int format, InputStream stream) + { + return null; + } + + public RobotPeer createRobot(GraphicsDevice screen) throws AWTException + { + throw new AWTException("Robot is not supported on Haiku yet"); + } + + public EmbeddedWindowPeer createEmbeddedWindow(EmbeddedWindow w) + { + throw new UnsupportedOperationException("Not yet implemented."); + } + + // Top-level windows. + + protected FramePeer createFrame(Frame target) + { + return new HaikuFramePeer(target); + } + + protected WindowPeer createWindow(Window target) + { + return new HaikuWindowPeer(target); + } + + protected DialogPeer createDialog(Dialog target) + { + return new HaikuDialogPeer(target); + } + + // Widgets, implemented with Swing. + + protected ButtonPeer createButton(Button target) + { + return new HaikuButtonPeer(target); + } + + protected TextFieldPeer createTextField(TextField target) + { + return new HaikuTextFieldPeer(target); + } + + protected LabelPeer createLabel(Label target) + { + return new HaikuLabelPeer(target); + } + + protected ListPeer createList(List target) + { + return new HaikuListPeer(target); + } + + protected CheckboxPeer createCheckbox(Checkbox target) + { + return new HaikuCheckboxPeer(target); + } + + protected TextAreaPeer createTextArea(TextArea target) + { + return new SwingTextAreaPeer(target); + } + + protected CanvasPeer createCanvas(Canvas target) + { + return new SwingCanvasPeer(target); + } + + protected PanelPeer createPanel(Panel target) + { + return new HaikuPanelPeer(target); + } + + protected ScrollbarPeer createScrollbar(Scrollbar target) + { + return new HaikuScrollbarPeer(target); + } + + protected ScrollPanePeer createScrollPane(ScrollPane target) + { + return new HaikuScrollPanePeer(target); + } + + protected ChoicePeer createChoice(Choice target) + { + return new HaikuChoicePeer(target); + } + + // Menus and file dialogs are native. + + protected MenuBarPeer createMenuBar(MenuBar target) + { + return new HaikuMenuBarPeer(target); + } + + protected MenuPeer createMenu(Menu target) + { + return new HaikuMenuPeer(target); + } + + protected PopupMenuPeer createPopupMenu(PopupMenu target) + { + return new HaikuPopupMenuPeer(target); + } + + protected MenuItemPeer createMenuItem(MenuItem target) + { + return new HaikuMenuItemPeer(target); + } + + protected FileDialogPeer createFileDialog(FileDialog target) + { + return new HaikuFileDialogPeer(target); + } + + protected CheckboxMenuItemPeer createCheckboxMenuItem(CheckboxMenuItem target) + { + return new HaikuCheckboxMenuItemPeer(target); + } + + protected FontPeer getFontPeer(String name, int style) + { + HashMap attrs = new HashMap(); + ClasspathFontPeer.copyStyleToAttrs(style, attrs); + return getClasspathFontPeer(name, attrs); + } + + // Screen. + + public Dimension getScreenSize() + { + java.awt.Rectangle r = getConfig().getBounds(); + return new Dimension(r.width, r.height); + } + + public int getScreenResolution() + { + return getConfig().getResolution(); + } + + public ColorModel getColorModel() + { + if (colorModel == null) + colorModel = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF); + return colorModel; + } + + // Fonts. + + public String[] getFontList() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment() + .getAvailableFontFamilyNames(); + } + + public FontMetrics getFontMetrics(Font font) + { + ClasspathFontPeer peer = (ClasspathFontPeer) font.getPeer(); + return peer.getFontMetrics(font); + } + + public void sync() + { + // Blits are synchronous. + } + + // Images. Decoding is done in Java with javax.imageio. + + public Image getImage(String name) + { + try + { + return getImage(new File(name).toURL()); + } + catch (MalformedURLException ex) + { + return createErrorImage(); + } + } + + public Image getImage(URL url) + { + synchronized (imageCache) + { + Image image = imageCache.get(url); + if (image == null) + { + image = createImage(url); + imageCache.put(url, image); + } + return image; + } + } + + public Image createImage(String filename) + { + try + { + return createImage(new File(filename).toURL()); + } + catch (MalformedURLException ex) + { + return createErrorImage(); + } + } + + public Image createImage(URL url) + { + try + { + InputStream in = url.openStream(); + try + { + return createImage(in); + } + finally + { + in.close(); + } + } + catch (IOException ex) + { + return createErrorImage(); + } + } + + public Image createImage(byte[] data, int offset, int len) + { + try + { + return createImage(new ByteArrayInputStream(data, offset, len)); + } + catch (IOException ex) + { + return createErrorImage(); + } + } + + private Image createImage(InputStream in) + throws IOException + { + if (DEBUG) + System.err.println("haiku: decoding image"); + BufferedImage img = ImageIO.read(in); + if (DEBUG) + System.err.println("haiku: decoded " + img); + return img != null ? toDirect(img) : createErrorImage(); + } + + /** + * Converts an image to INT_RGB or INT_ARGB. HaikuGraphics2D draws those + * directly; other types (such as the indexed images of GIF files) take + * a slow generic path that does not draw them correctly. + */ + static BufferedImage toDirect(BufferedImage img) + { + int type = img.getType(); + if (type == BufferedImage.TYPE_INT_RGB + || type == BufferedImage.TYPE_INT_ARGB) + return img; + int w = img.getWidth(); + int h = img.getHeight(); + boolean alpha = img.getColorModel().hasAlpha(); + BufferedImage out = + new BufferedImage(w, h, alpha ? BufferedImage.TYPE_INT_ARGB + : BufferedImage.TYPE_INT_RGB); + int[] dst = ((java.awt.image.DataBufferInt) out.getRaster() + .getDataBuffer()).getData(); + if (img.getColorModel() instanceof java.awt.image.IndexColorModel + && img.getRaster().getNumBands() == 1) + { + // Map the palette indices; much faster than getRGB() per pixel. + java.awt.image.IndexColorModel icm = + (java.awt.image.IndexColorModel) img.getColorModel(); + int[] palette = new int[icm.getMapSize()]; + icm.getRGBs(palette); + java.awt.image.Raster r = img.getRaster(); + int[] idx = new int[w]; + for (int y = 0; y < h; y++) + { + r.getSamples(r.getMinX(), r.getMinY() + y, w, 1, 0, idx); + int off = y * w; + for (int x = 0; x < w; x++) + { + int i = idx[x]; + dst[off + x] = i < palette.length ? palette[i] : 0; + } + } + return out; + } + for (int y = 0; y < h; y++) + img.getRGB(0, y, w, 1, dst, y * w, w); + return out; + } + + private static Image createErrorImage() + { + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + + public Image createImage(ImageProducer producer) + { + ImageConverter conv = new ImageConverter(); + producer.startProduction(conv); + Image img = conv.getImage(); + if (img instanceof BufferedImage) + img = toDirect((BufferedImage) img); + return img; + } + + public boolean prepareImage(Image image, int width, int height, + ImageObserver observer) + { + // Images are loaded synchronously. + return true; + } + + public int checkImage(Image image, int width, int height, + ImageObserver observer) + { + return ImageObserver.ALLBITS | ImageObserver.WIDTH + | ImageObserver.HEIGHT; + } + + // Misc. + + /** Shows Haiku's page setup and print dialogs; null if cancelled. */ + public PrintJob getPrintJob(Frame frame, String title, Properties props) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPrintJobAccess(); + return HaikuPrintJob.create(title); + } + + public void beep() + { + HaikuNative.beep(); + } + + /** The system clipboard, shared with other Haiku applications. */ + public synchronized Clipboard getSystemClipboard() + { + if (clipboard == null) + { + HaikuNative.ensureInitialized(); + clipboard = new HaikuClipboard(); + } + return clipboard; + } + + public Cursor createCustomCursor(Image cursor, Point hotSpot, String name) + { + HaikuNative.ensureInitialized(); + return new HaikuCursor(cursor, hotSpot, name); + } + + /** Haiku cursors can have any size; keep them reasonable. */ + public Dimension getBestCursorSize(int w, int h) + { + return new Dimension(Math.max(1, Math.min(w, 64)), + Math.max(1, Math.min(h, 64))); + } + + public int getMaximumCursorColors() + { + return 1 << 24; + } + + /** Haiku windows can be minimized and zoomed. */ + public boolean isFrameStateSupported(int state) + { + return state == Frame.NORMAL || state == Frame.ICONIFIED + || state == Frame.MAXIMIZED_BOTH + || state == (Frame.ICONIFIED | Frame.MAXIMIZED_BOTH); + } + + protected synchronized EventQueue getSystemEventQueueImpl() + { + if (eventQueue == null) + eventQueue = new EventQueue(); + return eventQueue; + } + + public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent e) + { + return HaikuDragDrop.createDragSourceContextPeer(); + } + + /** Mouse drags start drag gestures (GNU Classpath's generic recognizer). */ + public T + createDragGestureRecognizer(Class recognizer, + java.awt.dnd.DragSource ds, + java.awt.Component comp, int actions, + java.awt.dnd.DragGestureListener l) + { + if (! recognizer.isAssignableFrom( + gnu.java.awt.dnd.GtkMouseDragGestureRecognizer.class)) + return null; + gnu.java.awt.dnd.GtkMouseDragGestureRecognizer r = + new gnu.java.awt.dnd.GtkMouseDragGestureRecognizer(ds, comp, actions, l); + // GNU Classpath's DragGestureRecognizer constructor does not do this. + if (comp != null) + r.registerListeners(); + return recognizer.cast(r); + } + + /** + * Composed text: dotted underline for raw text, inverse colours for the + * part being converted, a solid underline for converted text. + */ + public Map mapInputMethodHighlight(InputMethodHighlight highlight) + { + java.util.HashMap m = + new java.util.HashMap(); + if (highlight.isSelected()) + m.put(java.awt.font.TextAttribute.SWAP_COLORS, + java.awt.font.TextAttribute.SWAP_COLORS_ON); + else if (highlight.getState() == InputMethodHighlight.RAW_TEXT) + m.put(java.awt.font.TextAttribute.INPUT_METHOD_UNDERLINE, + java.awt.font.TextAttribute.UNDERLINE_LOW_DOTTED); + else + m.put(java.awt.font.TextAttribute.INPUT_METHOD_UNDERLINE, + java.awt.font.TextAttribute.UNDERLINE_LOW_ONE_PIXEL); + return m; + } + + public boolean isModalExclusionTypeSupported(ModalExclusionType type) + { + return false; + } + + /** Modal dialogs block the whole application. */ + public boolean isModalityTypeSupported(ModalityType type) + { + return type == ModalityType.MODELESS + || type == ModalityType.APPLICATION_MODAL; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuTransferable.java b/gnu/java/awt/peer/haiku/HaikuTransferable.java new file mode 100644 index 0000000000..91b8f48607 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuTransferable.java @@ -0,0 +1,301 @@ +/* HaikuTransferable.java -- data from a Haiku message + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.peer.haiku; + +import java.awt.Image; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * The data in a Haiku message: the clipboard, a drag or a drop. Text + * ("text/plain") is a String, files ("refs") are a file list, pictures + * (archived bitmaps or any image type a translator reads) are an Image, + * and other MIME types are an InputStream (and a String for text types). + * + * The data is read when it is asked for. If the message was replaced in + * the meantime, getTransferData() throws an IOException. + */ +final class HaikuTransferable + implements Transferable +{ + + private final int src; + + private final int generation; + + private final DataFlavor[] flavors; + + /** The MIME type in the message for each flavor. */ + private final HashMap types = + new HashMap(); + + private HaikuTransferable(int src) + { + this.src = src; + generation = HaikuNative.msgGeneration(src); + String[] names = HaikuNative.msgTypes(src); + ArrayList l = new ArrayList(); + boolean image = false; + for (int i = 0; i < names.length; i++) + { + String n = names[i]; + if (n.equals("text/plain")) + add(l, DataFlavor.stringFlavor, n); + else if (n.equals("refs")) + add(l, DataFlavor.javaFileListFlavor, n); + else if (n.startsWith("image/")) + { + if (! image) + add(l, DataFlavor.imageFlavor, n); + image = true; + if (n.indexOf('/') > 0 && ! n.equals("image/bitmap") + && ! n.equals("image/x-be-bitmap")) + addMime(l, n); + } + else if (n.indexOf('/') > 0) + addMime(l, n); + } + flavors = l.toArray(new DataFlavor[l.size()]); + } + + /** The data of a message source, or null if it has nothing usable. */ + static HaikuTransferable read(int src) + { + HaikuTransferable t = new HaikuTransferable(src); + return t.flavors.length > 0 ? t : null; + } + + private void add(List l, DataFlavor f, String type) + { + if (! types.containsKey(f)) + { + l.add(f); + types.put(f, type); + } + } + + private void addMime(List l, String mime) + { + try + { + add(l, new DataFlavor(mime + "; class=java.io.InputStream"), mime); + if (mime.startsWith("text/")) + add(l, new DataFlavor(mime + "; class=java.lang.String"), mime); + } + catch (Exception e) + { + // Not a MIME type that Java understands. + } + } + + public DataFlavor[] getTransferDataFlavors() + { + return (DataFlavor[]) flavors.clone(); + } + + public boolean isDataFlavorSupported(DataFlavor flavor) + { + return types.containsKey(flavor); + } + + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException + { + String type = types.get(flavor); + if (type == null) + throw new UnsupportedFlavorException(flavor); + if (HaikuNative.msgGeneration(src) != generation) + throw new IOException("the data is no longer available"); + if (flavor.equals(DataFlavor.javaFileListFlavor)) + { + String[] paths = HaikuNative.msgFiles(src); + ArrayList files = new ArrayList(); + for (int i = 0; i < paths.length; i++) + files.add(new File(paths[i])); + return files; + } + if (flavor.equals(DataFlavor.imageFlavor)) + { + int[] dims = new int[2]; + int[] argb = HaikuNative.msgImage(src, dims); + if (argb == null) + throw new IOException("cannot read the picture"); + BufferedImage img = new BufferedImage(dims[0], dims[1], + BufferedImage.TYPE_INT_ARGB); + img.setRGB(0, 0, dims[0], dims[1], argb, 0, dims[0]); + return img; + } + byte[] data = HaikuNative.msgData(src, type); + if (data == null) + throw new IOException("no data for " + type); + Class c = flavor.getRepresentationClass(); + if (c == String.class) + { + String cs = flavor.getParameter("charset"); + return new String(data, cs != null && ! flavor.equals(DataFlavor.stringFlavor) + ? cs : "UTF-8"); + } + return new ByteArrayInputStream(data); + } + + /** Copies an image to ARGB pixels; dims gets width and height. */ + static int[] pixels(Image img, int[] dims) + { + BufferedImage b; + if (img instanceof BufferedImage) + b = (BufferedImage) img; + else + { + int w = img.getWidth(null); + int h = img.getHeight(null); + if (w <= 0 || h <= 0) + return null; + b = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics g = b.createGraphics(); + g.drawImage(img, 0, 0, null); + g.dispose(); + } + dims[0] = b.getWidth(); + dims[1] = b.getHeight(); + return b.getRGB(0, 0, dims[0], dims[1], null, 0, dims[0]); + } + + /** + * Makes a Haiku message from a Transferable: text, files, a picture, + * and the other MIME types whose data is bytes, text or a stream. + */ + static long toMessage(Transferable t, int what) + { + long m = HaikuNative.msgNew(what); + if (t == null) + return m; + DataFlavor[] fs = t.getTransferDataFlavors(); + java.util.HashSet done = new java.util.HashSet(); + for (int i = 0; i < fs.length; i++) + { + DataFlavor f = fs[i]; + try + { + String mime = f.getPrimaryType() + "/" + f.getSubType(); + if (f.equals(DataFlavor.stringFlavor)) + { + if (done.add("text/plain")) + HaikuNative.msgAddData(m, "text/plain", + ((String) t.getTransferData(f)).getBytes("UTF-8")); + } + else if (f.equals(DataFlavor.imageFlavor)) + { + if (done.add("image")) + { + int[] dims = new int[2]; + int[] argb = pixels((Image) t.getTransferData(f), dims); + if (argb != null) + HaikuNative.msgAddImage(m, argb, dims[0], dims[1]); + } + } + else if (f.equals(DataFlavor.javaFileListFlavor)) + { + if (done.add("refs")) + { + List files = (List) t.getTransferData(f); + String[] paths = new String[files.size()]; + for (int k = 0; k < paths.length; k++) + paths[k] = ((File) files.get(k)).getAbsolutePath(); + HaikuNative.msgAddFiles(m, paths); + } + } + else if (! mime.startsWith("application/x-java") + && ! done.contains(mime)) + { + byte[] b = bytes(t.getTransferData(f), f); + if (b != null) + { + HaikuNative.msgAddData(m, mime, b); + done.add(mime); + } + } + } + catch (Exception e) + { + // Leave this flavor out. + } + } + return m; + } + + private static byte[] bytes(Object o, DataFlavor f) + throws IOException + { + if (o instanceof byte[]) + return (byte[]) o; + if (o instanceof String) + { + String cs = f.getParameter("charset"); + return ((String) o).getBytes(cs != null ? cs : "UTF-8"); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + if (o instanceof java.io.InputStream) + { + java.io.InputStream in = (java.io.InputStream) o; + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) > 0) + out.write(buf, 0, n); + return out.toByteArray(); + } + if (o instanceof java.io.Reader) + { + java.io.Reader in = (java.io.Reader) o; + StringBuilder sb = new StringBuilder(); + char[] buf = new char[4096]; + int n; + while ((n = in.read(buf)) > 0) + sb.append(buf, 0, n); + return sb.toString().getBytes("UTF-8"); + } + return null; + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuVolatileImage.java b/gnu/java/awt/peer/haiku/HaikuVolatileImage.java new file mode 100644 index 0000000000..a1223acbc7 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuVolatileImage.java @@ -0,0 +1,114 @@ +/* HaikuVolatileImage.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Graphics2D; +import java.awt.ImageCapabilities; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; +import java.awt.image.VolatileImage; + +/** + * A VolatileImage that is a plain Java BufferedImage. Its contents are + * never lost. + */ +class HaikuVolatileImage + extends VolatileImage +{ + + private final BufferedImage image; + + HaikuVolatileImage(int w, int h) + { + image = new BufferedImage(Math.max(w, 1), Math.max(h, 1), + BufferedImage.TYPE_INT_RGB); + } + + public BufferedImage getSnapshot() + { + return image; + } + + public int getWidth() + { + return image.getWidth(); + } + + public int getHeight() + { + return image.getHeight(); + } + + public int getWidth(ImageObserver observer) + { + return image.getWidth(); + } + + public int getHeight(ImageObserver observer) + { + return image.getHeight(); + } + + public Object getProperty(String name, ImageObserver observer) + { + return image.getProperty(name, observer); + } + + public Graphics2D createGraphics() + { + return new HaikuGraphics2D(image, null); + } + + public int validate(GraphicsConfiguration gc) + { + return IMAGE_OK; + } + + public boolean contentsLost() + { + return false; + } + + public ImageCapabilities getCapabilities() + { + return new ImageCapabilities(false); + } +} diff --git a/gnu/java/awt/peer/haiku/HaikuWindowPeer.java b/gnu/java/awt/peer/haiku/HaikuWindowPeer.java new file mode 100644 index 0000000000..9f0be051c2 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuWindowPeer.java @@ -0,0 +1,503 @@ +/* HaikuWindowPeer.java -- part of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.awt.peer.haiku; + +import gnu.java.awt.peer.ClasspathFontPeer; +import gnu.java.awt.peer.swing.SwingWindowPeer; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.PaintEvent; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.VolatileImage; + +/** + * A heavyweight window backed by a native BWindow. + * + * The window content is rendered in Java into {@link #backing}, an + * INT_RGB image the size of the native client area. Dirty areas are + * copied to the native window when a {@link HaikuGraphics2D} is disposed. + * + * AWT window bounds include the decorations (insets); the native window + * frame is the client area only. AWT coordinates (0, 0) are the outer + * corner, so window graphics are translated by (-left, -top). + */ +public class HaikuWindowPeer + extends SwingWindowPeer + implements java.awt.dnd.peer.DropTargetPeer +{ + + private static int nextId = 1; + + /** The background of windows that have no background colour. */ + static final Color DEFAULT_BACKGROUND = new Color(0xDDDDDD); + + final int id; + + final long handle; + + /** Decorator insets reported by the app_server. */ + private Insets insets; + + /** Client area in screen coordinates, as last set or reported. */ + private int clientX, clientY, clientW, clientH; + + /** The backing store, the size of the client area. */ + private BufferedImage backing; + + private boolean disposed; + + HaikuWindowPeer(Window window) + { + super(window); + // Like other toolkits, give windows default colours, so that + // components that inherit them never draw with a null colour. + if (window.getForeground() == null) + window.setForeground(Color.black); + if (window.getBackground() == null) + window.setBackground(DEFAULT_BACKGROUND); + synchronized (HaikuWindowPeer.class) + { + id = nextId++; + } + int kind; + String title = null; + if (window instanceof Frame) + { + Frame f = (Frame) window; + kind = f.isUndecorated() ? HaikuNative.KIND_WINDOW + : HaikuNative.KIND_FRAME; + title = f.getTitle(); + } + else if (window instanceof Dialog) + { + Dialog d = (Dialog) window; + kind = d.isUndecorated() ? HaikuNative.KIND_WINDOW + : HaikuNative.KIND_DIALOG; + title = d.getTitle(); + } + else + kind = HaikuNative.KIND_WINDOW; + + int x = window.getX(); + int y = window.getY(); + int w = Math.max(window.getWidth(), 1); + int h = Math.max(window.getHeight(), 1); + handle = HaikuNative.createWindow(id, kind, title, x, y, w, h); + + int[] in = new int[4]; + HaikuNative.getInsets(handle, in); + insets = new Insets(in[1], in[0], in[3], in[2]); + HaikuEventPump.get().register(this); + setClientFromAWT(x, y, w, h); + if (window instanceof Frame) + HaikuNative.setResizable(handle, ((Frame) window).isResizable()); + else if (window instanceof Dialog) + HaikuNative.setResizable(handle, ((Dialog) window).isResizable()); + if (window.isAlwaysOnTop()) + updateFeel(); + } + + /** + * Reads the insets again (for example after a menu bar was added) and + * lays out the window for the new client area. + */ + void insetsChanged() + { + int[] in = new int[4]; + HaikuNative.getInsets(handle, in); + Insets i = new Insets(in[1], in[0], in[3], in[2]); + synchronized (this) + { + if (i.equals(insets)) + return; + insets = i; + } + final Window w = getWindow(); + setClientFromAWT(w.getX(), w.getY(), w.getWidth(), w.getHeight()); + java.awt.EventQueue.invokeLater(new Runnable() + { + public void run() + { + w.invalidate(); + w.validate(); + w.repaint(); + } + }); + } + + Window getWindow() + { + return (Window) awtComponent; + } + + /** + * Applies AWT outer bounds to the native window and backing store. + */ + private synchronized void setClientFromAWT(int x, int y, int w, int h) + { + Insets i = insets; + clientX = x + i.left; + clientY = y + i.top; + clientW = Math.max(w - i.left - i.right, 1); + clientH = Math.max(h - i.top - i.bottom, 1); + HaikuNative.setBounds(handle, clientX, clientY, clientW, clientH); + ensureBacking(clientW, clientH); + } + + /** + * Makes sure the backing store has the given size. Old content is kept. + * + * @return true if the backing store was replaced + */ + private synchronized boolean ensureBacking(int w, int h) + { + if (backing != null && backing.getWidth() == w + && backing.getHeight() == h) + return false; + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + Color bg = awtComponent.getBackground(); + int rgb = (bg != null ? bg : DEFAULT_BACKGROUND).getRGB(); + int[] p = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + java.util.Arrays.fill(p, rgb); + if (backing != null) + { + int[] old = ((DataBufferInt) backing.getRaster().getDataBuffer()) + .getData(); + int ow = backing.getWidth(); + int cw = Math.min(ow, w); + int ch = Math.min(backing.getHeight(), h); + for (int row = 0; row < ch; row++) + System.arraycopy(old, row * ow, p, row * w, cw); + } + backing = img; + return true; + } + + /** + * Called by the event pump when the user resized the native window. + * + * @return true if the AWT bounds changed + */ + synchronized boolean nativeResized(int w, int h) + { + boolean changed = w != clientW || h != clientH; + clientW = w; + clientH = h; + if (ensureBacking(w, h)) + flushBacking(backing, 0, 0, w, h); + return changed; + } + + synchronized boolean nativeMoved(int x, int y) + { + boolean changed = x != clientX || y != clientY; + clientX = x; + clientY = y; + return changed; + } + + /** AWT outer bounds derived from the native client area. */ + synchronized Rectangle getOuterBounds() + { + Insets i = insets; + return new Rectangle(clientX - i.left, clientY - i.top, + clientW + i.left + i.right, + clientH + i.top + i.bottom); + } + + /** + * Copies part of the backing store to the native window. Ignored if + * img is no longer the backing store (the window was resized). + */ + synchronized void flushBacking(BufferedImage img, int x, int y, int w, + int h) + { + if (disposed || img != backing) + return; + int[] data = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + HaikuNative.blit(handle, data, img.getWidth(), img.getHeight(), + x, y, w, h); + } + + public Graphics getGraphics() + { + BufferedImage img; + synchronized (this) + { + img = backing; + } + HaikuGraphics2D g = new HaikuGraphics2D(img, this); + g.translate(-insets.left, -insets.top); + g.setColor(awtComponent.getForeground()); + Color bg = awtComponent.getBackground(); + g.setBackground(bg != null ? bg : DEFAULT_BACKGROUND); + g.setFont(awtComponent.getFont()); + return g; + } + + /** + * The window itself has no Swing component; clear the exposed area to + * the background colour before AWT paints on top of it. + */ + protected void peerPaintComponent(Graphics g) + { + Rectangle clip = g.getClipBounds(); + if (clip == null) + clip = new Rectangle(0, 0, awtComponent.getWidth(), + awtComponent.getHeight()); + g.clearRect(clip.x, clip.y, clip.width, clip.height); + } + + public Point getLocationOnScreen() + { + Rectangle r = getOuterBounds(); + return new Point(r.x, r.y); + } + + public Image createImage(int w, int h) + { + return new BufferedImage(Math.max(w, 1), Math.max(h, 1), + BufferedImage.TYPE_INT_RGB); + } + + public VolatileImage createVolatileImage(int w, int h) + { + return new HaikuVolatileImage(w, h); + } + + public GraphicsConfiguration getGraphicsConfiguration() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration(); + } + + public FontMetrics getFontMetrics(Font font) + { + ClasspathFontPeer fontPeer = (ClasspathFontPeer) font.getPeer(); + return fontPeer.getFontMetrics(font); + } + + public void show() + { + HaikuNative.setVisible(handle, true); + Window w = getWindow(); + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent( + new PaintEvent(w, PaintEvent.PAINT, + new Rectangle(0, 0, w.getWidth(), w.getHeight()))); + } + + public void hide() + { + HaikuNative.setVisible(handle, false); + } + + public void reshape(int x, int y, int width, int height) + { + setClientFromAWT(x, y, width, height); + } + + public void setBounds(int x, int y, int width, int height) + { + reshape(x, y, width, height); + } + + public void setBounds(int x, int y, int width, int height, int z) + { + reshape(x, y, width, height); + } + + public Rectangle getBounds() + { + return getOuterBounds(); + } + + public Insets insets() + { + return (Insets) insets.clone(); + } + + public Insets getInsets() + { + return insets(); + } + + public void setBackground(Color c) + { + // Nothing to do; the background is painted from Java. + } + + public void setForeground(Color c) + { + // Nothing to do. + } + + public void setFont(Font f) + { + peerFont = f; + } + + /** The cursor type and custom cursor shown in the native window. */ + private int shownCursorType = Cursor.DEFAULT_CURSOR; + private long shownCursorHandle; + + /** + * Shows a cursor in the native window. The event pump calls this when + * the mouse moves to a component with another cursor. + */ + void showCursor(Cursor c) + { + int type = c.getType(); + long custom = c instanceof HaikuCursor ? ((HaikuCursor) c).handle : 0; + synchronized (this) + { + if (disposed + || (type == shownCursorType && custom == shownCursorHandle)) + return; + shownCursorType = type; + shownCursorHandle = custom; + } + HaikuNative.setCursor(handle, type, custom); + } + + /** + * Called by Component.setCursor() for this window and its lightweight + * children. The cursor of the component under the mouse is shown. + */ + public void setCursor(Cursor cursor) + { + HaikuEventPump.get().refreshCursor(); + } + + public void toBack() + { + HaikuNative.toBack(handle); + } + + public void toFront() + { + HaikuNative.toFront(handle); + } + + /** True while this is a shown modal dialog. */ + private boolean modalShown; + + void setModalShown(boolean shown) + { + modalShown = shown; + updateFeel(); + } + + /** + * A modal dialog has the modal app feel; an always-on-top window floats + * above all windows of all applications. + */ + void updateFeel() + { + int feel = HaikuNative.B_NORMAL_WINDOW_FEEL; + if (modalShown) + feel = HaikuNative.B_MODAL_APP_WINDOW_FEEL; + else if (getWindow().isAlwaysOnTop()) + feel = HaikuNative.B_FLOATING_ALL_WINDOW_FEEL; + HaikuNative.setFeel(handle, feel); + } + + public void updateAlwaysOnTop() + { + updateFeel(); + } + + public void setAlwaysOnTop(boolean alwaysOnTop) + { + updateFeel(); + } + + // Drop targets are found by the event pump; nothing to register. + + public void addDropTarget(java.awt.dnd.DropTarget target) + { + } + + public void removeDropTarget(java.awt.dnd.DropTarget target) + { + } + + public boolean requestWindowFocus() + { + HaikuNative.toFront(handle); + return true; + } + + void setTitle(String title) + { + HaikuNative.setTitle(handle, title); + } + + public void setResizable(boolean resizable) + { + HaikuNative.setResizable(handle, resizable); + } + + public void dispose() + { + synchronized (this) + { + if (disposed) + return; + disposed = true; + } + HaikuEventPump.get().unregister(this); + HaikuNative.dispose(handle); + super.dispose(); + } +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuAudio.java b/gnu/javax/sound/sampled/haiku/HaikuAudio.java new file mode 100644 index 0000000000..43db6db994 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuAudio.java @@ -0,0 +1,111 @@ +/* HaikuAudio.java -- native sound output for Haiku + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +/** + * Native entry points of the Haiku sound lines. They are in libhaikupeer, + * which also holds the AWT peer, and use its BApplication. Each line owns + * one BSoundPlayer; all audio is 16-bit signed PCM here. + */ +final class HaikuAudio +{ + + private static boolean initialized; + + private HaikuAudio() + { + } + + /** Loads libhaikupeer and starts the BApplication. */ + static synchronized boolean ensureInitialized() + { + if (initialized) + return true; + try + { + String explicit = + System.getProperty("gnu.java.awt.peer.haiku.library"); + if (explicit != null) + System.load(explicit); + else + System.loadLibrary("haikupeer"); + initialized = init(); + } + catch (UnsatisfiedLinkError e) + { + initialized = false; + } + return initialized; + } + + private static native boolean init(); + + /** Returns 0 if the media server could not make a player. */ + static native long open(float rate, int channels); + + static native void close(long h); + + static native void loadClip(long h, short[] samples); + + /** loopEnd is exclusive; loops is -1 for ever. */ + static native void playClip(long h, long start, long loopStart, + long loopEnd, int loops); + + static native void startStream(long h, int frames); + + static native void pause(long h); + + static native boolean isPlaying(long h); + + static native long position(long h); + + static native void setPosition(long h, long frame); + + static native boolean waitIdle(long h, int timeoutMs); + + /** Returns the number of samples taken. */ + static native int write(long h, short[] samples, int off, int len, + int timeoutMs); + + /** Free space, in samples. */ + static native int available(long h); + + static native void flush(long h); + + static native void setGain(long h, float gain); +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuClip.java b/gnu/javax/sound/sampled/haiku/HaikuClip.java new file mode 100644 index 0000000000..0d68b7de77 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuClip.java @@ -0,0 +1,255 @@ +/* HaikuClip.java -- a Clip for the Haiku media server + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineUnavailableException; + +/** + * A clip. All frames are kept in the native player, which also does the + * looping, so loops have no gaps. + */ +final class HaikuClip + extends HaikuLine + implements Clip +{ + + private int frames; + + /** Loop points; loopEnd is the last frame, -1 for the end. */ + private int loopStart, loopEnd = -1; + + /** Counts start()/loop() calls, so old monitor threads stop. */ + private int generation; + + HaikuClip(HaikuMixer mixer) + { + super(mixer); + } + + public Info getLineInfo() + { + return new DataLine.Info(Clip.class, Pcm.FORMATS, + javax.sound.sampled.AudioSystem.NOT_SPECIFIED, + javax.sound.sampled.AudioSystem.NOT_SPECIFIED); + } + + public void open() + throws LineUnavailableException + { + throw new IllegalArgumentException("a clip needs audio data"); + } + + public void open(AudioInputStream ais) + throws LineUnavailableException, IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int n; + while ((n = ais.read(buf)) > 0) + out.write(buf, 0, n); + byte[] data = out.toByteArray(); + open(ais.getFormat(), data, 0, data.length); + } + + public void open(AudioFormat fmt, byte[] data, int offset, int len) + throws LineUnavailableException + { + if (isOpen()) + throw new IllegalStateException("the clip is already open"); + openNative(fmt); + short[] samples = Pcm.toShorts(fmt, data, offset, len); + long h; + synchronized (this) + { + h = handle; + frames = samples.length / Pcm.channels(fmt); + loopStart = 0; + loopEnd = -1; + } + HaikuAudio.loadClip(h, samples); + fire(LineEvent.Type.OPEN); + } + + public int getFrameLength() + { + return frames; + } + + public long getMicrosecondLength() + { + float rate = getFormat().getSampleRate(); + return rate > 0 ? (long) (frames * 1000000.0 / rate) : 0; + } + + public synchronized void setFramePosition(int where) + { + if (handle != 0) + HaikuAudio.setPosition(handle, where); + } + + public void setMicrosecondPosition(long us) + { + float rate = getFormat().getSampleRate(); + setFramePosition((int) (us * rate / 1000000.0)); + } + + public synchronized void setLoopPoints(int begin, int end) + { + if (end == -1) + end = frames - 1; + if (begin < 0 || end >= frames || begin > end) + throw new IllegalArgumentException("bad loop points"); + loopStart = begin; + loopEnd = end; + } + + public void start() + { + play(0); + } + + public void loop(int count) + { + play(count == LOOP_CONTINUOUSLY ? -1 : count); + } + + /** Plays from the current position; loops as in loop(). */ + private void play(int loops) + { + final int gen; + synchronized (this) + { + if (handle == 0) + return; + int end = (loopEnd < 0 ? frames - 1 : loopEnd) + 1; + long pos = HaikuAudio.position(handle); + if (pos >= frames && loops == 0) + pos = 0; // start() at the end plays again from the start + HaikuAudio.playClip(handle, pos, loopStart, end, loops); + running = true; + gen = ++generation; + } + fire(LineEvent.Type.START); + Thread t = new Thread("Haiku clip") + { + public void run() + { + monitor(gen); + } + }; + t.setDaemon(true); + t.start(); + } + + /** Sends STOP when the clip reaches its end. */ + private void monitor(int gen) + { + while (true) + { + long h; + synchronized (this) + { + if (gen != generation || ! running || handle == 0) + return; + h = handle; + } + if (HaikuAudio.waitIdle(h, 250)) + { + synchronized (this) + { + if (gen != generation || ! running) + return; + running = false; + } + fire(LineEvent.Type.STOP); + return; + } + } + } + + public void stop() + { + synchronized (this) + { + if (! running || handle == 0) + return; + HaikuAudio.pause(handle); + running = false; + generation++; + } + fire(LineEvent.Type.STOP); + } + + public void drain() + { + while (true) + { + long h; + synchronized (this) + { + if (! running || handle == 0) + return; + h = handle; + } + HaikuAudio.waitIdle(h, 250); + } + } + + public void flush() + { + // A clip has no queued data to discard. + } + + public int available() + { + return 0; + } + + public int getBufferSize() + { + return frames * Pcm.frameSize(getFormat()); + } +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuLine.java b/gnu/javax/sound/sampled/haiku/HaikuLine.java new file mode 100644 index 0000000000..a12b1d2f32 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuLine.java @@ -0,0 +1,234 @@ +/* HaikuLine.java -- base class of the Haiku sound lines + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import java.util.ArrayList; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.FloatControl; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; + +/** + * Common parts of the Haiku clip and source data line: the native + * player, the open and running state, line events and the gain control. + */ +abstract class HaikuLine + implements DataLine +{ + + final HaikuMixer mixer; + + /** The native player, 0 when the line is closed. */ + long handle; + + AudioFormat format; + + boolean running; + + private final ArrayList listeners = + new ArrayList(); + + private final Gain gain = new Gain(); + + HaikuLine(HaikuMixer mixer) + { + this.mixer = mixer; + } + + /** Opens the native player for format f. */ + void openNative(AudioFormat f) + throws LineUnavailableException + { + if (! Pcm.isSupported(f)) + throw new IllegalArgumentException("unsupported format: " + f); + if (! HaikuAudio.ensureInitialized()) + throw new LineUnavailableException("no Haiku sound library"); + float rate = f.getSampleRate() > 0 ? f.getSampleRate() : 44100; + long h = HaikuAudio.open(rate, Pcm.channels(f)); + if (h == 0) + throw new LineUnavailableException("the media server refused the line"); + synchronized (this) + { + handle = h; + format = f; + } + HaikuAudio.setGain(h, gain.linear()); + } + + public void close() + { + long h; + synchronized (this) + { + if (handle == 0) + return; + if (running) + { + HaikuAudio.pause(handle); + running = false; + } + h = handle; + handle = 0; + } + HaikuAudio.close(h); + fire(LineEvent.Type.CLOSE); + } + + public synchronized boolean isOpen() + { + return handle != 0; + } + + public synchronized boolean isRunning() + { + return running; + } + + public synchronized boolean isActive() + { + return running && handle != 0 && HaikuAudio.isPlaying(handle); + } + + public synchronized AudioFormat getFormat() + { + return format != null ? format : Pcm.FORMATS[1]; + } + + public float getLevel() + { + return AudioSystem.NOT_SPECIFIED; + } + + public int getFramePosition() + { + return (int) getLongFramePosition(); + } + + public synchronized long getLongFramePosition() + { + return handle != 0 ? HaikuAudio.position(handle) : 0; + } + + public long getMicrosecondPosition() + { + float rate = getFormat().getSampleRate(); + return rate > 0 ? (long) (getLongFramePosition() * 1000000.0 / rate) : 0; + } + + // Line events. + + public void addLineListener(LineListener l) + { + synchronized (listeners) + { + listeners.add(l); + } + } + + public void removeLineListener(LineListener l) + { + synchronized (listeners) + { + listeners.remove(l); + } + } + + /** Sends a line event. Do not call with the line locked. */ + void fire(LineEvent.Type type) + { + LineListener[] ls; + synchronized (listeners) + { + ls = listeners.toArray(new LineListener[listeners.size()]); + } + LineEvent ev = new LineEvent(this, type, getLongFramePosition()); + for (int i = 0; i < ls.length; i++) + ls[i].update(ev); + } + + // Controls. + + public Control[] getControls() + { + return new Control[] { gain }; + } + + public boolean isControlSupported(Control.Type type) + { + return FloatControl.Type.MASTER_GAIN.equals(type); + } + + public Control getControl(Control.Type type) + { + if (! isControlSupported(type)) + throw new IllegalArgumentException("unsupported control: " + type); + return gain; + } + + /** The master gain in decibels. */ + private final class Gain + extends FloatControl + { + Gain() + { + super(FloatControl.Type.MASTER_GAIN, -80f, 6f, 0.1f, -1, 0f, "dB"); + } + + float linear() + { + return (float) Math.pow(10.0, getValue() / 20.0); + } + + public void setValue(float value) + { + super.setValue(value); + long h; + synchronized (HaikuLine.this) + { + h = handle; + } + if (h != 0) + HaikuAudio.setGain(h, linear()); + } + } +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuMixer.java b/gnu/javax/sound/sampled/haiku/HaikuMixer.java new file mode 100644 index 0000000000..518458efb1 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuMixer.java @@ -0,0 +1,230 @@ +/* HaikuMixer.java -- the Haiku media server as a javax.sound mixer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import java.util.ArrayList; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.Control; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.SourceDataLine; + +/** + * A mixer that plays through the Haiku media server. It has Clip and + * SourceDataLine source lines and no target lines. + */ +final class HaikuMixer + implements Mixer +{ + + static final Mixer.Info INFO = new HaikuInfo(); + + private static final class HaikuInfo + extends Mixer.Info + { + HaikuInfo() + { + super("Haiku Media Kit", "GNU Classpath", + "Plays sound through the Haiku media server", "1.0"); + } + } + + private static final Line.Info[] NONE = new Line.Info[0]; + + public Mixer.Info getMixerInfo() + { + return INFO; + } + + public Line.Info[] getSourceLineInfo() + { + return new Line.Info[] { + new DataLine.Info(Clip.class, Pcm.FORMATS, AudioSystem.NOT_SPECIFIED, + AudioSystem.NOT_SPECIFIED), + new DataLine.Info(SourceDataLine.class, Pcm.FORMATS, + AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED) + }; + } + + public Line.Info[] getSourceLineInfo(Line.Info info) + { + ArrayList l = new ArrayList(); + Line.Info[] all = getSourceLineInfo(); + for (int i = 0; i < all.length; i++) + if (lineClass(info) == ((DataLine.Info) all[i]).getLineClass()) + l.add(all[i]); + return l.toArray(new Line.Info[l.size()]); + } + + public Line.Info[] getTargetLineInfo() + { + return NONE; + } + + public Line.Info[] getTargetLineInfo(Line.Info info) + { + return NONE; + } + + /** + * The line class that can serve info: Clip, SourceDataLine, or null. + */ + private static Class lineClass(Line.Info info) + { + Class c = info.getLineClass(); + Class result; + if (c.isAssignableFrom(SourceDataLine.class)) + result = SourceDataLine.class; + else if (c.isAssignableFrom(Clip.class)) + result = Clip.class; + else + return null; + if (info instanceof DataLine.Info) + { + javax.sound.sampled.AudioFormat[] f = + ((DataLine.Info) info).getFormats(); + if (f.length > 0) + { + boolean ok = false; + for (int i = 0; i < f.length && ! ok; i++) + ok = Pcm.isSupported(f[i]); + if (! ok) + return null; + } + } + return result; + } + + public boolean isLineSupported(Line.Info info) + { + return lineClass(info) != null; + } + + public Line getLine(Line.Info info) + throws LineUnavailableException + { + Class c = lineClass(info); + if (c == Clip.class) + return new HaikuClip(this); + if (c == SourceDataLine.class) + return new HaikuSourceDataLine(this); + throw new IllegalArgumentException("unsupported line: " + info); + } + + public int getMaxLines(Line.Info info) + { + return isLineSupported(info) ? AudioSystem.NOT_SPECIFIED : 0; + } + + /** + * New, closed lines. GNU Classpath's AudioSystem.getClip() takes its + * clip from here. + */ + public Line[] getSourceLines() + { + return new Line[] { new HaikuClip(this), new HaikuSourceDataLine(this) }; + } + + public Line[] getTargetLines() + { + return new Line[0]; + } + + public boolean isSynchronizationSupported(Line[] lines, boolean sync) + { + return false; + } + + public void synchronize(Line[] lines, boolean sync) + { + throw new IllegalArgumentException("synchronization is not supported"); + } + + public void unsynchronize(Line[] lines) + { + throw new IllegalArgumentException("synchronization is not supported"); + } + + // The mixer itself is always open. + + public Line.Info getLineInfo() + { + return new Line.Info(Mixer.class); + } + + public void open() + { + } + + public void close() + { + } + + public boolean isOpen() + { + return true; + } + + public Control[] getControls() + { + return new Control[0]; + } + + public boolean isControlSupported(Control.Type type) + { + return false; + } + + public Control getControl(Control.Type type) + { + throw new IllegalArgumentException("unsupported control: " + type); + } + + public void addLineListener(LineListener l) + { + } + + public void removeLineListener(LineListener l) + { + } +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuMixerProvider.java b/gnu/javax/sound/sampled/haiku/HaikuMixerProvider.java new file mode 100644 index 0000000000..cfb68dc674 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuMixerProvider.java @@ -0,0 +1,66 @@ +/* HaikuMixerProvider.java -- provides the Haiku mixer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import javax.sound.sampled.Mixer; +import javax.sound.sampled.spi.MixerProvider; + +/** + * Makes the Haiku media server available to javax.sound.sampled. It is + * listed in META-INF/services/javax.sound.sampled.spi.MixerProvider. + */ +public class HaikuMixerProvider + extends MixerProvider +{ + + private static HaikuMixer mixer; + + public Mixer.Info[] getMixerInfo() + { + return new Mixer.Info[] { HaikuMixer.INFO }; + } + + public synchronized Mixer getMixer(Mixer.Info info) + { + if (! HaikuMixer.INFO.equals(info)) + throw new IllegalArgumentException("not a Haiku mixer: " + info); + if (mixer == null) + mixer = new HaikuMixer(); + return mixer; + } +} diff --git a/gnu/javax/sound/sampled/haiku/HaikuSourceDataLine.java b/gnu/javax/sound/sampled/haiku/HaikuSourceDataLine.java new file mode 100644 index 0000000000..152cefab44 --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/HaikuSourceDataLine.java @@ -0,0 +1,205 @@ +/* HaikuSourceDataLine.java -- a SourceDataLine for the Haiku media server + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; + +/** + * A source data line. Written data goes into a native ring buffer that + * the player reads. + */ +final class HaikuSourceDataLine + extends HaikuLine + implements SourceDataLine +{ + + /** The ring buffer size in frames. */ + private int bufferFrames; + + /** Counts flush() calls, so a blocked write() can return. */ + private int flushes; + + HaikuSourceDataLine(HaikuMixer mixer) + { + super(mixer); + } + + public Info getLineInfo() + { + return new DataLine.Info(SourceDataLine.class, Pcm.FORMATS, + AudioSystem.NOT_SPECIFIED, + AudioSystem.NOT_SPECIFIED); + } + + public void open() + throws LineUnavailableException + { + open(new AudioFormat(44100, 16, 2, true, false)); + } + + public void open(AudioFormat fmt) + throws LineUnavailableException + { + open(fmt, AudioSystem.NOT_SPECIFIED); + } + + /** size is the buffer size in bytes; the default is half a second. */ + public void open(AudioFormat fmt, int size) + throws LineUnavailableException + { + if (isOpen()) + throw new IllegalStateException("the line is already open"); + openNative(fmt); + int fs = Pcm.frameSize(fmt); + float rate = fmt.getSampleRate() > 0 ? fmt.getSampleRate() : 44100; + int frames = size > 0 ? size / fs : (int) (rate / 2); + long h; + synchronized (this) + { + h = handle; + bufferFrames = Math.max(frames, 256); + } + // Make the ring buffer now: data can be written before start(). + HaikuAudio.startStream(h, bufferFrames); + HaikuAudio.pause(h); + fire(LineEvent.Type.OPEN); + } + + /** + * Writes whole frames. Blocks until all data is in the buffer, or the + * line is flushed or closed. + */ + public int write(byte[] buf, int offset, int length) + { + AudioFormat f = getFormat(); + int fs = Pcm.frameSize(f); + if (length % fs != 0) + throw new IllegalArgumentException("length is not a whole number of frames"); + short[] samples = Pcm.toShorts(f, buf, offset, length); + int channels = Pcm.channels(f); + int done = 0; + int flushesAtStart; + synchronized (this) + { + flushesAtStart = flushes; + } + while (done < samples.length) + { + long h; + synchronized (this) + { + if (handle == 0 || flushes != flushesAtStart) + break; + h = handle; + } + done += HaikuAudio.write(h, samples, done, samples.length - done, + 250); + } + return done / channels * fs; + } + + public void start() + { + synchronized (this) + { + if (handle == 0 || running) + return; + HaikuAudio.startStream(handle, bufferFrames); + running = true; + } + fire(LineEvent.Type.START); + } + + public void stop() + { + synchronized (this) + { + if (handle == 0 || ! running) + return; + HaikuAudio.pause(handle); + running = false; + } + fire(LineEvent.Type.STOP); + } + + /** Waits until all buffered data has played. */ + public void drain() + { + while (true) + { + long h; + synchronized (this) + { + if (handle == 0 || ! running) + return; + h = handle; + } + if (HaikuAudio.waitIdle(h, 250)) + return; + } + } + + public void flush() + { + synchronized (this) + { + if (handle == 0) + return; + HaikuAudio.flush(handle); + flushes++; + } + } + + public synchronized int available() + { + if (handle == 0) + return 0; + AudioFormat f = getFormat(); + return HaikuAudio.available(handle) / Pcm.channels(f) * Pcm.frameSize(f); + } + + public synchronized int getBufferSize() + { + return bufferFrames * Pcm.frameSize(getFormat()); + } +} diff --git a/gnu/javax/sound/sampled/haiku/Pcm.java b/gnu/javax/sound/sampled/haiku/Pcm.java new file mode 100644 index 0000000000..7b29df748d --- /dev/null +++ b/gnu/javax/sound/sampled/haiku/Pcm.java @@ -0,0 +1,174 @@ +/* Pcm.java -- audio format conversion for the Haiku sound lines + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.javax.sound.sampled.haiku; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +/** + * Converts the formats the Haiku lines accept to 16-bit signed PCM: + * signed and unsigned 8 and 16 bit PCM in both byte orders, mu-law and + * a-law, with one or two channels. + */ +final class Pcm +{ + + private static final short[] ULAW = new short[256]; + private static final short[] ALAW = new short[256]; + + static + { + for (int i = 0; i < 256; i++) + { + int u = ~i & 0xff; + int t = (((u & 0x0f) << 3) + 0x84) << ((u & 0x70) >> 4); + ULAW[i] = (short) ((u & 0x80) != 0 ? 0x84 - t : t - 0x84); + + int a = i ^ 0x55; + int v = (a & 0x0f) << 4; + int seg = (a & 0x70) >> 4; + if (seg == 0) + v += 8; + else + { + v += 0x108; + if (seg > 1) + v <<= seg - 1; + } + ALAW[i] = (short) ((a & 0x80) != 0 ? v : -v); + } + } + + /** All formats the lines accept, at any sample rate. */ + static final AudioFormat[] FORMATS = makeFormats(); + + private static AudioFormat[] makeFormats() + { + float any = AudioSystem.NOT_SPECIFIED; + java.util.ArrayList l = new java.util.ArrayList(); + for (int ch = 1; ch <= 2; ch++) + { + l.add(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, any, 16, ch, + 2 * ch, any, false)); + l.add(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, any, 16, ch, + 2 * ch, any, true)); + l.add(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, any, 8, ch, + ch, any, false)); + l.add(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, any, 8, ch, + ch, any, false)); + l.add(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, any, 16, ch, + 2 * ch, any, false)); + l.add(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, any, 16, ch, + 2 * ch, any, true)); + l.add(new AudioFormat(AudioFormat.Encoding.ULAW, any, 8, ch, ch, any, + false)); + l.add(new AudioFormat(AudioFormat.Encoding.ALAW, any, 8, ch, ch, any, + false)); + } + return l.toArray(new AudioFormat[l.size()]); + } + + private Pcm() + { + } + + /** True if the lines can play this format. */ + static boolean isSupported(AudioFormat f) + { + AudioFormat.Encoding e = f.getEncoding(); + int bits = f.getSampleSizeInBits(); + int ch = f.getChannels(); + if (ch != AudioSystem.NOT_SPECIFIED && (ch < 1 || ch > 2)) + return false; + if (e.equals(AudioFormat.Encoding.ULAW) + || e.equals(AudioFormat.Encoding.ALAW)) + return bits == 8 || bits == AudioSystem.NOT_SPECIFIED; + if (e.equals(AudioFormat.Encoding.PCM_SIGNED) + || e.equals(AudioFormat.Encoding.PCM_UNSIGNED)) + return bits == 8 || bits == 16 || bits == AudioSystem.NOT_SPECIFIED; + return false; + } + + /** Bytes per frame, with a sensible value if it is not specified. */ + static int frameSize(AudioFormat f) + { + if (f.getFrameSize() > 0) + return f.getFrameSize(); + int bits = f.getSampleSizeInBits() > 0 ? f.getSampleSizeInBits() : 16; + return (bits / 8) * channels(f); + } + + static int channels(AudioFormat f) + { + return f.getChannels() > 0 ? f.getChannels() : 1; + } + + /** Converts whole frames of data[off .. off + len) to 16-bit samples. */ + static short[] toShorts(AudioFormat f, byte[] data, int off, int len) + { + int fs = frameSize(f); + int ch = channels(f); + int frames = len / fs; + int bytesPerSample = fs / ch; + short[] out = new short[frames * ch]; + AudioFormat.Encoding e = f.getEncoding(); + boolean signed = e.equals(AudioFormat.Encoding.PCM_SIGNED); + boolean big = f.isBigEndian(); + int n = frames * ch; + if (e.equals(AudioFormat.Encoding.ULAW)) + for (int i = 0; i < n; i++) + out[i] = ULAW[data[off + i] & 0xff]; + else if (e.equals(AudioFormat.Encoding.ALAW)) + for (int i = 0; i < n; i++) + out[i] = ALAW[data[off + i] & 0xff]; + else if (bytesPerSample == 1) + for (int i = 0; i < n; i++) + out[i] = (short) (signed ? data[off + i] << 8 + : ((data[off + i] & 0xff) - 128) << 8); + else + for (int i = 0; i < n; i++) + { + int p = off + i * bytesPerSample; + int hi = big ? data[p] : data[p + 1]; + int lo = (big ? data[p + 1] : data[p]) & 0xff; + int v = (hi << 8) | lo; + out[i] = (short) (signed ? v : v ^ 0x8000); + } + return out; + } +} diff --git a/include/Makefile.am b/include/Makefile.am index 37be91df4f..a87eb5f191 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -122,6 +122,11 @@ gnu_java_awt_peer_qt_MainQtThread.h \ gnu_java_awt_peer_qt_QtFileDialogPeer.h \ gnu_java_awt_peer_qt_QtEmbeddedWindowPeer.h +HAIKUPEER_H_FILES = \ +gnu_java_awt_peer_haiku_HaikuNative.h \ +gnu_java_awt_peer_haiku_HaikuRaster.h \ +gnu_javax_sound_sampled_haiku_HaikuAudio.h + GCONF_PREFS_FILES = \ gnu_java_util_prefs_gconf_GConfNativePeer.h @@ -131,6 +136,7 @@ $(GST_PEER_H_FILES) \ $(XMLJ_H_FILES) \ $(GTKPEER_H_FILES) \ $(QTPEER_H_FILES) \ +$(HAIKUPEER_H_FILES) \ $(GCONF_PREFS_FILES) \ gnu_java_math_GMP.h \ gnu_java_net_VMPlainSocketImpl.h \ @@ -287,6 +293,12 @@ gnu_java_awt_peer_qt_QtCheckboxPeer.h: $(top_srcdir)/gnu/java/awt/peer/qt/QtChec $(JAVAH) -o $@ gnu.java.awt.peer.qt.QtCheckboxPeer gnu_java_awt_peer_qt_QtToolkit.h: $(top_srcdir)/gnu/java/awt/peer/qt/QtToolkit.java $(JAVAH) -o $@ gnu.java.awt.peer.qt.QtToolkit +gnu_java_awt_peer_haiku_HaikuNative.h: $(top_srcdir)/gnu/java/awt/peer/haiku/HaikuNative.java + $(JAVAH) -o $@ gnu.java.awt.peer.haiku.HaikuNative +gnu_java_awt_peer_haiku_HaikuRaster.h: $(top_srcdir)/gnu/java/awt/peer/haiku/HaikuRaster.java + $(JAVAH) -o $@ gnu.java.awt.peer.haiku.HaikuRaster +gnu_javax_sound_sampled_haiku_HaikuAudio.h: $(top_srcdir)/gnu/javax/sound/sampled/haiku/HaikuAudio.java + $(JAVAH) -o $@ gnu.javax.sound.sampled.haiku.HaikuAudio gnu_java_awt_peer_qt_QtAudioClip.h: $(top_srcdir)/gnu/java/awt/peer/qt/QtAudioClip.java $(JAVAH) -o $@ gnu.java.awt.peer.qt.QtAudioClip gnu_java_awt_peer_qt_QtScreenDevice.h: $(top_srcdir)/gnu/java/awt/peer/qt/QtScreenDevice.java diff --git a/native/jni/Makefile.am b/native/jni/Makefile.am index e8db33214a..ba39243944 100644 --- a/native/jni/Makefile.am +++ b/native/jni/Makefile.am @@ -20,6 +20,10 @@ if CREATE_QT_PEER_LIBRARIES CLASSPATH_QT_PEER_DIR = qt-peer endif +if CREATE_HAIKU_PEER_LIBRARIES + HAIKUDIR = haiku-peer +endif + if CREATE_GCONF_PEER_LIBRARIES CLASSPATH_GCONF_PEER_DIR = gconf-peer endif @@ -38,9 +42,10 @@ endif SUBDIRS = classpath $(JNIDIRS) \ $(ALSADIR) $(DSSIDIR) $(GTKDIR) $(CLASSPATH_QT_PEER_DIR) $(XMLJDIR) \ - $(CLASSPATH_GCONF_PEER_DIR) $(CLASSPATH_GSTREAMER_PEER_DIR) $(GMPDIR) + $(CLASSPATH_GCONF_PEER_DIR) $(CLASSPATH_GSTREAMER_PEER_DIR) $(GMPDIR) \ + $(HAIKUDIR) DIST_SUBDIRS = classpath java-io java-lang java-math java-net java-nio java-util \ - gtk-peer gconf-peer gstreamer-peer qt-peer xmlj midi-alsa \ + gtk-peer gconf-peer gstreamer-peer qt-peer haiku-peer xmlj midi-alsa \ midi-dssi native-lib all-local: diff --git a/native/jni/haiku-peer/Makefile.am b/native/jni/haiku-peer/Makefile.am new file mode 100644 index 0000000000..0ccf95bb2c --- /dev/null +++ b/native/jni/haiku-peer/Makefile.am @@ -0,0 +1,13 @@ +# Haiku AWT and sound peer for GNU Classpath (libhaikupeer). +# Java side: gnu/java/awt/peer/haiku and gnu/javax/sound/sampled/haiku. + +nativeexeclib_LTLIBRARIES = libhaikupeer.la + +libhaikupeer_la_SOURCES = gnu_java_awt_peer_haiku_HaikuNative.cpp \ + gnu_java_awt_peer_haiku_HaikuRaster.cpp + +libhaikupeer_la_LIBADD = -lbe -ltracker -lmedia -ltranslation + +AM_LDFLAGS = @CLASSPATH_MODULE@ +AM_CPPFLAGS = @CLASSPATH_INCLUDES@ +AM_CXXFLAGS = -Wall -Wno-unused-parameter diff --git a/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuNative.cpp b/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuNative.cpp new file mode 100644 index 0000000000..a3d2aead0a --- /dev/null +++ b/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuNative.cpp @@ -0,0 +1,3384 @@ +/* gnu_java_awt_peer_haiku_HaikuNative.cpp -- native side of the Haiku AWT peer + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +/* + * Design notes. + * + * All rendering happens in Java (gnu.java.awt.java2d.AbstractGraphics2D + * over an int[] backing image). The native side only: + * - runs one BApplication on its own native thread, + * - creates one BWindow + BView per AWT heavyweight window, + * - copies dirty rectangles of the Java backing image into a BBitmap + * that the BView draws in Draw(), + * - queues input/window events. + * + * Native threads never call into the JVM. Java polls the event queue + * with nextEvent(), which blocks for a short time and returns a packed + * int record. This keeps Haiku looper threads unknown to the VM's + * thread system and garbage collector. + */ + +#include + +#include "gnu_java_awt_peer_haiku_HaikuNative.h" +#include "gnu_javax_sound_sampled_haiku_HaikuAudio.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Event types. Keep in sync with HaikuNative.java. */ +enum { + EV_CLOSE = 1, /* window close box */ + EV_RESIZED = 2, /* a = client width, b = client height */ + EV_MOVED = 3, /* a = client x, b = client y (screen) */ + EV_MOUSE_DOWN = 4, /* a = x, b = y, c = buttons, d = modifiers, e = clicks */ + EV_MOUSE_UP = 5, /* a = x, b = y, c = buttons, d = modifiers */ + EV_MOUSE_MOVED = 6, /* a = x, b = y, c = buttons, d = modifiers */ + EV_MOUSE_ENTER = 7, /* a = x, b = y, c = buttons, d = modifiers */ + EV_MOUSE_EXIT = 8, /* a = x, b = y, c = buttons, d = modifiers */ + EV_KEY_DOWN = 9, /* a = raw char, b = unicode char, c = modifiers, d = key, e = repeat */ + EV_KEY_UP = 10, /* a = raw char, b = unicode char, c = modifiers, d = key */ + EV_ACTIVATED = 11, /* a = 1 active, 0 inactive */ + EV_WHEEL = 12, /* a = x, b = y, c = delta y (wheel clicks), d = modifiers */ + EV_APP_QUIT = 13, /* application quit requested (e.g. from Deskbar) */ + EV_MENU = 14, /* a = menu item id, b = modifiers */ + EV_CHOICE = 15, /* window id = choice id, a = selected index */ + EV_FILE_PANEL = 16, /* window id = dialog id, a = 1 if a file was chosen */ + EV_STATE = 17, /* a = 1 if minimized, b = 1 if zoomed */ + EV_EDIT = 18, /* a = EDIT_CUT, EDIT_COPY, EDIT_PASTE or EDIT_SELECT_ALL */ + EV_FILTER = 19, /* window id = dialog id; a file panel waits for filterAnswer() */ + EV_DRAG = 20, /* a = x, b = y, c = DRAG_ENTER/OVER/EXIT, d = modifiers */ + EV_DROP = 21, /* a = x, b = y, d = modifiers; data in message source SRC_DROP */ + EV_IM = 22 /* a = input method opcode, b = confirmed, c, d = selection */ +}; + +/* Transits of EV_DRAG. */ +enum { + DRAG_ENTER = 1, + DRAG_OVER = 2, + DRAG_EXIT = 3 +}; + +/* Message sources that Java can read: a copy of the clipboard, the + message of the drag over a window, and the last dropped message. */ +enum { + SRC_CLIPBOARD = 0, + SRC_DRAG = 1, + SRC_DROP = 2, + SRC_COUNT = 3 +}; + +static void setSource(int32 src, BMessage *msg); +static void imChanged(int32 id, const char *text, int32 len, + int32 selStart, int32 selEnd, bool confirmed); + +/* Commands of EV_EDIT: the Command-X/C/V/A shortcuts of a BWindow. */ +enum { + EDIT_CUT = 1, + EDIT_COPY = 2, + EDIT_PASTE = 3, + EDIT_SELECT_ALL = 4 +}; + +/* Message codes used by menus and file panels. */ +enum { + MSG_MENU = 'jmnu', + MSG_CHOICE = 'jcho', + MSG_FILE = 'jfil' +}; + +#define EV_INTS 8 + +struct PeerEvent +{ + int32 v[EV_INTS]; /* type, window id, a, b, c, d, e, f */ +}; + +static pthread_mutex_t gQueueLock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t gQueueCond = PTHREAD_COND_INITIALIZER; +static std::deque gQueue; + +static void +postEvent(int32 type, int32 win, int32 a = 0, int32 b = 0, int32 c = 0, + int32 d = 0, int32 e = 0, int32 f = 0) +{ + PeerEvent ev; + ev.v[0] = type; ev.v[1] = win; ev.v[2] = a; ev.v[3] = b; + ev.v[4] = c; ev.v[5] = d; ev.v[6] = e; ev.v[7] = f; + pthread_mutex_lock(&gQueueLock); + /* Coalesce consecutive motion and resize events for the same window. */ + if (!gQueue.empty()) + { + PeerEvent &last = gQueue.back(); + if (last.v[0] == type && last.v[1] == win + && (type == EV_MOUSE_MOVED || type == EV_RESIZED + || type == EV_MOVED)) + { + last = ev; + pthread_mutex_unlock(&gQueueLock); + return; + } + } + gQueue.push_back(ev); + pthread_cond_signal(&gQueueCond); + pthread_mutex_unlock(&gQueueLock); +} + +/* An absolute time timeoutMs milliseconds from now, for + pthread_cond_timedwait. */ +static void +deadlineIn(struct timespec *until, int32 timeoutMs) +{ + struct timeval now; + gettimeofday(&now, NULL); + long long ns = (long long) now.tv_usec * 1000 + + (long long) timeoutMs * 1000000; + until->tv_sec = now.tv_sec + ns / 1000000000; + until->tv_nsec = ns % 1000000000; +} + +/* ------------------------------------------------------------------ */ + +/* File panel results, keyed by dialog id. Guarded by gQueueLock. */ +static std::map gFileResults; + +static void +setFileResult(int32 id, const char *path) +{ + pthread_mutex_lock(&gQueueLock); + gFileResults[id] = path; + pthread_mutex_unlock(&gQueueLock); +} + +class PeerApp : public BApplication +{ +public: + PeerApp(sem_id ready) + : BApplication("application/x-vnd.gnu-classpath-awt"), fReady(ready) + { + } + + virtual void ReadyToRun() + { + release_sem(fReady); + } + + virtual bool QuitRequested() + { + /* Let Java decide (it posts WINDOW_CLOSING to every window). */ + postEvent(EV_APP_QUIT, 0); + return false; + } + + virtual void MessageReceived(BMessage *msg) + { + int32 id = 0, index = 0; + switch (msg->what) + { + case MSG_MENU: + if (msg->FindInt32("jid", &id) == B_OK) + postEvent(EV_MENU, 0, id, modifiers()); + return; + case MSG_CHOICE: + if (msg->FindInt32("jid", &id) == B_OK + && msg->FindInt32("jindex", &index) == B_OK) + postEvent(EV_CHOICE, id, index); + return; + case MSG_FILE: + { + /* Open panel: "refs"; save panel: "directory" + "name". */ + msg->FindInt32("jid", &id); + entry_ref ref; + const char *name = NULL; + if (msg->FindRef("refs", &ref) == B_OK) + { + BPath path(&ref); + if (path.InitCheck() == B_OK) + setFileResult(id, path.Path()); + } + else if (msg->FindRef("directory", &ref) == B_OK + && msg->FindString("name", &name) == B_OK) + { + BPath path(&ref); + if (path.InitCheck() == B_OK && path.Append(name) == B_OK) + setFileResult(id, path.Path()); + } + return; + } + case B_CANCEL: + { + /* Sent when a file panel closes, after a selection or not. */ + int32 oldWhat = 0; + if (msg->FindInt32("old_what", &oldWhat) == B_OK + && oldWhat == MSG_FILE) + { + void *source = NULL; + msg->FindPointer("source", &source); + if (msg->FindInt32("jid", &id) != B_OK) + id = -1; + PanelClosed((BFilePanel *) source, id); + } + return; + } + } + BApplication::MessageReceived(msg); + } + + void PanelClosed(BFilePanel *panel, int32 id); + +private: + sem_id fReady; +}; + +/* Open file panels and the dialog id each belongs to. */ +static pthread_mutex_t gPanelLock = PTHREAD_MUTEX_INITIALIZER; +static std::map gPanels; + +/* ------------------------------------------------------------------ */ +/* FilenameFilter support. + * + * A BRefFilter runs in the file panel's thread and cannot call Java. For + * each directory it asks Java once: it queues the directory, posts + * EV_FILTER and waits. Java runs the FilenameFilter on every name in the + * directory and sends the answers with filterAnswer(). + */ + +typedef std::map FilterNames; +typedef std::map FilterDirs; + +static pthread_mutex_t gFilterLock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t gFilterCond = PTHREAD_COND_INITIALIZER; +static std::map gFilterCache; +static std::map > gFilterRequests; +static std::map > gFilterPending; + +/* Forgets the answers of a dialog and releases a waiting panel. */ +static void +filterForget(int32 id) +{ + pthread_mutex_lock(&gFilterLock); + gFilterCache.erase(id); + gFilterRequests.erase(id); + gFilterPending.erase(id); + pthread_cond_broadcast(&gFilterCond); + pthread_mutex_unlock(&gFilterLock); +} + +static bool +filterLookup(int32 id, const std::string &dir, const std::string &name) +{ + struct timespec until; + deadlineIn(&until, 3000); + bool requested = false; + pthread_mutex_lock(&gFilterLock); + while (true) + { + FilterDirs &dirs = gFilterCache[id]; + FilterDirs::iterator d = dirs.find(dir); + if (d != dirs.end()) + { + FilterNames::iterator n = d->second.find(name); + if (n != d->second.end()) + { + bool accept = n->second; + pthread_mutex_unlock(&gFilterLock); + return accept; + } + if (requested) + break; + /* The file is newer than the answer: ask again. */ + dirs.erase(d); + } + if (!requested) + { + if (gFilterPending[id].insert(dir).second) + { + gFilterRequests[id].push_back(dir); + postEvent(EV_FILTER, id); + } + requested = true; + } + if (pthread_cond_timedwait(&gFilterCond, &gFilterLock, &until) + == ETIMEDOUT) + break; + } + pthread_mutex_unlock(&gFilterLock); + return true; +} + +class JRefFilter : public BRefFilter +{ +public: + JRefFilter(int32 id) : fId(id) {} + + virtual bool Filter(const entry_ref *ref, BNode *, struct stat_beos *, + const char *mimeType) + { + /* Always show folders, volumes and links, so the user can move + around. */ + BEntry entry(ref); + if (entry.IsDirectory() || entry.IsSymLink()) + return true; + if (mimeType != NULL + && (strcmp(mimeType, "application/x-vnd.Be-directory") == 0 + || strcmp(mimeType, "application/x-vnd.Be-volume") == 0 + || strcmp(mimeType, "application/x-vnd.Be-symlink") == 0)) + return true; + BPath path(ref); + BPath parent; + if (path.InitCheck() != B_OK || path.GetParent(&parent) != B_OK) + return true; + return filterLookup(fId, parent.Path(), path.Leaf()); + } + +private: + int32 fId; +}; + +void +PeerApp::PanelClosed(BFilePanel *panel, int32 id) +{ + bool known = false; + pthread_mutex_lock(&gPanelLock); + std::map::iterator it = gPanels.find(panel); + if (it != gPanels.end()) + { + if (id < 0) + id = it->second; + gPanels.erase(it); + known = true; + } + pthread_mutex_unlock(&gPanelLock); + if (id < 0) + return; + bool chosen; + pthread_mutex_lock(&gQueueLock); + chosen = gFileResults.find(id) != gFileResults.end(); + pthread_mutex_unlock(&gQueueLock); + filterForget(id); + postEvent(EV_FILE_PANEL, id, chosen ? 1 : 0); + if (known) + { + BRefFilter *filter = panel->RefFilter(); + delete panel; + delete filter; + } +} + +static void +currentMouseState(BView *view, int32 *buttons, int32 *clicks) +{ + *buttons = 0; + *clicks = 1; + BMessage *msg = view->Window()->CurrentMessage(); + if (msg != NULL) + { + msg->FindInt32("buttons", buttons); + msg->FindInt32("clicks", clicks); + } +} + +class PeerView : public BView +{ +public: + PeerView(BRect frame, int32 id) + : BView(frame, "awt", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS), + fId(id), fBitmap(NULL), fButtons(0), fDragInside(false) + { + /* We repaint every pixel from the bitmap; no app_server erase. */ + SetViewColor(B_TRANSPARENT_COLOR); + SetLowColor(220, 220, 220); + } + + virtual ~PeerView() + { + delete fBitmap; + } + + /* Called with the window locked. */ + void EnsureBitmap(int32 w, int32 h) + { + if (fBitmap != NULL) + { + BRect b = fBitmap->Bounds(); + if (b.IntegerWidth() + 1 == w && b.IntegerHeight() + 1 == h) + return; + delete fBitmap; + fBitmap = NULL; + } + if (w > 0 && h > 0) + fBitmap = new BBitmap(BRect(0, 0, w - 1, h - 1), B_RGB32); + } + + BBitmap *Bitmap() { return fBitmap; } + + virtual void Draw(BRect update) + { + if (fBitmap != NULL) + { + BRect b = fBitmap->Bounds(); + BRect r = update & b; + if (r.IsValid()) + DrawBitmap(fBitmap, r, r); + /* Fill area not covered by the bitmap (window grew before Java + repainted). */ + if (update.right > b.right) + FillRect(BRect(b.right + 1, update.top, update.right, + update.bottom), B_SOLID_LOW); + if (update.bottom > b.bottom) + FillRect(BRect(update.left, b.bottom + 1, + update.right < b.right ? update.right : b.right, + update.bottom), B_SOLID_LOW); + } + else + FillRect(update, B_SOLID_LOW); + } + + virtual void MouseDown(BPoint where) + { + int32 buttons, clicks; + currentMouseState(this, &buttons, &clicks); + fButtons = buttons; + MakeFocus(true); + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + postEvent(EV_MOUSE_DOWN, fId, (int32) where.x, (int32) where.y, + buttons, modifiers(), clicks); + } + + virtual void MouseUp(BPoint where) + { + int32 buttons, clicks; + currentMouseState(this, &buttons, &clicks); + /* Report the buttons that were released. */ + int32 released = fButtons & ~buttons; + fButtons = buttons; + postEvent(EV_MOUSE_UP, fId, (int32) where.x, (int32) where.y, + released, modifiers()); + } + + virtual void MouseMoved(BPoint where, uint32 transit, const BMessage *drag) + { + if (drag != NULL) + { + /* A drag (from any application) is over this window. */ + int32 t = DRAG_OVER; + if (transit == B_ENTERED_VIEW) + { + setSource(SRC_DRAG, new BMessage(*drag)); + t = DRAG_ENTER; + } + else if (transit == B_EXITED_VIEW || transit == B_OUTSIDE_VIEW) + t = DRAG_EXIT; + if (!fDragInside && t == DRAG_OVER) + { + setSource(SRC_DRAG, new BMessage(*drag)); + t = DRAG_ENTER; + } + fDragInside = t != DRAG_EXIT; + postEvent(EV_DRAG, fId, (int32) where.x, (int32) where.y, t, + modifiers()); + return; + } + if (fDragInside) + { + fDragInside = false; + postEvent(EV_DRAG, fId, (int32) where.x, (int32) where.y, DRAG_EXIT, + modifiers()); + } + int32 buttons, clicks; + currentMouseState(this, &buttons, &clicks); + int32 type = EV_MOUSE_MOVED; + if (transit == B_ENTERED_VIEW) + type = EV_MOUSE_ENTER; + else if (transit == B_EXITED_VIEW) + type = EV_MOUSE_EXIT; + postEvent(type, fId, (int32) where.x, (int32) where.y, buttons, + modifiers()); + if (type != EV_MOUSE_MOVED) + postEvent(EV_MOUSE_MOVED, fId, (int32) where.x, (int32) where.y, + buttons, modifiers()); + } + + virtual void KeyDown(const char *bytes, int32 numBytes) + { + PostKey(EV_KEY_DOWN, bytes, numBytes); + } + + virtual void KeyUp(const char *bytes, int32 numBytes) + { + PostKey(EV_KEY_UP, bytes, numBytes); + } + + virtual void MessageReceived(BMessage *msg) + { + if (msg->WasDropped()) + { + BPoint p = msg->DropPoint(); + ConvertFromScreen(&p); + setSource(SRC_DROP, new BMessage(*msg)); + fDragInside = false; + postEvent(EV_DROP, fId, (int32) p.x, (int32) p.y, 0, modifiers()); + return; + } + switch (msg->what) + { + case B_INPUT_METHOD_EVENT: + InputMethodEvent(msg); + return; + case B_CUT: + postEvent(EV_EDIT, fId, EDIT_CUT); + return; + case B_COPY: + postEvent(EV_EDIT, fId, EDIT_COPY); + return; + case B_PASTE: + postEvent(EV_EDIT, fId, EDIT_PASTE); + return; + case B_SELECT_ALL: + postEvent(EV_EDIT, fId, EDIT_SELECT_ALL); + return; + } + if (msg->what == B_MOUSE_WHEEL_CHANGED) + { + float dy = 0; + msg->FindFloat("be:wheel_delta_y", &dy); + BPoint where; + uint32 b; + GetMouse(&where, &b, false); + postEvent(EV_WHEEL, fId, (int32) where.x, (int32) where.y, + (int32) dy, modifiers()); + return; + } + BView::MessageReceived(msg); + } + +private: + void InputMethodEvent(BMessage *msg) + { + int32 opcode = -1; + msg->FindInt32("be:opcode", &opcode); + switch (opcode) + { + case B_INPUT_METHOD_STARTED: + { + BMessenger reply; + if (msg->FindMessenger("be:reply_to", &reply) == B_OK) + fIMReply = reply; + postEvent(EV_IM, fId, opcode); + break; + } + case B_INPUT_METHOD_CHANGED: + { + const char *text = ""; + bool confirmed = false; + int32 selStart = 0, selEnd = 0; + msg->FindString("be:string", &text); + msg->FindBool("be:confirmed", &confirmed); + msg->FindInt32("be:selection", 0, &selStart); + msg->FindInt32("be:selection", 1, &selEnd); + imChanged(fId, text, strlen(text), selStart, selEnd, confirmed); + break; + } + case B_INPUT_METHOD_LOCATION_REQUEST: + case B_INPUT_METHOD_STOPPED: + postEvent(EV_IM, fId, opcode); + break; + } + } + + void PostKey(int32 type, const char *bytes, int32 numBytes) + { + BMessage *msg = Window()->CurrentMessage(); + int32 key = 0, mods = 0, rawChar = 0, repeat = 0; + if (msg != NULL) + { + msg->FindInt32("key", &key); + msg->FindInt32("modifiers", &mods); + msg->FindInt32("raw_char", &rawChar); + msg->FindInt32("be:key_repeat", &repeat); + } + postEvent(type, fId, rawChar, decodeUTF8(bytes, numBytes), mods, key, + repeat); + } + + static int32 decodeUTF8(const char *s, int32 n) + { + const unsigned char *u = (const unsigned char *) s; + if (n <= 0) + return 0; + if (u[0] < 0x80) + return u[0]; + if ((u[0] & 0xE0) == 0xC0 && n >= 2) + return ((u[0] & 0x1F) << 6) | (u[1] & 0x3F); + if ((u[0] & 0xF0) == 0xE0 && n >= 3) + return ((u[0] & 0x0F) << 12) | ((u[1] & 0x3F) << 6) | (u[2] & 0x3F); + if ((u[0] & 0xF8) == 0xF0 && n >= 4) + return ((u[0] & 0x07) << 18) | ((u[1] & 0x3F) << 12) + | ((u[2] & 0x3F) << 6) | (u[3] & 0x3F); + return 0; + } + +public: + /* The input method that talks to this view. */ + BMessenger fIMReply; + +private: + int32 fId; + BBitmap *fBitmap; + int32 fButtons; + bool fDragInside; +}; + +class PeerWindow : public BWindow +{ +public: + PeerWindow(BRect frame, const char *title, window_look look, + window_feel feel, uint32 flags, int32 id) + : BWindow(frame, title, look, feel, flags), fShown(false), fId(id), + fMenuBar(NULL), fMenuHeight(0), fZoomed(false) + { + fView = new PeerView(Bounds(), id); + AddChild(fView); + fView->MakeFocus(true); + } + + virtual bool QuitRequested() + { + postEvent(EV_CLOSE, fId); + return false; + } + + /* Events and bounds use the content area: the window frame minus the + menu bar at its top. */ + virtual void FrameResized(float w, float h) + { + postEvent(EV_RESIZED, fId, (int32) w + 1, + (int32) h + 1 - fMenuHeight); + /* A zoomed window that the user resizes is no longer zoomed. */ + if (fZoomed && (fabsf(w - fZoomedFrame.Width()) > 1 + || fabsf(h - fZoomedFrame.Height()) > 1)) + { + fZoomed = false; + PostState(); + } + } + + virtual void Minimize(bool minimize) + { + BWindow::Minimize(minimize); + PostState(); + } + + /* The zoom button and Zoom() toggle between the zoomed frame and the + frame the window had before. */ + virtual void Zoom(BPoint origin, float w, float h) + { + if (fZoomed) + { + fZoomed = false; + BWindow::Zoom(fNormalFrame.LeftTop(), fNormalFrame.Width(), + fNormalFrame.Height()); + } + else + { + fNormalFrame = Frame(); + fZoomedFrame = BRect(origin.x, origin.y, origin.x + w, + origin.y + h); + fZoomed = true; + BWindow::Zoom(origin, w, h); + } + PostState(); + } + + using BWindow::Zoom; + + bool IsZoomed() { return fZoomed; } + + void PostState() + { + postEvent(EV_STATE, fId, IsMinimized() ? 1 : 0, fZoomed ? 1 : 0); + } + + virtual void FrameMoved(BPoint p) + { + postEvent(EV_MOVED, fId, (int32) p.x, (int32) p.y + fMenuHeight); + } + + /* Called with the window locked. The window frame stays the same; the + content view shrinks or grows. */ + void SetJMenuBar(BMenuBar *bar) + { + if (fMenuBar == bar) + return; + if (fMenuBar != NULL) + RemoveChild(fMenuBar); + fMenuBar = bar; + fMenuHeight = 0; + if (bar != NULL) + { + AddChild(bar); + float pw, ph; + bar->GetPreferredSize(&pw, &ph); + bar->MoveTo(0, 0); + bar->ResizeTo(Bounds().Width(), ph); + fMenuHeight = (int32) ph + 1; + } + BRect b = Bounds(); + fView->MoveTo(0, fMenuHeight); + fView->ResizeTo(b.Width(), b.Height() - fMenuHeight); + } + + int32 MenuHeight() { return fMenuHeight; } + BMenuBar *JMenuBar() { return fMenuBar; } + + virtual void WindowActivated(bool active) + { + postEvent(EV_ACTIVATED, fId, active ? 1 : 0); + } + + /* Input method events sent to the window go to the view. */ + virtual void MessageReceived(BMessage *msg) + { + if (msg->what == B_INPUT_METHOD_EVENT) + { + fView->MessageReceived(msg); + return; + } + BWindow::MessageReceived(msg); + } + + PeerView *View() { return fView; } + bool fShown; + +private: + int32 fId; + PeerView *fView; + BMenuBar *fMenuBar; + int32 fMenuHeight; + bool fZoomed; + BRect fNormalFrame; + BRect fZoomedFrame; +}; + +static void releaseMenuBar(BMenuBar *bar); + +/* ------------------------------------------------------------------ */ + +static sem_id gAppReady = -1; +static thread_id gAppThread = -1; + +static int32 +appThread(void *) +{ + PeerApp *app = new PeerApp(gAppReady); + app->Run(); + delete app; + return 0; +} + +static PeerWindow * +win(jlong handle) +{ + return (PeerWindow *) (intptr_t) handle; +} + +static char * +utf8(JNIEnv *env, jstring s) +{ + if (s == NULL) + return strdup(""); + const char *c = env->GetStringUTFChars(s, NULL); + char *r = strdup(c != NULL ? c : ""); + if (c != NULL) + env->ReleaseStringUTFChars(s, c); + return r; +} + +/* Starts the BApplication thread once. AWT and sound share it. */ +static pthread_mutex_t gInitLock = PTHREAD_MUTEX_INITIALIZER; + +static bool +startApp() +{ + pthread_mutex_lock(&gInitLock); + bool ok = true; + if (be_app == NULL) + { + gAppReady = create_sem(0, "awt app ready"); + gAppThread = spawn_thread(appThread, "AWT BApplication", + B_DISPLAY_PRIORITY, NULL); + if (gAppThread < 0) + ok = false; + else + { + resume_thread(gAppThread); + status_t st; + do + st = acquire_sem_etc(gAppReady, 1, B_RELATIVE_TIMEOUT, 10000000); + while (st == B_INTERRUPTED); + ok = st == B_OK; + } + } + pthread_mutex_unlock(&gInitLock); + return ok; +} + +extern "C" { + +JNIEXPORT jboolean JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_init(JNIEnv *, jclass) +{ + return startApp() ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_screenInfo(JNIEnv *env, jclass, + jintArray out) +{ + BScreen screen(B_MAIN_SCREEN_ID); + BRect f = screen.Frame(); + jint v[3]; + v[0] = f.IntegerWidth() + 1; + v[1] = f.IntegerHeight() + 1; + v[2] = 72; + monitor_info info; + if (screen.GetMonitorInfo(&info) == B_OK && info.width > 0) + v[2] = (jint) ((f.Width() + 1) / (info.width / 2.54)); + if (v[2] < 72) + v[2] = 72; + env->SetIntArrayRegion(out, 0, 3, v); +} + +/* kind: 0 = frame, 1 = dialog, 2 = undecorated window */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_createWindow(JNIEnv *env, jclass, + jint id, jint kind, + jstring title, + jint x, jint y, + jint w, jint h) +{ + char *t = utf8(env, title); + window_look look = B_TITLED_WINDOW_LOOK; + window_feel feel = B_NORMAL_WINDOW_FEEL; + uint32 flags = B_ASYNCHRONOUS_CONTROLS; + if (kind == 1) + look = B_TITLED_WINDOW_LOOK; + else if (kind == 2) + { + look = B_NO_BORDER_WINDOW_LOOK; + flags |= B_AVOID_FOCUS; + } + if (w < 1) w = 1; + if (h < 1) h = 1; + PeerWindow *pw = new PeerWindow(BRect(x, y, x + w - 1, y + h - 1), t, + look, feel, flags, id); + free(t); + return (jlong) (intptr_t) pw; +} + +/* out = left, top, right, bottom decorator insets. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_getInsets(JNIEnv *env, jclass, + jlong h, jintArray out) +{ + jint v[4] = { 0, 0, 0, 0 }; + PeerWindow *pw = win(h); + if (pw->Lock()) + { + BRect f = pw->Frame(); + BRect d = pw->DecoratorFrame(); + if (d.IsValid() && d != f) + { + v[0] = (jint) (f.left - d.left); + v[1] = (jint) (f.top - d.top); + v[2] = (jint) (d.right - f.right); + v[3] = (jint) (d.bottom - f.bottom); + } + v[1] += pw->MenuHeight(); + pw->Unlock(); + } + env->SetIntArrayRegion(out, 0, 4, v); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setTitle(JNIEnv *env, jclass, + jlong h, jstring title) +{ + char *t = utf8(env, title); + PeerWindow *pw = win(h); + if (pw->Lock()) + { + pw->SetTitle(t); + pw->Unlock(); + } + free(t); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setVisible(JNIEnv *, jclass, + jlong h, jboolean v) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + if (v && !pw->fShown) + { + pw->Show(); + pw->fShown = true; + } + else if (!v && pw->fShown) + { + pw->Hide(); + pw->fShown = false; + } + pw->Unlock(); + } +} + +/* x, y, w, h describe the client area in screen coordinates. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setBounds(JNIEnv *, jclass, + jlong h, jint x, jint y, + jint w, jint hh) +{ + PeerWindow *pw = win(h); + if (w < 1) w = 1; + if (hh < 1) hh = 1; + if (pw->Lock()) + { + y -= pw->MenuHeight(); + hh += pw->MenuHeight(); + BRect f = pw->Frame(); + if ((jint) f.left != x || (jint) f.top != y) + pw->MoveTo(x, y); + if (f.IntegerWidth() + 1 != w || f.IntegerHeight() + 1 != hh) + pw->ResizeTo(w - 1, hh - 1); + pw->Unlock(); + } +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setResizable(JNIEnv *, jclass, + jlong h, jboolean r) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + uint32 flags = pw->Flags(); + if (r) + flags &= ~(B_NOT_RESIZABLE | B_NOT_ZOOMABLE); + else + flags |= B_NOT_RESIZABLE | B_NOT_ZOOMABLE; + pw->SetFlags(flags); + pw->Unlock(); + } +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_toFront(JNIEnv *, jclass, jlong h) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + pw->Activate(true); + pw->Unlock(); + } +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_toBack(JNIEnv *, jclass, jlong h) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + pw->SendBehind(NULL); + pw->Unlock(); + } +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_dispose(JNIEnv *, jclass, jlong h) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + /* Menus belong to their Java peers; keep them out of the window's + destructor. */ + BMenuBar *bar = pw->JMenuBar(); + pw->SetJMenuBar(NULL); + pw->Quit(); + if (bar != NULL) + releaseMenuBar(bar); + } +} + +/* Copy the rectangle (x, y, w, h) of the Java image pixels (imgW x imgH, + 0x00RRGGBB, scanline imgW) into the window bitmap and redraw it. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_blit(JNIEnv *env, jclass, jlong h, + jintArray pixels, jint imgW, + jint imgH, jint x, jint y, + jint w, jint hh) +{ + PeerWindow *pw = win(h); + if (x < 0) { w += x; x = 0; } + if (y < 0) { hh += y; y = 0; } + if (x + w > imgW) w = imgW - x; + if (y + hh > imgH) hh = imgH - y; + if (w <= 0 || hh <= 0) + return; + if (!pw->Lock()) + return; + PeerView *view = pw->View(); + BBitmap *old = view->Bitmap(); + view->EnsureBitmap(imgW, imgH); + BBitmap *bmp = view->Bitmap(); + if (bmp != old) + { + /* New bitmap: copy everything, not just the dirty area. */ + x = 0; y = 0; w = imgW; hh = imgH; + } + if (bmp != NULL) + { + jint *src = (jint *) env->GetPrimitiveArrayCritical(pixels, NULL); + if (src != NULL) + { + uint8 *bits = (uint8 *) bmp->Bits(); + int32 bpr = bmp->BytesPerRow(); + for (jint row = 0; row < hh; row++) + { + uint32 *dst = (uint32 *) (bits + (y + row) * bpr) + x; + const jint *s = src + (y + row) * imgW + x; + /* Java 0x00RRGGBB == B_RGB32 (B, G, R, A in memory). */ + for (jint col = 0; col < w; col++) + dst[col] = (uint32) s[col] | 0xFF000000; + } + env->ReleasePrimitiveArrayCritical(pixels, src, JNI_ABORT); + } + view->Invalidate(BRect(x, y, x + w - 1, y + hh - 1)); + } + pw->Unlock(); +} + +/* Wait up to timeoutMs for an event; returns the number of ints written to + out (0 on timeout). */ +JNIEXPORT jint JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_nextEvent(JNIEnv *env, jclass, + jintArray out, + jint timeoutMs) +{ + PeerEvent ev; + bool have = false; + pthread_mutex_lock(&gQueueLock); + if (gQueue.empty() && timeoutMs > 0) + { + struct timespec until; + deadlineIn(&until, timeoutMs); + while (gQueue.empty()) + { + int rc = pthread_cond_timedwait(&gQueueCond, &gQueueLock, &until); + if (rc == ETIMEDOUT) + break; + } + } + if (!gQueue.empty()) + { + ev = gQueue.front(); + gQueue.pop_front(); + have = true; + } + pthread_mutex_unlock(&gQueueLock); + if (!have) + return 0; + env->SetIntArrayRegion(out, 0, EV_INTS, (jint *) ev.v); + return EV_INTS; +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_beep(JNIEnv *, jclass) +{ + beep(); +} + +} /* extern "C" */ + +/* ------------------------------------------------------------------ */ +/* Menus. + * + * Every AWT menu component peer owns one native object: a BMenuBar, a + * BMenu, a BPopUpMenu or a BMenuItem. Selections are sent to be_app as + * MSG_MENU with the Java item id and come back to Java as EV_MENU. + * + * Menu objects are owned by Java. When a peer is disposed while its + * object is still in a parent menu, it is only marked; it is deleted when + * it is removed from the parent. A submenu cannot be separated from the + * item that holds it, so that small item is not deleted while the Java + * submenu is alive. + */ + +static pthread_mutex_t gMenuLock = PTHREAD_MUTEX_INITIALIZER; +static std::map gDisposed; /* Java objects disposed while attached */ + +/* Locks the window (or open menu window) that shows a menu tree. */ +class MenuLocker +{ +public: + MenuLocker(BMenu *m) : fRoot(NULL) + { + if (m == NULL) + return; + BMenu *root = m; + while (root->Supermenu() != NULL) + root = root->Supermenu(); + if (root->LockLooper()) + fRoot = root; + } + ~MenuLocker() + { + if (fRoot != NULL) + fRoot->UnlockLooper(); + } +private: + BMenu *fRoot; +}; + +static bool +takeDisposed(void *p) +{ + pthread_mutex_lock(&gMenuLock); + std::map::iterator it = gDisposed.find(p); + bool was = it != gDisposed.end(); + if (was) + gDisposed.erase(it); + pthread_mutex_unlock(&gMenuLock); + return was; +} + +static void +markDisposed(void *p) +{ + pthread_mutex_lock(&gMenuLock); + gDisposed[p] = true; + pthread_mutex_unlock(&gMenuLock); +} + +static void detachAll(BMenu *m); + +/* An item was taken out of its menu: delete what Java no longer owns. */ +static void +releaseItem(BMenuItem *item) +{ + BMenu *sub = item->Submenu(); + if (sub != NULL) + { + /* The holder item deletes its submenu, so only delete both when the + Java submenu was disposed. */ + if (takeDisposed(sub)) + { + detachAll(sub); + delete item; + } + return; + } + /* Separators made by menuAddSeparator have no Java owner. Those made + for an AWT "-" item are JSeparatorItems, which carry a message. */ + if (dynamic_cast(item) != NULL && item->Message() == NULL) + { + delete item; + return; + } + if (takeDisposed(item)) + delete item; +} + +/* Removes every item from m and releases it. */ +static void +detachAll(BMenu *m) +{ + BMenuItem *item; + while ((item = m->RemoveItem((int32) 0)) != NULL) + releaseItem(item); +} + +/* A menu bar was taken out of its window: delete it if Java disposed it. */ +static void +releaseMenuBar(BMenuBar *bar) +{ + if (takeDisposed(bar)) + { + detachAll(bar); + delete bar; + } +} + +static BMenu *menuOf(jlong h) { return (BMenu *) (intptr_t) h; } +static BMenuItem *itemOf(jlong h) { return (BMenuItem *) (intptr_t) h; } + +/* Java-made separators: a BSeparatorItem with a marker message. */ +class JSeparatorItem : public BSeparatorItem +{ +public: + JSeparatorItem() { SetMessage(new BMessage('jsep')); } +}; + +extern "C" { + +/* kind: 0 = menu bar, 1 = menu, 2 = popup menu */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuCreate(JNIEnv *env, jclass, + jint kind, jstring label) +{ + char *l = utf8(env, label); + BMenu *m; + if (kind == 0) + m = new BMenuBar(BRect(0, 0, 100, 20), "menubar"); + else if (kind == 2) + m = new BPopUpMenu(l, false, false); + else + m = new BMenu(l); + free(l); + return (jlong) (intptr_t) m; +} + +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuItemCreate(JNIEnv *env, jclass, + jint id, jstring label, + jint shortcut, + jint mods, + jboolean separator) +{ + if (separator) + return (jlong) (intptr_t) new JSeparatorItem(); + char *l = utf8(env, label); + BMessage *msg = new BMessage(MSG_MENU); + msg->AddInt32("jid", id); + BMenuItem *item = new BMenuItem(l, msg, (char) shortcut, (uint32) mods); + item->SetTarget(be_app); + free(l); + return (jlong) (intptr_t) item; +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuAddItem(JNIEnv *, jclass, + jlong menu, jlong item, + jint index) +{ + BMenu *m = menuOf(menu); + BMenuItem *i = itemOf(item); + MenuLocker lock(m); + if (i->Menu() != NULL) + return; + if (index < 0 || index > m->CountItems()) + m->AddItem(i); + else + m->AddItem(i, index); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuAddSubmenu(JNIEnv *, jclass, + jlong menu, jlong sub, + jint index) +{ + BMenu *m = menuOf(menu); + BMenu *s = menuOf(sub); + MenuLocker lock(m); + if (s->Superitem() != NULL) + return; + if (index < 0 || index > m->CountItems()) + m->AddItem(s); + else + m->AddItem(s, index); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuAddSeparator(JNIEnv *, jclass, + jlong menu) +{ + BMenu *m = menuOf(menu); + MenuLocker lock(m); + m->AddSeparatorItem(); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuRemoveItem(JNIEnv *, jclass, + jlong menu, + jint index) +{ + BMenu *m = menuOf(menu); + MenuLocker lock(m); + BMenuItem *item = m->RemoveItem(index); + if (item != NULL) + releaseItem(item); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuItemSetLabel(JNIEnv *env, jclass, + jlong item, + jstring label) +{ + BMenuItem *i = itemOf(item); + char *l = utf8(env, label); + MenuLocker lock(i->Menu()); + i->SetLabel(l); + free(l); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuItemSetEnabled(JNIEnv *, jclass, + jlong item, + jboolean e) +{ + BMenuItem *i = itemOf(item); + MenuLocker lock(i->Menu()); + i->SetEnabled(e); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuItemSetMarked(JNIEnv *, jclass, + jlong item, + jboolean mark) +{ + BMenuItem *i = itemOf(item); + MenuLocker lock(i->Menu()); + i->SetMarked(mark); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuSetLabel(JNIEnv *env, jclass, + jlong menu, + jstring label) +{ + BMenu *m = menuOf(menu); + char *l = utf8(env, label); + MenuLocker lock(m); + if (m->Superitem() != NULL) + m->Superitem()->SetLabel(l); + else + m->SetName(l); + free(l); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuSetEnabled(JNIEnv *, jclass, + jlong menu, + jboolean e) +{ + BMenu *m = menuOf(menu); + MenuLocker lock(m); + m->SetEnabled(e); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuItemDispose(JNIEnv *, jclass, + jlong item) +{ + BMenuItem *i = itemOf(item); + BMenu *parent = i->Menu(); + if (parent != NULL) + { + MenuLocker lock(parent); + if (i->Menu() != NULL) + { + markDisposed(i); + return; + } + } + delete i; +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_menuDispose(JNIEnv *, jclass, + jlong menu) +{ + BMenu *m = menuOf(menu); + { + MenuLocker lock(m); + if (m->Superitem() != NULL || m->Window() != NULL) + { + markDisposed(m); + return; + } + } + detachAll(m); + delete m; +} + +/* Attaches a menu bar to a window (0 detaches). Returns its height. */ +JNIEXPORT jint JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setMenuBar(JNIEnv *, jclass, + jlong h, jlong bar) +{ + PeerWindow *pw = win(h); + jint height = 0; + BMenuBar *old = NULL; + if (pw->Lock()) + { + old = pw->JMenuBar(); + pw->SetJMenuBar((BMenuBar *) (intptr_t) bar); + height = pw->MenuHeight(); + pw->Unlock(); + } + if (old != NULL && old != (BMenuBar *) (intptr_t) bar) + releaseMenuBar(old); + return height; +} + +/* Shows a popup menu at screen point (x, y) without blocking. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_popupShow(JNIEnv *, jclass, + jlong menu, jint x, jint y) +{ + BPopUpMenu *pm = dynamic_cast(menuOf(menu)); + if (pm == NULL || pm->Window() != NULL) + return; + pm->Go(BPoint(x, y), true, false, false, true); +} + +/* Shows the items of a Choice in a temporary popup at screen point (x, y). + The selection comes back as EV_CHOICE. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_choicePopup(JNIEnv *env, jclass, + jint id, + jobjectArray items, + jint selected, + jint x, jint y) +{ + BPopUpMenu *pm = new BPopUpMenu("choice", false, false); + jsize n = env->GetArrayLength(items); + for (jsize i = 0; i < n; i++) + { + jstring js = (jstring) env->GetObjectArrayElement(items, i); + char *l = utf8(env, js); + if (js != NULL) + env->DeleteLocalRef(js); + BMessage *msg = new BMessage(MSG_CHOICE); + msg->AddInt32("jid", id); + msg->AddInt32("jindex", i); + BMenuItem *item = new BMenuItem(l, msg); + item->SetTarget(be_app); + if (i == selected) + item->SetMarked(true); + pm->AddItem(item); + free(l); + } + pm->Go(BPoint(x, y), true, false, true, true); +} + +/* ------------------------------------------------------------------ */ +/* File panels. */ + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_filePanelShow(JNIEnv *env, jclass, + jint id, + jboolean save, + jstring title, + jstring dir, + jstring file, + jboolean filtered) +{ + pthread_mutex_lock(&gQueueLock); + gFileResults.erase(id); + pthread_mutex_unlock(&gQueueLock); + filterForget(id); + + BMessage msg(MSG_FILE); + msg.AddInt32("jid", id); + BMessenger target(be_app); + BFilePanel *panel = new BFilePanel(save ? B_SAVE_PANEL : B_OPEN_PANEL, + &target, NULL, 0, false, &msg, + filtered ? new JRefFilter(id) : NULL); + char *d = utf8(env, dir); + char *f = utf8(env, file); + char *t = utf8(env, title); + if (*d != '\0') + panel->SetPanelDirectory(d); + if (save && *f != '\0') + panel->SetSaveText(f); + if (*t != '\0' && panel->Window()->Lock()) + { + panel->Window()->SetTitle(t); + panel->Window()->Unlock(); + } + free(d); + free(f); + free(t); + pthread_mutex_lock(&gPanelLock); + gPanels[panel] = id; + pthread_mutex_unlock(&gPanelLock); + panel->Show(); +} + +/* The path chosen in the last file panel of a dialog, or null. */ +JNIEXPORT jstring JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fileResult(JNIEnv *env, jclass, + jint id) +{ + std::string path; + bool have = false; + pthread_mutex_lock(&gQueueLock); + std::map::iterator it = gFileResults.find(id); + if (it != gFileResults.end()) + { + path = it->second; + have = true; + gFileResults.erase(it); + } + pthread_mutex_unlock(&gQueueLock); + return have ? env->NewStringUTF(path.c_str()) : NULL; +} + +} /* extern "C" */ + +/* ------------------------------------------------------------------ */ +/* Frame state, cursors, clipboard and file panel filters. */ + +/* UTF-8 to UTF-16, for text that is not in modified UTF-8. */ +static jstring +newStringFromUTF8(JNIEnv *env, const char *s, size_t n) +{ + std::vector out; + out.reserve(n); + const unsigned char *u = (const unsigned char *) s; + size_t i = 0; + while (i < n) + { + uint32 c = u[i]; + int extra = 0; + if (c >= 0xF0 && c < 0xF8) { c &= 0x07; extra = 3; } + else if (c >= 0xE0) { c &= 0x0F; extra = 2; } + else if (c >= 0xC0) { c &= 0x1F; extra = 1; } + else if (c >= 0x80) c = 0xFFFD; + i++; + for (int k = 0; k < extra; k++, i++) + { + if (i >= n || (u[i] & 0xC0) != 0x80) + { + c = 0xFFFD; + break; + } + c = (c << 6) | (u[i] & 0x3F); + } + if (c >= 0x10000) + { + c -= 0x10000; + out.push_back((jchar) (0xD800 + (c >> 10))); + out.push_back((jchar) (0xDC00 + (c & 0x3FF))); + } + else if (c != 0) + out.push_back((jchar) c); + } + return env->NewString(out.empty() ? NULL : &out[0], (jsize) out.size()); +} + +/* UTF-16 to UTF-8, with surrogate pairs as one 4-byte sequence. */ +static std::string +utf8FromString(JNIEnv *env, jstring js) +{ + std::string out; + if (js == NULL) + return out; + jsize n = env->GetStringLength(js); + const jchar *c = env->GetStringChars(js, NULL); + if (c == NULL) + return out; + for (jsize i = 0; i < n; i++) + { + uint32 v = c[i]; + if (v >= 0xD800 && v < 0xDC00 && i + 1 < n + && c[i + 1] >= 0xDC00 && c[i + 1] < 0xE000) + { + v = 0x10000 + ((v - 0xD800) << 10) + (c[i + 1] - 0xDC00); + i++; + } + if (v < 0x80) + out += (char) v; + else if (v < 0x800) + { + out += (char) (0xC0 | (v >> 6)); + out += (char) (0x80 | (v & 0x3F)); + } + else if (v < 0x10000) + { + out += (char) (0xE0 | (v >> 12)); + out += (char) (0x80 | ((v >> 6) & 0x3F)); + out += (char) (0x80 | (v & 0x3F)); + } + else + { + out += (char) (0xF0 | (v >> 18)); + out += (char) (0x80 | ((v >> 12) & 0x3F)); + out += (char) (0x80 | ((v >> 6) & 0x3F)); + out += (char) (0x80 | (v & 0x3F)); + } + } + env->ReleaseStringChars(js, c); + return out; +} + +/* Java cursor types (java.awt.Cursor) to Haiku system cursors. */ +static BCursorID +cursorIdFor(jint type) +{ + switch (type) + { + case 1: return B_CURSOR_ID_CROSS_HAIR; + case 2: return B_CURSOR_ID_I_BEAM; + case 3: return B_CURSOR_ID_PROGRESS; + case 4: return B_CURSOR_ID_RESIZE_SOUTH_WEST; + case 5: return B_CURSOR_ID_RESIZE_SOUTH_EAST; + case 6: return B_CURSOR_ID_RESIZE_NORTH_WEST; + case 7: return B_CURSOR_ID_RESIZE_NORTH_EAST; + case 8: return B_CURSOR_ID_RESIZE_NORTH; + case 9: return B_CURSOR_ID_RESIZE_SOUTH; + case 10: return B_CURSOR_ID_RESIZE_WEST; + case 11: return B_CURSOR_ID_RESIZE_EAST; + case 12: return B_CURSOR_ID_FOLLOW_LINK; + case 13: return B_CURSOR_ID_MOVE; + default: return B_CURSOR_ID_SYSTEM_DEFAULT; + } +} + +static pthread_mutex_t gCursorLock = PTHREAD_MUTEX_INITIALIZER; +static std::map gSystemCursors; + +static const BCursor * +systemCursor(jint type) +{ + BCursorID cid = cursorIdFor(type); + pthread_mutex_lock(&gCursorLock); + BCursor *&c = gSystemCursors[(int32) cid]; + if (c == NULL) + c = new BCursor(cid); + BCursor *r = c; + pthread_mutex_unlock(&gCursorLock); + return r; +} + +extern "C" { + +/* Minimizes, restores or zooms a window. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setFrameState(JNIEnv *, jclass, + jlong h, + jboolean minimized, + jboolean zoomed) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + if ((bool) zoomed != pw->IsZoomed()) + pw->Zoom(); + if ((bool) minimized != pw->IsMinimized()) + pw->Minimize(minimized); + pw->Unlock(); + } +} + +/* out = minimized, zoomed. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_getFrameState(JNIEnv *env, jclass, + jlong h, + jbooleanArray out) +{ + jboolean v[2] = { JNI_FALSE, JNI_FALSE }; + PeerWindow *pw = win(h); + if (pw->Lock()) + { + v[0] = pw->IsMinimized(); + v[1] = pw->IsZoomed(); + pw->Unlock(); + } + env->SetBooleanArrayRegion(out, 0, 2, v); +} + +/* The largest size of a zoomed window (outer size, as in AWT). */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setZoomLimits(JNIEnv *, jclass, + jlong h, jint w, + jint hh) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + pw->SetZoomLimits(w > 0 ? w - 1 : 32768, hh > 0 ? hh - 1 : 32768); + pw->Unlock(); + } +} + +/* Sets the cursor of a window: a java.awt.Cursor type, or a cursor made + with cursorCreate() when custom is not 0. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setCursor(JNIEnv *, jclass, + jlong h, jint type, + jlong custom) +{ + const BCursor *c = custom != 0 ? (const BCursor *) (intptr_t) custom + : systemCursor(type); + PeerWindow *pw = win(h); + if (pw->Lock()) + { + pw->View()->SetViewCursor(c, true); + pw->Unlock(); + } +} + +/* Makes a cursor from w x h ARGB pixels with the hot spot (hx, hy). */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_cursorCreate(JNIEnv *env, jclass, + jintArray pixels, + jint w, jint h, + jint hx, jint hy) +{ + if (w < 1 || h < 1) + return 0; + BBitmap bmp(BRect(0, 0, w - 1, h - 1), B_RGBA32); + if (bmp.InitCheck() != B_OK) + return 0; + jint *src = env->GetIntArrayElements(pixels, NULL); + if (src == NULL) + return 0; + uint8 *bits = (uint8 *) bmp.Bits(); + int32 bpr = bmp.BytesPerRow(); + /* Java 0xAARRGGBB == B_RGBA32 (B, G, R, A in memory). */ + for (jint row = 0; row < h; row++) + memcpy(bits + row * bpr, src + row * w, w * 4); + env->ReleaseIntArrayElements(pixels, src, JNI_ABORT); + return (jlong) (intptr_t) new BCursor(&bmp, BPoint(hx, hy)); +} + +/* Changes every time any application writes to the clipboard. */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_clipboardCount(JNIEnv *, jclass) +{ + return be_clipboard->SystemCount(); +} + +/* The next directory a file panel wants filtered, or null. */ +JNIEXPORT jstring JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_filterNext(JNIEnv *env, jclass, + jint id) +{ + std::string dir; + bool have = false; + pthread_mutex_lock(&gFilterLock); + std::deque &q = gFilterRequests[id]; + if (!q.empty()) + { + dir = q.front(); + q.pop_front(); + have = true; + } + pthread_mutex_unlock(&gFilterLock); + return have ? newStringFromUTF8(env, dir.data(), dir.size()) : NULL; +} + +/* The FilenameFilter answers for every name in a directory. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_filterAnswer(JNIEnv *env, jclass, + jint id, jstring dir, + jobjectArray names, + jbooleanArray accepted) +{ + std::string d = utf8FromString(env, dir); + FilterNames answers; + jsize n = env->GetArrayLength(names); + jboolean *acc = env->GetBooleanArrayElements(accepted, NULL); + for (jsize i = 0; i < n; i++) + { + jstring js = (jstring) env->GetObjectArrayElement(names, i); + answers[utf8FromString(env, js)] = acc != NULL && acc[i]; + if (js != NULL) + env->DeleteLocalRef(js); + } + if (acc != NULL) + env->ReleaseBooleanArrayElements(accepted, acc, JNI_ABORT); + pthread_mutex_lock(&gFilterLock); + gFilterCache[id][d] = answers; + gFilterPending[id].erase(d); + pthread_cond_broadcast(&gFilterCond); + pthread_mutex_unlock(&gFilterLock); +} + +} /* extern "C" */ + +/* ------------------------------------------------------------------ */ +/* Sound output for javax.sound.sampled (gnu.javax.sound.sampled.haiku). + * + * Each line owns one BSoundPlayer. Java converts all audio to 16-bit + * signed PCM. A clip keeps all its frames here and loops in the player + * callback; a stream line has a ring buffer that Java writes to. + */ + +struct JSound +{ + BSoundPlayer *player; + pthread_mutex_t lock; + pthread_cond_t cond; + int32 channels; + bool started; + bool playing; + float gain; + /* Clip mode. */ + bool clipMode; + int16 *clip; + int64 clipFrames; + int64 pos; + int64 loopStart; + int64 loopEnd; /* exclusive */ + int32 loops; /* -1 = forever */ + /* Stream mode. */ + int16 *ring; + int64 ringFrames; + int64 head; + int64 count; + int64 played; +}; + +static void +soundFill(void *cookie, void *buffer, size_t size, + const media_raw_audio_format &fmt) +{ + JSound *s = (JSound *) cookie; + int32 outCh = fmt.channel_count > 0 ? fmt.channel_count : 1; + int32 sampleSize = fmt.format & media_raw_audio_format::B_AUDIO_SIZE_MASK; + if (sampleSize == 0) + sampleSize = 2; + size_t frames = size / (outCh * sampleSize); + pthread_mutex_lock(&s->lock); + float gain = s->gain; + for (size_t i = 0; i < frames; i++) + { + int32 l = 0, r = 0; + if (s->playing) + { + const int16 *f = NULL; + if (s->clipMode) + { + if (s->loops != 0 && s->pos == s->loopEnd) + { + s->pos = s->loopStart; + if (s->loops > 0) + s->loops--; + } + if (s->pos < s->clipFrames) + f = s->clip + s->pos++ * s->channels; + else + s->playing = false; + } + else if (s->count > 0) + { + f = s->ring + s->head * s->channels; + s->head = (s->head + 1) % s->ringFrames; + s->count--; + } + if (f != NULL) + { + l = f[0]; + r = s->channels > 1 ? f[1] : l; + s->played++; + } + } + for (int32 c = 0; c < outCh; c++) + { + float v = (outCh == 1 ? (l + r) / 2 : (c == 0 ? l : c == 1 ? r : 0)) + * gain; + if (v > 32767) v = 32767; + if (v < -32768) v = -32768; + size_t k = i * outCh + c; + switch (fmt.format) + { + case media_raw_audio_format::B_AUDIO_FLOAT: + ((float *) buffer)[k] = v / 32768.0f; + break; + case media_raw_audio_format::B_AUDIO_DOUBLE: + ((double *) buffer)[k] = v / 32768.0; + break; + case media_raw_audio_format::B_AUDIO_INT: + ((int32 *) buffer)[k] = (int32) v << 16; + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + ((uint8 *) buffer)[k] = (uint8) (((int32) v >> 8) + 128); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + ((int8 *) buffer)[k] = (int8) ((int32) v >> 8); + break; + default: + ((int16 *) buffer)[k] = (int16) v; + break; + } + } + } + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->lock); +} + +static JSound * +snd(jlong h) +{ + return (JSound *) (intptr_t) h; +} + +/* Starts the player the first time something plays. Called with + s->lock held. */ +static void +soundEnsureStarted(JSound *s) +{ + if (s->started) + return; + s->started = true; + pthread_mutex_unlock(&s->lock); + s->player->Start(); + s->player->SetHasData(true); + pthread_mutex_lock(&s->lock); +} + +extern "C" { + +JNIEXPORT jboolean JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_init(JNIEnv *, jclass) +{ + return startApp() ? JNI_TRUE : JNI_FALSE; +} + +/* Opens an output line. Returns 0 if the media server refused it. */ +JNIEXPORT jlong JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_open(JNIEnv *, jclass, + jfloat rate, + jint channels) +{ + JSound *s = new JSound; + memset(s, 0, sizeof(*s)); + pthread_mutex_init(&s->lock, NULL); + pthread_cond_init(&s->cond, NULL); + s->channels = channels < 1 ? 1 : channels > 2 ? 2 : channels; + s->gain = 1.0f; + media_raw_audio_format f = media_raw_audio_format::wildcard; + f.frame_rate = rate; + f.channel_count = s->channels; + f.format = media_raw_audio_format::B_AUDIO_FLOAT; + f.byte_order = B_MEDIA_HOST_ENDIAN; + f.buffer_size = 1024 * s->channels * sizeof(float); + s->player = new BSoundPlayer(&f, "Java sound", soundFill, NULL, s); + if (s->player->InitCheck() != B_OK) + { + delete s->player; + pthread_mutex_destroy(&s->lock); + pthread_cond_destroy(&s->cond); + delete s; + return 0; + } + return (jlong) (intptr_t) s; +} + +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_close(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + if (s->started) + s->player->Stop(true, true); + delete s->player; + pthread_mutex_destroy(&s->lock); + pthread_cond_destroy(&s->cond); + free(s->clip); + free(s->ring); + delete s; +} + +/* Gives a clip line its frames (interleaved 16-bit samples). */ +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_loadClip(JNIEnv *env, jclass, + jlong h, + jshortArray data) +{ + JSound *s = snd(h); + jsize n = env->GetArrayLength(data); + int16 *buf = (int16 *) malloc((n > 0 ? n : 1) * sizeof(int16)); + env->GetShortArrayRegion(data, 0, n, (jshort *) buf); + pthread_mutex_lock(&s->lock); + free(s->clip); + s->clip = buf; + s->clipFrames = n / s->channels; + s->pos = 0; + s->clipMode = true; + s->playing = false; + pthread_mutex_unlock(&s->lock); +} + +/* Plays a clip from frame start. At loopEnd (exclusive) it goes back to + loopStart, loops times (-1 = forever), then plays to the end. */ +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_playClip(JNIEnv *, jclass, + jlong h, jlong start, + jlong loopStart, + jlong loopEnd, + jint loops) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + s->clipMode = true; + s->pos = start < 0 ? 0 : start > s->clipFrames ? s->clipFrames : start; + s->loopStart = loopStart; + s->loopEnd = loopEnd; + s->loops = loopEnd > loopStart ? loops : 0; + s->playing = s->pos < s->clipFrames || s->loops != 0; + soundEnsureStarted(s); + pthread_mutex_unlock(&s->lock); +} + +/* Starts or continues a stream line with a ring buffer of the given + size. */ +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_startStream(JNIEnv *, jclass, + jlong h, + jint frames) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + if (s->ring == NULL) + { + s->ringFrames = frames < 256 ? 256 : frames; + s->ring = (int16 *) malloc(s->ringFrames * s->channels + * sizeof(int16)); + s->head = 0; + s->count = 0; + } + s->clipMode = false; + s->playing = true; + soundEnsureStarted(s); + pthread_mutex_unlock(&s->lock); +} + +/* Pauses playback. The position and buffered data stay. */ +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_pause(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + s->playing = false; + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->lock); +} + +JNIEXPORT jboolean JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_isPlaying(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + bool p = s->playing && (s->clipMode || s->count > 0); + pthread_mutex_unlock(&s->lock); + return p; +} + +/* Clip lines: the frame position. Stream lines: the frames played. */ +JNIEXPORT jlong JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_position(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + jlong p = s->clipMode ? s->pos : s->played; + pthread_mutex_unlock(&s->lock); + return p; +} + +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_setPosition(JNIEnv *, jclass, + jlong h, + jlong frame) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + if (frame < 0) + frame = 0; + if (frame > s->clipFrames) + frame = s->clipFrames; + s->pos = frame; + pthread_mutex_unlock(&s->lock); +} + +/* Waits until a clip stops or a stream line has played all its data. + Returns true if that happened within timeoutMs. */ +JNIEXPORT jboolean JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_waitIdle(JNIEnv *, jclass, + jlong h, + jint timeoutMs) +{ + JSound *s = snd(h); + struct timespec until; + deadlineIn(&until, timeoutMs); + pthread_mutex_lock(&s->lock); + bool idle; + while (!(idle = !s->playing || (!s->clipMode && s->count == 0))) + if (pthread_cond_timedwait(&s->cond, &s->lock, &until) == ETIMEDOUT) + break; + pthread_mutex_unlock(&s->lock); + return idle; +} + +/* Copies whole frames from data[off .. off + len) into the ring buffer, + waiting up to timeoutMs for space. Returns the samples taken. */ +JNIEXPORT jint JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_write(JNIEnv *env, jclass, + jlong h, + jshortArray data, + jint off, jint len, + jint timeoutMs) +{ + JSound *s = snd(h); + struct timespec until; + deadlineIn(&until, timeoutMs); + jint frames = len / s->channels; + pthread_mutex_lock(&s->lock); + if (s->ring == NULL) + { + pthread_mutex_unlock(&s->lock); + return 0; + } + while (s->count >= s->ringFrames) + if (pthread_cond_timedwait(&s->cond, &s->lock, &until) == ETIMEDOUT) + break; + int64 space = s->ringFrames - s->count; + if (frames > space) + frames = (jint) space; + jshort *src = (jshort *) env->GetPrimitiveArrayCritical(data, NULL); + if (src != NULL) + { + for (jint i = 0; i < frames; i++) + { + int64 slot = (s->head + s->count) % s->ringFrames; + memcpy(s->ring + slot * s->channels, + src + off + i * s->channels, s->channels * sizeof(int16)); + s->count++; + } + env->ReleasePrimitiveArrayCritical(data, src, JNI_ABORT); + } + else + frames = 0; + pthread_mutex_unlock(&s->lock); + return frames * s->channels; +} + +/* Free space in the ring buffer, in samples. */ +JNIEXPORT jint JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_available(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + jint a = s->ring == NULL ? 0 + : (jint) ((s->ringFrames - s->count) * s->channels); + pthread_mutex_unlock(&s->lock); + return a; +} + +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_flush(JNIEnv *, jclass, + jlong h) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + s->count = 0; + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->lock); +} + +JNIEXPORT void JNICALL +Java_gnu_javax_sound_sampled_haiku_HaikuAudio_setGain(JNIEnv *, jclass, + jlong h, jfloat gain) +{ + JSound *s = snd(h); + pthread_mutex_lock(&s->lock); + s->gain = gain; + pthread_mutex_unlock(&s->lock); +} + +} /* extern "C" */ + +/* ------------------------------------------------------------------ */ +/* Window feel, data transfer (clipboard and drag and drop), printing and + input methods. */ + +/* Message sources, guarded by gSourceLock. Each new message gets a new + generation number, so Java can see that its data went away. */ +static pthread_mutex_t gSourceLock = PTHREAD_MUTEX_INITIALIZER; +static BMessage *gSources[SRC_COUNT]; +static int32 gSourceGen[SRC_COUNT]; + +static void +setSource(int32 src, BMessage *msg) +{ + pthread_mutex_lock(&gSourceLock); + delete gSources[src]; + gSources[src] = msg; + gSourceGen[src]++; + pthread_mutex_unlock(&gSourceLock); +} + +/* Converts any bitmap to B_RGBA32, which Java reads as 0xAARRGGBB. */ +static BBitmap * +toRGBA32(BBitmap *bmp) +{ + if (bmp == NULL || bmp->ColorSpace() == B_RGBA32) + return bmp; + BBitmap *out = new BBitmap(bmp->Bounds(), B_RGBA32); + if (out->InitCheck() != B_OK + || out->ImportBits(bmp) != B_OK) + { + delete out; + out = NULL; + } + delete bmp; + return out; +} + +/* The picture in a message, as B_RGBA32, or NULL. Archived bitmaps + ("image/bitmap", "image/x-be-bitmap") come first, then any image data + that a translator can read. */ +static BBitmap * +messageImage(BMessage *msg) +{ + static const char *archives[] = { "image/bitmap", "image/x-be-bitmap" }; + for (int i = 0; i < 2; i++) + { + BMessage archive; + if (msg->FindMessage(archives[i], &archive) == B_OK) + { + BArchivable *obj = BBitmap::Instantiate(&archive); + BBitmap *bmp = dynamic_cast(obj); + if (bmp != NULL) + return toRGBA32(bmp); + delete obj; + } + } + char *name; + type_code type; + for (int32 i = 0; msg->GetInfo(B_MIME_TYPE, i, &name, &type) == B_OK; i++) + { + if (strncmp(name, "image/", 6) != 0) + continue; + const void *data; + ssize_t len; + if (msg->FindData(name, B_MIME_TYPE, &data, &len) != B_OK) + continue; + BMemoryIO io(data, len); + BBitmap *bmp = BTranslationUtils::GetBitmap(&io); + if (bmp != NULL) + return toRGBA32(bmp); + } + return NULL; +} + +/* A B_RGBA32 bitmap from Java ARGB pixels. */ +static BBitmap * +bitmapFromPixels(JNIEnv *env, jintArray pixels, jint w, jint h) +{ + if (pixels == NULL || w < 1 || h < 1) + return NULL; + BBitmap *bmp = new BBitmap(BRect(0, 0, w - 1, h - 1), B_RGBA32); + if (bmp->InitCheck() != B_OK) + { + delete bmp; + return NULL; + } + jint *src = env->GetIntArrayElements(pixels, NULL); + if (src == NULL) + { + delete bmp; + return NULL; + } + uint8 *bits = (uint8 *) bmp->Bits(); + for (jint row = 0; row < h; row++) + memcpy(bits + row * bmp->BytesPerRow(), src + row * w, w * 4); + env->ReleaseIntArrayElements(pixels, src, JNI_ABORT); + return bmp; +} + +/* Puts a composing text window near the text. It never takes focus. */ +class CompositionView : public BView +{ +public: + CompositionView(BRect r) + : BView(r, "composition", B_FOLLOW_ALL_SIDES, B_WILL_DRAW) {} + + void SetText(const char *t, int32 selStart, int32 selEnd) + { + fText = t; + fSelStart = selStart; + fSelEnd = selEnd; + Invalidate(); + } + + virtual void Draw(BRect) + { + SetHighColor(255, 255, 230); + FillRect(Bounds()); + font_height fh; + GetFontHeight(&fh); + float base = 3 + fh.ascent; + /* Selected (converting) part: inverted. */ + if (fSelEnd > fSelStart) + { + float x0 = 4 + StringWidth(fText.c_str(), fSelStart); + float x1 = 4 + StringWidth(fText.c_str(), fSelEnd); + SetHighColor(40, 60, 140); + FillRect(BRect(x0, 2, x1, base + fh.descent)); + } + SetLowColor(255, 255, 230); + SetHighColor(0, 0, 0); + SetDrawingMode(B_OP_OVER); + DrawString(fText.c_str(), BPoint(4, base)); + SetDrawingMode(B_OP_COPY); + StrokeLine(BPoint(4, base + 2), + BPoint(4 + StringWidth(fText.c_str()), base + 2)); + SetHighColor(150, 150, 150); + StrokeRect(Bounds()); + } + +private: + std::string fText; + int32 fSelStart, fSelEnd; +}; + +static pthread_mutex_t gIMLock = PTHREAD_MUTEX_INITIALIZER; +static std::map gIMText; +static BWindow *gCompWindow; +static CompositionView *gCompView; + +/* Number of UTF-16 units in the first n bytes of UTF-8 text. */ +static int32 +utf16Length(const char *s, int32 n) +{ + int32 units = 0; + for (int32 i = 0; i < n; i++) + { + unsigned char c = s[i]; + if ((c & 0xC0) != 0x80) + units += c >= 0xF0 ? 2 : 1; + } + return units; +} + +static void +imChanged(int32 id, const char *text, int32 len, int32 selStart, + int32 selEnd, bool confirmed) +{ + pthread_mutex_lock(&gIMLock); + gIMText[id] = std::string(text, len); + pthread_mutex_unlock(&gIMLock); + if (selStart < 0 || selStart > len) selStart = 0; + if (selEnd < selStart || selEnd > len) selEnd = selStart; + postEvent(EV_IM, id, B_INPUT_METHOD_CHANGED, confirmed ? 1 : 0, + utf16Length(text, selStart), utf16Length(text, selEnd)); +} + +/* The view that prints one page: it draws the page bitmap scaled to its + bounds (the printable area, in points). */ +class PrintView : public BView +{ +public: + PrintView(BRect r, BBitmap *page) + : BView(r, "page", B_FOLLOW_NONE, B_WILL_DRAW), fPage(page) {} + + virtual void Draw(BRect) + { + DrawBitmap(fPage, fPage->Bounds(), Bounds(), B_FILTER_BITMAP_BILINEAR); + } + +private: + BBitmap *fPage; +}; + +static BPrintJob * +pjob(jlong h) +{ + return (BPrintJob *) (intptr_t) h; +} + +static BMessage * +msgOf(jlong h) +{ + return (BMessage *) (intptr_t) h; +} + +extern "C" { + +/* Sets the window feel: B_NORMAL_WINDOW_FEEL, B_MODAL_APP_WINDOW_FEEL, + B_FLOATING_ALL_WINDOW_FEEL... */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_setFeel(JNIEnv *, jclass, jlong h, + jint feel) +{ + PeerWindow *pw = win(h); + if (pw->Lock()) + { + if (pw->Feel() != (window_feel) feel) + pw->SetFeel((window_feel) feel); + pw->Unlock(); + } +} + +/* Messages that Java fills, then puts on the clipboard or drags. */ + +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgNew(JNIEnv *, jclass, jint what) +{ + return (jlong) (intptr_t) new BMessage((uint32) what); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgDelete(JNIEnv *, jclass, jlong h) +{ + delete msgOf(h); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgAddData(JNIEnv *env, jclass, + jlong h, jstring mime, + jbyteArray data) +{ + std::string m = utf8FromString(env, mime); + jsize n = env->GetArrayLength(data); + jbyte *b = env->GetByteArrayElements(data, NULL); + if (b == NULL) + return; + msgOf(h)->AddData(m.c_str(), B_MIME_TYPE, b, n); + env->ReleaseByteArrayElements(data, b, JNI_ABORT); +} + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgAddInt(JNIEnv *env, jclass, + jlong h, jstring name, + jint v) +{ + std::string n = utf8FromString(env, name); + msgOf(h)->AddInt32(n.c_str(), v); +} + +/* Adds a picture as an archived bitmap ("image/bitmap", as ShowImage + copies it) and as PNG data. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgAddImage(JNIEnv *env, jclass, + jlong h, + jintArray pixels, + jint w, jint hh) +{ + BBitmap *bmp = bitmapFromPixels(env, pixels, w, hh); + if (bmp == NULL) + return; + BMessage archive; + if (bmp->Archive(&archive) == B_OK) + msgOf(h)->AddMessage("image/bitmap", &archive); + BBitmapStream stream(bmp); + BMallocIO png; + if (BTranslatorRoster::Default()->Translate(&stream, NULL, NULL, &png, + B_PNG_FORMAT) == B_OK) + msgOf(h)->AddData("image/png", B_MIME_TYPE, png.Buffer(), + png.BufferLength()); + BBitmap *back = NULL; + stream.DetachBitmap(&back); + delete back; +} + +/* Adds files as entry_refs ("refs"), as Tracker drags them. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgAddFiles(JNIEnv *env, jclass, + jlong h, + jobjectArray paths) +{ + jsize n = env->GetArrayLength(paths); + for (jsize i = 0; i < n; i++) + { + jstring js = (jstring) env->GetObjectArrayElement(paths, i); + std::string p = utf8FromString(env, js); + if (js != NULL) + env->DeleteLocalRef(js); + entry_ref ref; + if (get_ref_for_path(p.c_str(), &ref) == B_OK) + msgOf(h)->AddRef("refs", &ref); + } +} + +/* Replaces the clipboard with the fields of a message (and deletes the + message). Returns the new clipboard count. */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_clipboardPut(JNIEnv *, jclass, + jlong h) +{ + BMessage *msg = msgOf(h); + jlong count = -1; + if (be_clipboard->Lock()) + { + be_clipboard->Clear(); + BMessage *data = be_clipboard->Data(); + if (data != NULL) + { + *data = *msg; + be_clipboard->Commit(); + } + count = be_clipboard->SystemCount(); + be_clipboard->Unlock(); + } + delete msg; + return count; +} + +/* Copies the clipboard into SRC_CLIPBOARD. Returns the clipboard count. */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_clipboardSnapshot(JNIEnv *, jclass) +{ + jlong count = -1; + if (be_clipboard->Lock()) + { + BMessage *data = be_clipboard->Data(); + setSource(SRC_CLIPBOARD, data != NULL ? new BMessage(*data) + : new BMessage()); + count = be_clipboard->SystemCount(); + be_clipboard->Unlock(); + } + return count; +} + +/* Reading message sources. */ + +JNIEXPORT jint JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgGeneration(JNIEnv *, jclass, + jint src) +{ + pthread_mutex_lock(&gSourceLock); + jint g = gSourceGen[src]; + pthread_mutex_unlock(&gSourceLock); + return g; +} + +/* The data types in a source: MIME types, "refs" for files, and the + names of archived bitmaps. */ +JNIEXPORT jobjectArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgTypes(JNIEnv *env, jclass, + jint src) +{ + std::vector types; + pthread_mutex_lock(&gSourceLock); + BMessage *msg = gSources[src]; + if (msg != NULL) + { + char *name; + type_code type; + int32 count; + for (int32 i = 0; + msg->GetInfo(B_ANY_TYPE, i, &name, &type, &count) == B_OK; i++) + if (type == B_MIME_TYPE || type == B_REF_TYPE + || (type == B_MESSAGE_TYPE && strncmp(name, "image/", 6) == 0)) + types.push_back(name); + } + pthread_mutex_unlock(&gSourceLock); + jobjectArray arr = env->NewObjectArray(types.size(), + env->FindClass("java/lang/String"), + NULL); + for (size_t i = 0; i < types.size(); i++) + { + jstring js = newStringFromUTF8(env, types[i].data(), types[i].size()); + env->SetObjectArrayElement(arr, i, js); + env->DeleteLocalRef(js); + } + return arr; +} + +JNIEXPORT jbyteArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgData(JNIEnv *env, jclass, + jint src, jstring mime) +{ + std::string m = utf8FromString(env, mime); + jbyteArray result = NULL; + pthread_mutex_lock(&gSourceLock); + BMessage *msg = gSources[src]; + const void *data; + ssize_t len; + if (msg != NULL + && msg->FindData(m.c_str(), B_MIME_TYPE, &data, &len) == B_OK) + { + result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, (jbyte *) data); + } + pthread_mutex_unlock(&gSourceLock); + return result; +} + +/* The picture in a source as ARGB pixels; dims = width, height. */ +JNIEXPORT jintArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgImage(JNIEnv *env, jclass, + jint src, jintArray dims) +{ + BMessage copy; + pthread_mutex_lock(&gSourceLock); + bool have = gSources[src] != NULL; + if (have) + copy = *gSources[src]; + pthread_mutex_unlock(&gSourceLock); + BBitmap *bmp = have ? messageImage(©) : NULL; + if (bmp == NULL) + return NULL; + jint w = bmp->Bounds().IntegerWidth() + 1; + jint h = bmp->Bounds().IntegerHeight() + 1; + jintArray result = env->NewIntArray(w * h); + const uint8 *bits = (const uint8 *) bmp->Bits(); + for (jint row = 0; row < h; row++) + env->SetIntArrayRegion(result, row * w, w, + (jint *) (bits + row * bmp->BytesPerRow())); + delete bmp; + jint d[2] = { w, h }; + env->SetIntArrayRegion(dims, 0, 2, d); + return result; +} + +/* The files ("refs") in a source as paths. */ +JNIEXPORT jobjectArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgFiles(JNIEnv *env, jclass, + jint src) +{ + std::vector paths; + pthread_mutex_lock(&gSourceLock); + BMessage *msg = gSources[src]; + entry_ref ref; + for (int32 i = 0; msg != NULL && msg->FindRef("refs", i, &ref) == B_OK; i++) + { + BPath path(&ref); + if (path.InitCheck() == B_OK) + paths.push_back(path.Path()); + } + pthread_mutex_unlock(&gSourceLock); + jobjectArray arr = env->NewObjectArray(paths.size(), + env->FindClass("java/lang/String"), + NULL); + for (size_t i = 0; i < paths.size(); i++) + { + jstring js = newStringFromUTF8(env, paths[i].data(), paths[i].size()); + env->SetObjectArrayElement(arr, i, js); + env->DeleteLocalRef(js); + } + return arr; +} + +JNIEXPORT jint JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_msgInt(JNIEnv *env, jclass, + jint src, jstring name, + jint def) +{ + std::string n = utf8FromString(env, name); + int32 v = def; + pthread_mutex_lock(&gSourceLock); + if (gSources[src] != NULL) + gSources[src]->FindInt32(n.c_str(), &v); + pthread_mutex_unlock(&gSourceLock); + return v; +} + +/* Starts dragging a message (deleted here) from a window. The drag shows + the picture (with the mouse at offset) or else the rectangle. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_dragStart(JNIEnv *env, jclass, + jlong h, jlong m, + jint x, jint y, jint w, + jint hh, + jintArray pixels, + jint iw, jint ih, + jint offX, jint offY) +{ + PeerWindow *pw = win(h); + BMessage *msg = msgOf(m); + BBitmap *bmp = bitmapFromPixels(env, pixels, iw, ih); + if (pw->Lock()) + { + if (bmp != NULL) + pw->View()->DragMessage(msg, bmp, B_OP_ALPHA, BPoint(offX, offY)); + else + pw->View()->DragMessage(msg, BRect(x, y, x + w - 1, y + hh - 1)); + pw->Unlock(); + } + else + delete bmp; + delete msg; +} + +/* Printing with BPrintJob. Pages are bitmaps that Java renders. */ + +/* Makes a print job. Shows the page setup and print dialogs if asked; + returns 0 if the user cancelled. */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printCreate(JNIEnv *env, jclass, + jstring name, + jboolean pageDialog, + jboolean jobDialog) +{ + std::string n = utf8FromString(env, name); + BPrintJob *job = new BPrintJob(n.empty() ? "Java" : n.c_str()); + if ((pageDialog && job->ConfigPage() != B_OK) + || (jobDialog && job->ConfigJob() != B_OK)) + { + delete job; + return 0; + } + if (!pageDialog && !jobDialog) + { + /* No dialog: use the print server's default settings. BeginJob() + needs a settings message, and BPrintJob keeps its defaults to + itself. */ + static const char *kPrintServer = "application/x-vnd.Be-PSRV"; + BMessenger server(kPrintServer); + if (!server.IsValid()) + { + be_roster->Launch(kPrintServer); + for (int i = 0; i < 50 && !server.IsValid(); i++) + { + snooze(100000); + server = BMessenger(kPrintServer); + } + } + BMessage request('pdef'); /* PSRV_GET_DEFAULT_SETTINGS */ + BMessage *settings = new BMessage; + if (server.SendMessage(&request, settings, 10000000, 10000000) != B_OK + || settings->what == (uint32) B_ERROR + || settings->CountNames(B_ANY_TYPE) <= 0) + { + delete settings; + delete job; + return 0; + } + /* The page setup dialog adds the scale; without it, drivers such + as Preview draw the page at size 0. */ + if (!settings->HasFloat("scale")) + settings->AddFloat("scale", 100.0f); + job->SetSettings(settings); + } + return (jlong) (intptr_t) job; +} + +/* Shows the page setup or print dialog again; false if cancelled. */ +JNIEXPORT jboolean JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printDialog(JNIEnv *, jclass, + jlong h, + jboolean page) +{ + BPrintJob *job = pjob(h); + return (page ? job->ConfigPage() : job->ConfigJob()) == B_OK; +} + +/* out = paper left, top, right, bottom, printable left, top, right, + bottom (points), first page, last page, x dpi, y dpi. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printInfo(JNIEnv *env, jclass, + jlong h, jfloatArray out) +{ + BPrintJob *job = pjob(h); + BRect paper = job->PaperRect(); + BRect printable = job->PrintableRect(); + int32 xdpi = 72, ydpi = 72; + job->GetResolution(&xdpi, &ydpi); + jfloat v[12] = { paper.left, paper.top, paper.right, paper.bottom, + printable.left, printable.top, printable.right, + printable.bottom, (jfloat) job->FirstPage(), + (jfloat) job->LastPage(), (jfloat) xdpi, (jfloat) ydpi }; + env->SetFloatArrayRegion(out, 0, 12, v); +} + +JNIEXPORT jboolean JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printBegin(JNIEnv *, jclass, + jlong h) +{ + BPrintJob *job = pjob(h); + job->BeginJob(); + return job->CanContinue(); +} + +/* Spools one page: w x h ARGB pixels that fill the printable area. */ +JNIEXPORT jboolean JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printPage(JNIEnv *env, jclass, + jlong h, jintArray pixels, + jint w, jint hh) +{ + BPrintJob *job = pjob(h); + if (!job->CanContinue()) + return JNI_FALSE; + BBitmap *page = bitmapFromPixels(env, pixels, w, hh); + if (page == NULL) + return JNI_FALSE; + BRect area = job->PrintableRect().OffsetToCopy(0, 0); + BWindow *window = new BWindow(area, "print", B_NO_BORDER_WINDOW_LOOK, + B_NORMAL_WINDOW_FEEL, B_AVOID_FOCUS); + PrintView *view = new PrintView(area, page); + window->Lock(); + window->AddChild(view); + window->Unlock(); + job->DrawView(view, area, BPoint(0, 0)); + job->SpoolPage(); + window->Lock(); + window->Quit(); + delete page; + return job->CanContinue(); +} + +/* Sends the job to the printer (commit) or drops it, and frees it. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_printEnd(JNIEnv *, jclass, jlong h, + jboolean commit) +{ + BPrintJob *job = pjob(h); + if (commit) + job->CommitJob(); + else + job->CancelJob(); + delete job; +} + +/* Input methods. */ + +/* The text of the last B_INPUT_METHOD_CHANGED in a window. */ +JNIEXPORT jstring JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_imText(JNIEnv *env, jclass, + jint id) +{ + std::string t; + pthread_mutex_lock(&gIMLock); + t = gIMText[id]; + pthread_mutex_unlock(&gIMLock); + return newStringFromUTF8(env, t.data(), t.size()); +} + +/* Answers B_INPUT_METHOD_LOCATION_REQUEST: the screen position of each + character of the composed text, and the line height. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_imLocationReply(JNIEnv *env, + jclass, jlong h, + jintArray xs, + jintArray ys, + jfloat height) +{ + PeerWindow *pw = win(h); + BMessenger reply; + if (pw->Lock()) + { + reply = pw->View()->fIMReply; + pw->Unlock(); + } + if (!reply.IsValid()) + return; + BMessage msg(B_INPUT_METHOD_EVENT); + msg.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); + jsize n = env->GetArrayLength(xs); + jint *x = env->GetIntArrayElements(xs, NULL); + jint *y = env->GetIntArrayElements(ys, NULL); + for (jsize i = 0; x != NULL && y != NULL && i < n; i++) + msg.AddPoint("be:location_reply", BPoint(x[i], y[i])); + if (x != NULL) + env->ReleaseIntArrayElements(xs, x, JNI_ABORT); + if (y != NULL) + env->ReleaseIntArrayElements(ys, y, JNI_ABORT); + msg.AddFloat("be:height_reply", height); + reply.SendMessage(&msg); +} + +/* Shows composing text (null hides it) in a small window at screen point + (x, y); selStart and selEnd are UTF-16 offsets of the converting part. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_imShowComposition(JNIEnv *env, + jclass, + jstring text, + jint x, jint y, + jint selStart, + jint selEnd) +{ + pthread_mutex_lock(&gIMLock); + if (gCompWindow == NULL) + { + gCompWindow = new BWindow(BRect(0, 0, 10, 10), "composition", + B_NO_BORDER_WINDOW_LOOK, + B_FLOATING_ALL_WINDOW_FEEL, + B_AVOID_FOCUS | B_NOT_MOVABLE + | B_NOT_RESIZABLE); + gCompView = new CompositionView(gCompWindow->Bounds()); + gCompWindow->AddChild(gCompView); + gCompWindow->Hide(); + gCompWindow->Show(); + } + BWindow *w = gCompWindow; + CompositionView *v = gCompView; + pthread_mutex_unlock(&gIMLock); + if (!w->Lock()) + return; + if (text == NULL || env->GetStringLength(text) == 0) + { + if (!w->IsHidden()) + w->Hide(); + } + else + { + std::string t = utf8FromString(env, text); + /* UTF-16 offsets to byte offsets. */ + int32 bytes[2] = { 0, 0 }; + jint want[2] = { selStart, selEnd }; + for (int k = 0; k < 2; k++) + { + int32 units = 0; + size_t i = 0; + while (i < t.size() && units < want[k]) + { + unsigned char c = t[i]; + units += c >= 0xF0 ? 2 : 1; + i++; + while (i < t.size() && ((unsigned char) t[i] & 0xC0) == 0x80) + i++; + } + bytes[k] = i; + } + font_height fh; + v->GetFontHeight(&fh); + float width = v->StringWidth(t.c_str()) + 8; + float height = fh.ascent + fh.descent + 7; + w->ResizeTo(width, height); + w->MoveTo(x, y); + v->SetText(t.c_str(), bytes[0], bytes[1]); + if (w->IsHidden()) + w->Show(); + } + w->Unlock(); +} + +} /* extern "C" */ + +/* ------------------------------------------------------------------ */ +/* Java text with Haiku's fonts. + * + * Java fonts are BFonts. Java measures text with BFont escapements and + * the app_server draws it (with Haiku's hinting, anti-aliasing and font + * fallback) into an off-screen bitmap; Java blends the coverage. Glyph + * outlines (BFont::GetGlyphShapes) serve shapes, transformed text and + * printing at other scales. + */ + +static BFont * +fontOf(jlong h) +{ + return (BFont *) (intptr_t) h; +} + +/* A string as UTF-8, and for each UTF-16 unit the index of its code + point (-1 for the second half of a surrogate pair). */ +struct Utf8Text +{ + std::string utf8; + std::vector unitToPoint; + int32 points; +}; + +static void +utf8Text(JNIEnv *env, jstring js, Utf8Text *out) +{ + out->utf8 = utf8FromString(env, js); + jsize n = js != NULL ? env->GetStringLength(js) : 0; + out->unitToPoint.resize(n); + const jchar *c = n > 0 ? env->GetStringChars(js, NULL) : NULL; + int32 point = 0; + for (jsize i = 0; i < n; i++) + { + out->unitToPoint[i] = point; + if (c[i] >= 0xD800 && c[i] < 0xDC00 && i + 1 < n + && c[i + 1] >= 0xDC00 && c[i + 1] < 0xE000) + { + out->unitToPoint[++i] = -1; + } + point++; + } + if (c != NULL) + env->ReleaseStringChars(js, c); + out->points = point; +} + +/* One off-screen bitmap for drawing text, grown when needed. */ +static pthread_mutex_t gTextLock = PTHREAD_MUTEX_INITIALIZER; +static BBitmap *gTextBitmap; +static BView *gTextView; + +static bool +textCanvas(int32 w, int32 h) +{ + if (gTextBitmap != NULL) + { + BRect b = gTextBitmap->Bounds(); + if (b.IntegerWidth() + 1 >= w && b.IntegerHeight() + 1 >= h) + return true; + w = w > b.IntegerWidth() + 1 ? w : b.IntegerWidth() + 1; + h = h > b.IntegerHeight() + 1 ? h : b.IntegerHeight() + 1; + delete gTextBitmap; + gTextBitmap = NULL; + gTextView = NULL; + } + if (w < 256) w = 256; + if (h < 64) h = 64; + gTextBitmap = new BBitmap(BRect(0, 0, w - 1, h - 1), B_RGBA32, true); + if (gTextBitmap->InitCheck() != B_OK) + { + delete gTextBitmap; + gTextBitmap = NULL; + return false; + } + gTextView = new BView(gTextBitmap->Bounds(), "text", B_FOLLOW_NONE, 0); + gTextBitmap->AddChild(gTextView); + return true; +} + +/* Collects a BShape as a flat float path for Java: 0 x y = move, + 1 x y = line, 2 x1 y1 x2 y2 x3 y3 = cubic, 3 = close. */ +class PathCollector : public BShapeIterator +{ +public: + PathCollector(std::vector *out, float dx, float dy) + : fOut(out), fDx(dx), fDy(dy) {} + + virtual status_t IterateMoveTo(BPoint *p) + { + Add(0, p, 1); + return B_OK; + } + + virtual status_t IterateLineTo(int32 count, BPoint *p) + { + for (int32 i = 0; i < count; i++) + Add(1, p + i, 1); + return B_OK; + } + + virtual status_t IterateBezierTo(int32 count, BPoint *p) + { + for (int32 i = 0; i < count; i++) + Add(2, p + i * 3, 3); + return B_OK; + } + + virtual status_t IterateClose() + { + fOut->push_back(3); + return B_OK; + } + +private: + void Add(float op, BPoint *p, int n) + { + fOut->push_back(op); + for (int i = 0; i < n; i++) + { + fOut->push_back(p[i].x + fDx); + fOut->push_back(p[i].y + fDy); + } + } + + std::vector *fOut; + float fDx, fDy; +}; + +extern "C" { + +/* Opens a font: family ("" = the plain font) and face flags + (B_BOLD_FACE, B_ITALIC_FACE...). Returns 0 if the family is unknown. */ +JNIEXPORT jlong JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontOpen(JNIEnv *env, jclass, + jstring family, jint face) +{ + std::string f = utf8FromString(env, family); + BFont *font = new BFont(be_plain_font); + if (!f.empty()) + { + font_family fam; + strlcpy(fam, f.c_str(), sizeof(fam)); + if (font->SetFamilyAndFace(fam, (uint16) face) != B_OK) + { + delete font; + return 0; + } + } + else if (face != B_REGULAR_FACE) + font->SetFace((uint16) face); + return (jlong) (intptr_t) font; +} + +/* The installed font families; which = 0: all, 1: the plain font's, + 2: the fixed font's. */ +JNIEXPORT jobjectArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontFamilies(JNIEnv *env, jclass, + jint which) +{ + std::vector names; + font_family fam; + font_style sty; + if (which == 1 || which == 2) + { + (which == 1 ? be_plain_font : be_fixed_font)->GetFamilyAndStyle(&fam, + &sty); + names.push_back(fam); + } + else + { + int32 n = count_font_families(); + for (int32 i = 0; i < n; i++) + { + uint32 flags; + if (get_font_family(i, &fam, &flags) == B_OK) + names.push_back(fam); + } + } + jobjectArray arr = env->NewObjectArray(names.size(), + env->FindClass("java/lang/String"), + NULL); + for (size_t i = 0; i < names.size(); i++) + { + jstring js = newStringFromUTF8(env, names[i].data(), names[i].size()); + env->SetObjectArrayElement(arr, i, js); + env->DeleteLocalRef(js); + } + return arr; +} + +/* out = ascent, descent, leading, max advance (pixels at this size). */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontMetrics(JNIEnv *env, jclass, + jlong h, jfloat size, + jfloatArray out) +{ + BFont font(*fontOf(h)); + font.SetSize(size); + font_height fh; + font.GetHeight(&fh); + BRect box = font.BoundingBox(); + jfloat v[4] = { fh.ascent, fh.descent, fh.leading, + box.Width() * size }; + env->SetFloatArrayRegion(out, 0, 4, v); +} + +/* The advance of each UTF-16 unit of text (0 for the second half of a + surrogate pair). */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontAdvances(JNIEnv *env, jclass, + jlong h, jfloat size, + jstring text, + jfloatArray out) +{ + Utf8Text t; + utf8Text(env, text, &t); + size_t n = t.unitToPoint.size(); + if (n == 0) + return; + BFont font(*fontOf(h)); + font.SetSize(size); + std::vector esc(t.points + 1); + font.GetEscapements(t.utf8.c_str(), t.points, &esc[0]); + std::vector adv(n); + for (size_t i = 0; i < n; i++) + adv[i] = t.unitToPoint[i] < 0 ? 0 : esc[t.unitToPoint[i]] * size; + env->SetFloatArrayRegion(out, 0, n, &adv[0]); +} + +JNIEXPORT jfloat JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontWidth(JNIEnv *env, jclass, + jlong h, jfloat size, + jstring text) +{ + std::string t = utf8FromString(env, text); + BFont font(*fontOf(h)); + font.SetSize(size); + return font.StringWidth(t.c_str()); +} + +/* True for each UTF-16 unit that the font (or Haiku's fallback fonts) + can show. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontHasGlyphs(JNIEnv *env, jclass, + jlong h, jstring text, + jbooleanArray out) +{ + Utf8Text t; + utf8Text(env, text, &t); + size_t n = t.unitToPoint.size(); + if (n == 0) + return; + bool *has = new bool[t.points + 1]; + fontOf(h)->GetHasGlyphs(t.utf8.c_str(), t.points, has, true); + std::vector r(n); + for (size_t i = 0; i < n; i++) + r[i] = t.unitToPoint[i] < 0 ? JNI_TRUE : has[t.unitToPoint[i]]; + delete[] has; + env->SetBooleanArrayRegion(out, 0, n, &r[0]); +} + +/* Draws text with the app_server and returns its coverage (0..255); + dims = width, height, baseline y, origin x in the mask. */ +JNIEXPORT jbyteArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontRender(JNIEnv *env, jclass, + jlong h, jfloat size, + jstring text, + jintArray dims) +{ + std::string t = utf8FromString(env, text); + if (t.empty() || size <= 0) + return NULL; + BFont font(*fontOf(h)); + font.SetSize(size); + font_height fh; + font.GetHeight(&fh); + int32 pad = (int32) ceilf(size / 4) + 1; /* italic and overhang room */ + int32 ascent = (int32) ceilf(fh.ascent) + 1; + int32 w = (int32) ceilf(font.StringWidth(t.c_str())) + 2 * pad; + int32 hh = ascent + (int32) ceilf(fh.descent) + 2; + jbyteArray result = NULL; + pthread_mutex_lock(&gTextLock); + if (textCanvas(w, hh) && gTextBitmap->Lock()) + { + BRect area(0, 0, w - 1, hh - 1); + gTextView->SetHighColor(0, 0, 0); + gTextView->FillRect(area); + gTextView->SetHighColor(255, 255, 255); + gTextView->SetLowColor(0, 0, 0); + gTextView->SetFont(&font); + gTextView->SetDrawingMode(B_OP_OVER); + gTextView->DrawString(t.c_str(), BPoint(pad, ascent)); + gTextView->Sync(); + result = env->NewByteArray(w * hh); + std::vector row(w); + const uint8 *bits = (const uint8 *) gTextBitmap->Bits(); + int32 bpr = gTextBitmap->BytesPerRow(); + for (int32 y = 0; y < hh; y++) + { + const uint8 *p = bits + y * bpr; + for (int32 x = 0; x < w; x++) + /* Average of the channels, also right for subpixel AA. */ + row[x] = (jbyte) ((p[x * 4] + p[x * 4 + 1] + p[x * 4 + 2]) / 3); + env->SetByteArrayRegion(result, y * w, w, &row[0]); + } + gTextBitmap->Unlock(); + jint d[4] = { w, hh, ascent, pad }; + env->SetIntArrayRegion(dims, 0, 4, d); + } + pthread_mutex_unlock(&gTextLock); + return result; +} + +/* The outline of text as a flat path (see PathCollector); xs are the x + positions of the UTF-16 units (null: laid out with the advances). */ +JNIEXPORT jfloatArray JNICALL +Java_gnu_java_awt_peer_haiku_HaikuNative_fontOutline(JNIEnv *env, jclass, + jlong h, jfloat size, + jstring text, + jfloatArray xs) +{ + Utf8Text t; + utf8Text(env, text, &t); + if (t.points == 0) + return env->NewFloatArray(0); + BFont font(*fontOf(h)); + font.SetSize(size); + std::vector esc(t.points + 1); + font.GetEscapements(t.utf8.c_str(), t.points, &esc[0]); + std::vector pointX(t.points); + float x = 0; + for (int32 i = 0; i < t.points; i++) + { + pointX[i] = x; + x += esc[i] * size; + } + if (xs != NULL) + { + jsize n = env->GetArrayLength(xs); + jfloat *v = env->GetFloatArrayElements(xs, NULL); + for (jsize i = 0; v != NULL && i < n && i < (jsize) t.unitToPoint.size(); + i++) + if (t.unitToPoint[i] >= 0) + pointX[t.unitToPoint[i]] = v[i]; + if (v != NULL) + env->ReleaseFloatArrayElements(xs, v, JNI_ABORT); + } + std::vector shapes(t.points); + for (int32 i = 0; i < t.points; i++) + shapes[i] = new BShape(); + font.GetGlyphShapes(t.utf8.c_str(), t.points, &shapes[0]); + std::vector path; + for (int32 i = 0; i < t.points; i++) + { + PathCollector c(&path, pointX[i], 0); + c.Iterate(shapes[i]); + delete shapes[i]; + } + jfloatArray result = env->NewFloatArray(path.size()); + if (!path.empty()) + env->SetFloatArrayRegion(result, 0, path.size(), &path[0]); + return result; +} + +} /* extern "C" */ diff --git a/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuRaster.cpp b/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuRaster.cpp new file mode 100644 index 0000000000..298f5308cf --- /dev/null +++ b/native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuRaster.cpp @@ -0,0 +1,795 @@ +/* gnu_java_awt_peer_haiku_HaikuRaster.cpp -- native pixel loops for HaikuGraphics2D + Copyright (C) 2026 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +/* + * HaikuGraphics2D draws into int[] images (0x00RRGGBB or non-premultiplied + * 0xAARRGGBB). On Kaffe's interpreter, per-pixel Java loops are very slow, + * so the common operations are done here, directly on the Java arrays: + * rectangles, 1-pixel lines, paths (with or without anti-aliasing), + * images (any affine transform), and copyArea. + * + * Paint: a solid colour, or a linear gradient given in user space with + * the inverse of the device transform. Clip: a device rectangle. + */ + +#include + +#include "gnu_java_awt_peer_haiku_HaikuRaster.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +/* The destination: pixels, scanline, size, alpha, clip. */ +struct Target +{ + uint32_t *px; + int scan; + bool alpha; + int cx0, cy0, cx1, cy1; /* clip, exclusive end */ +}; + +/* Non-premultiplied ARGB src over dst, with coverage a (0..255) already + applied to the source alpha. */ +inline uint32_t +blend(uint32_t s, uint32_t d, bool dstAlpha) +{ + uint32_t a = s >> 24; + if (a == 255) + return s; + if (a == 0) + return d; + uint32_t na = 255 - a; + uint32_t r = (((s >> 16) & 0xff) * a + ((d >> 16) & 0xff) * na + 127) / 255; + uint32_t g = (((s >> 8) & 0xff) * a + ((d >> 8) & 0xff) * na + 127) / 255; + uint32_t b = ((s & 0xff) * a + (d & 0xff) * na + 127) / 255; + uint32_t oa = dstAlpha ? a + ((d >> 24) * na + 127) / 255 : 255; + return (oa << 24) | (r << 16) | (g << 8) | b; +} + +inline uint32_t +withAlpha(uint32_t argb, uint32_t cover) +{ + uint32_t a = ((argb >> 24) * cover + 127) / 255; + return (a << 24) | (argb & 0xffffff); +} + +/* A paint: solid colour or linear gradient. */ +struct Paint +{ + bool gradient; + uint32_t argb; + /* Gradient: user-space points and colours, and the inverse transform + (device to user). */ + double inv[6]; + double gx, gy, gdx, gdy, glen2; + uint32_t c1, c2; + bool cyclic; + uint32_t xorBits; + + inline uint32_t at(int x, int y) const + { + if (!gradient) + return argb; + double dx = x + 0.5, dy = y + 0.5; + double ux = inv[0] * dx + inv[2] * dy + inv[4]; + double uy = inv[1] * dx + inv[3] * dy + inv[5]; + double t = glen2 > 0 ? ((ux - gx) * gdx + (uy - gy) * gdy) / glen2 : 0; + if (cyclic) + { + t = fmod(t, 2.0); + if (t < 0) t += 2; + if (t > 1) t = 2 - t; + } + else + t = t < 0 ? 0 : t > 1 ? 1 : t; + int k = (int) (t * 256); + if (k > 256) k = 256; + int nk = 256 - k; + uint32_t a = (((c1 >> 24) & 0xff) * nk + ((c2 >> 24) & 0xff) * k) >> 8; + uint32_t r = (((c1 >> 16) & 0xff) * nk + ((c2 >> 16) & 0xff) * k) >> 8; + uint32_t g = (((c1 >> 8) & 0xff) * nk + ((c2 >> 8) & 0xff) * k) >> 8; + uint32_t b = ((c1 & 0xff) * nk + (c2 & 0xff) * k) >> 8; + return (a << 24) | (r << 16) | (g << 8) | b; + } +}; + +/* Paints pixels x0..x1-1 of row y with coverage 255. */ +inline void +span(const Target &t, const Paint &p, int y, int x0, int x1) +{ + if (x0 < t.cx0) x0 = t.cx0; + if (x1 > t.cx1) x1 = t.cx1; + if (y < t.cy0 || y >= t.cy1 || x0 >= x1) + return; + uint32_t *row = t.px + (size_t) y * t.scan; + if (p.xorBits != 0) + { + for (int x = x0; x < x1; x++) + row[x] ^= p.xorBits; + return; + } + if (!p.gradient && (p.argb >> 24) == 255) + { + uint32_t v = p.argb; + for (int x = x0; x < x1; x++) + row[x] = v; + return; + } + for (int x = x0; x < x1; x++) + row[x] = blend(p.at(x, y), row[x], t.alpha); +} + +/* One pixel with coverage (0..255). */ +inline void +plot(const Target &t, const Paint &p, int x, int y, uint32_t cover) +{ + uint32_t *d = t.px + (size_t) y * t.scan + x; + if (p.xorBits != 0) + { + if (cover >= 128) + *d ^= p.xorBits; + return; + } + *d = blend(withAlpha(p.at(x, y), cover), *d, t.alpha); +} + +struct Edge +{ + float x0, y0, x1, y1; /* y0 < y1 */ + float slope; /* dx / dy */ + int dir; +}; + +/* Flattens a path (0 x y move, 1 x y line, 2 quad, 3 cubic, 4 close) to + edges. Tolerance about 0.2 pixels. */ +void +flatten(const float *d, int n, std::vector *edges) +{ + float sx = 0, sy = 0, cx = 0, cy = 0; + bool open = false; + auto line = [&](float x0, float y0, float x1, float y1) { + if (y0 == y1) + return; + Edge e; + if (y0 < y1) + { e.x0 = x0; e.y0 = y0; e.x1 = x1; e.y1 = y1; e.dir = 1; } + else + { e.x0 = x1; e.y0 = y1; e.x1 = x0; e.y1 = y0; e.dir = -1; } + e.slope = (e.x1 - e.x0) / (e.y1 - e.y0); + edges->push_back(e); + }; + int k = 0; + while (k < n) + { + int op = (int) d[k++]; + switch (op) + { + case 0: + if (open) + line(cx, cy, sx, sy); + sx = cx = d[k]; sy = cy = d[k + 1]; k += 2; + open = true; + break; + case 1: + line(cx, cy, d[k], d[k + 1]); + cx = d[k]; cy = d[k + 1]; k += 2; + break; + case 2: + case 3: + { + int np = op == 2 ? 2 : 3; + float px[4] = { cx, d[k], d[k + 2], op == 3 ? d[k + 4] : 0 }; + float py[4] = { cy, d[k + 1], d[k + 3], op == 3 ? d[k + 5] : 0 }; + k += 2 * np; + float len = 0; + for (int i = 0; i < np; i++) + len += hypotf(px[i + 1] - px[i], py[i + 1] - py[i]); + int steps = (int) ceilf(sqrtf(len) * 1.2f); + if (steps < 2) steps = 2; + if (steps > 256) steps = 256; + float lx = cx, ly = cy; + for (int s = 1; s <= steps; s++) + { + float u = (float) s / steps, v = 1 - u, x, y; + if (op == 2) + { + x = v * v * px[0] + 2 * v * u * px[1] + u * u * px[2]; + y = v * v * py[0] + 2 * v * u * py[1] + u * u * py[2]; + } + else + { + x = v * v * v * px[0] + 3 * v * v * u * px[1] + + 3 * v * u * u * px[2] + u * u * u * px[3]; + y = v * v * v * py[0] + 3 * v * v * u * py[1] + + 3 * v * u * u * py[2] + u * u * u * py[3]; + } + line(lx, ly, x, y); + lx = x; ly = y; + } + cx = lx; cy = ly; + break; + } + default: /* close */ + if (open) + line(cx, cy, sx, sy); + cx = sx; cy = sy; + break; + } + } + if (open) + line(cx, cy, sx, sy); +} + +/* Draws a 1-pixel line (Bresenham) with both end points. skipFirst leaves + out the first point, so joined segments do not paint a point twice + (that matters in XOR mode and with translucent colours). */ +void +line1(const Target &t, const Paint &p, int x0, int y0, int x1, int y1, + bool skipFirst) +{ + int dx = abs(x1 - x0), dy = -abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1; + int err = dx + dy, x = x0, y = y0; + bool first = true; + for (;;) + { + if (!(first && skipFirst) + && x >= t.cx0 && x < t.cx1 && y >= t.cy0 && y < t.cy1) + { + uint32_t *px = t.px + (size_t) y * t.scan + x; + if (p.xorBits) + *px ^= p.xorBits; + else + *px = blend(p.at(x, y), *px, t.alpha); + } + first = false; + if (x == x1 && y == y1) + break; + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x += sx; } + if (e2 <= dx) { err += dx; y += sy; } + } +} + +/* Strokes a path with 1-pixel lines: curves are flattened, points are + rounded to pixels as java.awt does for thin lines. */ +void +drawPathThin(const Target &t, const Paint &p, const float *d, int n) +{ + float sx = 0, sy = 0, cx = 0, cy = 0; + int lastX = 0, lastY = 0; + bool have = false; + auto to = [&](float x, float y) { + int ix = (int) floorf(x), iy = (int) floorf(y); + if (have) + line1(t, p, lastX, lastY, ix, iy, true); + else + line1(t, p, ix, iy, ix, iy, false); + lastX = ix; lastY = iy; have = true; + }; + int k = 0; + while (k < n) + { + int op = (int) d[k++]; + if (op == 0) + { + sx = cx = d[k]; sy = cy = d[k + 1]; k += 2; + have = false; + to(cx, cy); + } + else if (op == 1) + { + cx = d[k]; cy = d[k + 1]; k += 2; + to(cx, cy); + } + else if (op == 2 || op == 3) + { + int np = op == 2 ? 2 : 3; + float px[4] = { cx, d[k], d[k + 2], op == 3 ? d[k + 4] : 0 }; + float py[4] = { cy, d[k + 1], d[k + 3], op == 3 ? d[k + 5] : 0 }; + k += 2 * np; + float len = 0; + for (int i = 0; i < np; i++) + len += hypotf(px[i + 1] - px[i], py[i + 1] - py[i]); + int steps = (int) ceilf(sqrtf(len) * 1.2f); + if (steps < 2) steps = 2; + if (steps > 256) steps = 256; + for (int s = 1; s <= steps; s++) + { + float u = (float) s / steps, v = 1 - u, x, y; + if (op == 2) + { + x = v * v * px[0] + 2 * v * u * px[1] + u * u * px[2]; + y = v * v * py[0] + 2 * v * u * py[1] + u * u * py[2]; + } + else + { + x = v * v * v * px[0] + 3 * v * v * u * px[1] + + 3 * v * u * u * px[2] + u * u * u * px[3]; + y = v * v * v * py[0] + 3 * v * v * u * py[1] + + 3 * v * u * u * py[2] + u * u * u * py[3]; + } + to(x, y); + } + cx = px[np]; cy = py[np]; + } + else + { + if (have && ((int) floorf(sx) != lastX || (int) floorf(sy) != lastY)) + to(sx, sy); + cx = sx; cy = sy; + } + } +} + +struct Crossing +{ + float x; + int dir; + bool operator<(const Crossing &o) const { return x < o.x; } +}; + +/* Crossings of the edges with the horizontal line at sy, sorted. */ +inline void +crossings(const std::vector &edges, size_t first, float sy, + std::vector *out) +{ + out->clear(); + for (size_t i = first; i < edges.size(); i++) + { + const Edge &e = edges[i]; + if (e.y0 > sy) + break; /* edges are sorted by y0 */ + if (sy < e.y1) + { + Crossing c = { e.x0 + (sy - e.y0) * e.slope, e.dir }; + out->push_back(c); + } + } + std::sort(out->begin(), out->end()); +} + +void +fillPath(const Target &t, const Paint &p, const float *d, int n, + bool evenOdd, bool aa) +{ + std::vector edges; + flatten(d, n, &edges); + if (edges.empty()) + return; + std::sort(edges.begin(), edges.end(), + [](const Edge &a, const Edge &b) { return a.y0 < b.y0; }); + float ymin = edges[0].y0, ymax = 0; + for (size_t i = 0; i < edges.size(); i++) + if (edges[i].y1 > ymax) + ymax = edges[i].y1; + int y0 = (int) floorf(ymin), y1 = (int) ceilf(ymax); + if (y0 < t.cy0) y0 = t.cy0; + if (y1 > t.cy1) y1 = t.cy1; + std::vector cr; + if (!aa) + { + for (int y = y0; y < y1; y++) + { + float sy = y + 0.5f; + crossings(edges, 0, sy, &cr); + int w = 0; + for (size_t i = 0; i + 1 < cr.size(); i++) + { + w += cr[i].dir; + bool inside = evenOdd ? (i % 2 == 0) : w != 0; + if (inside) + span(t, p, y, (int) ceilf(cr[i].x - 0.5f), + (int) ceilf(cr[i + 1].x - 0.5f)); + } + } + return; + } + /* Anti-aliased: 4 sub-scanlines per row, exact horizontal coverage. */ + const int SS = 4; + int cw = t.cx1 - t.cx0; + if (cw <= 0) + return; + std::vector acc(cw + 2, 0.0f); + for (int y = y0; y < y1; y++) + { + int minx = cw, maxx = -1; + for (int s = 0; s < SS; s++) + { + float sy = y + (s + 0.5f) / SS; + crossings(edges, 0, sy, &cr); + int w = 0; + for (size_t i = 0; i + 1 < cr.size(); i++) + { + w += cr[i].dir; + bool inside = evenOdd ? (i % 2 == 0) : w != 0; + if (!inside) + continue; + float xa = cr[i].x - t.cx0, xb = cr[i + 1].x - t.cx0; + if (xa < 0) xa = 0; + if (xb > cw) xb = cw; + if (xa >= xb) + continue; + int ia = (int) xa, ib = (int) xb; + if (ia < minx) minx = ia; + if (ib > maxx) maxx = ib < cw ? ib : cw - 1; + if (ia == ib) + acc[ia] += xb - xa; + else + { + acc[ia] += ia + 1 - xa; + for (int x = ia + 1; x < ib; x++) + acc[x] += 1; + if (ib < cw) + acc[ib] += xb - ib; + } + } + } + for (int x = minx; x <= maxx && x < cw; x++) + { + float a = acc[x]; + acc[x] = 0; + if (a <= 0) + continue; + uint32_t cover = (uint32_t) (a * (255.0f / SS) + 0.5f); + if (cover > 255) cover = 255; + if (cover == 255 && !p.gradient && (p.argb >> 24) == 255 + && p.xorBits == 0) + t.px[(size_t) y * t.scan + x + t.cx0] = p.argb; + else + plot(t, p, x + t.cx0, y, cover); + } + } +} + +/* Reads the arguments that every call shares. */ +struct Pinned +{ + JNIEnv *env; + jintArray arr; + jint *ptr; + Pinned(JNIEnv *e, jintArray a) : env(e), arr(a) + { + ptr = (jint *) e->GetPrimitiveArrayCritical(a, NULL); + } + ~Pinned() + { + if (ptr != NULL) + env->ReleasePrimitiveArrayCritical(arr, ptr, 0); + } +}; + +Target +target(jint *px, jint scan, jboolean alpha, jintArray clip, JNIEnv *env) +{ + Target t; + jint c[4]; + env->GetIntArrayRegion(clip, 0, 4, c); + t.px = (uint32_t *) px; + t.scan = scan; + t.alpha = alpha; + t.cx0 = c[0]; t.cy0 = c[1]; t.cx1 = c[0] + c[2]; t.cy1 = c[1] + c[3]; + return t; +} + +Paint +solid(jint argb, jint xorBits) +{ + Paint p; + memset(&p, 0, sizeof(p)); + p.argb = (uint32_t) argb; + p.xorBits = (uint32_t) xorBits; + return p; +} + +/* g = x1, y1, x2, y2 (user space), then the inverse transform (6). */ +Paint +gradient(JNIEnv *env, jdoubleArray g, jint c1, jint c2, jboolean cyclic) +{ + Paint p; + memset(&p, 0, sizeof(p)); + jdouble v[10]; + env->GetDoubleArrayRegion(g, 0, 10, v); + p.gradient = true; + p.gx = v[0]; p.gy = v[1]; + p.gdx = v[2] - v[0]; p.gdy = v[3] - v[1]; + p.glen2 = p.gdx * p.gdx + p.gdy * p.gdy; + for (int i = 0; i < 6; i++) + p.inv[i] = v[4 + i]; + p.c1 = (uint32_t) c1; + p.c2 = (uint32_t) c2; + p.cyclic = cyclic; + return p; +} + +inline uint32_t +sampleNearest(const uint32_t *src, int scan, int w, int h, double u, + double v, bool srcAlpha, bool *inside) +{ + int x = (int) floor(u), y = (int) floor(v); + if (x < 0 || y < 0 || x >= w || y >= h) + { + *inside = false; + return 0; + } + *inside = true; + uint32_t s = src[(size_t) y * scan + x]; + return srcAlpha ? s : s | 0xff000000; +} + +inline uint32_t +sampleBilinear(const uint32_t *src, int scan, int w, int h, double u, + double v, bool srcAlpha, bool *inside) +{ + u -= 0.5; v -= 0.5; + int x = (int) floor(u), y = (int) floor(v); + if (x < -1 || y < -1 || x >= w || y >= h) + { + *inside = false; + return 0; + } + *inside = true; + int fx = (int) ((u - x) * 256), fy = (int) ((v - y) * 256); + uint32_t q[4]; + for (int i = 0; i < 4; i++) + { + int xx = x + (i & 1), yy = y + (i >> 1); + if (xx < 0) xx = 0; + if (yy < 0) yy = 0; + if (xx >= w) xx = w - 1; + if (yy >= h) yy = h - 1; + uint32_t s = src[(size_t) yy * scan + xx]; + q[i] = srcAlpha ? s : s | 0xff000000; + } + uint32_t out = 0; + for (int sh = 0; sh < 32; sh += 8) + { + int a = (q[0] >> sh) & 0xff, b = (q[1] >> sh) & 0xff; + int c = (q[2] >> sh) & 0xff, d = (q[3] >> sh) & 0xff; + int top = a * (256 - fx) + b * fx, bot = c * (256 - fx) + d * fx; + uint32_t r = (uint32_t) ((top * (256 - fy) + bot * fy) >> 16); + out |= (r & 0xff) << sh; + } + return out; +} + +} /* namespace */ + +extern "C" { + +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_fillRect(JNIEnv *env, jclass, + jintArray dst, jint scan, + jboolean alpha, + jintArray clip, jint x, + jint y, jint w, jint h, + jint argb, jint xorBits) +{ + Pinned d(env, dst); + if (d.ptr == NULL) + return; + Target t = target(d.ptr, scan, alpha, clip, env); + Paint p = solid(argb, xorBits); + for (int yy = y; yy < y + h; yy++) + span(t, p, yy, x, x + w); +} + +/* A 1-pixel line (Bresenham), both end points included. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_drawLine(JNIEnv *env, jclass, + jintArray dst, jint scan, + jboolean alpha, + jintArray clip, jint x0, + jint y0, jint x1, jint y1, + jint argb, jint xorBits) +{ + Pinned d(env, dst); + if (d.ptr == NULL) + return; + Target t = target(d.ptr, scan, alpha, clip, env); + Paint p = solid(argb, xorBits); + int dx = abs(x1 - x0), dy = -abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1; + int err = dx + dy, x = x0, y = y0; + for (;;) + { + if (x >= t.cx0 && x < t.cx1 && y >= t.cy0 && y < t.cy1) + { + uint32_t *px = t.px + (size_t) y * t.scan + x; + if (p.xorBits) + *px ^= p.xorBits; + else + *px = blend(p.argb, *px, t.alpha); + } + if (x == x1 && y == y1) + break; + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x += sx; } + if (e2 <= dx) { err += dx; y += sy; } + } +} + +/* Strokes a device-space path with 1-pixel lines in a colour. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_drawPathThin(JNIEnv *env, jclass, + jintArray dst, + jint scan, + jboolean alpha, + jintArray clip, + jfloatArray path, + jint n, jint argb, + jint xorBits) +{ + std::vector d(n > 0 ? n : 1); + env->GetFloatArrayRegion(path, 0, n, &d[0]); + Pinned px(env, dst); + if (px.ptr == NULL) + return; + Target t = target(px.ptr, scan, alpha, clip, env); + drawPathThin(t, solid(argb, xorBits), &d[0], n); +} + +/* Fills a device-space path with a colour. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_fillPath(JNIEnv *env, jclass, + jintArray dst, jint scan, + jboolean alpha, + jintArray clip, + jfloatArray path, jint n, + jboolean evenOdd, + jboolean aa, jint argb, + jint xorBits) +{ + std::vector d(n > 0 ? n : 1); + env->GetFloatArrayRegion(path, 0, n, &d[0]); + Pinned px(env, dst); + if (px.ptr == NULL) + return; + Target t = target(px.ptr, scan, alpha, clip, env); + fillPath(t, solid(argb, xorBits), &d[0], n, evenOdd, aa); +} + +/* Fills a device-space path with a linear gradient. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_fillPathGradient(JNIEnv *env, jclass, + jintArray dst, jint scan, jboolean alpha, + jintArray clip, jfloatArray path, jint n, jboolean evenOdd, jboolean aa, + jdoubleArray g, jint c1, jint c2, jboolean cyclic) +{ + std::vector d(n > 0 ? n : 1); + env->GetFloatArrayRegion(path, 0, n, &d[0]); + Paint p = gradient(env, g, c1, c2, cyclic); + Pinned px(env, dst); + if (px.ptr == NULL) + return; + Target t = target(px.ptr, scan, alpha, clip, env); + fillPath(t, p, &d[0], n, evenOdd, aa); +} + +/* Draws the source rectangle (sx, sy, sw, sh) of an int image through an + affine transform. inv maps device points to source points; box is + the device area to visit. extraAlpha is 0..255. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_drawImage(JNIEnv *env, jclass, + jintArray dst, jint scan, jboolean alpha, + jintArray clip, jintArray src, jint sscan, jboolean srcAlpha, jint sx, + jint sy, jint sw, jint sh, jdoubleArray inverse, jintArray box, + jboolean bilinear, jint extraAlpha) +{ + jdouble m[6]; + env->GetDoubleArrayRegion(inverse, 0, 6, m); + jint b[4]; + env->GetIntArrayRegion(box, 0, 4, b); + Pinned dp(env, dst); + if (dp.ptr == NULL) + return; + Target t = target(dp.ptr, scan, alpha, clip, env); + Pinned sp(env, src); + if (sp.ptr == NULL) + return; + const uint32_t *s = (const uint32_t *) sp.ptr + (size_t) sy * sscan + sx; + int x0 = b[0] > t.cx0 ? b[0] : t.cx0, y0 = b[1] > t.cy0 ? b[1] : t.cy0; + int x1 = b[0] + b[2] < t.cx1 ? b[0] + b[2] : t.cx1; + int y1 = b[1] + b[3] < t.cy1 ? b[1] + b[3] : t.cy1; + bool translate = m[0] == 1 && m[1] == 0 && m[2] == 0 && m[3] == 1 + && m[4] == floor(m[4]) && m[5] == floor(m[5]); + for (int y = y0; y < y1; y++) + { + uint32_t *row = t.px + (size_t) y * t.scan; + if (translate && extraAlpha == 255) + { + /* Pure integer translation: copy or blend a row. */ + int v = y + (int) m[5]; + if (v < 0 || v >= sh) + continue; + int u0 = x0 + (int) m[4], u1 = x1 + (int) m[4]; + int xs = x0; + if (u0 < 0) { xs -= u0; u0 = 0; } + if (u1 > sw) u1 = sw; + const uint32_t *srow = s + (size_t) v * sscan; + if (!srcAlpha && !t.alpha) + { + if (u1 > u0) + memcpy(row + xs, srow + u0, (u1 - u0) * 4); + } + else + for (int u = u0; u < u1; u++, xs++) + row[xs] = srcAlpha ? blend(srow[u], row[xs], t.alpha) + : srow[u] | 0xff000000; + continue; + } + for (int x = x0; x < x1; x++) + { + double dx = x + 0.5, dy = y + 0.5; + double u = m[0] * dx + m[2] * dy + m[4]; + double v = m[1] * dx + m[3] * dy + m[5]; + bool inside; + uint32_t c = bilinear + ? sampleBilinear(s, sscan, sw, sh, u, v, srcAlpha, &inside) + : sampleNearest(s, sscan, sw, sh, u, v, srcAlpha, &inside); + if (!inside) + continue; + if (extraAlpha != 255) + c = withAlpha(c, extraAlpha); + row[x] = blend(c, row[x], t.alpha); + } + } +} + +/* Copies a device rectangle by (dx, dy); source and target may overlap. */ +JNIEXPORT void JNICALL +Java_gnu_java_awt_peer_haiku_HaikuRaster_copyArea(JNIEnv *env, jclass, + jintArray dst, jint scan, + jint x, jint y, jint w, + jint h, jint dx, jint dy) +{ + Pinned d(env, dst); + if (d.ptr == NULL || w <= 0 || h <= 0) + return; + uint32_t *p = (uint32_t *) d.ptr; + if (dy > 0) + for (int r = h - 1; r >= 0; r--) + memmove(p + (size_t) (y + r + dy) * scan + x + dx, + p + (size_t) (y + r) * scan + x, (size_t) w * 4); + else + for (int r = 0; r < h; r++) + memmove(p + (size_t) (y + r + dy) * scan + x + dx, + p + (size_t) (y + r) * scan + x, (size_t) w * 4); +} + +} /* extern "C" */ diff --git a/native/jni/native-lib/cpnet.c b/native/jni/native-lib/cpnet.c index 534eaa46ed..eeced7d6a2 100644 --- a/native/jni/native-lib/cpnet.c +++ b/native/jni/native-lib/cpnet.c @@ -645,7 +645,12 @@ jint cpnet_getHostByName (JNIEnv *env, const char *hostname, cpnet_address ***ad { buf = (char *)JCL_malloc(env, buflen); -#ifdef HAVE_GETHOSTBYNAME_R +/* Use gethostbyname_r only if its declaration was found. Haiku has the + function in libnetwork but does not declare it. */ +#if defined(HAVE_GETHOSTBYNAME_R) \ + && (defined(HAVE_FUNC_GETHOSTBYNAME_R_6) \ + || defined(HAVE_FUNC_GETHOSTBYNAME_R_5) \ + || defined(HAVE_FUNC_GETHOSTBYNAME_R_3)) # if defined(HAVE_FUNC_GETHOSTBYNAME_R_6) ret = gethostbyname_r (hostname, &hret, buf, buflen, &result, &herr); # elif defined(HAVE_FUNC_GETHOSTBYNAME_R_5) diff --git a/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in b/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in index 03d1b5cd22..780c68060a 100644 --- a/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in +++ b/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in @@ -1 +1,2 @@ @GSTREAMER_MIXER_PROVIDER@ +@HAIKU_MIXER_PROVIDER@