Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtWindow.java
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4package org.qtproject.qt.android;
5
6import android.annotation.SuppressLint;
7import android.app.ActionBar;
8import android.app.Activity;
9import android.content.Context;
10import android.content.res.TypedArray;
11import android.graphics.Insets;
12
13import android.view.DisplayCutout;
14import android.view.GestureDetector;
15import android.view.MotionEvent;
16import android.view.Surface;
17import android.view.View;
18import android.view.ViewGroup;
19import android.view.ViewTreeObserver;
20import android.view.Window;
21import android.view.WindowInsets;
22import android.view.WindowInsetsController;
23import android.os.Build;
24
25import java.util.HashMap;
26
27@SuppressLint("ViewConstructor")
28class QtWindow extends QtLayout implements QtSurfaceInterface {
29 private View m_surfaceContainer;
30 private View m_nativeView;
31 private final HashMap<Integer, QtWindow> m_childWindows = new HashMap<>();
32 private QtWindow m_parentWindow;
33 private GestureDetector m_gestureDetector;
34 private final QtEditText m_editText;
35 private final QtInputConnection.QtInputConnectionListener m_inputConnectionListener;
36 private boolean m_firstSafeMarginsDelivered = false;
37 private int m_actionBarHeight = -1;
38
39 private static native void setSurface(int windowId, Surface surface);
40 private static native void safeAreaMarginsChanged(Insets insets, int id);
41 static native void windowFocusChanged(boolean hasFocus, int id);
42 static native void updateWindows();
43
44 QtWindow(Context context, boolean isForeignWindow, QtWindow parentWindow,
45 QtInputConnection.QtInputConnectionListener listener)
46 {
47 super(context);
48 setId(View.generateViewId());
49 m_inputConnectionListener = listener;
50 setParent(parentWindow);
51 setFocusableInTouchMode(true);
52 setDefaultFocusHighlightEnabled(false);
53 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
54
55 // Views are by default visible, but QWindows are not.
56 // We should ideally pick up the actual QWindow state here,
57 // but QWindowPrivate::setVisible() expects to control the
58 // order of events tightly, so we need to wait for a call
59 // to QAndroidPlatformWindow::setVisible().
60 setVisible(false);
61
62 if (!isForeignWindow && context instanceof Activity) {
63 // TODO QTBUG-122552 - Service keyboard input not implemented
64 m_editText = new QtEditText(context, listener);
65 m_editText.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
66 LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
67 ViewGroup.LayoutParams.WRAP_CONTENT);
68 QtNative.runAction(() -> addView(m_editText, layoutParams));
69 } else {
70 m_editText = null;
71 }
72
73 QtNative.runAction(() -> {
74 m_gestureDetector =
75 new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
76 @Override
77 public void onLongPress(MotionEvent event) {
78 QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY());
79 }
80 });
81 m_gestureDetector.setIsLongpressEnabled(true);
82 });
83
84 registerSafeAreaMarginsListener();
85 }
86
87 void registerSafeAreaMarginsListener()
88 {
89 if (!(getContext() instanceof QtActivityBase))
90 return;
91
92 setOnApplyWindowInsetsListener((view, insets) -> {
93 WindowInsets windowInsets = view.onApplyWindowInsets(insets);
94 reportSafeAreaMargins(windowInsets, getId());
95
96 return windowInsets;
97 });
98
99 // If the window is attached, try to directly deliver root insets
100 if (isAttachedToWindow()) {
101 WindowInsets insets = getRootWindowInsets();
102 if (insets != null) {
103 getRootView().post(() -> reportSafeAreaMargins(insets, getId()));
104 m_firstSafeMarginsDelivered = true;
105 }
106 } else { // Otherwise request it upon attachement
107 addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
108 @Override
109 public void onViewAttachedToWindow(View view) {
110 view.removeOnAttachStateChangeListener(this);
111 view.requestApplyInsets();
112 }
113
114 @Override
115 public void onViewDetachedFromWindow(View view) {}
116 });
117 }
118
119 // Further, tag into pre draw to deliver safe area margins early on
120 if (!m_firstSafeMarginsDelivered) {
121 ViewTreeObserver.OnPreDrawListener listener = new ViewTreeObserver.OnPreDrawListener() {
122 @Override
123 public boolean onPreDraw() {
124 if (isAttachedToWindow()) {
125 WindowInsets insets = getRootWindowInsets();
126 if (insets != null) {
127 getViewTreeObserver().removeOnPreDrawListener(this);
128 getRootView().post(() -> reportSafeAreaMargins(insets, getId()));
129 m_firstSafeMarginsDelivered = true;
130 return true;
131 }
132 }
133
134 requestApplyInsets();
135
136 return true;
137 }
138 };
139 getViewTreeObserver().addOnPreDrawListener(listener);
140 }
141
142 addOnLayoutChangeListener((view, l, t, r, b, oldl, oldt, oldr, oldb) -> {
143 WindowInsets insets = getRootWindowInsets();
144 if (insets != null)
145 getRootView().post(() -> reportSafeAreaMargins(insets, getId()));
146 });
147 }
148
149 @SuppressWarnings("deprecation")
150 Insets getSafeInsets(View view, WindowInsets insets)
151 {
152 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
153 int types = WindowInsets.Type.displayCutout() | WindowInsets.Type.systemBars();
154 return insets.getInsets(types);
155 }
156
157 // Android R and older
158 int left = insets.getSystemWindowInsetLeft();
159 int top = insets.getSystemWindowInsetTop();
160 int right = insets.getSystemWindowInsetRight();
161 int bottom = insets.getSystemWindowInsetBottom();
162
163 // Android 9 and 10 emulators don't seem to be able
164 // to handle this, but let's have the logic here anyway
165 DisplayCutout cutout = insets.getDisplayCutout();
166 if (cutout != null) {
167 left = Math.max(left, cutout.getSafeInsetLeft());
168 top = Math.max(top, cutout.getSafeInsetTop());
169 right = Math.max(right, cutout.getSafeInsetRight());
170 bottom = Math.max(bottom, cutout.getSafeInsetBottom());
171 }
172
173 // If a theme supports an action bar, sometimes it even if it's hidden
174 // the insets might report values including the bar's height, not entirely
175 // sure whether it's due to a delay or a bug, either way ensure the top
176 // margin doesn't include the action bar's height.
177 ActionBar actionBar = ((Activity) getContext()).getActionBar();
178 if (actionBar == null || !actionBar.isShowing()) {
179 int topWithoutActionBar = top - actionBarHeight();
180 if (topWithoutActionBar > 0)
181 top = topWithoutActionBar;
182 }
183
184 return Insets.of(left, top, right, bottom);
185 }
186
187 private void reportSafeAreaMargins(WindowInsets insets, int id)
188 {
189 View rootView = getRootView();
190
191 int[] rootLocation = new int[2];
192 rootView.getLocationOnScreen(rootLocation);
193 int rootX = rootLocation[0];
194 int rootY = rootLocation[1];
195
196 int[] windowLocation = new int[2];
197 getLocationOnScreen(windowLocation);
198 int windowX = windowLocation[0];
199 int windowY = windowLocation[1];
200
201 // Offset values of window from root
202 int leftOffset = windowX - rootX;
203 int topOffset = windowY - rootY;
204 int rightOffset = (rootX + rootView.getWidth()) - (windowX + getWidth());
205 int bottomOffset = (rootY + rootView.getHeight()) - (windowY + getHeight());
206
207 // Find the remaining minimum safe margins
208 Insets safeInsets = getSafeInsets(rootView, insets);
209 int left = Math.max(0, Math.min(safeInsets.left, safeInsets.left - leftOffset));
210 int top = Math.max(0, Math.min(safeInsets.top, safeInsets.top - topOffset));
211 int right = Math.max(0, Math.min(safeInsets.right, safeInsets.right - rightOffset));
212 int bottom = Math.max(0, Math.min(safeInsets.bottom, safeInsets.bottom - bottomOffset));
213
214 safeAreaMarginsChanged(Insets.of(left, top, right, bottom), id);
215 }
216
217 private int actionBarHeight()
218 {
219 if (m_actionBarHeight == -1) {
220 TypedArray ta = getContext().getTheme().obtainStyledAttributes(
221 new int[] { android.R.attr.actionBarSize });
222 try {
223 m_actionBarHeight = ta.getDimensionPixelSize(0, 0);
224 } finally {
225 ta.recycle();
226 }
227 }
228 return m_actionBarHeight;
229 }
230
231 @UsedFromNativeCode
232 void setVisible(boolean visible) {
233 QtNative.runAction(() -> setVisibility(visible ? View.VISIBLE : View.INVISIBLE));
234 }
235
236 @Override
237 public void onSurfaceChanged(Surface surface)
238 {
239 setSurface(getId(), surface);
240 }
241
242 @Override
243 public boolean onTouchEvent(MotionEvent event)
244 {
245 windowFocusChanged(true, getId());
246 if (m_editText != null && m_inputConnectionListener != null)
247 m_inputConnectionListener.onEditTextChanged(m_editText);
248
249 QtInputDelegate.sendTouchEvent(event, getId());
250 m_gestureDetector.onTouchEvent(event);
251 return true;
252 }
253
254 @Override
255 public boolean onTrackballEvent(MotionEvent event)
256 {
257 QtInputDelegate.sendTrackballEvent(event, getId());
258 return true;
259 }
260
261 @Override
262 public boolean onGenericMotionEvent(MotionEvent event)
263 {
264 return QtInputDelegate.sendGenericMotionEvent(event, getId());
265 }
266
267 @UsedFromNativeCode
268 void removeWindow()
269 {
270 if (m_parentWindow != null)
271 m_parentWindow.removeChildWindow(getId());
272 }
273
274 @UsedFromNativeCode
275 void createSurface(final boolean onTop,
276 final int imageDepth, final boolean isOpaque,
277 final int surfaceContainerType) // TODO constant for type
278 {
279 QtNative.runAction(()-> {
280 if (m_surfaceContainer != null)
281 removeView(m_surfaceContainer);
282
283 if (surfaceContainerType == 0) {
284 m_surfaceContainer = new QtSurface(getContext(), QtWindow.this,
285 onTop, imageDepth);
286 } else {
287 m_surfaceContainer = new QtTextureView(getContext(), QtWindow.this, isOpaque);
288 }
289 m_surfaceContainer.setLayoutParams(new QtLayout.LayoutParams(
290 ViewGroup.LayoutParams.MATCH_PARENT,
291 ViewGroup.LayoutParams.MATCH_PARENT));
292 // The surface container of this window will be added as the first of the stack.
293 // All other views are stacked based on the order they are created.
294 addView(m_surfaceContainer, 0);
295 });
296 }
297
298 @UsedFromNativeCode
299 void destroySurface()
300 {
301 QtNative.runAction(()-> {
302 if (m_surfaceContainer != null) {
303 removeView(m_surfaceContainer);
304 m_surfaceContainer = null;
305 }
306 }, false);
307 }
308
309 @UsedFromNativeCode
310 void setGeometry(final int x, final int y, final int w, final int h)
311 {
312 QtNative.runAction(()-> {
313 if (getContext() instanceof QtActivityBase)
314 setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
315 });
316 }
317
318 void addChildWindow(QtWindow window)
319 {
320 QtNative.runAction(()-> {
321 m_childWindows.put(window.getId(), window);
322 addView(window, getChildCount());
323 });
324 }
325
326 void removeChildWindow(int id)
327 {
328 QtNative.runAction(()-> {
329 if (m_childWindows.containsKey(id))
330 removeView(m_childWindows.remove(id));
331 });
332 }
333
334 @UsedFromNativeCode
335 void setNativeView(final View view)
336 {
337 QtNative.runAction(()-> {
338 if (m_nativeView != null)
339 removeView(m_nativeView);
340
341 m_nativeView = view;
342 m_nativeView.setLayoutParams(new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
343 ViewGroup.LayoutParams.MATCH_PARENT));
344 addView(m_nativeView);
345 });
346 }
347
348 @UsedFromNativeCode
349 void bringChildToFront(int id)
350 {
351 QtNative.runAction(()-> {
352 View view = m_childWindows.get(id);
353 if (view != null) {
354 if (getChildCount() > 0)
355 moveChild(view, getChildCount() - 1);
356 }
357 });
358 }
359
360 @UsedFromNativeCode
361 void bringChildToBack(int id) {
362 QtNative.runAction(()-> {
363 View view = m_childWindows.get(id);
364 if (view != null) {
365 moveChild(view, 0);
366 }
367 });
368 }
369
370 @UsedFromNativeCode
371 void removeNativeView()
372 {
373 QtNative.runAction(()-> {
374 if (m_nativeView != null) {
375 removeView(m_nativeView);
376 m_nativeView = null;
377 }
378 });
379 }
380
381 void setParent(QtWindow parentWindow)
382 {
383 if (m_parentWindow == parentWindow)
384 return;
385
386 if (m_parentWindow != null)
387 m_parentWindow.removeChildWindow(getId());
388
389 m_parentWindow = parentWindow;
390 if (m_parentWindow != null)
391 m_parentWindow.addChildWindow(this);
392 }
393
394 @UsedFromNativeCode
395 void updateFocusedEditText()
396 {
397 if (m_editText != null && m_inputConnectionListener != null)
398 m_inputConnectionListener.onEditTextChanged(m_editText);
399 }
400}
QPainter Context
memberSheet setVisible(index, false)
static const QString context()
Definition java.cpp:398
static void updateWindows(JNIEnv *env, jobject object)
static const double leftOffset
static const double rightOffset
GLboolean GLboolean GLboolean b
GLdouble GLdouble right
GLint GLint GLint GLint GLint x
GLfloat GLfloat GLfloat w
[0]
GLint GLint bottom
GLboolean r
GLdouble GLdouble GLdouble GLdouble top
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLint left
struct _cl_event * event
GLdouble GLdouble t
Definition qopenglext.h:243
static bool onTop(QWaylandQuickShellSurfaceItem *surf)
aWidget window() -> setWindowTitle("New Window Title")
[2]
file setParent(multiPart)
QQuickView * view
[0]
if(foo.startsWith("("+type+") 0x")) ... QString hello("hello")
[0]