1 # Copyright (C) 2007, One Laptop Per Child
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 from sugar
.activity
import activity
24 from sugar
.datastore
import datastore
25 from sugar
.graphics
import style
26 from sugar
.graphics
import units
27 from sugar
.graphics
.canvasicon
import CanvasIcon
29 from collapsedentry
import CollapsedEntry
30 from expandedentry
import ExpandedEntry
32 class ListView(gtk
.HBox
):
33 __gtype_name__
= 'ListView'
36 'expanded' : (bool, None, None, False,
37 gobject
.PARAM_READWRITE
)
41 'entry-activated': (gobject
.SIGNAL_RUN_FIRST
,
48 _INITIAL_CACHE_PAGES
= 3
51 self
._expanded
= False
57 gtk
.HBox
.__init
__(self
)
58 self
.set_flags(gtk
.HAS_FOCUS|gtk
.CAN_FOCUS
)
59 self
.connect('key-press-event', self
._key
_press
_event
_cb
)
61 self
._box
= hippo
.CanvasBox()
62 self
._box
.props
.orientation
= hippo
.ORIENTATION_VERTICAL
63 self
._box
.props
.background_color
= style
.COLOR_PANEL_GREY
.get_int()
64 self
._box
.props
.spacing
= units
.points_to_pixels(5)
65 self
._box
.props
.padding
= units
.points_to_pixels(5)
67 canvas
= hippo
.Canvas()
68 canvas
.set_root(self
._box
)
70 self
.pack_start(canvas
)
73 self
._vadjustment
= gtk
.Adjustment(value
=0,
77 page_incr
=ListView
._PAGE
_SIZE
,
78 page_size
=ListView
._PAGE
_SIZE
)
79 self
._vadjustment
.connect('value-changed', self
._vadjustment
_value
_changed
_cb
)
80 self
._vadjustment
.connect('changed', self
._vadjustment
_changed
_cb
)
82 self
._vscrollbar
= gtk
.VScrollbar(self
._vadjustment
)
83 self
.pack_end(self
._vscrollbar
, expand
=False, fill
=False)
84 self
._vscrollbar
.show()
86 self
.connect('scroll-event', self
._scroll
_event
_cb
)
89 self
._pressed
_button
= None
90 self
._press
_start
_x
= None
91 self
._press
_start
_y
= None
92 self
._last
_clicked
_entry
= None
93 canvas
.drag_source_set(0, [], 0)
94 canvas
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK |
95 gtk
.gdk
.POINTER_MOTION_HINT_MASK
)
96 canvas
.connect("motion_notify_event",
97 self
._canvas
_motion
_notify
_event
_cb
)
98 canvas
.connect("button_press_event",
99 self
._canvas
_button
_press
_event
_cb
)
100 canvas
.connect("drag_end", self
._drag
_end
_cb
)
101 canvas
.connect("drag_data_get", self
._drag
_data
_get
_cb
)
103 def _vadjustment_changed_cb(self
, vadjustment
):
104 logging
.debug('_vadjustment_changed_cb:\n \t%r\n \t%r\n \t%r\n \t%r\n \t%r\n' % \
105 (vadjustment
.props
.lower
, vadjustment
.props
.page_increment
,
106 vadjustment
.props
.page_size
, vadjustment
.props
.step_increment
,
107 vadjustment
.props
.upper
))
108 if (vadjustment
.props
.upper
- vadjustment
.props
.lower
) > vadjustment
.props
.page_size
:
109 self
._vscrollbar
.show()
111 self
._vscrollbar
.hide()
113 def _vadjustment_value_changed_cb(self
, vadjustment
):
116 value
= int(vadjustment
.props
.value
)
117 logging
.debug('value: %i offset: %i cache: %i' %
118 (value
, self
._offset
, len(self
._cache
)))
120 if value
< self
._offset
:
121 remaining_forward_entries
= 0
123 remaining_forward_entries
= self
._offset
+ len(self
._cache
) - value
125 if value
> self
._offset
+ len(self
._cache
):
126 remaining_backwards_entries
= 0
128 remaining_backwards_entries
= value
- self
._offset
130 total_count
= int(vadjustment
.props
.upper
)
131 last_cached_entry
= self
._offset
+ len(self
._cache
)
133 if remaining_forward_entries
< 1 and remaining_backwards_entries
< 1:
134 # Total cache miss: remake it
135 offset
= max(0, value
- ListView
._INITIAL
_CACHE
_PAGES
* \
136 ListView
._PAGE
_SIZE
/ 2)
137 logging
.debug('remaking cache, offset: %r' % offset
)
138 jobjects
, total_count
= datastore
.find(self
._query
,
141 limit
=ListView
._INITIAL
_CACHE
_PAGES
* ListView
._PAGE
_SIZE
)
142 self
._vadjustment
.props
.upper
= total_count
- 1
143 self
._vadjustment
.changed()
145 self
._cache
= jobjects
146 self
._offset
= offset
148 elif remaining_forward_entries
< 20 and last_cached_entry
< total_count
:
149 # Add one page to the end of cache
150 logging
.debug('appending one more page, offset: %r' % last_cached_entry
)
151 jobjects
, total_count
= datastore
.find(self
._query
,
153 offset
=last_cached_entry
,
154 limit
=ListView
._PAGE
_SIZE
)
155 self
._vadjustment
.props
.upper
= total_count
- 1
156 self
._vadjustment
.changed()
158 self
._cache
.extend(jobjects
)
160 if len(self
._cache
) > ListView
._MAX
_CACHE
_PAGES
* ListView
._PAGE
_SIZE
:
161 self
._offset
+= ListView
._PAGE
_SIZE
162 self
._cache
= self
._cache
[ListView
._PAGE
_SIZE
:]
164 elif remaining_backwards_entries
< 20 and self
._offset
>= ListView
._PAGE
_SIZE
:
165 # Add one page to the beginning of cache
166 logging
.debug('prepending one more page, offset: %r' %
167 (self
._offset
- ListView
._PAGE
_SIZE
))
168 jobjects
, total_count
= datastore
.find(self
._query
,
170 offset
=self
._offset
- ListView
._PAGE
_SIZE
,
171 limit
=ListView
._PAGE
_SIZE
)
172 self
._vadjustment
.props
.upper
= total_count
- 1
173 self
._vadjustment
.changed()
175 jobjects
.extend(self
._cache
)
176 self
._cache
= jobjects
177 self
._offset
-= ListView
._PAGE
_SIZE
179 max_cache_size
= ListView
._MAX
_CACHE
_PAGES
* ListView
._PAGE
_SIZE
180 if len(self
._cache
) > max_cache_size
:
181 self
._cache
= self
._cache
[:max_cache_size
]
183 self
._refresh
_view
(value
)
185 logging
.debug('_vadjustment_value_changed_cb %r\n' % (time
.time() - t
))
187 def _refresh_view(self
, position
):
188 # Refresh view and create the entries if they don't exist yet.
189 for i
in range(0, ListView
._PAGE
_SIZE
):
190 if (position
- self
._offset
+ i
) in range(0, len(self
._cache
)):
191 if i
>= len(self
._entries
):
192 entry
= CollapsedEntry(self
._cache
[position
- self
._offset
+ i
])
193 entry
.connect('entry-activated', self
._entry
_activated
_cb
)
194 self
._box
.append(entry
)
195 self
._entries
.append(entry
)
197 entry
= self
._entries
[i
]
198 entry
.jobject
= self
._cache
[position
- self
._offset
+ i
]
199 entry
.set_visible(True)
201 if i
< len(self
._entries
):
202 entry
= self
._entries
[i
]
203 entry
.set_visible(False)
205 def update_with_query(self
, query
):
211 self
._cache
, total_count
= datastore
.find(self
._query
,
213 limit
=3 * ListView
._PAGE
_SIZE
)
214 self
._vadjustment
.props
.upper
= total_count
- 1
215 self
._vadjustment
.changed()
216 self
._vadjustment
.props
.value
= 0
217 self
._vadjustment
.value_changed()
219 def do_set_property(self
, pspec
, value
):
220 if pspec
.name
== 'expanded':
221 if self
._expanded
!= value
:
222 self
._expanded
= value
225 def do_get_property(self
, pspec
):
226 if pspec
.name
== 'expanded':
227 return self
._expanded
229 def _entry_activated_cb(self
, entry_view
):
230 self
.emit('entry-activated', entry_view
)
232 def _scroll_event_cb(self
, hbox
, event
):
233 if event
.direction
== gtk
.gdk
.SCROLL_UP
:
234 if self
._vadjustment
.props
.value
> self
._vadjustment
.props
.lower
:
235 self
._vadjustment
.props
.value
-= 1
236 self
._vadjustment
.value_changed()
237 elif event
.direction
== gtk
.gdk
.SCROLL_DOWN
:
238 if self
._vadjustment
.props
.value
< \
239 (self
._vadjustment
.props
.upper
- self
._vadjustment
.props
.page_size
):
240 self
._vadjustment
.props
.value
+= 1
241 self
._vadjustment
.value_changed()
243 def do_focus(self
, direction
):
244 if not self
.is_focus():
249 def _key_press_event_cb(self
, widget
, event
):
250 keyname
= gtk
.gdk
.keyval_name(event
.keyval
)
252 if self
._vadjustment
.props
.value
> self
._vadjustment
.props
.lower
:
253 self
._vadjustment
.props
.value
-= 1
254 self
._vadjustment
.value_changed()
258 elif keyname
== 'Down':
259 if self
._vadjustment
.props
.value
< \
260 (self
._vadjustment
.props
.upper
- self
._vadjustment
.props
.page_size
):
261 self
._vadjustment
.props
.value
+= 1
262 self
._vadjustment
.value_changed()
266 elif keyname
== 'Page_Up':
267 new_position
= max(0, self
._vadjustment
.props
.value
- ListView
._PAGE
_SIZE
)
268 if new_position
!= self
._vadjustment
.props
.value
:
269 self
._vadjustment
.props
.value
= new_position
270 self
._vadjustment
.value_changed()
274 elif keyname
== 'Page_Down':
275 new_position
= min(self
._vadjustment
.props
.upper
- ListView
._PAGE
_SIZE
+ 1,
276 self
._vadjustment
.props
.value
+ ListView
._PAGE
_SIZE
)
277 if new_position
!= self
._vadjustment
.props
.value
:
278 self
._vadjustment
.props
.value
= new_position
279 self
._vadjustment
.value_changed()
286 # TODO: Dnd methods. This should be merged somehow inside hippo-canvas.
287 def _canvas_motion_notify_event_cb(self
, widget
, event
):
288 if not self
._pressed
_button
:
291 # if the mouse button is not pressed, no drag should occurr
292 if not event
.state
& gtk
.gdk
.BUTTON1_MASK
:
293 self
._pressed
_button
= None
296 logging
.debug("motion_notify_event_cb")
299 x
, y
, state
= event
.window
.get_pointer()
305 if widget
.drag_check_threshold(int(self
._press
_start
_x
),
306 int(self
._press
_start
_y
),
309 context
= widget
.drag_begin([('text/uri-list', 0, 0),
310 ('journal-object-id', 0, 0)],
317 def _drag_end_cb(self
, widget
, drag_context
):
318 logging
.debug("drag_end_cb")
319 self
._pressed
_button
= None
320 self
._press
_start
_x
= None
321 self
._press
_start
_y
= None
322 self
._last
_clicked
_entry
= None
324 def _drag_data_get_cb(self
, widget
, context
, selection
, targetType
, eventTime
):
325 logging
.debug("drag_data_get_cb: requested target " + selection
.target
)
327 jobject
= self
._last
_clicked
_entry
.jobject
328 if selection
.target
== 'text/uri-list':
329 selection
.set(selection
.target
, 8, jobject
.file_path
)
330 elif selection
.target
== 'journal-object-id':
331 selection
.set(selection
.target
, 8, jobject
.object_id
)
333 def _canvas_button_press_event_cb(self
, widget
, event
):
334 logging
.debug("button_press_event_cb")
336 if event
.button
== 1 and event
.type == gtk
.gdk
.BUTTON_PRESS
:
337 self
._last
_clicked
_entry
= self
._get
_entry
_at
_coords
(event
.x
, event
.y
)
338 if self
._last
_clicked
_entry
:
339 self
._pressed
_button
= event
.button
340 self
._press
_start
_x
= event
.x
341 self
._press
_start
_y
= event
.y
345 def _get_entry_at_coords(self
, x
, y
):
346 for entry
in self
._box
.get_children():
347 frame
= entry
.get_children()[1]
348 frame_x
, frame_y
= frame
.get_context().translate_to_widget(frame
)
349 frame_width
, frame_height
= frame
.get_allocation()
351 if (x
>= frame_x
) and (x
<= frame_x
+ frame_width
) and \
352 (y
>= frame_y
) and (y
<= frame_y
+ frame_height
):