Package dtk :: Package ui :: Module listview

Source Code for Module dtk.ui.listview

   1  #! /usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3   
   4  # Copyright (C) 2011 ~ 2012 Deepin, Inc. 
   5  #               2011 ~ 2012 Wang Yong 
   6  #  
   7  # Author:     Wang Yong <lazycat.manatee@gmail.com> 
   8  # Maintainer: Wang Yong <lazycat.manatee@gmail.com> 
   9  #  
  10  # This program is free software: you can redistribute it and/or modify 
  11  # it under the terms of the GNU General Public License as published by 
  12  # the Free Software Foundation, either version 3 of the License, or 
  13  # any later version. 
  14  #  
  15  # This program is distributed in the hope that it will be useful, 
  16  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  17  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  18  # GNU General Public License for more details. 
  19  #  
  20  # You should have received a copy of the GNU General Public License 
  21  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  22   
  23  from cache_pixbuf import CachePixbuf 
  24  from constant import DEFAULT_FONT_SIZE, ALIGN_END, ALIGN_START 
  25  from contextlib import contextmanager  
  26  from draw import draw_pixbuf, draw_vlinear, draw_text 
  27  from keymap import get_keyevent_name, has_ctrl_mask, has_shift_mask 
  28  from skin_config import skin_config 
  29  from theme import ui_theme 
  30  import copy 
  31  import gobject 
  32  import gtk 
  33  import os 
  34  import pango 
  35  import subprocess 
  36  import tempfile 
  37  from utils import (map_value, mix_list_max, get_content_size,  
  38                     unzip, last_index, set_cursor, get_match_parent,  
  39                     remove_file, 
  40                     cairo_state, get_event_coords, is_left_button,  
  41                     is_right_button, is_double_click, is_single_click,  
  42                     is_in_rect, get_disperse_index, get_window_shadow_size) 
