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 @Override
279 protected void onAttachedToWindow()
280 {
281 super.onAttachedToWindow();
282 setOnDragListener(QtDragManager.getInstance());
283 }
284
285 @Override
286 protected void onDetachedFromWindow()
287 {
288 QtDragManager.getInstance().onSourceWindowDetached(this);
289 setOnDragListener(null);
290 super.onDetachedFromWindow();
291 }
292
293 @UsedFromNativeCode
294 void removeWindow()
295 {
296 if (m_parentWindow != null)
297 m_parentWindow.removeChildWindow(getId());
298 }
299
300 @UsedFromNativeCode
301 void createSurface(final boolean onTop,
302 final int imageDepth, final boolean isOpaque,
303 final int surfaceContainerType) // TODO constant for type
304 {
305 QtNative.runAction(()-> {
306 if (m_surfaceContainer != null)
307 removeView(m_surfaceContainer);
308
309 if (surfaceContainerType == 0) {
310 m_surfaceContainer = new QtSurface(getContext(), QtWindow.this,
311 onTop, imageDepth);
312 } else {
313 m_surfaceContainer = new QtTextureView(getContext(), QtWindow.this, isOpaque);
314 }
315 m_surfaceContainer.setLayoutParams(new QtLayout.LayoutParams(
316 ViewGroup.LayoutParams.MATCH_PARENT,
317 ViewGroup.LayoutParams.MATCH_PARENT));
318 // The surface container of this window will be added as the first of the stack.
319 // All other views are stacked based on the order they are created.
320 addView(m_surfaceContainer, 0);
321 });
322 }
323
324 @UsedFromNativeCode
325 void destroySurface()
326 {
327 QtNative.runAction(()-> {
328 if (m_surfaceContainer != null) {
329 removeView(m_surfaceContainer);
330 m_surfaceContainer = null;
331 }
332 }, false);
333 }
334
335 @UsedFromNativeCode
336 void setGeometry(final int x, final int y, final int w, final int h)
337 {
338 QtNative.runAction(()-> {
339 if (getContext() instanceof QtActivityBase)
340 setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
341 });
342 }
343
344 void addChildWindow(QtWindow window)
345 {
346 QtNative.runAction(()-> {
347 m_childWindows.put(window.getId(), window);
348 addView(window, getChildCount());
349 });
350 }
351
352 void removeChildWindow(int id)
353 {
354 QtNative.runAction(()-> {
355 if (m_childWindows.containsKey(id))
356 removeView(m_childWindows.remove(id));
357 });
358 }
359
360 @UsedFromNativeCode
361 void setNativeView(final View view)
362 {
363 QtNative.runAction(()-> {
364 if (m_nativeView != null)
365 removeView(m_nativeView);
366
367 m_nativeView = view;
368 m_nativeView.setLayoutParams(new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
369 ViewGroup.LayoutParams.MATCH_PARENT));
370 addView(m_nativeView);
371 });
372 }
373
374 @UsedFromNativeCode
375 void bringChildToFront(int id)
376 {
377 QtNative.runAction(()-> {
378 View view = m_childWindows.get(id);
379 if (view != null) {
380 if (getChildCount() > 0)
381 moveChild(view, getChildCount() - 1);
382 }
383 });
384 }
385
386 @UsedFromNativeCode
387 void bringChildToBack(int id) {
388 QtNative.runAction(()-> {
389 View view = m_childWindows.get(id);
390 if (view != null) {
391 moveChild(view, 0);
392 }
393 });
394 }
395
396 @UsedFromNativeCode
397 void removeNativeView()
398 {
399 QtNative.runAction(()-> {
400 if (m_nativeView != null) {
401 removeView(m_nativeView);
402 m_nativeView = null;
403 }
404 });
405 }
406
407 void setParent(QtWindow parentWindow)
408 {
409 if (m_parentWindow == parentWindow)
410 return;
411
412 if (m_parentWindow != null)
413 m_parentWindow.removeChildWindow(getId());
414
415 m_parentWindow = parentWindow;
416 if (m_parentWindow != null)
417 m_parentWindow.addChildWindow(this);
418 }
419
420 @UsedFromNativeCode
421 void updateFocusedEditText()
422 {
423 if (m_editText != null && m_inputConnectionListener != null)
424 m_inputConnectionListener.onEditTextChanged(m_editText);
425 }
426}
QPainter Context
memberSheet setVisible(index, false)
static const QString context()
Definition java.cpp:396
Napi::TypedArray TypedArray
Definition qnapi_p.h:76
static void updateWindows(JNIEnv *env, jobject object)
self window
[2]
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)
file setParent(multiPart)
QQuickView * view
[0]
if(foo.startsWith("("+type+") 0x")) ... QString hello("hello")
[0]