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