43 44 -class ListView(gtk.DrawingArea):
45 ''' 46 Powerful listview widget. 47 48 @undocumented: update_redraw_request_list 49 @undocumented: set_adjust_cursor 50 @undocumented: realize_list_view 51 @undocumented: size_allocate_list_view 52 @undocumented: expose_list_view 53 @undocumented: motion_list_view 54 @undocumented: hover_item 55 @undocumented: button_press_list_view 56 @undocumented: click_item 57 @undocumented: button_release_list_view 58 @undocumented: release_item 59 @undocumented: drag_select_items_at_cursor 60 @undocumented: leave_list_view 61 @undocumented: key_press_list_view 62 @undocumented: key_release_list_view 63 64 ''' 65 66 SORT_DESCENDING = False 67 SORT_ASCENDING = True 68 SORT_PADDING_X = 5 69 TITLE_PADDING = 5 70 71 __gsignals__ = { 72 "delete-select-items" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), 73 "button-press-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int, int)), 74 "single-click-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int, int)), 75 "double-click-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int, int)), 76 "motion-notify-item" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, int, int, int)), 77 "right-press-items" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, int, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)), 78 } 79
80 - def __init__(self, 81 sorts=[], 82 drag_data=None, # (targets, actions, button_masks) 83 enable_multiple_select=True, 84 enable_drag_drop=True, 85 drag_icon_pixbuf=ui_theme.get_pixbuf("listview/drag_preview.png"), 86 drag_out_offset=50, 87 ):
88 ''' 89 Initialize ListView widget. 90 91 @param sorts: Sort function for column of listview. 92 @param drag_data: Drag data for drag data from listview, format: (targets, actions, button_masks) 93 @param enable_multiple_select: Whether allow user select multiple item, default is True. 94 @param enable_drag_drop: Whether allow user drag drop on listview, default is True. 95 @param drag_icon_pixbuf: Drag icon. 96 @param drag_out_offset: Out offset value to trigger drag action on listview, default is 50 pixel, if cursor not drag more than 50 pixel, listview won't think it is B{drag out action}. 97 ''' 98 # Init. 99 gtk.DrawingArea.__init__(self) 100 self.sorts = sorts 101 self.drag_data = drag_data 102 self.add_events(gtk.gdk.ALL_EVENTS_MASK) 103 self.set_can_focus(True) # can focus to response key-press signal 104 self.items = [] 105 self.cell_widths = [] 106 self.cell_min_widths = [] 107 self.cell_min_heights = [] 108 self.left_button_press = False 109 self.hover_row = None 110 self.titles = None 111 self.title_sorts = None 112 self.single_click_row = None 113 self.double_click_row = None 114 self.start_select_item = None 115 self.enable_drag_drop = enable_drag_drop 116 self.start_drag = False 117 self.drag_item = None 118 self.highlight_item = None 119 self.before_drag_items = [] 120 self.after_drag_items = [] 121 self.title_offset_y = 0 122 self.item_height = 0 123 self.press_ctrl = False 124 self.press_shift = False 125 self.select_rows = [] 126 self.start_select_row = None 127 self.press_in_select_rows = None 128 self.expand_column = None 129 self.drag_reference_row = None 130 self.drag_preview_pixbuf = None 131 self.drag_line_pixbuf = CachePixbuf() 132 self.enable_multiple_select = enable_multiple_select 133 self.drag_icon_pixbuf = drag_icon_pixbuf 134 self.drag_out_offset = drag_out_offset 135 136 # Signal. 137 self.connect("realize", self.realize_list_view) 138 self.connect("size-allocate", self.size_allocate_list_view) 139 self.connect("expose-event", self.expose_list_view) 140 self.connect("motion-notify-event", self.motion_list_view) 141 self.connect("button-press-event", self.button_press_list_view) 142 self.connect("button-release-event", self.button_release_list_view) 143 self.connect("leave-notify-event", self.leave_list_view) 144 self.connect("key-press-event", self.key_press_list_view) 145 self.connect("key-release-event", self.key_release_list_view) 146 147 # Unset drag source if drag data is not None. 148 if self.drag_data: 149 # We will manually start drags, details look function `hover-item`. 150 self.drag_source_unset() 151 152 # Redraw. 153 self.redraw_request_list = [] 154 self.redraw_delay = 100 # 100 milliseconds should be enough for redraw 155 gtk.timeout_add(self.redraw_delay, self.update_redraw_request_list) 156 157 # Add key map. 158 self.keymap = { 159 "Home" : self.select_first_item, 160 "End" : self.select_last_item, 161 "Page_Up" : self.scroll_page_up, 162 "Page_Down" : self.scroll_page_down, 163 "Return" : self.double_click_item, 164 "Up" : self.select_prev_item, 165 "Down" : self.select_next_item, 166 "Delete" : self.delete_select_items, 167 "Shift + Up" : self.select_to_prev_item, 168 "Shift + Down" : self.select_to_next_item, 169 "Shift + Home" : self.select_to_first_item, 170 "Shift + End" : self.select_to_last_item, 171 "Ctrl + a" : self.select_all_items, 172 }
173
174 - def set_expand_column(self, column):
175 ''' 176 Set expand column. 177 178 @param column: Column index to expand space. 179 ''' 180 self.expand_column = column
181
182 - def update_redraw_request_list(self):
183 ''' 184 Internal fucntion to update redraw request list. 185 ''' 186 # Redraw when request list is not empty. 187 if len(self.redraw_request_list) > 0: 188 # Get offset. 189 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 190 191 # Get viewport index. 192 start_y = offset_y - self.title_offset_y 193 end_y = offset_y + viewport.allocation.height - self.title_offset_y 194 start_index = max(start_y / self.item_height, 0) 195 if (end_y - end_y / self.item_height * self.item_height) == 0: 196 end_index = min(end_y / self.item_height + 1, len(self.items)) 197 else: 198 end_index = min(end_y / self.item_height + 2, len(self.items)) 199 200 # Redraw whole viewport area once found any request item in viewport. 201 viewport_range = range(start_index, end_index) 202 for item in self.redraw_request_list: 203 if item.get_index() in viewport_range: 204 self.queue_draw() 205 break 206 207 # Clear redraw request list. 208 self.redraw_request_list = [] 209 210 return True
211
212 - def add_titles(self, titles, title_height=24):
213 ''' 214 Add titles. 215 216 @param titles: A list of title. 217 @param title_height: Height of title. 218 ''' 219 self.titles = titles 220 self.title_select_column = None 221 self.title_adjust_column = None 222 self.title_separator_width = 2 223 self.title_clicks = map_value(self.titles, lambda _: False) 224 self.title_sort_column = None 225 self.title_sorts = map_value(self.titles, lambda _: self.SORT_DESCENDING) 226 self.set_title_height(title_height) 227 228 (title_widths, title_heights) = self.get_title_sizes() 229 self.cell_widths = mix_list_max(self.cell_widths, title_widths) 230 self.cell_min_widths = mix_list_max(self.cell_min_widths, title_widths) 231 self.cell_min_heights = mix_list_max(self.cell_min_heights, title_heights) 232 233 self.title_cache_pixbufs = [] 234 for title in self.titles: 235 self.title_cache_pixbufs.append(CachePixbuf())
236
237 - def get_title_sizes(self):
238 ''' 239 Get title sizes. 240 241 @return: Return title size, as format (title_width, title_height). 242 ''' 243 widths = [] 244 heights = [] 245 if self.titles != None: 246 for title in self.titles: 247 (title_width, title_height) = get_content_size(title, DEFAULT_FONT_SIZE) 248 widths.append(title_width + self.TITLE_PADDING * 2) 249 heights.append(title_height) 250 251 return (widths, heights)
252
253 - def add_items(self, items, insert_pos=None, sort_list=False):
254 ''' 255 Add items in listview. 256 257 @param items: A list of item. 258 @param insert_pos: The position to insert, default is None will insert new item at end of list. 259 @param sort_list: Whether sort list after insert, default is False. 260 ''' 261 # Add new items. 262 with self.keep_select_status(): 263 if insert_pos == None: 264 self.items += items 265 else: 266 self.items = self.items[0:insert_pos] + items + self.items[insert_pos::] 267 268 # Re-calcuate. 269 (title_widths, title_heights) = self.get_title_sizes() 270 sort_pixbuf = ui_theme.get_pixbuf("listview/sort_descending.png").get_pixbuf() 271 sort_icon_width = sort_pixbuf.get_width() + self.SORT_PADDING_X * 2 272 sort_icon_height = sort_pixbuf.get_height() 273 274 cell_min_sizes = [] 275 for item in items: 276 # Binding redraw request signal. 277 item.connect("redraw_request", self.redraw_item) 278 279 sizes = item.get_column_sizes() 280 if cell_min_sizes == []: 281 cell_min_sizes = sizes 282 else: 283 for (index, (width, height)) in enumerate(sizes): 284 if self.titles == None: 285 max_width = max([cell_min_sizes[index][0], width]) 286 max_height = max([cell_min_sizes[index][1], sort_icon_height, height]) 287 else: 288 max_width = max([cell_min_sizes[index][0], title_widths[index] + sort_icon_width * 2, width]) 289 max_height = max([cell_min_sizes[index][1], title_heights[index], sort_icon_height, height]) 290 291 cell_min_sizes[index] = (max_width, max_height) 292 293 # Get value. 294 (cell_min_widths, cell_min_heights) = unzip(cell_min_sizes) 295 self.cell_min_widths = mix_list_max(self.cell_min_widths, cell_min_widths) 296 self.cell_min_heights = mix_list_max(self.cell_min_heights, cell_min_heights) 297 self.cell_widths = mix_list_max(self.cell_widths, copy.deepcopy(cell_min_widths)) 298 299 self.item_height = max(self.item_height, max(copy.deepcopy(cell_min_heights))) 300 301 # Sort list if sort_list enable. 302 if sort_list and self.sorts != [] and self.title_sort_column != None: 303 if self.title_sorts == None: 304 reverse_order = False 305 else: 306 reverse_order = self.title_sorts[0] 307 308 with self.keep_select_status(): 309 self.items = sorted(self.items, 310 key=self.sorts[self.title_sort_column][0], 311 cmp=self.sorts[self.title_sort_column][1], 312 reverse=reverse_order) 313 314 # Update vertical adjustment. 315 self.update_vadjustment() 316 317 # Update item index. 318 self.update_item_index()
319
320 - def sort_items(self, compare_method, sort_reverse=False):
321 ''' 322 Sort items with given method. 323 324 @param compare_method: Compare method to sort. 325 @param sort_reverse: Whether sort reverse, default is False. 326 ''' 327 # Sort items. 328 with self.keep_select_status(): 329 self.items = sorted(self.items, 330 cmp=compare_method, 331 reverse=sort_reverse) 332 333 # Update item index. 334 self.update_item_index() 335 336 # Redraw. 337 self.queue_draw()
338
339 - def redraw_item(self, list_item):
340 ''' 341 Redraw item. 342 343 @param list_item: List item need to redraw. 344 ''' 345 self.redraw_request_list.append(list_item)
346
347 - def update_item_index(self):
348 ''' 349 Update index of items. 350 ''' 351 for (index, item) in enumerate(self.items): 352 item.set_index(index)
353
354 - def set_title_height(self, title_height):
355 ''' 356 Set title height. 357 ''' 358 self.title_height = title_height 359 if self.titles: 360 self.title_offset_y = self.title_height 361 else: 362 self.title_offset_y = 0
363
364 - def get_column_sort_type(self, column):
365 ''' 366 Get sort type with given column index. 367 368 @param column: Column index. 369 370 @return: Return sort type with given column index, return None if haven't found match column index. 371 ''' 372 if 0 <= column <= last_index(self.title_sorts): 373 return self.title_sorts[column] 374 else: 375 return None
376
377 - def set_column_sort_type(self, column, sort_type):
378 ''' 379 Set sort type with given value. 380 381 @param column: Column index. 382 @param sort_type: Sort type. 383 ''' 384 if 0 <= column <= last_index(self.title_sorts): 385 self.title_sorts[column] = sort_type
386
387 - def get_cell_widths(self):
388 ''' 389 Get cell width of columns. 390 ''' 391 return self.cell_widths
392
393 - def set_cell_width(self, column, width):
394 ''' 395 Set cell width with given value. 396 397 @param column: Column index. 398 @param width: Column width. 399 ''' 400 if column <= last_index(self.cell_min_widths) and width >= self.cell_min_widths[column]: 401 self.cell_widths[column] = width
402
403 - def set_adjust_cursor(self):
404 ''' 405 Internal function to set cursor type when adjust size. 406 ''' 407 set_cursor(self, gtk.gdk.SB_H_DOUBLE_ARROW) 408 self.adjust_cursor = True
409
410 - def reset_cursor(self):
411 ''' 412 Reset cursor type. 413 ''' 414 set_cursor(self, None) 415 self.adjust_cursor = False
416
417 - def get_offset_coordinate(self, widget):
418 ''' 419 Get viewport offset coordinate and viewport. 420 421 @param widget: ListView widget. 422 @return: Return viewport offset and viewport: (offset_x, offset_y, viewport). 423 ''' 424 # Init. 425 rect = widget.allocation 426 427 # Get coordinate. 428 viewport = get_match_parent(widget, ["Viewport"]) 429 if viewport: 430 coordinate = widget.translate_coordinates(viewport, rect.x, rect.y) 431 if len(coordinate) == 2: 432 (offset_x, offset_y) = coordinate 433 return (-offset_x, -offset_y, viewport) 434 else: 435 return (0, 0, viewport) 436 437 else: 438 return (0, 0, viewport)
439
440 - def draw_shadow_mask(self, cr, x, y, w, h):
441 ''' 442 Shadow mask interface for overwrite. 443 444 @param cr: Cairo context. 445 @param x: X coordiante of draw area. 446 @param y: Y coordiante of draw area. 447 @param w: Width of draw area. 448 @param h: Height of draw area. 449 ''' 450 pass
451
452 - def draw_mask(self, cr, x, y, w, h):
453 ''' 454 Draw mask interface. 455 456 @param cr: Cairo context. 457 @param x: X coordiante of draw area. 458 @param y: Y coordiante of draw area. 459 @param w: Width of draw area. 460 @param h: Height of draw area. 461 ''' 462 draw_vlinear(cr, x, y, w, h, 463 ui_theme.get_shadow_color("linear_background").get_color_info() 464 )
465
466 - def draw_item_hover(self, cr, x, y, w, h):
467 ''' 468 Draw item hover interface. 469 470 @param cr: Cairo context. 471 @param x: X coordiante of draw area. 472 @param y: Y coordiante of draw area. 473 @param w: Width of draw area. 474 @param h: Height of draw area. 475 ''' 476 draw_vlinear(cr, x, y, w, h, ui_theme.get_shadow_color("listview_hover").get_color_info())
477
478 - def draw_item_select(self, cr, x, y, w, h):
479 ''' 480 Draw item select interface. 481 482 @param cr: Cairo context. 483 @param x: X coordiante of draw area. 484 @param y: Y coordiante of draw area. 485 @param w: Width of draw area. 486 @param h: Height of draw area. 487 ''' 488 draw_vlinear(cr, x, y, w, h, ui_theme.get_shadow_color("listview_select").get_color_info())
489
490 - def draw_item_highlight(self, cr, x, y, w, h):
491 ''' 492 Draw item highlight interface. 493 494 @param cr: Cairo context. 495 @param x: X coordiante of draw area. 496 @param y: Y coordiante of draw area. 497 @param w: Width of draw area. 498 @param h: Height of draw area. 499 ''' 500 draw_vlinear(cr, x, y, w, h, ui_theme.get_shadow_color("listview_highlight").get_color_info())
501
502 - def realize_list_view(self, widget):
503 ''' 504 Internal fucntion for realize listview. 505 506 @param widget: ListView wiget. 507 ''' 508 self.grab_focus() # focus key after realize 509 510 rect = widget.allocation 511 if 0 <= self.expand_column < len(self.cell_widths): 512 self.set_cell_width(self.expand_column, rect.width - (sum(self.cell_widths) - self.cell_widths[self.expand_column]))
513
514 - def size_allocate_list_view(self, widget, allocation):
515 ''' 516 Internal callback for `size_allocated` signal. 517 518 @param widget: ListView widget. 519 @param allocation: ListView allocation. 520 ''' 521 rect = widget.allocation 522 if 0 <= self.expand_column < len(self.cell_widths): 523 self.set_cell_width(self.expand_column, rect.width - (sum(self.cell_widths) - self.cell_widths[self.expand_column]))
524
525 - def expose_list_view(self, widget, event):
526 ''' 527 Internal callback for `expose-event` signal. 528 529 @param widget: ListView widget. 530 @param event: Expose event. 531 ''' 532 # Init. 533 cr = widget.window.cairo_create() 534 rect = widget.allocation 535 cell_widths = self.get_cell_widths() 536 537 # Get offset. 538 (offset_x, offset_y, viewport) = self.get_offset_coordinate(widget) 539 540 # Draw background. 541 with cairo_state(cr): 542 scrolled_window = get_match_parent(self, ["ScrolledWindow"]) 543 cr.translate(-scrolled_window.allocation.x, -scrolled_window.allocation.y) 544 cr.rectangle(offset_x, offset_y, 545 scrolled_window.allocation.x + scrolled_window.allocation.width, 546 scrolled_window.allocation.y + scrolled_window.allocation.height) 547 cr.clip() 548 549 (shadow_x, shadow_y) = get_window_shadow_size(self.get_toplevel()) 550 skin_config.render_background(cr, self, offset_x + shadow_x, offset_y + shadow_y) 551 552 # Draw mask. 553 self.draw_mask(cr, offset_x, offset_y, viewport.allocation.width, viewport.allocation.height) 554 555 if len(self.items) > 0: 556 with cairo_state(cr): 557 # Don't draw any item under title area. 558 cr.rectangle(offset_x, offset_y + self.title_offset_y, 559 viewport.allocation.width, viewport.allocation.height - self.title_offset_y) 560 cr.clip() 561 562 # Draw hover row. 563 highlight_row = None 564 if self.highlight_item: 565 highlight_row = self.highlight_item.get_index() 566 567 if self.hover_row != None and not self.hover_row in self.select_rows and self.hover_row != highlight_row: 568 self.draw_item_hover( 569 cr, offset_x, self.title_offset_y + self.hover_row * self.item_height, 570 viewport.allocation.width, self.item_height) 571 572 # Draw select rows. 573 for select_row in self.select_rows: 574 if select_row != highlight_row: 575 self.draw_item_select( 576 cr, offset_x, self.title_offset_y + select_row * self.item_height, 577 viewport.allocation.width, self.item_height) 578 579 # Draw highlight row. 580 if self.highlight_item: 581 self.draw_item_highlight( 582 cr, offset_x, self.title_offset_y + self.highlight_item.get_index() * self.item_height, 583 viewport.allocation.width, self.item_height) 584 585 # Get viewport index. 586 start_y = offset_y - self.title_offset_y 587 end_y = offset_y + viewport.allocation.height - self.title_offset_y 588 start_index = max(start_y / self.item_height, 0) 589 if (end_y - end_y / self.item_height * self.item_height) == 0: 590 end_index = min(end_y / self.item_height + 1, len(self.items)) 591 else: 592 end_index = min(end_y / self.item_height + 2, len(self.items)) 593 594 # Draw list item. 595 for (row, item) in enumerate(self.items[start_index:end_index]): 596 renders = item.get_renders() 597 for (column, render) in enumerate(renders): 598 cell_width = cell_widths[column] 599 cell_x = sum(cell_widths[0:column]) 600 render_x = rect.x + cell_x 601 render_y = rect.y + (row + start_index) * self.item_height + self.title_offset_y 602 render_width = cell_width 603 render_height = self.item_height 604 605 with cairo_state(cr): 606 # Don't allowed list item draw out of cell rectangle. 607 cr.rectangle(render_x, render_y, render_width, render_height) 608 cr.clip() 609 610 # Render cell. 611 render(cr, gtk.gdk.Rectangle(render_x, render_y, render_width, render_height), 612 (start_index + row) in self.select_rows, 613 item == self.highlight_item) 614 615 616 # Draw titles. 617 if self.titles: 618 for (column, width) in enumerate(cell_widths): 619 # Get offset x coordinate. 620 cell_offset_x = sum(cell_widths[0:column]) 621 622 # Calcuate current cell width. 623 if column == last_index(cell_widths): 624 if sum(cell_widths) < rect.width: 625 cell_width = rect.width - cell_offset_x 626 else: 627 cell_width = width 628 else: 629 cell_width = width 630 631 # Draw title column background. 632 if self.title_select_column == column: 633 if self.left_button_press: 634 header_pixbuf = ui_theme.get_pixbuf("listview/header_press.png").get_pixbuf() 635 else: 636 header_pixbuf = ui_theme.get_pixbuf("listview/header_hover.png").get_pixbuf() 637 else: 638 header_pixbuf = ui_theme.get_pixbuf("listview/header_normal.png").get_pixbuf() 639 self.title_cache_pixbufs[column].scale( 640 header_pixbuf, cell_width, self.title_height) 641 draw_pixbuf(cr, 642 self.title_cache_pixbufs[column].get_cache(), 643 cell_offset_x, offset_y) 644 645 # Draw title split line. 646 if cell_offset_x != 0: 647 draw_pixbuf(cr, 648 ui_theme.get_pixbuf("listview/split.png").get_pixbuf(), 649 cell_offset_x - 1, offset_y) 650 651 # Draw title. 652 draw_text(cr, self.titles[column], 653 cell_offset_x, offset_y, cell_widths[column], self.title_height, 654 DEFAULT_FONT_SIZE, 655 ui_theme.get_color("list_view_title").get_color(), 656 alignment=pango.ALIGN_CENTER) 657 658 # Draw sort icon. 659 if self.title_sort_column == column: 660 sort_type = self.get_column_sort_type(column) 661 if sort_type == self.SORT_DESCENDING: 662 sort_pixbuf = ui_theme.get_pixbuf("listview/sort_descending.png").get_pixbuf() 663 elif sort_type == self.SORT_ASCENDING: 664 sort_pixbuf = ui_theme.get_pixbuf("listview/sort_ascending.png").get_pixbuf() 665 666 draw_pixbuf(cr, sort_pixbuf, 667 cell_offset_x + cell_width - sort_pixbuf.get_width() - self.SORT_PADDING_X, 668 offset_y + (self.title_height - sort_pixbuf.get_height()) / 2) 669 670 # Draw shadow mask. 671 self.draw_shadow_mask(cr, offset_x, offset_y, viewport.allocation.width, viewport.allocation.height) 672 673 # Draw drag reference row. 674 if self.drag_reference_row != None: 675 drag_pixbuf = ui_theme.get_pixbuf("listview/drag_line.png").get_pixbuf() 676 self.drag_line_pixbuf.scale(drag_pixbuf, rect.width, drag_pixbuf.get_height()) 677 if self.drag_reference_row == 0: 678 drag_line_y = rect.y + self.title_offset_y 679 elif self.drag_reference_row == len(self.items): 680 drag_line_y = rect.y + (self.drag_reference_row) * self.item_height + self.title_offset_y - drag_pixbuf.get_height() 681 else: 682 drag_line_y = rect.y + self.drag_reference_row * self.item_height + self.title_offset_y 683 684 draw_pixbuf(cr, self.drag_line_pixbuf.get_cache(), rect.x, drag_line_y) 685 686 return False
687
688 - def motion_list_view(self, widget, event):
689 ''' 690 Internal callback for `motion-notify-event` signal. 691 692 @param widget: ListView widget. 693 @param event: Motion event. 694 ''' 695 if self.titles: 696 # Get offset. 697 (offset_x, offset_y, viewport) = self.get_offset_coordinate(widget) 698 699 if self.title_adjust_column != None: 700 # Set column width. 701 cell_min_end_x = sum(self.cell_widths[0:self.title_adjust_column]) + self.cell_min_widths[self.title_adjust_column] 702 # Adjust column width. 703 (ex, ey) = get_event_coords(event) 704 if ex >= cell_min_end_x: 705 self.set_cell_width(self.title_adjust_column, ex - sum(self.cell_widths[0:self.title_adjust_column])) 706 else: 707 if offset_y <= event.y <= offset_y + self.title_height: 708 cell_widths = self.get_cell_widths() 709 for (column, _) in enumerate(cell_widths): 710 if column == last_index(cell_widths): 711 cell_start_x = widget.allocation.width 712 cell_end_x = widget.allocation.width 713 else: 714 cell_start_x = sum(cell_widths[0:column + 1]) - self.title_separator_width 715 cell_end_x = sum(cell_widths[0:column + 1]) + self.title_separator_width 716 717 if event.x < cell_start_x: 718 self.title_select_column = column 719 self.reset_cursor() 720 break 721 elif cell_start_x <= event.x <= cell_end_x: 722 self.title_select_column = None 723 self.set_adjust_cursor() 724 break 725 elif len(self.items) > 0: 726 self.hover_item(event) 727 elif len(self.items) > 0: 728 self.hover_item(event) 729 730 # Disable press_in_select_rows once move mouse. 731 self.press_in_select_rows = None 732 733 # Redraw after motion. 734 self.queue_draw()
735
736 - def hover_item(self, event):
737 ''' 738 Internal function to handle hover item. 739 740 @param event: Motion notify event. 741 ''' 742 if self.left_button_press: 743 if self.start_drag: 744 if self.enable_drag_drop: 745 # Set drag cursor. 746 if self.drag_preview_pixbuf == None: 747 temp_filepath = tempfile.mktemp() 748 subprocess.Popen( 749 ["python", 750 os.path.join(os.path.dirname(__file__), "listview_preview_pixbuf.py"), 751 str(len(self.select_rows)), 752 str([(0, ("#40408c", 1)), 753 (1, ("#0093F9", 1))]), 754 "#FFFFFF", 755 temp_filepath]).wait() 756 drag_num_pixbuf = gtk.gdk.pixbuf_new_from_file(temp_filepath) 757 drag_icon_pixbuf = self.drag_icon_pixbuf.get_pixbuf() 758 drag_num_pixbuf.copy_area( 759 0, 0, drag_num_pixbuf.get_width(), drag_num_pixbuf.get_height(), 760 drag_icon_pixbuf, 761 (drag_icon_pixbuf.get_width() - drag_num_pixbuf.get_width()) / 2, 762 drag_icon_pixbuf.get_height() - drag_num_pixbuf.get_height()) 763 self.drag_preview_pixbuf = drag_icon_pixbuf 764 remove_file(temp_filepath) 765 766 self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.display_get_default(), 767 self.drag_preview_pixbuf, 768 0, 0)) 769 770 # Get hover row. 771 if self.is_in_visible_area(event): 772 # Scroll viewport when cursor almost reach bound of viewport. 773 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 774 if event.y > vadjust.get_value() + vadjust.get_page_size() - 2 * self.item_height: 775 vadjust.set_value(min(vadjust.get_value() + self.item_height, 776 vadjust.get_upper() - vadjust.get_page_size())) 777 elif event.y < vadjust.get_value() + 2 * self.item_height + self.title_offset_y: 778 vadjust.set_value(max(vadjust.get_value() - self.item_height, 779 vadjust.get_lower())) 780 781 # Get drag reference row. 782 self.drag_reference_row = self.get_event_row(event, 1) 783 784 self.queue_draw() 785 else: 786 # Begin drag is drag_data is not None. 787 if self.drag_data: 788 (targets, actions, button) = self.drag_data 789 self.drag_begin(targets, actions, button, event) 790 791 self.drag_reference_row = None 792 793 self.queue_draw() 794 else: 795 if self.enable_multiple_select and (not self.press_ctrl and not self.press_shift): 796 # Get hover row. 797 hover_row = self.get_event_row(event) 798 799 # Highlight drag area. 800 if hover_row != None and self.start_select_row != None: 801 # Update select area. 802 if hover_row > self.start_select_row: 803 self.select_rows = range(self.start_select_row, hover_row + 1) 804 elif hover_row < self.start_select_row: 805 self.select_rows = range(hover_row, self.start_select_row + 1) 806 else: 807 self.select_rows = [hover_row] 808 809 # Scroll viewport when cursor almost reach bound of viewport. 810 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 811 if event.y > vadjust.get_value() + vadjust.get_page_size() - 2 * self.item_height: 812 vadjust.set_value(min(vadjust.get_value() + self.item_height, 813 vadjust.get_upper() - vadjust.get_page_size())) 814 elif event.y < vadjust.get_value() + 2 * self.item_height + self.title_offset_y: 815 vadjust.set_value(max(vadjust.get_value() - self.item_height, 816 vadjust.get_lower())) 817 818 self.queue_draw() 819 else: 820 # Rest cursor and title select column. 821 self.title_select_column = None 822 self.reset_cursor() 823 824 # Set hover row. 825 self.hover_row = self.get_event_row(event) 826 827 # Emit motion notify event to item. 828 self.emit_item_event("motion-notify-item", event)
829
830 - def button_press_list_view(self, widget, event):
831 ''' 832 Internal callback for `button-press-event` signal. 833 834 @param widget: ListView widget. 835 @param event: Button press event. 836 ''' 837 # Grab focus when button press, otherwise key-press signal can't response. 838 self.grab_focus() 839 840 if is_left_button(event): 841 self.left_button_press = True 842 843 if self.titles: 844 # Get offset. 845 (offset_x, offset_y, viewport) = self.get_offset_coordinate(widget) 846 if offset_y <= event.y <= offset_y + self.title_height: 847 cell_widths = self.get_cell_widths() 848 for (column, _) in enumerate(cell_widths): 849 if column == last_index(cell_widths): 850 cell_end_x = widget.allocation.width 851 else: 852 cell_end_x = sum(cell_widths[0:column + 1]) - self.title_separator_width 853 854 if column == 0: 855 cell_start_x = 0 856 else: 857 cell_start_x = sum(cell_widths[0:column]) + self.title_separator_width 858 859 if cell_start_x < event.x < cell_end_x: 860 self.title_clicks[column] = True 861 break 862 elif cell_end_x <= event.x <= cell_end_x + self.title_separator_width * 2: 863 self.title_adjust_column = column 864 break 865 elif len(self.items) > 0: 866 self.click_item(event) 867 elif len(self.items) > 0: 868 self.click_item(event) 869 elif is_right_button(event): 870 if len(self.items) > 0: 871 self.click_item(event) 872 873 self.queue_draw()
874
875 - def click_item(self, event):
876 ''' 877 Internal function to handle click item. 878 879 @param event: Button press event. 880 ''' 881 click_row = self.get_event_row(event) 882 883 if self.left_button_press: 884 if click_row == None: 885 self.start_select_row = None 886 self.select_rows = [] 887 else: 888 if self.press_shift: 889 if self.select_rows == [] or self.start_select_row == None: 890 self.start_select_row = click_row 891 self.select_rows = [click_row] 892 else: 893 if len(self.select_rows) == 1: 894 self.start_select_row = self.select_rows[0] 895 896 if click_row < self.start_select_row: 897 self.select_rows = range(click_row, self.start_select_row + 1) 898 elif click_row > self.start_select_row: 899 self.select_rows = range(self.start_select_row, click_row + 1) 900 else: 901 self.select_rows = [click_row] 902 elif self.press_ctrl: 903 if click_row in self.select_rows: 904 self.select_rows.remove(click_row) 905 else: 906 self.start_select_row = click_row 907 self.select_rows.append(click_row) 908 self.select_rows = sorted(self.select_rows) 909 else: 910 if self.enable_drag_drop and click_row in self.select_rows: 911 self.start_drag = True 912 913 if self.start_select_row: 914 self.start_select_item = self.items[self.start_select_row] 915 916 self.before_drag_items = [] 917 self.after_drag_items = [] 918 919 for row in self.select_rows: 920 if row == click_row: 921 self.drag_item = self.items[click_row] 922 elif row < click_row: 923 self.before_drag_items.append(self.items[row]) 924 elif row > click_row: 925 self.after_drag_items.append(self.items[row]) 926 927 # Record press_in_select_rows, disable select rows if mouse not move after release button. 928 self.press_in_select_rows = click_row 929 else: 930 self.start_drag = False 931 932 self.start_select_row = click_row 933 self.select_rows = [click_row] 934 self.emit_item_event("button-press-item", event) 935 936 if is_double_click(event): 937 self.double_click_row = copy.deepcopy(click_row) 938 elif is_single_click(event): 939 self.single_click_row = copy.deepcopy(click_row) 940 else: 941 right_press_row = self.get_event_row(event) 942 if right_press_row == None: 943 self.start_select_row = None 944 self.select_rows = [] 945 946 self.queue_draw() 947 elif not right_press_row in self.select_rows: 948 self.start_select_row = right_press_row 949 self.select_rows = [right_press_row] 950 951 self.queue_draw() 952 953 # Emit right-press-items signal. 954 if self.start_select_row == None: 955 current_item = None 956 else: 957 current_item = self.items[self.start_select_row] 958 959 select_items = [] 960 for row in self.select_rows: 961 select_items.append(self.items[row]) 962 963 (wx, wy) = self.window.get_root_origin() 964 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 965 self.emit("right-press-items", 966 event.x_root, 967 event.y_root, 968 current_item, 969 select_items)
970
971 - def button_release_list_view(self, widget, event):
972 ''' 973 Internal callback for `button-release-event` signal. 974 975 @param widget: ListView widget. 976 @param event: Button release event. 977 ''' 978 if is_left_button(event): 979 self.left_button_press = False 980 if self.titles: 981 # Get offset. 982 (offset_x, offset_y, viewport) = self.get_offset_coordinate(widget) 983 if offset_y <= event.y <= offset_y + self.title_height: 984 cell_widths = self.get_cell_widths() 985 for (column, _) in enumerate(cell_widths): 986 if column == last_index(cell_widths): 987 cell_end_x = widget.allocation.width 988 else: 989 cell_end_x = sum(cell_widths[0:column + 1]) - self.title_separator_width 990 991 if column == 0: 992 cell_start_x = 0 993 else: 994 cell_start_x = sum(cell_widths[0:column]) + self.title_separator_width 995 996 if cell_start_x < event.x < cell_end_x: 997 if self.title_clicks[column]: 998 self.title_sort_column = column 999 self.title_sorts[column] = not self.title_sorts[column] 1000 self.title_clicks[column] = False 1001 1002 if len(self.sorts) >= column + 1: 1003 with self.keep_select_status(): 1004 # Re-sort. 1005 self.items = sorted(self.items, 1006 key=self.sorts[column][0], 1007 cmp=self.sorts[column][1], 1008 reverse=self.title_sorts[column]) 1009 1010 # Update item index. 1011 self.update_item_index() 1012 break 1013 elif len(self.items) > 0: 1014 self.release_item(event) 1015 elif len(self.items) > 0: 1016 self.release_item(event) 1017 1018 self.drag_reference_row = None 1019 self.drag_preview_pixbuf = None 1020 self.title_adjust_column = None 1021 self.queue_draw()
1022 1023 @contextmanager
1024 - def keep_select_status(self):
1025 ''' 1026 Handy function that change listview and keep select status not change. 1027 ''' 1028 # Save select items. 1029 start_select_item = None 1030 if self.start_select_row != None: 1031 start_select_item = self.items[self.start_select_row] 1032 1033 select_items = [] 1034 for row in self.select_rows: 1035 select_items.append(self.items[row]) 1036 1037 try: 1038 yield 1039 except Exception, e: 1040 print 'with an cairo error %s' % e 1041 else: 1042 # Restore select status. 1043 if start_select_item != None or select_items != []: 1044 # Init start select row. 1045 if start_select_item != None: 1046 self.start_select_row = None 1047 1048 # Init select rows. 1049 if select_items != []: 1050 self.select_rows = [] 1051 1052 for (index, item) in enumerate(self.items): 1053 # Try restore select row. 1054 if item in select_items: 1055 self.select_rows.append(index) 1056 select_items.remove(item) 1057 1058 # Try restore start select row. 1059 if item == start_select_item: 1060 self.start_select_row = index 1061 start_select_item = None 1062 1063 # Stop loop when finish restore row status. 1064 if select_items == [] and start_select_item == None: 1065 break
1066
1067 - def release_item(self, event):
1068 ''' 1069 Internal function to handle release item. 1070 1071 @param event: Button release event. 1072 ''' 1073 if is_left_button(event): 1074 release_row = self.get_event_row(event) 1075 1076 if self.double_click_row == release_row: 1077 self.emit_item_event("double-click-item", event) 1078 elif self.single_click_row == release_row: 1079 self.emit_item_event("single-click-item", event) 1080 1081 if self.start_drag and self.is_in_visible_area(event): 1082 self.drag_select_items_at_cursor(event) 1083 1084 self.reset_cursor() 1085 self.double_click_row = None 1086 self.single_click_row = None 1087 self.start_drag = False 1088 1089 # Disable select rows when press_in_select_rows valid after button release. 1090 if self.press_in_select_rows: 1091 self.start_select_row = self.press_in_select_rows 1092 self.select_rows = [self.press_in_select_rows] 1093 1094 self.press_in_select_rows = None 1095 1096 self.queue_draw()
1097
1098 - def is_in_visible_area(self, event):
1099 ''' 1100 Is event coordinate in visible area. 1101 1102 @param event: gtk.gdk.Event. 1103 1104 @return: Return True if event coordiante in visible area. 1105 ''' 1106 (event_x, event_y) = get_event_coords(event) 1107 scrolled_window = get_match_parent(self, ["ScrolledWindow"]) 1108 vadjust = scrolled_window.get_vadjustment() 1109 return (-self.drag_out_offset <= event_x <= scrolled_window.allocation.width + self.drag_out_offset 1110 and vadjust.get_value() - self.drag_out_offset <= event_y <= vadjust.get_value() + vadjust.get_page_size() + self.drag_out_offset)
1111
1112 - def drag_select_items_at_cursor(self, event):
1113 ''' 1114 Internal function to drag select items at cursor position. 1115 ''' 1116 (event_x, event_y) = get_event_coords(event) 1117 hover_row = min(max(int((event_y - self.title_offset_y) / self.item_height), 0), 1118 len(self.items)) 1119 1120 # Filt items around drag item. 1121 filter_items = self.before_drag_items + [self.drag_item] + self.after_drag_items 1122 1123 before_items = [] 1124 for item in self.items[0:hover_row]: 1125 if not item in filter_items: 1126 before_items.append(item) 1127 1128 after_items = [] 1129 for item in self.items[hover_row::]: 1130 if not item in filter_items: 1131 after_items.append(item) 1132 1133 # Update items order. 1134 self.items = before_items + self.before_drag_items + [self.drag_item] + self.after_drag_items + after_items 1135 1136 # Update select rows. 1137 self.select_rows = range(len(before_items), len(self.items) - len(after_items)) 1138 1139 # Update select start row. 1140 for row in self.select_rows: 1141 if self.items[row] == self.start_select_item: 1142 self.start_select_row = row 1143 break 1144 1145 1146 # Update item index. 1147 self.update_item_index() 1148 1149 # Redraw. 1150 self.queue_draw()
1151
1152 - def leave_list_view(self, widget, event):
1153 ''' 1154 Internal callback for `leave-notify-event` signal. 1155 1156 @param widget: ListView widget. 1157 @param event: Leave notify event. 1158 ''' 1159 # Reset. 1160 self.title_select_column = None 1161 self.title_adjust_column = None 1162 if not self.left_button_press: 1163 self.reset_cursor() 1164 1165 # Hide hover row when cursor out of viewport area. 1166 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1167 hadjust = get_match_parent(self, ["ScrolledWindow"]).get_hadjustment() 1168 if not is_in_rect((event.x, event.y), 1169 (hadjust.get_value(), vadjust.get_value(), hadjust.get_page_size(), vadjust.get_page_size())): 1170 self.hover_row = None 1171 1172 # Redraw. 1173 self.queue_draw()
1174
1175 - def key_press_list_view(self, widget, event):
1176 ''' 1177 Internal callback for `key-press-event` signal. 1178 1179 @param widget: ListView widget. 1180 @param event: Key press event. 1181 ''' 1182 if has_ctrl_mask(event): 1183 self.press_ctrl = True 1184 1185 if has_shift_mask(event): 1186 self.press_shift = True 1187 1188 key_name = get_keyevent_name(event) 1189 if self.keymap.has_key(key_name): 1190 self.keymap[key_name]() 1191 1192 # Hide hover row. 1193 if self.hover_row and not has_ctrl_mask(event) and not has_shift_mask(event): 1194 self.hover_row = None 1195 self.queue_draw() 1196 1197 return True
1198
1199 - def key_release_list_view(self, widget, event):
1200 ''' 1201 Internal callback for `key-release-event` signal. 1202 1203 @param widget: ListView widget. 1204 @param event: Key release event. 1205 ''' 1206 if has_ctrl_mask(event): 1207 self.press_ctrl = False 1208 1209 if has_shift_mask(event): 1210 self.press_shift = False
1211
1212 - def emit_item_event(self, event_name, event):
1213 ''' 1214 Wrap method for emit event signal. 1215 1216 @param event_name: Event name. 1217 @param event: Event. 1218 ''' 1219 (event_x, event_y) = get_event_coords(event) 1220 event_row = (event_y - self.title_offset_y) / self.item_height 1221 if 0 <= event_row <= last_index(self.items): 1222 offset_y = event_y - event_row * self.item_height - self.title_offset_y 1223 (event_column, offset_x) = get_disperse_index(self.cell_widths, event_x) 1224 1225 self.emit(event_name, self.items[event_row], event_column, offset_x, offset_y)
1226
1227 - def get_coordinate_row(self, y):
1228 ''' 1229 Get row with given y coordinate. 1230 1231 @param y: Y coordinate. 1232 @return: Return row that match given y coordinate, return None if haven't any row match y coordiante. 1233 ''' 1234 row = int((y - self.title_offset_y) / self.item_height) 1235 if 0 <= row <= last_index(self.items): 1236 return row 1237 else: 1238 return None
1239
1240 - def get_event_row(self, event, offset_index=0):
1241 ''' 1242 Get row at event. 1243 1244 @param event: gtk.gdk.Event instance. 1245 @param offset_index: Offset index base on event row. 1246 @return: Return row at event coordinate, return None if haven't any row match event coordiante. 1247 ''' 1248 (event_x, event_y) = get_event_coords(event) 1249 row = int((event_y - self.title_offset_y) / self.item_height) 1250 if 0 <= row <= last_index(self.items) + offset_index: 1251 return row 1252 else: 1253 return None
1254
1255 - def select_first_item(self):
1256 ''' 1257 Select first item. 1258 ''' 1259 if len(self.items) > 0: 1260 # Update select rows. 1261 self.start_select_row = 0 1262 self.select_rows = [0] 1263 1264 # Scroll to top. 1265 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1266 vadjust.set_value(vadjust.get_lower()) 1267 1268 # Redraw. 1269 self.queue_draw()
1270
1271 - def select_last_item(self):
1272 ''' 1273 Select last item. 1274 ''' 1275 if len(self.items) > 0: 1276 # Update select rows. 1277 last_row = last_index(self.items) 1278 self.start_select_row = last_row 1279 self.select_rows = [last_row] 1280 1281 # Scroll to bottom. 1282 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1283 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size()) 1284 1285 # Redraw. 1286 self.queue_draw()
1287
1288 - def scroll_page_up(self):
1289 ''' 1290 Scroll page up. 1291 ''' 1292 if self.select_rows == []: 1293 # Select row. 1294 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1295 select_y = max(vadjust.get_value() - vadjust.get_page_size(), self.title_offset_y) 1296 select_row = int((select_y - self.title_offset_y) / self.item_height) 1297 1298 # Update select row. 1299 self.start_select_row = select_row 1300 self.select_rows = [select_row] 1301 1302 # Scroll viewport make sure preview row in visible area. 1303 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1304 if select_row == 0: 1305 vadjust.set_value(vadjust.get_lower()) 1306 elif offset_y > select_row * self.item_height + self.title_offset_y: 1307 vadjust.set_value(max((select_row - 1) * self.item_height + self.title_offset_y, vadjust.get_lower())) 1308 1309 # Redraw. 1310 self.queue_draw() 1311 else: 1312 if self.start_select_row != None: 1313 # Record offset before scroll. 1314 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1315 scroll_offset_y = self.start_select_row * self.item_height + self.title_offset_y - vadjust.get_value() 1316 1317 # Get select row. 1318 select_y = max(self.start_select_row * self.item_height - vadjust.get_page_size(), self.title_offset_y) 1319 select_row = int((select_y - self.title_offset_y) / self.item_height) 1320 1321 # Update select row. 1322 self.start_select_row = select_row 1323 self.select_rows = [select_row] 1324 1325 # Scroll viewport make sure preview row in visible area. 1326 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1327 if select_row == 0: 1328 vadjust.set_value(vadjust.get_lower()) 1329 elif offset_y > select_row * self.item_height + self.title_offset_y: 1330 vadjust.set_value(max(select_row * self.item_height + self.title_offset_y - scroll_offset_y, 1331 vadjust.get_lower())) 1332 1333 # Redraw. 1334 self.queue_draw() 1335 else: 1336 print "scroll_page_up : impossible!"
1337
1338 - def scroll_page_down(self):
1339 ''' 1340 Scroll page down. 1341 ''' 1342 if self.select_rows == []: 1343 # Select row. 1344 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1345 select_y = min(vadjust.get_value() + vadjust.get_page_size(), 1346 vadjust.get_upper() - self.item_height) 1347 select_row = int((select_y - self.title_offset_y) / self.item_height) 1348 1349 # Update select row. 1350 self.start_select_row = select_row 1351 self.select_rows = [select_row] 1352 1353 # Scroll viewport make sure preview row in visible area. 1354 max_y = vadjust.get_upper() - vadjust.get_page_size() 1355 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1356 if offset_y + vadjust.get_page_size() < (select_row + 1) * self.item_height + self.title_offset_y: 1357 vadjust.set_value(min(max_y, (select_row - 1) * self.item_height + self.title_offset_y)) 1358 1359 # Redraw. 1360 self.queue_draw() 1361 else: 1362 if self.start_select_row != None: 1363 # Record offset before scroll. 1364 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1365 scroll_offset_y = self.start_select_row * self.item_height + self.title_offset_y - vadjust.get_value() 1366 1367 # Get select row. 1368 select_y = min(self.start_select_row * self.item_height + vadjust.get_page_size(), 1369 vadjust.get_upper() - self.item_height) 1370 select_row = int((select_y - self.title_offset_y) / self.item_height) 1371 1372 # Update select row. 1373 self.start_select_row = select_row 1374 self.select_rows = [select_row] 1375 1376 # Scroll viewport make sure preview row in visible area. 1377 max_y = vadjust.get_upper() - vadjust.get_page_size() 1378 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1379 if offset_y + vadjust.get_page_size() < (select_row + 1) * self.item_height + self.title_offset_y: 1380 vadjust.set_value(min(max_y, select_row * self.item_height + self.title_offset_y - scroll_offset_y)) 1381 1382 # Redraw. 1383 self.queue_draw() 1384 else: 1385 print "scroll_page_down : impossible!"
1386
1387 - def select_prev_item(self):
1388 ''' 1389 Select preview item. 1390 ''' 1391 if self.select_rows == []: 1392 self.select_first_item() 1393 else: 1394 # Get preview row. 1395 prev_row = max(0, self.start_select_row - 1) 1396 1397 # Redraw when preview row is not current row. 1398 if prev_row != self.start_select_row: 1399 # Select preview row. 1400 self.start_select_row = prev_row 1401 self.select_rows = [prev_row] 1402 1403 # Scroll viewport make sure preview row in visible area. 1404 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1405 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1406 if offset_y > prev_row * self.item_height: 1407 vadjust.set_value(max(vadjust.get_lower(), (prev_row - 1) * self.item_height + self.title_offset_y)) 1408 elif offset_y + vadjust.get_page_size() < prev_row * self.item_height + self.title_offset_y: 1409 vadjust.set_value(min(vadjust.get_upper() - vadjust.get_page_size(), 1410 (prev_row - 1) * self.item_height + self.title_offset_y)) 1411 1412 # Redraw. 1413 self.queue_draw() 1414 elif len(self.select_rows) > 1: 1415 # Select preview row. 1416 self.start_select_row = prev_row 1417 self.select_rows = [prev_row] 1418 1419 # Scroll viewport make sure preview row in visible area. 1420 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1421 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1422 if offset_y > prev_row * self.item_height + self.title_offset_y: 1423 vadjust.set_value(max(vadjust.get_lower(), (prev_row - 1) * self.item_height + self.title_offset_y)) 1424 1425 # Redraw. 1426 self.queue_draw()
1427
1428 - def select_next_item(self):
1429 ''' 1430 Select next item. 1431 ''' 1432 if self.select_rows == []: 1433 self.select_first_item() 1434 else: 1435 # Get next row. 1436 next_row = min(last_index(self.items), self.start_select_row + 1) 1437 1438 # Redraw when next row is not current row. 1439 if next_row != self.start_select_row: 1440 # Select next row. 1441 self.start_select_row = next_row 1442 self.select_rows = [next_row] 1443 1444 # Scroll viewport make sure next row in visible area. 1445 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1446 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1447 if offset_y + vadjust.get_page_size() < (next_row + 1) * self.item_height + self.title_offset_y or offset_y > next_row * self.item_height + self.title_offset_y: 1448 vadjust.set_value(max(vadjust.get_lower(), 1449 (next_row + 1) * self.item_height + self.title_offset_y - vadjust.get_page_size())) 1450 1451 # Redraw. 1452 self.queue_draw() 1453 elif len(self.select_rows) > 1: 1454 # Select next row. 1455 self.start_select_row = next_row 1456 self.select_rows = [next_row] 1457 1458 # Scroll viewport make sure next row in visible area. 1459 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1460 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1461 if offset_y + vadjust.get_page_size() < (next_row + 1) * self.item_height + self.title_offset_y: 1462 vadjust.set_value(max(vadjust.get_lower(), 1463 (next_row + 1) * self.item_height + self.title_offset_y - vadjust.get_page_size())) 1464 1465 # Redraw. 1466 self.queue_draw()
1467
1468 - def select_to_prev_item(self):
1469 ''' 1470 Select to preview item. 1471 ''' 1472 if self.select_rows == []: 1473 self.select_first_item() 1474 elif self.start_select_row != None: 1475 if self.start_select_row == self.select_rows[-1]: 1476 first_row = self.select_rows[0] 1477 if first_row > 0: 1478 prev_row = first_row - 1 1479 self.select_rows = [prev_row] + self.select_rows 1480 1481 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1482 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1483 if offset_y > prev_row * self.item_height: 1484 vadjust.set_value(max(vadjust.get_lower(), (prev_row - 1) * self.item_height + self.title_offset_y)) 1485 1486 self.queue_draw() 1487 elif self.start_select_row == self.select_rows[0]: 1488 last_row = self.select_rows[-1] 1489 self.select_rows.remove(last_row) 1490 1491 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1492 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1493 if offset_y > self.select_rows[-1] * self.item_height: 1494 vadjust.set_value(max(vadjust.get_lower(), 1495 (self.select_rows[-1] - 1) * self.item_height + self.title_offset_y)) 1496 1497 self.queue_draw() 1498 else: 1499 print "select_to_prev_item : impossible!"
1500
1501 - def select_to_next_item(self):
1502 ''' 1503 Select to next item. 1504 ''' 1505 if self.select_rows == []: 1506 self.select_first_item() 1507 elif self.start_select_row != None: 1508 if self.start_select_row == self.select_rows[0]: 1509 last_row = self.select_rows[-1] 1510 if last_row < last_index(self.items): 1511 next_row = last_row + 1 1512 self.select_rows.append(next_row) 1513 1514 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1515 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1516 if offset_y + vadjust.get_page_size() < next_row * self.item_height + self.title_offset_y: 1517 vadjust.set_value(max(vadjust.get_lower(), 1518 (next_row + 1) * self.item_height + self.title_offset_y - vadjust.get_page_size())) 1519 1520 self.queue_draw() 1521 elif self.start_select_row == self.select_rows[-1]: 1522 first_row = self.select_rows[0] 1523 self.select_rows.remove(first_row) 1524 1525 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1526 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1527 if offset_y + vadjust.get_page_size() < (self.select_rows[0] + 1) * self.item_height + self.title_offset_y: 1528 vadjust.set_value(max(vadjust.get_lower(), 1529 (self.select_rows[0] + 1) * self.item_height + self.title_offset_y - vadjust.get_page_size())) 1530 1531 self.queue_draw() 1532 else: 1533 print "select_to_next_item : impossible!"
1534
1535 - def select_to_first_item(self):
1536 ''' 1537 Select to first item. 1538 ''' 1539 if self.select_rows == []: 1540 self.select_first_item() 1541 elif self.start_select_row != None: 1542 if self.start_select_row == self.select_rows[-1]: 1543 self.select_rows = range(0, self.select_rows[-1] + 1) 1544 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1545 vadjust.set_value(vadjust.get_lower()) 1546 self.queue_draw() 1547 elif self.start_select_row == self.select_rows[0]: 1548 self.select_rows = range(0, self.select_rows[0] + 1) 1549 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1550 vadjust.set_value(vadjust.get_lower()) 1551 self.queue_draw() 1552 else: 1553 print "select_to_first_item : impossible!"
1554
1555 - def select_to_last_item(self):
1556 ''' 1557 Select to last item. 1558 ''' 1559 if self.select_rows == []: 1560 self.select_first_item() 1561 elif self.start_select_row != None: 1562 if self.start_select_row == self.select_rows[0]: 1563 self.select_rows = range(self.select_rows[0], len(self.items)) 1564 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1565 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size()) 1566 self.queue_draw() 1567 elif self.start_select_row == self.select_rows[-1]: 1568 self.select_rows = range(self.select_rows[-1], len(self.items)) 1569 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1570 vadjust.set_value(vadjust.get_upper() - vadjust.get_page_size()) 1571 self.queue_draw() 1572 else: 1573 print "select_to_end_item : impossible!"
1574
1575 - def select_all_items(self):
1576 ''' 1577 Select all items. 1578 ''' 1579 if self.select_rows == []: 1580 self.start_select_row = 0 1581 self.select_rows = range(0, len(self.items)) 1582 1583 self.queue_draw() 1584 else: 1585 self.select_rows = range(0, len(self.items)) 1586 1587 self.queue_draw()
1588
1589 - def delete_select_items(self):
1590 ''' 1591 Delete select items. 1592 ''' 1593 # Get select items. 1594 remove_items = [] 1595 for row in self.select_rows: 1596 remove_items.append(self.items[row]) 1597 1598 if remove_items != []: 1599 # Init select row. 1600 self.start_select_row = None 1601 self.select_rows = [] 1602 cache_remove_items = [] 1603 1604 # Remove select items. 1605 for remove_item in remove_items: 1606 cache_remove_items.append(remove_item) 1607 self.items.remove(remove_item) 1608 1609 # Emit remove items signal. 1610 self.emit("delete-select-items", cache_remove_items) 1611 1612 # Update item index. 1613 self.update_item_index() 1614 1615 # Update vertical adjustment. 1616 self.update_vadjustment() 1617 1618 # Redraw. 1619 self.queue_draw()
1620
1621 - def update_vadjustment(self):
1622 ''' 1623 Update vertical adjustment. 1624 ''' 1625 list_height = self.title_offset_y + len(self.items) * self.item_height 1626 self.set_size_request(sum(self.cell_min_widths), list_height) 1627 scrolled_window = get_match_parent(self, ["ScrolledWindow"]) 1628 if scrolled_window != None: 1629 vadjust = scrolled_window.get_vadjustment() 1630 vadjust.set_upper(list_height)
1631
1632 - def double_click_item(self):
1633 ''' 1634 Double click item. 1635 ''' 1636 if len(self.select_rows) == 1: 1637 self.emit("double-click-item", self.items[self.select_rows[0]], -1, 0, 0)
1638
1639 - def clear(self):
1640 ''' 1641 Clear all list. 1642 ''' 1643 # Clear list. 1644 self.start_select_row = None 1645 self.select_rows = [] 1646 self.items = [] 1647 1648 # Update vertical adjustment. 1649 self.update_vadjustment() 1650 1651 # Redraw. 1652 self.queue_draw()
1653
1654 - def get_current_item(self):
1655 ''' 1656 Get current item. 1657 1658 @return: Return select row, or return None if not any item selected. 1659 ''' 1660 if len(self.select_rows) != 1: 1661 return None 1662 else: 1663 return self.items[self.select_rows[0]]
1664
1665 - def set_highlight(self, item):
1666 ''' 1667 Set highlight with given item. 1668 ''' 1669 self.highlight_item = item 1670 1671 self.visible_highlight() 1672 1673 self.queue_draw()
1674
1675 - def clear_highlight(self):
1676 ''' 1677 Clear highlight status. 1678 ''' 1679 self.highlight_item = None 1680 self.queue_draw()
1681
1682 - def visible_highlight(self):
1683 ''' 1684 Visible highlight item. 1685 ''' 1686 if self.highlight_item == None: 1687 print "visible_highlight: highlight item is None." 1688 else: 1689 # Scroll viewport make sure highlight row in visible area. 1690 (offset_x, offset_y, viewport) = self.get_offset_coordinate(self) 1691 vadjust = get_match_parent(self, ["ScrolledWindow"]).get_vadjustment() 1692 highlight_index = self.highlight_item.get_index() 1693 if offset_y > highlight_index * self.item_height: 1694 vadjust.set_value(highlight_index * self.item_height) 1695 elif offset_y + vadjust.get_page_size() < (highlight_index + 1) * self.item_height: 1696 vadjust.set_value((highlight_index + 1) * self.item_height - vadjust.get_page_size() + self.title_offset_y)
1697 1698 gobject.type_register(ListView)
1699 1700 -class ListItem(gobject.GObject):
1701 ''' 1702 ListItem template to build your own item for L{ I{ListView} <ListView>}. 1703 1704 @note: This class just template to build list item, you should build new item with same interface. 1705 ''' 1706 1707 __gsignals__ = { 1708 "redraw-request" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 1709 } 1710
1711 - def __init__(self, title, artist, length):
1712 ''' 1713 Initialize ListItem class. 1714 1715 @param title: Title. 1716 @param artist: Artist. 1717 @param length: Length. 1718 ''' 1719 gobject.GObject.__init__(self) 1720 self.update(title, artist, length) 1721 self.index = None
1722
1723 - def set_index(self, index):
1724 ''' 1725 Update index. 1726 1727 This is ListView interface, you should implement it. 1728 1729 @param index: Index. 1730 ''' 1731 self.index = index
1732
1733 - def get_index(self):
1734 ''' 1735 Get index. 1736 1737 This is ListView interface, you should implement it. 1738 ''' 1739 return self.index
1740
1741 - def emit_redraw_request(self):
1742 ''' 1743 Emit redraw-request signal. 1744 1745 This is ListView interface, you should implement it. 1746 ''' 1747 self.emit("redraw-request")
1748
1749 - def update(self, title, artist, length):
1750 ''' 1751 Update. 1752 1753 This is ListView interface, you should implement it. 1754 1755 @param title: Title. 1756 @param artist: Artist. 1757 @param length: Length. 1758 ''' 1759 # Update. 1760 self.title = title 1761 self.artist = artist 1762 self.length = length 1763 1764 # Calculate item size. 1765 self.title_padding_x = 10 1766 self.title_padding_y = 5 1767 (self.title_width, self.title_height) = get_content_size(self.title, DEFAULT_FONT_SIZE) 1768 1769 self.artist_padding_x = 10 1770 self.artist_padding_y = 5 1771 (self.artist_width, self.artist_height) = get_content_size(self.artist, DEFAULT_FONT_SIZE) 1772 1773 self.length_padding_x = 10 1774 self.length_padding_y = 5 1775 (self.length_width, self.length_height) = get_content_size(self.length, DEFAULT_FONT_SIZE)
1776
1777 - def render_title(self, cr, rect, in_select, in_highlight):
1778 ''' 1779 Render title. 1780 1781 @param cr: Cairo context. 1782 @param rect: Redraw rectangle. 1783 @param in_select: Whether current item is selected, this value pass from ListView. 1784 @param in_highlight: Whether current item is highlighted, this value pass from ListView. 1785 ''' 1786 rect.x += self.title_padding_x 1787 rect.width -= self.title_padding_x * 2 1788 render_text(cr, rect, self.title, in_select, in_highlight)
1789
1790 - def render_artist(self, cr, rect, in_select, in_highlight):
1791 ''' 1792 Render artist. 1793 1794 @param cr: Cairo context. 1795 @param rect: Redraw rectangle. 1796 @param in_select: Whether current item is selected, this value pass from ListView. 1797 @param in_highlight: Whether current item is highlighted, this value pass from ListView. 1798 ''' 1799 rect.x += self.artist_padding_x 1800 rect.width -= self.title_padding_x * 2 1801 render_text(cr, rect, self.artist, in_select, in_highlight)
1802
1803 - def render_length(self, cr, rect, in_select, in_highlight):
1804 ''' 1805 Render length. 1806 1807 @param cr: Cairo context. 1808 @param rect: Redraw rectangle. 1809 @param in_select: Whether current item is selected, this value pass from ListView. 1810 @param in_highlight: Whether current item is highlighted, this value pass from ListView. 1811 ''' 1812 rect.width -= self.length_padding_x * 2 1813 render_text(cr, rect, self.length, in_select, in_highlight, align=ALIGN_END)
1814
1815 - def get_column_sizes(self):
1816 ''' 1817 Get column sizes. 1818 1819 This is ListView interface, you should implement it. 1820 1821 @return: Return column size tuple. 1822 ''' 1823 return [(self.title_width + self.title_padding_x * 2, 1824 self.title_height + self.title_padding_y * 2), 1825 (self.artist_width + self.artist_padding_x * 2, 1826 self.artist_height + self.artist_padding_y * 2), 1827 (self.length_width + self.length_padding_x * 2, 1828 self.length_height + self.length_padding_y * 2), 1829 ]
1830
1831 - def get_renders(self):
1832 ''' 1833 Get render callbacks. 1834 1835 This is ListView interface, you should implement it. 1836 1837 @return: Return render functions. 1838 ''' 1839 return [self.render_title, 1840 self.render_artist, 1841 self.render_length]
1842
1843 -def render_text(cr, rect, content, in_select, in_highlight, align=ALIGN_START, font_size=DEFAULT_FONT_SIZE):
1844 ''' 1845 Helper render text function for ListItem, you should implement your own. 1846 1847 @param cr: Cairo context. 1848 @param rect: Draw area. 1849 @param content: Content. 1850 @param in_select: Whether item is selected. 1851 @param in_highlight: Whether item is highlighted. 1852 @param align: Render alignment option, default is ALIGN_START. 1853 @param font_size: Render font size, default is DEFAULT_FONT_SIZE. 1854 ''' 1855 if in_select or in_highlight: 1856 color = ui_theme.get_color("list_item_select_text").get_color() 1857 else: 1858 color = ui_theme.get_color("list_item_text").get_color() 1859 draw_text(cr, content, 1860 rect.x, rect.y, rect.width, rect.height, 1861 font_size, 1862 color, 1863 alignment=align)
1864
1865 -def render_image(cr, rect, image_path, x, y):
1866 ''' 1867 Helper render image function for ListItem, you should implement your own. 1868 1869 @param cr: Cairo context. 1870 @param rect: Draw area. 1871 @param image_path: Image path. 1872 @param x: X coordiante of draw position. 1873 @param y: Y coordiante of draw position. 1874 ''' 1875 draw_pixbuf(cr, ui_theme.get_pixbuf(image_path).get_pixbuf(), x, y)
1876