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