1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Wayland module |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "compositor.h" |
52 | |
53 | #include <QMouseEvent> |
54 | #include <QKeyEvent> |
55 | #include <QTouchEvent> |
56 | |
57 | #include <QtWaylandCompositor/QWaylandXdgShellV5> |
58 | #include <QtWaylandCompositor/QWaylandWlShellSurface> |
59 | #include <QtWaylandCompositor/qwaylandseat.h> |
60 | #include <QtWaylandCompositor/qwaylanddrag.h> |
61 | |
62 | #include <QDebug> |
63 | #include <QOpenGLContext> |
64 | |
65 | #ifndef GL_TEXTURE_EXTERNAL_OES |
66 | #define GL_TEXTURE_EXTERNAL_OES 0x8D65 |
67 | #endif |
68 | |
69 | View::View(Compositor *compositor) |
70 | : m_compositor(compositor) |
71 | {} |
72 | |
73 | QOpenGLTexture *View::getTexture() |
74 | { |
75 | bool newContent = advance(); |
76 | QWaylandBufferRef buf = currentBuffer(); |
77 | if (!buf.hasContent()) |
78 | m_texture = nullptr; |
79 | if (newContent) { |
80 | m_texture = buf.toOpenGLTexture(); |
81 | if (surface()) { |
82 | m_size = surface()->destinationSize(); |
83 | m_origin = buf.origin() == QWaylandSurface::OriginTopLeft |
84 | ? QOpenGLTextureBlitter::OriginTopLeft |
85 | : QOpenGLTextureBlitter::OriginBottomLeft; |
86 | } |
87 | } |
88 | |
89 | return m_texture; |
90 | } |
91 | |
92 | QOpenGLTextureBlitter::Origin View::textureOrigin() const |
93 | { |
94 | return m_origin; |
95 | } |
96 | |
97 | QSize View::size() const |
98 | { |
99 | return surface() ? surface()->destinationSize() : m_size; |
100 | } |
101 | |
102 | bool View::isCursor() const |
103 | { |
104 | return surface() && surface()->isCursorSurface(); |
105 | } |
106 | |
107 | |
108 | void View::onXdgSetMaximized() |
109 | { |
110 | m_xdgSurface->sendMaximized(size: output()->geometry().size()); |
111 | |
112 | // An improvement here, would have been to wait for the commit after the ack_configure for the |
113 | // request above before moving the window. This would have prevented the window from being |
114 | // moved until the contents of the window had actually updated. This improvement is left as an |
115 | // exercise for the reader. |
116 | setPosition(QPoint(0, 0)); |
117 | } |
118 | |
119 | void View::onXdgUnsetMaximized() |
120 | { |
121 | m_xdgSurface->sendUnmaximized(); |
122 | } |
123 | |
124 | void View::onXdgSetFullscreen(QWaylandOutput* clientPreferredOutput) |
125 | { |
126 | QWaylandOutput *outputToFullscreen = clientPreferredOutput |
127 | ? clientPreferredOutput |
128 | : output(); |
129 | |
130 | m_xdgSurface->sendFullscreen(size: outputToFullscreen->geometry().size()); |
131 | |
132 | // An improvement here, would have been to wait for the commit after the ack_configure for the |
133 | // request above before moving the window. This would have prevented the window from being |
134 | // moved until the contents of the window had actually updated. This improvement is left as an |
135 | // exercise for the reader. |
136 | setPosition(outputToFullscreen->position()); |
137 | } |
138 | |
139 | void View::onOffsetForNextFrame(const QPoint &offset) |
140 | { |
141 | m_offset = offset; |
142 | setPosition(position() + offset); |
143 | } |
144 | |
145 | |
146 | void View::timerEvent(QTimerEvent *event) |
147 | { |
148 | if (event->timerId() != m_animationTimer.timerId()) |
149 | return; |
150 | |
151 | m_compositor->triggerRender(); |
152 | |
153 | if (m_animationCountUp) { |
154 | m_animationFactor += .08; |
155 | if (m_animationFactor > 1.0) { |
156 | m_animationFactor = 1.0; |
157 | m_animationTimer.stop(); |
158 | emit animationDone(); |
159 | } |
160 | } else { |
161 | m_animationFactor -= .08; |
162 | if (m_animationFactor < 0.01) { |
163 | m_animationFactor = 0.01; |
164 | m_animationTimer.stop(); |
165 | emit animationDone(); |
166 | } |
167 | } |
168 | } |
169 | |
170 | void View::startAnimation(bool countUp) |
171 | { |
172 | m_animationCountUp = countUp; |
173 | m_animationFactor = countUp ? .1 : 1.0; |
174 | m_animationTimer.start(msec: 20, obj: this); |
175 | } |
176 | |
177 | void View::cancelAnimation() |
178 | { |
179 | m_animationFactor = 1.0; |
180 | m_animationTimer.stop(); |
181 | } |
182 | |
183 | void View::onXdgUnsetFullscreen() |
184 | { |
185 | onXdgUnsetMaximized(); |
186 | } |
187 | |
188 | Compositor::Compositor(QWindow *window) |
189 | : m_window(window) |
190 | , m_wlShell(new QWaylandWlShell(this)) |
191 | , m_xdgShell(new QWaylandXdgShellV5(this)) |
192 | { |
193 | connect(sender: m_wlShell, signal: &QWaylandWlShell::wlShellSurfaceCreated, receiver: this, slot: &Compositor::onWlShellSurfaceCreated); |
194 | connect(sender: m_xdgShell, signal: &QWaylandXdgShellV5::xdgSurfaceCreated, receiver: this, slot: &Compositor::onXdgSurfaceCreated); |
195 | connect(sender: m_xdgShell, signal: &QWaylandXdgShellV5::xdgPopupRequested, receiver: this, slot: &Compositor::onXdgPopupRequested); |
196 | } |
197 | |
198 | Compositor::~Compositor() |
199 | { |
200 | } |
201 | |
202 | void Compositor::create() |
203 | { |
204 | QWaylandOutput *output = new QWaylandOutput(this, m_window); |
205 | QWaylandOutputMode mode(QSize(800, 600), 60000); |
206 | output->addMode(mode, preferred: true); |
207 | QWaylandCompositor::create(); |
208 | output->setCurrentMode(mode); |
209 | |
210 | connect(sender: this, signal: &QWaylandCompositor::surfaceCreated, receiver: this, slot: &Compositor::onSurfaceCreated); |
211 | connect(sender: defaultSeat(), signal: &QWaylandSeat::cursorSurfaceRequest, receiver: this, slot: &Compositor::adjustCursorSurface); |
212 | connect(sender: defaultSeat()->drag(), signal: &QWaylandDrag::dragStarted, receiver: this, slot: &Compositor::startDrag); |
213 | |
214 | connect(sender: this, signal: &QWaylandCompositor::subsurfaceChanged, receiver: this, slot: &Compositor::onSubsurfaceChanged); |
215 | } |
216 | |
217 | void Compositor::onSurfaceCreated(QWaylandSurface *surface) |
218 | { |
219 | connect(sender: surface, signal: &QWaylandSurface::surfaceDestroyed, receiver: this, slot: &Compositor::surfaceDestroyed); |
220 | connect(sender: surface, signal: &QWaylandSurface::hasContentChanged, receiver: this, slot: &Compositor::surfaceHasContentChanged); |
221 | connect(sender: surface, signal: &QWaylandSurface::redraw, receiver: this, slot: &Compositor::triggerRender); |
222 | |
223 | connect(sender: surface, signal: &QWaylandSurface::subsurfacePositionChanged, receiver: this, slot: &Compositor::onSubsurfacePositionChanged); |
224 | View *view = new View(this); |
225 | view->setSurface(surface); |
226 | view->setOutput(outputFor(window: m_window)); |
227 | m_views << view; |
228 | connect(sender: view, signal: &QWaylandView::surfaceDestroyed, receiver: this, slot: &Compositor::viewSurfaceDestroyed); |
229 | connect(sender: surface, signal: &QWaylandSurface::offsetForNextFrame, receiver: view, slot: &View::onOffsetForNextFrame); |
230 | } |
231 | |
232 | void Compositor::surfaceHasContentChanged() |
233 | { |
234 | QWaylandSurface *surface = qobject_cast<QWaylandSurface *>(object: sender()); |
235 | if (surface->hasContent()) { |
236 | if (surface->role() == QWaylandWlShellSurface::role() |
237 | || surface->role() == QWaylandXdgSurfaceV5::role() |
238 | || surface->role() == QWaylandXdgPopupV5::role()) { |
239 | defaultSeat()->setKeyboardFocus(surface); |
240 | } |
241 | } |
242 | triggerRender(); |
243 | } |
244 | |
245 | void Compositor::surfaceDestroyed() |
246 | { |
247 | triggerRender(); |
248 | } |
249 | |
250 | void Compositor::viewSurfaceDestroyed() |
251 | { |
252 | View *view = qobject_cast<View*>(object: sender()); |
253 | view->setBufferLocked(true); |
254 | view->startAnimation(countUp: false); |
255 | connect(sender: view, signal: &View::animationDone, receiver: this, slot: &Compositor::viewAnimationDone); |
256 | } |
257 | |
258 | |
259 | void Compositor::viewAnimationDone() |
260 | { |
261 | View *view = qobject_cast<View*>(object: sender()); |
262 | m_views.removeAll(t: view); |
263 | delete view; |
264 | } |
265 | |
266 | |
267 | View * Compositor::findView(const QWaylandSurface *s) const |
268 | { |
269 | for (View* view : m_views) { |
270 | if (view->surface() == s) |
271 | return view; |
272 | } |
273 | return nullptr; |
274 | } |
275 | |
276 | void Compositor::onWlShellSurfaceCreated(QWaylandWlShellSurface *wlShellSurface) |
277 | { |
278 | connect(sender: wlShellSurface, signal: &QWaylandWlShellSurface::startMove, receiver: this, slot: &Compositor::onStartMove); |
279 | connect(sender: wlShellSurface, signal: &QWaylandWlShellSurface::startResize, receiver: this, slot: &Compositor::onWlStartResize); |
280 | connect(sender: wlShellSurface, signal: &QWaylandWlShellSurface::setTransient, receiver: this, slot: &Compositor::onSetTransient); |
281 | connect(sender: wlShellSurface, signal: &QWaylandWlShellSurface::setPopup, receiver: this, slot: &Compositor::onSetPopup); |
282 | |
283 | View *view = findView(s: wlShellSurface->surface()); |
284 | Q_ASSERT(view); |
285 | view->m_wlShellSurface = wlShellSurface; |
286 | view->startAnimation(countUp: true); |
287 | } |
288 | |
289 | void Compositor::onXdgSurfaceCreated(QWaylandXdgSurfaceV5 *xdgSurface) |
290 | { |
291 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::startMove, receiver: this, slot: &Compositor::onStartMove); |
292 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::startResize, receiver: this, slot: &Compositor::onXdgStartResize); |
293 | |
294 | View *view = findView(s: xdgSurface->surface()); |
295 | Q_ASSERT(view); |
296 | view->m_xdgSurface = xdgSurface; |
297 | |
298 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::setMaximized, receiver: view, slot: &View::onXdgSetMaximized); |
299 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::setFullscreen, receiver: view, slot: &View::onXdgSetFullscreen); |
300 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::unsetMaximized, receiver: view, slot: &View::onXdgUnsetMaximized); |
301 | connect(sender: xdgSurface, signal: &QWaylandXdgSurfaceV5::unsetFullscreen, receiver: view, slot: &View::onXdgUnsetFullscreen); |
302 | view->startAnimation(countUp: true); |
303 | } |
304 | |
305 | void Compositor::onXdgPopupRequested(QWaylandSurface *surface, QWaylandSurface *parent, |
306 | QWaylandSeat *seat, const QPoint &position, |
307 | const QWaylandResource &resource) |
308 | { |
309 | Q_UNUSED(seat); |
310 | |
311 | QWaylandXdgPopupV5 * = new QWaylandXdgPopupV5(m_xdgShell, surface, parent, position, resource); |
312 | |
313 | View *view = findView(s: surface); |
314 | Q_ASSERT(view); |
315 | |
316 | View *parentView = findView(s: parent); |
317 | Q_ASSERT(parentView); |
318 | |
319 | view->setPosition(parentView->position() + position); |
320 | view->m_xdgPopup = xdgPopup; |
321 | } |
322 | |
323 | void Compositor::onStartMove() |
324 | { |
325 | closePopups(); |
326 | emit startMove(); |
327 | } |
328 | |
329 | void Compositor::onWlStartResize(QWaylandSeat *, QWaylandWlShellSurface::ResizeEdge edges) |
330 | { |
331 | closePopups(); |
332 | emit startResize(edge: int(edges), anchored: false); |
333 | } |
334 | |
335 | void Compositor::onXdgStartResize(QWaylandSeat *seat, |
336 | QWaylandXdgSurfaceV5::ResizeEdge edges) |
337 | { |
338 | Q_UNUSED(seat); |
339 | emit startResize(edge: int(edges), anchored: true); |
340 | } |
341 | |
342 | void Compositor::onSetTransient(QWaylandSurface *parent, const QPoint &relativeToParent, bool inactive) |
343 | { |
344 | Q_UNUSED(inactive); |
345 | |
346 | QWaylandWlShellSurface *wlShellSurface = qobject_cast<QWaylandWlShellSurface*>(object: sender()); |
347 | View *view = findView(s: wlShellSurface->surface()); |
348 | |
349 | if (view) { |
350 | raise(view); |
351 | View *parentView = findView(s: parent); |
352 | if (parentView) |
353 | view->setPosition(parentView->position() + relativeToParent); |
354 | } |
355 | } |
356 | |
357 | void Compositor::onSetPopup(QWaylandSeat *seat, QWaylandSurface *parent, const QPoint &relativeToParent) |
358 | { |
359 | Q_UNUSED(seat); |
360 | QWaylandWlShellSurface *surface = qobject_cast<QWaylandWlShellSurface*>(object: sender()); |
361 | View *view = findView(s: surface->surface()); |
362 | if (view) { |
363 | raise(view); |
364 | View *parentView = findView(s: parent); |
365 | if (parentView) |
366 | view->setPosition(parentView->position() + relativeToParent); |
367 | view->cancelAnimation(); |
368 | } |
369 | } |
370 | |
371 | void Compositor::onSubsurfaceChanged(QWaylandSurface *child, QWaylandSurface *parent) |
372 | { |
373 | View *view = findView(s: child); |
374 | View *parentView = findView(s: parent); |
375 | view->setParentView(parentView); |
376 | } |
377 | |
378 | void Compositor::onSubsurfacePositionChanged(const QPoint &position) |
379 | { |
380 | QWaylandSurface *surface = qobject_cast<QWaylandSurface*>(object: sender()); |
381 | if (!surface) |
382 | return; |
383 | View *view = findView(s: surface); |
384 | view->setPosition(position); |
385 | triggerRender(); |
386 | } |
387 | |
388 | void Compositor::triggerRender() |
389 | { |
390 | m_window->requestUpdate(); |
391 | } |
392 | |
393 | void Compositor::startRender() |
394 | { |
395 | QWaylandOutput *out = defaultOutput(); |
396 | if (out) |
397 | out->frameStarted(); |
398 | } |
399 | |
400 | void Compositor::endRender() |
401 | { |
402 | QWaylandOutput *out = defaultOutput(); |
403 | if (out) |
404 | out->sendFrameCallbacks(); |
405 | } |
406 | |
407 | void Compositor::updateCursor() |
408 | { |
409 | m_cursorView.advance(); |
410 | QImage image = m_cursorView.currentBuffer().image(); |
411 | if (!image.isNull()) |
412 | m_window->setCursor(QCursor(QPixmap::fromImage(image), m_cursorHotspotX, m_cursorHotspotY)); |
413 | } |
414 | |
415 | void Compositor::adjustCursorSurface(QWaylandSurface *surface, int hotspotX, int hotspotY) |
416 | { |
417 | if ((m_cursorView.surface() != surface)) { |
418 | if (m_cursorView.surface()) |
419 | disconnect(sender: m_cursorView.surface(), signal: &QWaylandSurface::redraw, receiver: this, slot: &Compositor::updateCursor); |
420 | if (surface) |
421 | connect(sender: surface, signal: &QWaylandSurface::redraw, receiver: this, slot: &Compositor::updateCursor); |
422 | } |
423 | |
424 | m_cursorView.setSurface(surface); |
425 | m_cursorHotspotX = hotspotX; |
426 | m_cursorHotspotY = hotspotY; |
427 | |
428 | if (surface && surface->hasContent()) |
429 | updateCursor(); |
430 | } |
431 | |
432 | void Compositor::() |
433 | { |
434 | m_wlShell->closeAllPopups(); |
435 | m_xdgShell->closeAllPopups(); |
436 | } |
437 | |
438 | void Compositor::handleMouseEvent(QWaylandView *target, QMouseEvent *me) |
439 | { |
440 | auto popClient = popupClient(); |
441 | if (target && me->type() == QEvent::MouseButtonPress |
442 | && popClient && popClient != target->surface()->client()) { |
443 | closePopups(); |
444 | } |
445 | |
446 | QWaylandSeat *seat = defaultSeat(); |
447 | QWaylandSurface *surface = target ? target->surface() : nullptr; |
448 | switch (me->type()) { |
449 | case QEvent::MouseButtonPress: |
450 | seat->sendMousePressEvent(button: me->button()); |
451 | if (surface != seat->keyboardFocus()) { |
452 | if (surface == nullptr |
453 | || surface->role() == QWaylandWlShellSurface::role() |
454 | || surface->role() == QWaylandXdgSurfaceV5::role() |
455 | || surface->role() == QWaylandXdgPopupV5::role()) { |
456 | seat->setKeyboardFocus(surface); |
457 | } |
458 | } |
459 | break; |
460 | case QEvent::MouseButtonRelease: |
461 | seat->sendMouseReleaseEvent(button: me->button()); |
462 | break; |
463 | case QEvent::MouseMove: |
464 | seat->sendMouseMoveEvent(surface: target, localPos: me->localPos(), outputSpacePos: me->globalPos()); |
465 | default: |
466 | break; |
467 | } |
468 | } |
469 | |
470 | void Compositor::handleResize(View *target, const QSize &initialSize, const QPoint &delta, int edge) |
471 | { |
472 | QWaylandWlShellSurface *wlShellSurface = target->m_wlShellSurface; |
473 | if (wlShellSurface) { |
474 | QWaylandWlShellSurface::ResizeEdge edges = QWaylandWlShellSurface::ResizeEdge(edge); |
475 | QSize newSize = wlShellSurface->sizeForResize(size: initialSize, delta, edges); |
476 | wlShellSurface->sendConfigure(size: newSize, edges); |
477 | } |
478 | |
479 | QWaylandXdgSurfaceV5 *xdgSurface = target->m_xdgSurface; |
480 | if (xdgSurface) { |
481 | QWaylandXdgSurfaceV5::ResizeEdge edges = static_cast<QWaylandXdgSurfaceV5::ResizeEdge>(edge); |
482 | QSize newSize = xdgSurface->sizeForResize(size: initialSize, delta, edge: edges); |
483 | xdgSurface->sendResizing(maxSize: newSize); |
484 | } |
485 | } |
486 | |
487 | void Compositor::startDrag() |
488 | { |
489 | QWaylandDrag *currentDrag = defaultSeat()->drag(); |
490 | Q_ASSERT(currentDrag); |
491 | View *iconView = findView(s: currentDrag->icon()); |
492 | iconView->setPosition(m_window->mapFromGlobal(pos: QCursor::pos())); |
493 | |
494 | emit dragStarted(dragIcon: iconView); |
495 | } |
496 | |
497 | void Compositor::handleDrag(View *target, QMouseEvent *me) |
498 | { |
499 | QPointF pos = me->localPos(); |
500 | QWaylandSurface *surface = nullptr; |
501 | if (target) { |
502 | pos -= target->position(); |
503 | surface = target->surface(); |
504 | } |
505 | QWaylandDrag *currentDrag = defaultSeat()->drag(); |
506 | currentDrag->dragMove(target: surface, pos); |
507 | if (me->buttons() == Qt::NoButton) { |
508 | m_views.removeOne(t: findView(s: currentDrag->icon())); |
509 | currentDrag->drop(); |
510 | } |
511 | } |
512 | |
513 | QWaylandClient *Compositor::() const |
514 | { |
515 | auto client = m_wlShell->popupClient(); |
516 | return client ? client : m_xdgShell->popupClient(); |
517 | } |
518 | |
519 | // We only have a flat list of views, plus pointers from child to parent, |
520 | // so maintaining a stacking order gets a bit complex. A better data |
521 | // structure is left as an exercise for the reader. |
522 | |
523 | static int findEndOfChildTree(const QList<View*> &list, int index) |
524 | { |
525 | int n = list.count(); |
526 | View *parent = list.at(i: index); |
527 | while (index + 1 < n) { |
528 | if (list.at(i: index+1)->parentView() != parent) |
529 | break; |
530 | index = findEndOfChildTree(list, index: index + 1); |
531 | } |
532 | return index; |
533 | } |
534 | |
535 | void Compositor::raise(View *view) |
536 | { |
537 | int startPos = m_views.indexOf(t: view); |
538 | int endPos = findEndOfChildTree(list: m_views, index: startPos); |
539 | |
540 | int n = m_views.count(); |
541 | int tail = n - endPos - 1; |
542 | |
543 | //bubble sort: move the child tree to the end of the list |
544 | for (int i = 0; i < tail; i++) { |
545 | int source = endPos + 1 + i; |
546 | int dest = startPos + i; |
547 | for (int j = source; j > dest; j--) |
548 | m_views.swapItemsAt(i: j, j: j-1); |
549 | } |
550 | } |
551 | |