From 027dcd194fa9cbffdf6330901aa403bcc6c5f998 Mon Sep 17 00:00:00 2001 From: duncanmcqueen Date: Wed, 14 Oct 2026 08:26:59 -0500 Subject: [PATCH 1/3] cpnet: use gethostbyname_r() only when its declaration is known On Haiku, gethostbyname_r() is in libnetwork but no header declares it, so configure finds the function but not the number of arguments, and cpnet.c stops with #error. Use gethostbyname() in that case. Co-Authored-By: Claude Opus 5.5 Signed-off-by: duncanmcqueen --- native/jni/native-lib/cpnet.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/native/jni/native-lib/cpnet.c b/native/jni/native-lib/cpnet.c index 534eaa46e..eeced7d6a 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) From 30d90343a727654293a7a37308e2dabe1446667a Mon Sep 17 00:00:00 2001 From: duncanmcqueen Date: Wed, 14 Oct 2026 08:27:00 -0500 Subject: [PATCH 2/3] Add an AWT toolkit and a sound mixer for Haiku gnu.java.awt.peer.haiku is an AWT toolkit for Haiku. Windows, menus, file panels, cursors, the clipboard, drag and drop, printing (through the print server) and input methods are native Haiku parts. The other widgets use the Swing peers. Haiku's font engine measures and draws the text. gnu.javax.sound.sampled.haiku is a javax.sound.sampled mixer with Clip and SourceDataLine lines that play through the Haiku media server. Both use libhaikupeer (native/jni/haiku-peer, -lbe -ltracker -lmedia -ltranslation). configure --enable-haiku-peer builds them; it is on by default when the host is Haiku, and it then makes HaikuToolkit the default toolkit and registers the mixer. Co-Authored-By: Claude Opus 5.5 Signed-off-by: duncanmcqueen --- configure.ac | 28 + gnu/java/awt/peer/haiku/HaikuButtonPeer.java | 97 + .../peer/haiku/HaikuCheckboxMenuItemPeer.java | 86 + .../awt/peer/haiku/HaikuCheckboxPeer.java | 185 + gnu/java/awt/peer/haiku/HaikuChoicePeer.java | 211 + gnu/java/awt/peer/haiku/HaikuClipboard.java | 93 + gnu/java/awt/peer/haiku/HaikuCursor.java | 77 + gnu/java/awt/peer/haiku/HaikuDialogPeer.java | 94 + gnu/java/awt/peer/haiku/HaikuDragDrop.java | 570 +++ gnu/java/awt/peer/haiku/HaikuEventPump.java | 758 ++++ .../awt/peer/haiku/HaikuFileDialogPeer.java | 205 + gnu/java/awt/peer/haiku/HaikuFontMetrics.java | 143 + gnu/java/awt/peer/haiku/HaikuFontPeer.java | 404 ++ gnu/java/awt/peer/haiku/HaikuFramePeer.java | 170 + gnu/java/awt/peer/haiku/HaikuGlyphVector.java | 282 ++ gnu/java/awt/peer/haiku/HaikuGraphics2D.java | 854 +++++ .../haiku/HaikuGraphicsConfiguration.java | 130 + .../awt/peer/haiku/HaikuGraphicsDevice.java | 78 + .../peer/haiku/HaikuGraphicsEnvironment.java | 104 + .../awt/peer/haiku/HaikuInputMethods.java | 238 ++ gnu/java/awt/peer/haiku/HaikuLabelPeer.java | 109 + gnu/java/awt/peer/haiku/HaikuListPeer.java | 307 ++ gnu/java/awt/peer/haiku/HaikuMenuBarPeer.java | 101 + .../awt/peer/haiku/HaikuMenuItemPeer.java | 166 + gnu/java/awt/peer/haiku/HaikuMenuPeer.java | 124 + gnu/java/awt/peer/haiku/HaikuNative.java | 364 ++ gnu/java/awt/peer/haiku/HaikuPanelPeer.java | 106 + .../awt/peer/haiku/HaikuPopupMenuPeer.java | 73 + gnu/java/awt/peer/haiku/HaikuPrintJob.java | 214 ++ gnu/java/awt/peer/haiku/HaikuPrinterJob.java | 242 ++ .../awt/peer/haiku/HaikuScrollPanePeer.java | 335 ++ .../awt/peer/haiku/HaikuScrollbarPeer.java | 130 + gnu/java/awt/peer/haiku/HaikuSwingPeer.java | 126 + .../awt/peer/haiku/HaikuSwingWrapper.java | 254 ++ .../awt/peer/haiku/HaikuTextFieldPeer.java | 107 + gnu/java/awt/peer/haiku/HaikuToolkit.java | 631 +++ .../awt/peer/haiku/HaikuTransferable.java | 301 ++ .../awt/peer/haiku/HaikuVolatileImage.java | 114 + gnu/java/awt/peer/haiku/HaikuWindowPeer.java | 503 +++ gnu/javax/sound/sampled/haiku/HaikuAudio.java | 111 + gnu/javax/sound/sampled/haiku/HaikuClip.java | 255 ++ gnu/javax/sound/sampled/haiku/HaikuLine.java | 234 ++ gnu/javax/sound/sampled/haiku/HaikuMixer.java | 230 ++ .../sampled/haiku/HaikuMixerProvider.java | 66 + .../sampled/haiku/HaikuSourceDataLine.java | 205 + gnu/javax/sound/sampled/haiku/Pcm.java | 174 + include/Makefile.am | 9 + native/jni/Makefile.am | 9 +- native/jni/haiku-peer/Makefile.am | 12 + .../gnu_java_awt_peer_haiku_HaikuNative.cpp | 3384 +++++++++++++++++ .../javax.sound.sampled.spi.MixerProvider.in | 1 + 51 files changed, 13802 insertions(+), 2 deletions(-) create mode 100644 gnu/java/awt/peer/haiku/HaikuButtonPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuCheckboxMenuItemPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuCheckboxPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuChoicePeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuClipboard.java create mode 100644 gnu/java/awt/peer/haiku/HaikuCursor.java create mode 100644 gnu/java/awt/peer/haiku/HaikuDialogPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuDragDrop.java create mode 100644 gnu/java/awt/peer/haiku/HaikuEventPump.java create mode 100644 gnu/java/awt/peer/haiku/HaikuFileDialogPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuFontMetrics.java create mode 100644 gnu/java/awt/peer/haiku/HaikuFontPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuFramePeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuGlyphVector.java create mode 100644 gnu/java/awt/peer/haiku/HaikuGraphics2D.java create mode 100644 gnu/java/awt/peer/haiku/HaikuGraphicsConfiguration.java create mode 100644 gnu/java/awt/peer/haiku/HaikuGraphicsDevice.java create mode 100644 gnu/java/awt/peer/haiku/HaikuGraphicsEnvironment.java create mode 100644 gnu/java/awt/peer/haiku/HaikuInputMethods.java create mode 100644 gnu/java/awt/peer/haiku/HaikuLabelPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuListPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuMenuBarPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuMenuItemPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuMenuPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuNative.java create mode 100644 gnu/java/awt/peer/haiku/HaikuPanelPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuPopupMenuPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuPrintJob.java create mode 100644 gnu/java/awt/peer/haiku/HaikuPrinterJob.java create mode 100644 gnu/java/awt/peer/haiku/HaikuScrollPanePeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuScrollbarPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuSwingPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuSwingWrapper.java create mode 100644 gnu/java/awt/peer/haiku/HaikuTextFieldPeer.java create mode 100644 gnu/java/awt/peer/haiku/HaikuToolkit.java create mode 100644 gnu/java/awt/peer/haiku/HaikuTransferable.java create mode 100644 gnu/java/awt/peer/haiku/HaikuVolatileImage.java create mode 100644 gnu/java/awt/peer/haiku/HaikuWindowPeer.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuAudio.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuClip.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuLine.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuMixer.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuMixerProvider.java create mode 100644 gnu/javax/sound/sampled/haiku/HaikuSourceDataLine.java create mode 100644 gnu/javax/sound/sampled/haiku/Pcm.java create mode 100644 native/jni/haiku-peer/Makefile.am create mode 100644 native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuNative.cpp diff --git a/configure.ac b/configure.ac index 2a9d5317f..72ee6513d 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 000000000..99775ea03 --- /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 000000000..a23ae3b70 --- /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 000000000..04f20d1a9 --- /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 000000000..965c5c01e --- /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 000000000..f9136a403 --- /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 000000000..07849525a --- /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 000000000..7421b8cf5 --- /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 000000000..59fe93c56 --- /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 000000000..da68df176 --- /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 000000000..20f2b4802 --- /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 000000000..f31f46d77 --- /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 000000000..2d01c9dca --- /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 000000000..11f094633 --- /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 000000000..99731e086 --- /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 000000000..5427c0812 --- /dev/null +++ b/gnu/java/awt/peer/haiku/HaikuGraphics2D.java @@ -0,0 +1,854 @@ +/* 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.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; + } + + /** 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) + { + if (pixels == null || c == null || c.getAlpha() != 255 + || ! isPlainSrcOver() || ! isSimpleTransform()) + return false; + Rectangle r = new Rectangle(x, y, w, h).intersection(deviceClip()); + if (r.width <= 0 || r.height <= 0) + return true; + int rgb = c.getRGB() | 0xFF000000; + int[] p = pixels; + for (int row = r.y; row < r.y + r.height; row++) + { + int off = row * scan + r.x; + if (xor != 0) + for (int i = off; i < off + r.width; i++) + p[i] ^= xor; + else + java.util.Arrays.fill(p, off, off + r.width, rgb); + } + markDirty(r.x, r.y, r.width, r.height); + return true; + } + + /** + * 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(); + if (pixels != null && p instanceof Color + && ((Color) p).getAlpha() == 255 && isPlainSrcOver()) + { + Rectangle clip = deviceClip(); + int rgb = ((Color) p).getRGB() | 0xFF000000; + int xor = xorBits((Color) p); + int dx = Math.abs(x1 - x0); + int dy = -Math.abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + int x = x0; + int y = y0; + int cx1 = clip.x + clip.width; + int cy1 = clip.y + clip.height; + while (true) + { + if (x >= clip.x && x < cx1 && y >= clip.y && y < cy1) + { + if (xor != 0) + pixels[y * scan + x] ^= xor; + else + pixels[y * scan + x] = rgb; + } + if (x == x1 && y == y1) + break; + int e2 = 2 * err; + if (e2 >= dy) + { + err += dy; + x += sx; + } + if (e2 <= dx) + { + err += dx; + y += sy; + } + } + markDirty(Math.min(x0, x1), Math.min(y0, y1), dx + 1, -dy + 1); + return; + } + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + 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); + } + } + + protected boolean rawDrawImage(Image img, int x, int y, ImageObserver obs) + { + img = unwrap(img); + if (pixels != null && img instanceof BufferedImage && isPlainSrcOver()) + { + BufferedImage src = (BufferedImage) img; + int type = src.getType(); + Raster sr = src.getRaster(); + DataBuffer sdb = sr.getDataBuffer(); + if ((type == BufferedImage.TYPE_INT_RGB + || type == BufferedImage.TYPE_INT_ARGB) + && sdb instanceof DataBufferInt && sdb.getNumBanks() == 1 + && sdb.getOffset() == 0 + && sr.getSampleModel() instanceof SinglePixelPackedSampleModel + && sr.getMinX() == 0 && sr.getMinY() == 0 + && sr.getSampleModelTranslateX() == 0 + && sr.getSampleModelTranslateY() == 0) + { + int[] sp = ((DataBufferInt) sdb).getData(); + int sscan = ((SinglePixelPackedSampleModel) sr.getSampleModel()) + .getScanlineStride(); + Rectangle r = new Rectangle(x, y, src.getWidth(), src.getHeight()) + .intersection(deviceClip()); + if (r.width > 0 && r.height > 0) + { + boolean opaque = type == BufferedImage.TYPE_INT_RGB; + for (int row = r.y; row < r.y + r.height; row++) + { + int soff = (row - y) * sscan + (r.x - x); + int doff = row * scan + r.x; + if (opaque) + { + if (hasAlpha) + for (int i = 0; i < r.width; i++) + pixels[doff + i] = sp[soff + i] | 0xFF000000; + else + System.arraycopy(sp, soff, pixels, doff, r.width); + } + else + for (int i = 0; i < r.width; i++) + pixels[doff + i] = blend(sp[soff + i], + pixels[doff + i]); + } + markDirty(r.x, r.y, r.width, r.height); + } + return true; + } + } + // Device coordinates; drawImage() applies the transform itself. + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + return drawImage(img, java.awt.geom.AffineTransform + .getTranslateInstance(x - tx, y - ty), 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) + { + return super.drawImage(unwrap(img), xform, obs); + } + + public boolean drawImage(Image img, int x, int y, ImageObserver obs) + { + return super.drawImage(unwrap(img), x, y, obs); + } + + // The generic AbstractGraphics2D code draws scaled images and images + // with a background colour through an ImagePaint, one pixel at a time. + // That is very slow on Kaffe, so these overloads scale the image here + // (nearest neighbour) and use the direct copy in rawDrawImage(). + + 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); + if (bg != null && img instanceof BufferedImage) + { + BufferedImage b = (BufferedImage) img; + fillBackground(bg, x, y, b.getWidth(), b.getHeight()); + return drawImage(img, x, y, obs); + } + return super.drawImage(img, x, y, bg, obs); + } + + public boolean drawImage(Image img, int x, int y, int w, int h, Color bg, + ImageObserver obs) + { + img = unwrap(img); + if (img instanceof BufferedImage && w > 0 && h > 0) + { + BufferedImage b = (BufferedImage) img; + return drawImage(b, x, y, x + w, y + h, 0, 0, b.getWidth(), + b.getHeight(), bg, obs); + } + return super.drawImage(img, x, y, w, h, 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); + } + + 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 (img instanceof BufferedImage && dx2 > dx1 && dy2 > dy1 + && sx2 > sx1 && sy2 > sy1) + { + BufferedImage b = HaikuToolkit.toDirect((BufferedImage) img); + int dw = dx2 - dx1; + int dh = dy2 - dy1; + BufferedImage part = scaled(b, sx1, sy1, sx2 - sx1, sy2 - sy1, dw, dh); + if (bg != null) + fillBackground(bg, dx1, dy1, dw, dh); + return drawImage(part, dx1, dy1, obs); + } + return super.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bg, + obs); + } + + /** Fills a rectangle (user coordinates) with c, keeping the paint. */ + private void fillBackground(Color c, int x, int y, int w, int h) + { + Paint saved = getPaint(); + setPaint(c); + fillRect(x, y, w, h); + setPaint(saved); + } + + /** + * Copies the source rectangle of an INT_RGB or INT_ARGB image, scaled + * to dw x dh with nearest neighbour sampling. The source rectangle is + * clipped to the image; pixels outside it are transparent. + */ + private static BufferedImage scaled(BufferedImage src, int sx, int sy, + int sw, int sh, int dw, int dh) + { + int iw = src.getWidth(); + int ih = src.getHeight(); + if (sx == 0 && sy == 0 && sw == iw && sh == ih && dw == iw && dh == ih) + return src; + BufferedImage out = new BufferedImage(dw, dh, + BufferedImage.TYPE_INT_ARGB); + int[] dst = ((DataBufferInt) out.getRaster().getDataBuffer()).getData(); + int[] row = new int[iw]; + boolean opaque = src.getType() == BufferedImage.TYPE_INT_RGB; + int lastSy = -1; + for (int y = 0; y < dh; y++) + { + int syy = sy + (int) ((long) y * sh / dh); + if (syy < 0 || syy >= ih) + continue; + if (syy != lastSy) + { + src.getRGB(0, syy, iw, 1, row, 0, iw); + lastSy = syy; + } + int off = y * dw; + for (int x = 0; x < dw; x++) + { + int sxx = sx + (int) ((long) x * sw / dw); + if (sxx >= 0 && sxx < iw) + dst[off + x] = opaque ? row[sxx] | 0xFF000000 : row[sxx]; + } + } + return out; + } + + /** 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; + // Read the source part that maps onto dst, then write it. + 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 000000000..a59f0bf39 --- /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 000000000..b1ed538c7 --- /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 000000000..d5287bbfa --- /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 000000000..15626cb6b --- /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 000000000..456acf1ff --- /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 000000000..1d56e5a40 --- /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 000000000..c565c43d8 --- /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 000000000..c7e709f2c --- /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 000000000..d96f7c095 --- /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 000000000..149b5ff83 --- /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 000000000..e6f117f70 --- /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 000000000..a576d8b1e --- /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 000000000..71de7b6c0 --- /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 000000000..b10493013 --- /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/HaikuScrollPanePeer.java b/gnu/java/awt/peer/haiku/HaikuScrollPanePeer.java new file mode 100644 index 000000000..4d352e6d0 --- /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 000000000..6c025b7ca --- /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 000000000..91a59d92a --- /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 000000000..7160ceaab --- /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 000000000..9b2b191a0 --- /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 000000000..49a4a4200 --- /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 000000000..91b8f4860 --- /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 000000000..a1223acbc --- /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 000000000..9f0be051c --- /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 000000000..43db6db99 --- /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 000000000..0d68b7de7 --- /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 000000000..a12b1d2f3 --- /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 000000000..518458efb --- /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 000000000..cfb68dc67 --- /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 000000000..152cefab4 --- /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 000000000..7b29df748 --- /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 37be91df4..414d19a41 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -122,6 +122,10 @@ 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_javax_sound_sampled_haiku_HaikuAudio.h + GCONF_PREFS_FILES = \ gnu_java_util_prefs_gconf_GConfNativePeer.h @@ -131,6 +135,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 +292,10 @@ 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_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 e8db33214..ba3924394 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 000000000..dad4dac4e --- /dev/null +++ b/native/jni/haiku-peer/Makefile.am @@ -0,0 +1,12 @@ +# 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 + +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 000000000..a3d2aead0 --- /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/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in b/resource/META-INF/services/javax.sound.sampled.spi.MixerProvider.in index 03d1b5cd2..780c68060 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@ From 5ec6e0a66bf70b9bd72562e381eb8a056afc83a3 Mon Sep 17 00:00:00 2001 From: duncanmcqueen Date: Wed, 14 Oct 2026 12:09:51 -0500 Subject: [PATCH 3/3] Haiku peer: draw shapes and images with native code On Kaffe's interpreter, per-pixel Java code is very slow. Draw the common cases natively, directly in the int[] pixels of the image: rectangles, 1-pixel lines, any path (curves flattened, both winding rules, with or without anti-aliasing), thin outlines as 1-pixel lines, solid colours and linear gradients, images through any affine transform (nearest neighbour or bilinear), and copyArea. Other cases (clips that are not rectangles, texture paints, other composite rules) use the Java code of AbstractGraphics2D as before. Measured on Kaffe (microseconds per operation, before / after): fillRect 100x100 1065 / 30, fillOval 7350 / 650, line of width 4 42100 / 310, gradient fill 160600 / 250, translucent image 16730 / 170, scaled image 327720 / 580, rotated image 2965950 / 200, copyArea 207620 / 60. Co-Authored-By: Claude Opus 5.5 Signed-off-by: duncanmcqueen --- gnu/java/awt/peer/haiku/HaikuGraphics2D.java | 542 ++++++++---- gnu/java/awt/peer/haiku/HaikuRaster.java | 192 +++++ include/Makefile.am | 3 + native/jni/haiku-peer/Makefile.am | 3 +- .../gnu_java_awt_peer_haiku_HaikuRaster.cpp | 795 ++++++++++++++++++ 5 files changed, 1348 insertions(+), 187 deletions(-) create mode 100644 gnu/java/awt/peer/haiku/HaikuRaster.java create mode 100644 native/jni/haiku-peer/gnu_java_awt_peer_haiku_HaikuRaster.cpp diff --git a/gnu/java/awt/peer/haiku/HaikuGraphics2D.java b/gnu/java/awt/peer/haiku/HaikuGraphics2D.java index 5427c0812..b28d6ced2 100644 --- a/gnu/java/awt/peer/haiku/HaikuGraphics2D.java +++ b/gnu/java/awt/peer/haiku/HaikuGraphics2D.java @@ -50,6 +50,7 @@ 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; @@ -342,6 +343,66 @@ 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) { @@ -350,78 +411,163 @@ private boolean fastFill(int x, int y, int w, int h, Color c) private boolean fastFill(int x, int y, int w, int h, Color c, int xor) { - if (pixels == null || c == null || c.getAlpha() != 255 - || ! isPlainSrcOver() || ! isSimpleTransform()) + int ea = srcOverAlpha(); + if (pixels == null || c == null || ea < 0) return false; - Rectangle r = new Rectangle(x, y, w, h).intersection(deviceClip()); - if (r.width <= 0 || r.height <= 0) + 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; - int rgb = c.getRGB() | 0xFF000000; - int[] p = pixels; - for (int row = r.y; row < r.y + r.height; row++) + boolean[] evenOdd = new boolean[1]; + float[] path = HaikuRaster.path(s, transform, evenOdd); + if (path.length == 0) + return true; + if (p instanceof Color) { - int off = row * scan + r.x; - if (xor != 0) - for (int i = off; i < off + r.width; i++) - p[i] ^= xor; - else - java.util.Arrays.fill(p, off, off + r.width, rgb); + int xor = xorBits((Color) p); + HaikuRaster.fillPath(pixels, scan, hasAlpha, clip, path, path.length, + evenOdd[0], aa && xor == 0, + argb((Color) p, ea), xor); } - markDirty(r.x, r.y, r.width, r.height); + 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); + } + /** - * Draws a 1-pixel line. Called with device coordinates; the default - * implementation would apply the transform a second time. + * 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(). */ - protected void rawDrawLine(int x0, int y0, int x1, int y1) + public void draw(java.awt.Shape s) { + java.awt.Stroke st = getStroke(); Paint p = getPaint(); - if (pixels != null && p instanceof Color - && ((Color) p).getAlpha() == 255 && isPlainSrcOver()) + 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) { - Rectangle clip = deviceClip(); - int rgb = ((Color) p).getRGB() | 0xFF000000; - int xor = xorBits((Color) p); - int dx = Math.abs(x1 - x0); - int dy = -Math.abs(y1 - y0); - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; - int err = dx + dy; - int x = x0; - int y = y0; - int cx1 = clip.x + clip.width; - int cy1 = clip.y + clip.height; - while (true) + int[] clip = nativeClip(); + if (clip != null) { - if (x >= clip.x && x < cx1 && y >= clip.y && y < cy1) - { - if (xor != 0) - pixels[y * scan + x] ^= xor; - else - pixels[y * scan + x] = rgb; - } - if (x == x1 && y == y1) - break; - int e2 = 2 * err; - if (e2 >= dy) + if (clip[2] > 0) { - err += dy; - x += sx; - } - if (e2 <= dx) - { - err += dx; - y += sy; + 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; } - markDirty(Math.min(x0, x1), Math.min(y0, y1), dx + 1, -dy + 1); + } + 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(); - draw(new java.awt.geom.Line2D.Float(x0 - tx, y0 - ty, x1 - tx, y1 - ty)); + super.draw(new java.awt.geom.Line2D.Float(x0 - tx, y0 - ty, x1 - tx, + y1 - ty)); } /** @@ -472,59 +618,114 @@ public void clearRect(int x, int y, int width, int height) } } - protected boolean rawDrawImage(Image img, int x, int y, ImageObserver obs) + /** + * 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) { - img = unwrap(img); - if (pixels != null && img instanceof BufferedImage && isPlainSrcOver()) + 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) { - BufferedImage src = (BufferedImage) img; - int type = src.getType(); - Raster sr = src.getRaster(); - DataBuffer sdb = sr.getDataBuffer(); - if ((type == BufferedImage.TYPE_INT_RGB - || type == BufferedImage.TYPE_INT_ARGB) - && sdb instanceof DataBufferInt && sdb.getNumBanks() == 1 - && sdb.getOffset() == 0 - && sr.getSampleModel() instanceof SinglePixelPackedSampleModel - && sr.getMinX() == 0 && sr.getMinY() == 0 - && sr.getSampleModelTranslateX() == 0 - && sr.getSampleModelTranslateY() == 0) - { - int[] sp = ((DataBufferInt) sdb).getData(); - int sscan = ((SinglePixelPackedSampleModel) sr.getSampleModel()) - .getScanlineStride(); - Rectangle r = new Rectangle(x, y, src.getWidth(), src.getHeight()) - .intersection(deviceClip()); - if (r.width > 0 && r.height > 0) - { - boolean opaque = type == BufferedImage.TYPE_INT_RGB; - for (int row = r.y; row < r.y + r.height; row++) - { - int soff = (row - y) * sscan + (r.x - x); - int doff = row * scan + r.x; - if (opaque) - { - if (hasAlpha) - for (int i = 0; i < r.width; i++) - pixels[doff + i] = sp[soff + i] | 0xFF000000; - else - System.arraycopy(sp, soff, pixels, doff, r.width); - } - else - for (int i = 0; i < r.width; i++) - pixels[doff + i] = blend(sp[soff + i], - pixels[doff + i]); - } - markDirty(r.x, r.y, r.width, r.height); - } - return true; - } + // 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); } - // Device coordinates; drawImage() applies the transform itself. - int tx = (int) transform.getTranslateX(); - int ty = (int) transform.getTranslateY(); - return drawImage(img, java.awt.geom.AffineTransform - .getTranslateInstance(x - tx, y - ty), obs); + 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); } /** @@ -543,18 +744,30 @@ private static Image unwrap(Image img) public boolean drawImage(Image img, java.awt.geom.AffineTransform xform, ImageObserver obs) { - return super.drawImage(unwrap(img), xform, 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) { - return super.drawImage(unwrap(img), x, y, 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. - // That is very slow on Kaffe, so these overloads scale the image here - // (nearest neighbour) and use the direct copy in rawDrawImage(). + // 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) @@ -566,26 +779,25 @@ public boolean drawImage(Image img, int x, int y, Color bg, ImageObserver obs) { img = unwrap(img); - if (bg != null && img instanceof BufferedImage) - { - BufferedImage b = (BufferedImage) img; - fillBackground(bg, x, y, b.getWidth(), b.getHeight()); - return drawImage(img, x, y, obs); - } - return super.drawImage(img, x, y, bg, obs); + 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); - if (img instanceof BufferedImage && w > 0 && h > 0) - { - BufferedImage b = (BufferedImage) img; - return drawImage(b, x, y, x + w, y + h, 0, 0, b.getWidth(), - b.getHeight(), bg, obs); - } - return super.drawImage(img, x, y, w, h, bg, obs); + 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, @@ -595,72 +807,25 @@ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 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 (img instanceof BufferedImage && dx2 > dx1 && dy2 > dy1 - && sx2 > sx1 && sy2 > sy1) - { - BufferedImage b = HaikuToolkit.toDirect((BufferedImage) img); - int dw = dx2 - dx1; - int dh = dy2 - dy1; - BufferedImage part = scaled(b, sx1, sy1, sx2 - sx1, sy2 - sy1, dw, dh); - if (bg != null) - fillBackground(bg, dx1, dy1, dw, dh); - return drawImage(part, dx1, dy1, obs); - } - return super.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bg, - obs); - } - - /** Fills a rectangle (user coordinates) with c, keeping the paint. */ - private void fillBackground(Color c, int x, int y, int w, int h) - { - Paint saved = getPaint(); - setPaint(c); - fillRect(x, y, w, h); - setPaint(saved); - } - - /** - * Copies the source rectangle of an INT_RGB or INT_ARGB image, scaled - * to dw x dh with nearest neighbour sampling. The source rectangle is - * clipped to the image; pixels outside it are transparent. - */ - private static BufferedImage scaled(BufferedImage src, int sx, int sy, - int sw, int sh, int dw, int dh) - { - int iw = src.getWidth(); - int ih = src.getHeight(); - if (sx == 0 && sy == 0 && sw == iw && sh == ih && dw == iw && dh == ih) - return src; - BufferedImage out = new BufferedImage(dw, dh, - BufferedImage.TYPE_INT_ARGB); - int[] dst = ((DataBufferInt) out.getRaster().getDataBuffer()).getData(); - int[] row = new int[iw]; - boolean opaque = src.getType() == BufferedImage.TYPE_INT_RGB; - int lastSy = -1; - for (int y = 0; y < dh; y++) - { - int syy = sy + (int) ((long) y * sh / dh); - if (syy < 0 || syy >= ih) - continue; - if (syy != lastSy) - { - src.getRGB(0, syy, iw, 1, row, 0, iw); - lastSy = syy; - } - int off = y * dw; - for (int x = 0; x < dw; x++) - { - int sxx = sx + (int) ((long) x * sw / dw); - if (sxx >= 0 && sxx < iw) - dst[off + x] = opaque ? row[sxx] | 0xFF000000 : row[sxx]; - } - } - return out; + 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. */ @@ -693,10 +858,15 @@ public void copyArea(int x, int y, int width, int height, int dx, int dy) src.height).intersection(deviceClip()); if (dst.width <= 0 || dst.height <= 0) return; - // Read the source part that maps onto dst, then write it. - 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); + 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); } diff --git a/gnu/java/awt/peer/haiku/HaikuRaster.java b/gnu/java/awt/peer/haiku/HaikuRaster.java new file mode 100644 index 000000000..8f814d9f6 --- /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/include/Makefile.am b/include/Makefile.am index 414d19a41..a87eb5f19 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -124,6 +124,7 @@ 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 = \ @@ -294,6 +295,8 @@ gnu_java_awt_peer_qt_QtToolkit.h: $(top_srcdir)/gnu/java/awt/peer/qt/QtToolkit.j $(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 diff --git a/native/jni/haiku-peer/Makefile.am b/native/jni/haiku-peer/Makefile.am index dad4dac4e..0ccf95bb2 100644 --- a/native/jni/haiku-peer/Makefile.am +++ b/native/jni/haiku-peer/Makefile.am @@ -3,7 +3,8 @@ nativeexeclib_LTLIBRARIES = libhaikupeer.la -libhaikupeer_la_SOURCES = gnu_java_awt_peer_haiku_HaikuNative.cpp +libhaikupeer_la_SOURCES = gnu_java_awt_peer_haiku_HaikuNative.cpp \ + gnu_java_awt_peer_haiku_HaikuRaster.cpp libhaikupeer_la_LIBADD = -lbe -ltracker -lmedia -ltranslation 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 000000000..298f5308c --- /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" */