File: _qquicklistview.py

package info (click to toggle)
lomiri-ui-toolkit 1.3.5110%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 26,436 kB
  • sloc: cpp: 85,830; python: 5,537; sh: 1,344; javascript: 919; ansic: 573; makefile: 204
file content (259 lines) | stat: -rw-r--r-- 10,397 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2012, 2013, 2014, 2015 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging
import time

from autopilot import (
    input,
    logging as autopilot_logging,
    platform
)
from autopilot.introspection import dbus

from lomiriuitoolkit._custom_proxy_objects import _common, _flickable

logger = logging.getLogger(__name__)


class QQuickListView(_flickable.QQuickFlickable):

    @autopilot_logging.log_action(logger.info)
    def click_element(self, object_name, direction=None):
        """Click an element from the list.

        It swipes the element into view if it's center is not visible.

        :parameter object_name: The objectName property of the element to
            click.
        :parameter direction: The direction where the element is, it can be
            either 'above' or 'below'. Default value is None, which means we
            don't know where the object is and we will need to search the full
            list.

        """
        try:
            element = self.select_single(objectName=object_name)
        except dbus.StateNotFoundError:
            # The element might be on a part of the list that hasn't been
            # created yet. We have to search for it scrolling the entire list.
            element = self._find_element(object_name, direction)
        self.swipe_child_into_view(element)
        self.pointing_device.click_object(element)
        return element

    @autopilot_logging.log_action(logger.info)
    def _find_element(self, object_name, direction=None):
        if direction is None:
            # We don't know where the object is so we start looking for it from
            # the top or left, depending on the ListView orientation.
            if self.orientation == 2:
                # 2 == ListView.Vertical
                self.swipe_to_top()
                direction = 'below'
            else:
                # orientation == 1 == ListView.Horizontal
                self.swipe_to_leftmost()
                direction = 'right'

        if direction == 'below':
            def fail_condition():
                return self.atYEnd
            swipe_method = self.swipe_to_show_more_below
        elif direction == 'above':
            def fail_condition():
                return self.atYBeginning
            swipe_method = self.swipe_to_show_more_above
        elif direction == 'left':
            def fail_condition():
                return self.atXBeginning
            swipe_method = self.swipe_to_show_more_left
        elif direction == 'right':
            def fail_condition():
                return self.atXEnd
            swipe_method = self.swipe_to_show_more_right
        else:
            raise _common.ToolkitException(
                'Invalid direction: {}'.format(direction))

        containers = self._get_containers()
        while not fail_condition():
            try:
                return self.select_single(objectName=object_name)
            except dbus.StateNotFoundError:
                swipe_method(containers)
        else:
            # We have reached the start or the end of the list.
            try:
                return self.select_single(objectName=object_name)
            except dbus.StateNotFoundError:
                raise _common.ToolkitException(
                    'List element with objectName "{}" not found.'.format(
                        object_name))

    def _is_element_cached(self, object_name):
        """Return the object with the given object name if the element
           is cached, or None if the element is not cached.
        """
        try:
            child = self.select_single(objectName=object_name)
        except dbus.StateNotFoundError:
            return None

        return child

    def _is_element_visible(self, object_name):
        """Return True if the center of element with the given object name
           is visible and False otherwise.
        """
        child = self._is_element_cached(object_name)
        if not child:
            return False

        containers = self._get_containers()
        return self._is_child_visible(child, containers)

    @autopilot_logging.log_action(logger.info)
    def enable_select_mode(self):
        """Default implementation to enable select mode. Performs a long tap
           over the first list item in the ListView. The delegates must be
           the new ListItem components.
        """
        self.swipe_to_top()
        first_item = self._get_first_item()
        self.pointing_device.click_object(first_item, press_duration=2)
        try:
            self.wait_select_single('QQuickItem',
                                    objectName='selection_panel0')
        except dbus.StateNotFoundError:
            raise _common.ToolkitException(
                'ListView delegate is not a ListItem or not in selectMode')

    def _get_first_item(self):
        """Returns the first item from the ListView."""
        items = self.get_children_by_type('QQuickItem')[0].get_children()
        items = sorted(items, key=lambda item: item.globalRect.y)
        return items[0]

    @autopilot_logging.log_action(logger.info)
    def drag_item(self, from_index, to_index):
        if platform.model() != 'Desktop':
            raise NotImplementedError(
                'Drag does not work on the phone because of bug #1266601')

        self._enable_drag_mode()

        both_items_visible = (
            self._is_drag_panel_visible(from_index) and
            self._is_drag_panel_visible(to_index))
        if both_items_visible:
            self._drag_both_items_visible(from_index, to_index)
        else:
            self._drag_item_with_pagination(from_index, to_index)
        # wait 1 second till all animations complete
        time.sleep(1)

    @autopilot_logging.log_action(logger.debug)
    def _enable_drag_mode(self):
        self.swipe_to_top()
        first_item = self._get_first_item()
        self.pointing_device.click_object(first_item, press_duration=2)
        self._get_drag_panel(wait=True)

    def _is_drag_panel_visible(self, index):
        try:
            drag_panel = self._get_drag_panel(index)
        except:
            return False
        else:
            return self.is_child_visible(drag_panel)

    def _get_drag_panel(self, index=0, wait=False):
        select_method = self.wait_select_single if wait else self.select_single
        try:
            return select_method(
                'QQuickItem', objectName='drag_panel{}'.format(index))
        except ValueError:
            # While we are dragging, each panel item is duplicated.
            # A ValueError will be raised in this scenario. We can take
            # any of the returned elements, as they have the same position.
            return self.select_many(
                'QQuickItem', objectName='drag_panel{}'.format(index))[0]

    def _drag_both_items_visible(self, from_index, to_index):
        from_drag_handler = self._get_drag_panel(from_index)
        to_drag_handler = self._get_drag_panel(to_index)
        start_x, start_y = input.get_center_point(from_drag_handler)
        stop_x, stop_y = input.get_center_point(to_drag_handler)
        self.pointing_device.drag(start_x, start_y, stop_x, stop_y)

    def _drag_item_with_pagination(self, from_index, to_index):
        # the from_index might be invisible
        from_item = self._find_element('listitem{}'.format(from_index))
        from_item.swipe_into_view()
        from_drag_panel = self._get_drag_panel(from_index)
        containers = self._get_containers()
        if from_index < to_index:
            self._drag_downwards(from_drag_panel, to_index, containers)
        else:
            self._drag_upwards(from_drag_panel, to_index, containers)

    def _drag_downwards(self, drag_panel, to_index, containers):
        visible_bottom = _flickable._get_visible_container_bottom(
            containers)
        start_x, start_y = input.get_center_point(drag_panel)

        self.pointing_device.move(start_x, start_y)
        self.pointing_device.press()
        stop_x = start_x
        self.pointing_device.move(stop_x, visible_bottom)
        to_drag_panel = self._get_drag_panel(to_index, wait=True)
        while not self.is_child_visible(to_drag_panel):
            pass
        # stop moving
        h = to_drag_panel.height / 2
        self.pointing_device.move(stop_x, self.pointing_device.y - h)
        # move under the item with the to_index
        to_drag_panel = self._get_drag_panel(to_index - 1)
        stop_y = (
            to_drag_panel.globalRect.y +
            to_drag_panel.globalRect.height)
        self.pointing_device.move(stop_x, stop_y)
        self.pointing_device.release()

    def _drag_upwards(self, handler, to_index, containers):
        visible_top = _flickable._get_visible_container_top(
            containers)
        start_x, start_y = input.get_center_point(handler)

        self.pointing_device.move(start_x, start_y)
        self.pointing_device.press()
        stop_x = start_x
        # Header alters topMargin, therefore drag only till that edge
        self.pointing_device.move(stop_x, visible_top + self.topMargin)
        to_drag_panel = self._get_drag_panel(to_index, wait=True)
        while not self.is_child_visible(to_drag_panel):
            pass
        # stop moving
        h = to_drag_panel.height / 2
        self.pointing_device.move(stop_x, self.pointing_device.y + h)
        # move after the item with the to_index
        to_drag_panel = self._get_drag_panel(to_index)
        stop_y = (
            to_drag_panel.globalRect.y +
            to_drag_panel.globalRect.height)
        self.pointing_device.move(stop_x, stop_y)
        self.pointing_device.release()