diff options
author | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
---|---|---|
committer | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
commit | 14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch) | |
tree | 31c83bdd188ae7b64d7169974d6f066ccfe95367 /mvc/widgets/osx/tableview.py | |
parent | eb1896583afbbb622cadcde1a24e17173f61904f (diff) | |
download | librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip |
rename mvc at lvc
Diffstat (limited to 'mvc/widgets/osx/tableview.py')
-rw-r--r-- | mvc/widgets/osx/tableview.py | 1629 |
1 files changed, 0 insertions, 1629 deletions
diff --git a/mvc/widgets/osx/tableview.py b/mvc/widgets/osx/tableview.py deleted file mode 100644 index 2d2256f..0000000 --- a/mvc/widgets/osx/tableview.py +++ /dev/null @@ -1,1629 +0,0 @@ -# @Base: Miro - an RSS based video player application -# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 -# Participatory Culture Foundation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -""".tableview -- TableView widget and it's -associated classes. -""" - -import math -import logging -from contextlib import contextmanager -from collections import namedtuple - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc import signals -from mvc import errors -from mvc.widgets import widgetconst -from mvc.widgets.tableselection import SelectionOwnerMixin -from mvc.widgets.tablescroll import ScrollbarOwnerMixin -from .utils import filename_to_unicode -import wrappermap -import tablemodel -import osxmenus -from .base import Widget -from .simple import Image -from .drawing import DrawingContext, DrawingStyle, Gradient, ImageSurface -from .helpers import NotificationForwarder -from .layoutmanager import LayoutManager - -EXPANDER_PADDING = 6 -HEADER_HEIGHT = 17 -CUSTOM_HEADER_HEIGHT = 25 - -def iter_range(ns_range): - """Iterate over an NSRange object""" - return xrange(ns_range.location, ns_range.location + ns_range.length) - -Rect = namedtuple('Rect', 'x y width height') -def NSRectToRect(nsrect): - origin, size = nsrect.origin, nsrect.size - return Rect(origin.x, origin.y, size.width, size.height) - -Point = namedtuple('Point', 'x y') -def NSPointToPoint(nspoint): - return Point(int(nspoint.x), int(nspoint.y)) - -class HotspotTracker(object): - """Contains the info on the currently tracked hotspot. See: - https://develop.participatoryculture.org/index.php/WidgetAPITableView - """ - def __init__(self, tableview, point): - self.tableview = tableview - self.row = tableview.rowAtPoint_(point) - self.column = tableview.columnAtPoint_(point) - if self.row == -1 or self.column == -1: - self.hit = False - return - model = tableview.dataSource().model - self.iter = model.iter_for_row(tableview, self.row) - self.table_column = tableview.tableColumns()[self.column] - self.cell = self.table_column.dataCell() - self.update_position(point) - if isinstance(self.cell, CustomTableCell): - self.name = self.calc_hotspot() - else: - self.name = None - self.hit = (self.name is not None) - - def is_for_context_menu(self): - return self.name == '#show-context-menu' - - def calc_cell_hotspot(self, column, row): - if (self.hit and self.column == column and self.row == row): - return self.name - else: - return None - - def update_position(self, point): - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - self.pos = NSPoint(point.x - cell_frame.origin.x, - point.y - cell_frame.origin.y) - - def update_hit(self): - old_hit = self.hit - self.hit = (self.calc_hotspot() == self.name) - if old_hit != self.hit: - self.redraw_cell() - - def set_cell_data(self): - model = self.tableview.dataSource().model - row = model[self.iter] - value_dict = model.get_column_data(row, self.table_column) - self.cell.setObjectValue_(value_dict) - self.cell.set_wrapper_data() - - def calc_hotspot(self): - self.set_cell_data() - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - style = self.cell.make_drawing_style(cell_frame, self.tableview) - layout_manager = self.cell.layout_manager - layout_manager.reset() - return self.cell.wrapper.hotspot_test(style, layout_manager, - self.pos.x, self.pos.y, cell_frame.size.width, - cell_frame.size.height) - - def redraw_cell(self): - # Check to see if we removed the table in response to a hotspot click. - if self.tableview.superview() is not nil: - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - self.tableview.setNeedsDisplayInRect_(cell_frame) - -def _calc_interior_frame(total_frame, tableview): - """Calculate the inner cell area for a table cell. - - We tell cocoa that the intercell spacing is (0, 0) and instead handle the - spacing ourselves. This method calculates the area that a cell should - render to, given the total spacing. - """ - return NSMakeRect(total_frame.origin.x + tableview.column_spacing // 2, - total_frame.origin.y + tableview.row_spacing // 2, - total_frame.size.width - tableview.column_spacing, - total_frame.size.height - tableview.row_spacing) - -class MiroTableCell(NSTextFieldCell): - def init(self): - return super(MiroTableCell, self).initTextCell_('') - - def calcHeight_(self, view): - font = self.font() - return math.ceil(font.ascender() + abs(font.descender()) + - font.leading()) - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - if isinstance(value_dict, dict): - NSCell.setObjectValue_(self, value_dict['value']) - else: - # OS X calls setObjectValue_('') on intialization - NSCell.setObjectValue_(self, value_dict) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSTextFieldCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class MiroTableInfoListTextCell(MiroTableCell): - def initWithAttrGetter_(self, attr_getter): - self = self.init() - self.setWraps_(NO) - self.attr_getter = attr_getter - self._textColor = self.textColor() - return self - - def drawWithFrame_inView_(self, frame, view): - # adjust frame based on the cell spacing - frame = _calc_interior_frame(frame, view) - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight) and view.window().isMainWindow()): - self.setTextColor_(NSColor.whiteColor()) - else: - self.setTextColor_(self._textColor) - return MiroTableCell.drawWithFrame_inView_(self, frame, view) - - def titleRectForBounds_(self, rect): - frame = MiroTableCell.titleRectForBounds_(self, rect) - text_size = self.attributedStringValue().size() - frame.origin.y = rect.origin.y + (rect.size.height - text_size.height) / 2.0 - return frame - - def drawInteriorWithFrame_inView_(self, frame, view): - rect = self.titleRectForBounds_(frame) - self.attributedStringValue().drawInRect_(rect) - - def setObjectValue_(self, value): - if isinstance(value, tuple): - info, attrs, group_info = value - cell_text = self.attr_getter(info) - NSCell.setObjectValue_(self, cell_text) - else: - # Getting set to a something other than a model row, usually this - # happens in initialization - NSCell.setObjectValue_(self, '') - -class MiroTableImageCell(NSImageCell): - def calcHeight_(self, view): - return self.value_dict['image'].size().height - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - NSImageCell.setObjectValue_(self, value_dict['image']) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSImageCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class MiroCheckboxCell(NSButtonCell): - def init(self): - self = super(MiroCheckboxCell, self).init() - self.setButtonType_(NSSwitchButton) - self.setTitle_('') - return self - - def calcHeight_(self, view): - return self.cellSize().height - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - if isinstance(value_dict, dict): - NSButtonCell.setObjectValue_(self, value_dict['value']) - else: - # OS X calls setObjectValue_('') on intialization - NSCell.setObjectValue_(self, value_dict) - - def startTrackingAt_inView_(self, point, view): - return YES - - def continueTracking_at_inView_(self, lastPoint, at, view): - return YES - - def stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, tableview, mouseIsUp): - if mouseIsUp: - column = tableview.columnAtPoint_(at) - row = tableview.rowAtPoint_(at) - if column != -1 and row != -1: - wrapper = wrappermap.wrapper(tableview) - column = wrapper.columns[column] - itr = wrapper.model.iter_for_row(tableview, row) - column.renderer.emit('clicked', itr) - return NSButtonCell.stopTracking_at_inView_mouseIsUp_(self, lastPoint, - at, tableview, mouseIsUp) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSButtonCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class CellRendererBase(object): - DRAW_BACKGROUND = True - - def set_index(self, index): - self.index = index - - def get_index(self): - return self.index - -class CellRenderer(CellRendererBase): - def __init__(self): - self.cell = self.build_cell() - self._font_scale_factor = 1.0 - self._font_bold = False - self.set_align('left') - - def build_cell(self): - return MiroTableCell.alloc().init() - - def setDataCell_(self, column): - column.setDataCell_(self.cell) - - def set_text_size(self, size): - if size == widgetconst.SIZE_NORMAL: - self._font_scale_factor = 1.0 - elif size == widgetconst.SIZE_SMALL: - # make the scale factor such so that the font size is 11.0 - self._font_scale_factor = 11.0 / NSFont.systemFontSize() - else: - raise ValueError("Unknown size: %s" % size) - self._set_font() - - def set_font_scale(self, scale_factor): - self._font_scale_factor = scale_factor - self._set_font() - - def set_bold(self, bold): - self._font_bold = bold - self._set_font() - - def _set_font(self): - size = NSFont.systemFontSize() * self._font_scale_factor - if self._font_bold: - font = NSFont.boldSystemFontOfSize_(size) - else: - font = NSFont.systemFontOfSize_(size) - self.cell.setFont_(font) - - def set_color(self, color): - color = NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], - color[1], color[2], 1.0) - self.cell._textColor = color - self.cell.setTextColor_(color) - - def set_align(self, align): - if align == 'left': - ns_alignment = NSLeftTextAlignment - elif align == 'center': - ns_alignment = NSCenterTextAlignment - elif align == 'right': - ns_alignment = NSRightTextAlignment - else: - raise ValueError("unknown alignment: %s", align) - self.cell.setAlignment_(ns_alignment) - -class ImageCellRenderer(CellRendererBase): - def setDataCell_(self, column): - column.setDataCell_(MiroTableImageCell.alloc().init()) - -class CheckboxCellRenderer(CellRendererBase, signals.SignalEmitter): - def __init__(self): - signals.SignalEmitter.__init__(self, 'clicked') - self.size = widgetconst.SIZE_NORMAL - - def set_control_size(self, size): - self.size = size - - def setDataCell_(self, column): - cell = MiroCheckboxCell.alloc().init() - if self.size == widgetconst.SIZE_SMALL: - cell.setControlSize_(NSSmallControlSize) - column.setDataCell_(cell) - -class CustomTableCell(NSCell): - def init(self): - self = super(CustomTableCell, self).init() - self.layout_manager = LayoutManager() - self.hotspot = None - self.default_drawing_style = DrawingStyle() - return self - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def calcHeight_(self, view): - self.layout_manager.reset() - self.set_wrapper_data() - cell_size = self.wrapper.get_size(self.default_drawing_style, - self.layout_manager) - return cell_size[1] - - def make_drawing_style(self, frame, view): - text_color = None - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight) and view.window().isMainWindow()): - text_color = NSColor.whiteColor() - return DrawingStyle(text_color=text_color) - - def drawInteriorWithFrame_inView_(self, frame, view): - NSGraphicsContext.currentContext().saveGraphicsState() - if not self.wrapper.IGNORE_PADDING: - # adjust frame based on the cell spacing. We also have to adjust - # the hover position to account for the new frame - original_frame = frame - frame = _calc_interior_frame(frame, view) - hover_adjustment = (frame.origin.x - original_frame.origin.x, - frame.origin.y - original_frame.origin.y) - else: - hover_adjustment = (0, 0) - if self.wrapper.outline_column: - pad_left = EXPANDER_PADDING - else: - pad_left = 0 - drawing_rect = NSMakeRect(frame.origin.x + pad_left, frame.origin.y, - frame.size.width - pad_left, frame.size.height) - context = DrawingContext(view, drawing_rect, drawing_rect) - context.style = self.make_drawing_style(frame, view) - self.layout_manager.reset() - self.set_wrapper_data() - column = self.wrapper.get_index() - hover_pos = view.get_hover(self.row, column) - if hover_pos is not None: - hover_pos = [hover_pos[0] - hover_adjustment[0], - hover_pos[1] - hover_adjustment[1]] - self.wrapper.render(context, self.layout_manager, self.isHighlighted(), - self.hotspot, hover_pos) - NSGraphicsContext.currentContext().restoreGraphicsState() - - def setObjectValue_(self, value): - self.object_value = value - - def set_wrapper_data(self): - self.wrapper.__dict__.update(self.object_value) - -class CustomCellRenderer(CellRendererBase): - CellClass = CustomTableCell - - IGNORE_PADDING = False - - def __init__(self): - self.outline_column = False - self.index = None - - def setDataCell_(self, column): - # Note that the ownership is the opposite of what happens in widgets. - # The NSObject owns it's wrapper widget. This happens for a couple - # reasons: - # 1) The data cell gets copied a bunch of times, so wrappermap won't - # work with it. - # 2) The Wrapper should only needs to stay around as long as the - # NSCell that it's wrapping is around. Once the column gets removed - # from the table, the wrapper can be deleted. - nscell = self.CellClass.alloc().init() - nscell.wrapper = self - column.setDataCell_(nscell) - - def hotspot_test(self, style, layout, x, y, width, height): - return None - -class InfoListTableCell(CustomTableCell): - def set_wrapper_data(self): - self.wrapper.info, self.wrapper.attrs, self.wrapper.group_info = \ - self.object_value - -class InfoListRenderer(CustomCellRenderer): - CellClass = InfoListTableCell - - def hotspot_test(self, style, layout, x, y, width, height): - return None - -class InfoListRendererText(CellRenderer): - def build_cell(self): - cell = MiroTableInfoListTextCell.alloc() - return cell.initWithAttrGetter_(self.get_value) - -def calc_row_height(view, model_row): - max_height = 0 - model = view.dataSource().model - for column in view.tableColumns(): - cell = column.dataCell() - data = model.get_column_data(model_row, column) - cell.setObjectValue_(data) - cell_height = cell.calcHeight_(view) - max_height = max(max_height, cell_height) - if max_height == 0: - max_height = 12 - return max_height + view.row_spacing - -class TableViewDelegate(NSObject): - def tableView_willDisplayCell_forTableColumn_row_(self, view, cell, - column, row): - column = view.column_index_map[column] - cell.column = column - cell.row = row - if view.hotspot_tracker: - cell.hotspot = view.hotspot_tracker.calc_cell_hotspot(column, row) - else: - cell.hotspot = None - - def tableView_didClickTableColumn_(self, tableview, column): - wrapper = wrappermap.wrapper(tableview) - for column_wrapper in wrapper.columns: - if column_wrapper._column is column: - column_wrapper.emit('clicked') - - def tableView_toolTipForCell_rect_tableColumn_row_mouseLocation_(self, tableview, cell, rect, column, row, location): - wrapper = wrappermap.wrapper(tableview) - iter = tableview.dataSource().model.iter_for_row(tableview, row) - for wrapper_column in wrapper.columns: - if wrapper_column._column is column: - break - return (wrapper.get_tooltip(iter, wrapper_column), rect) - -class VariableHeightTableViewDelegate(TableViewDelegate): - def tableView_heightOfRow_(self, table_view, row): - model = table_view.dataSource().model - iter = model.iter_for_row(table_view, row) - if iter is None: - return 12 - return calc_row_height(table_view, model[iter]) - - -# TableViewCommon is a hack to do a Mixin class. We want the same behaviour -# for our table views and our outline views. Normally we would use a Mixin, -# but that doesn't work with pyobjc. Instead we define the common code in -# TableViewCommon, then copy it into MiroTableView and MiroOutlineView - -class TableViewCommon(object): - def init(self): - self = super(self.__class__, self).init() - self.hotspot_tracker = None - self._tracking_rects = [] - self.hover_info = None - self.column_index_map = {} - self.setFocusRingType_(NSFocusRingTypeNone) - self.handled_last_mouse_down = False - self.gradientHighlight = False - self.tracking_area = None - self.group_lines_enabled = False - self.group_line_width = 1 - self.group_line_color = (0, 0, 0, 1.0) - # we handle cell spacing manually - self.setIntercellSpacing_(NSSize(0, 0)) - self.column_spacing = 3 - self.row_spacing = 2 - return self - - def updateTrackingAreas(self): - # remove existing tracking area if needed - if self.tracking_area: - self.removeTrackingArea_(self.tracking_area) - - # create a new tracking area for the entire view. This allows us to - # get mouseMoved events whenever the mouse is inside our view. - self.tracking_area = NSTrackingArea.alloc() - self.tracking_area.initWithRect_options_owner_userInfo_( - self.visibleRect(), - NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | - NSTrackingActiveInKeyWindow, - self, - nil) - self.addTrackingArea_(self.tracking_area) - - def addTableColumn_(self, column): - index = len(self.tableColumns()) - column.set_index(index) - self.column_index_map[column._column] = index - self.SuperClass.addTableColumn_(self, column._column) - - def removeTableColumn_(self, column): - self.SuperClass.removeTableColumn_(self, column) - removed = self.column_index_map.pop(column) - for key, index in self.column_index_map.items(): - if index > removed: - self.column_index_map[key] -= 1 - - def moveColumn_toColumn_(self, src, dest): - # Need to switch the TableColumn objects too - columns = wrappermap.wrapper(self).columns - columns[src], columns[dest] = columns[dest], columns[src] - for index, column in enumerate(columns): - column.set_index(index) - self.SuperClass.moveColumn_toColumn_(self, src, dest) - - def highlightSelectionInClipRect_(self, rect): - if wrappermap.wrapper(self).draws_selection: - if not self.gradientHighlight: - return self.SuperClass.highlightSelectionInClipRect_(self, - rect) - context = NSGraphicsContext.currentContext() - focused = self.isDescendantOf_(self.window().firstResponder()) - for row in tablemodel.list_from_nsindexset(self.selectedRowIndexes()): - self.drawBackgroundGradient(context, focused, row) - - def setFrameSize_(self, size): - if size.height == 0: - size.height = 4 - self.SuperClass.setFrameSize_(self, size) - - def drawBackgroundGradient(self, context, focused, row): - widget = wrappermap.wrapper(self) - window = widget.get_window() - if window and window.is_active(): - if focused: - start_color = (0.588, 0.717, 0.843) - end_color = (0.416, 0.568, 0.713) - top_line_color = (0.416, 0.569, 0.714, 1.0) - bottom_line_color = (0.416, 0.569, 0.714, 1.0) - else: - start_color = (168 / 255.0, 188 / 255.0, 208 / 255.0) - end_color = (129 / 255.0, 152 / 255.0, 176 / 255.0) - top_line_color = (129 / 255.0, 152 / 255.0, 175 / 255.0, 1.0) - bottom_line_color = (0.416, 0.569, 0.714, 1.0) - else: - start_color = (0.675, 0.722, 0.765) - end_color = (0.592, 0.659, 0.710) - top_line_color = (0.596, 0.635, 0.671, 1.0) - bottom_line_color = (0.522, 0.576, 0.620, 1.0) - - rect = self.rectOfRow_(row) - top = NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, 1) - context.saveGraphicsState() - # draw the top line - NSColor.colorWithDeviceRed_green_blue_alpha_(*top_line_color).set() - NSRectFill(top) - bottom = NSMakeRect(rect.origin.x, rect.origin.y + rect.size.height - 2, - rect.size.width, 1) - NSColor.colorWithDeviceRed_green_blue_alpha_(*bottom_line_color).set() - NSRectFill(bottom) - highlight = NSMakeRect(rect.origin.x, rect.origin.y + rect.size.height - 1, - rect.size.width, 1) - NSColor.colorWithDeviceRed_green_blue_alpha_(0.918, 0.925, 0.941, 1.0).set() - NSRectFill(highlight) - - # draw the gradient - rect.origin.y += 1 - rect.size.height -= 3 - NSRectClip(rect) - gradient = Gradient(rect.origin.x, rect.origin.y, - rect.origin.x, rect.origin.y + rect.size.height) - gradient.set_start_color(start_color) - gradient.set_end_color(end_color) - gradient.draw() - context.restoreGraphicsState() - - def drawBackgroundInClipRect_(self, clip_rect): - # save our graphics state, since we are about to modify the clip path - graphics_context = NSGraphicsContext.currentContext() - graphics_context.saveGraphicsState() - # create a NSBezierPath that contains the rects of the columns with - # DRAW_BACKGROUND True. - clip_path = NSBezierPath.bezierPath() - number_of_columns = len(self.tableColumns()) - for col_index in iter_range(self.columnsInRect_(clip_rect)): - column = wrappermap.wrapper(self.tableColumns()[col_index]) - column_rect = None - if column.renderer.DRAW_BACKGROUND: - # We should draw the background for this column, add it's rect - # to our clip rect. - column_rect = self.rectOfColumn_(col_index) - clip_path.appendBezierPathWithRect_(column_rect) - else: - # We shouldn't draw the background for this column. Don't add - # anything to the clip rect, but do draw the area before the - # first row and after the last row. - self.drawBackgroundOutsideContent_clipRect_(col_index, - clip_rect) - if col_index == number_of_columns - 1: # last column - if not column_rect: - column_rect = self.rectOfColumn_(col_index) - column_right = column_rect.origin.x + column_rect.size.width - clip_right = clip_rect.origin.x + clip_rect.size.width - if column_right < clip_right: - # there's space to the right, so add that to the clip_rect - remaining = clip_right - column_right - left_rect = NSMakeRect(column_right, clip_rect.origin.y, - remaining, clip_rect.size.height) - clip_path.appendBezierPathWithRect_(left_rect) - # clip to that path - clip_path.addClip() - # do the default drawing - self.SuperClass.drawBackgroundInClipRect_(self, clip_rect) - # restore graphics state - graphics_context.restoreGraphicsState() - - def drawBackgroundOutsideContent_clipRect_(self, index, clip_rect): - """Draw our background outside the rows with content - - We call this for cells with DRAW_BACKGROUND set to False. For those, - we let the cell draw their own background, but we still need to draw - the background before the first cell and after the last cell. - """ - - self.backgroundColor().set() - - total_rect = NSIntersectionRect(self.rectOfColumn_(index), clip_rect) - - if self.numberOfRows() == 0: - # if no rows are selected, draw the background over everything - NSRectFill(total_rect) - return - - # fill the area above the first row - first_row_rect = self.rectOfRow_(0) - if first_row_rect.origin.y > total_rect.origin.y: - height = first_row_rect.origin.y - total_rect.origin.y - NSRectFill(NSMakeRect(total_rect.origin.x, total_rect.origin.y, - total_rect.size.width, height)) - - # fill the area below the last row - last_row_rect = self.rectOfRow_(self.numberOfRows()-1) - if NSMaxY(last_row_rect) < NSMaxY(total_rect): - y = NSMaxY(last_row_rect) + 1 - height = NSMaxY(total_rect) - NSMaxY(last_row_rect) - NSRectFill(NSMakeRect(total_rect.origin.x, y, - total_rect.size.width, height)) - - def drawRow_clipRect_(self, row, clip_rect): - self.SuperClass.drawRow_clipRect_(self, row, clip_rect) - if self.group_lines_enabled: - self.drawGroupLine_(row) - - def drawGroupLine_(self, row): - infolist = wrappermap.wrapper(self).model - if (not isinstance(infolist, tablemodel.InfoListModel) or - infolist.get_grouping() is None): - return - - info, attrs, group_info = infolist[row] - if group_info[0] == group_info[1] - 1: - rect = self.rectOfRow_(row) - rect.origin.y = NSMaxY(rect) - self.group_line_width - rect.size.height = self.group_line_width - NSColor.colorWithDeviceRed_green_blue_alpha_( - *self.group_line_color).set() - NSRectFill(rect) - - def canDragRowsWithIndexes_atPoint_(self, indexes, point): - return YES - - def draggingSourceOperationMaskForLocal_(self, local): - drag_source = wrappermap.wrapper(self).drag_source - if drag_source and local: - return drag_source.allowed_actions() - return NSDragOperationNone - - def mouseMoved_(self, event): - location = self.convertPoint_fromView_(event.locationInWindow(), nil) - row = self.rowAtPoint_(location) - column = self.columnAtPoint_(location) - if (self.hover_info is not None and self.hover_info != (row, column)): - # left a cell, redraw it the old one - rect = self.frameOfCellAtColumn_row_(self.hover_info[1], - self.hover_info[0]) - self.setNeedsDisplayInRect_(rect) - if row == -1 or column == -1: - # corner case: we got a mouseMoved_ event, but the pointer is - # outside the view - self.hover_pos = self.hover_info = None - return - # queue a redraw on the cell currently hovered over - rect = self.frameOfCellAtColumn_row_(column, row) - self.setNeedsDisplayInRect_(rect) - # recalculate hover_pos and hover_info - self.hover_pos = (location[0] - rect[0][0], - location[0] - rect[0][1]) - self.hover_info = (row, column) - - def mouseExited_(self, event): - if self.hover_info: - # mouse left our window, unset hover and redraw the cell that the - # mouse was in - rect = self.frameOfCellAtColumn_row_(self.hover_info[1], - self.hover_info[0]) - self.setNeedsDisplayInRect_(rect) - self.hover_pos = self.hover_info = None - - def get_hover(self, row, column): - if self.hover_info == (row, column): - return self.hover_pos - else: - return None - - def mouseDown_(self, event): - if event.modifierFlags() & NSControlKeyMask: - self.handleContextMenu_(event) - self.handled_last_mouse_down = True - return - - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - - if event.clickCount() == 2: - if self.handled_last_mouse_down: - return - wrapper = wrappermap.wrapper(self) - row = self.rowAtPoint_(point) - if (row != -1 and self.point_should_click(point, row)): - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-activated', iter) - return - - # Like clickCount() == 2 but keep running so we can get to run the - # hotspot tracker et al. - if event.clickCount() == 1: - wrapper = wrappermap.wrapper(self) - row = self.rowAtPoint_(point) - if (row != -1 and self.point_should_click(point, row)): - - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-clicked', iter) - - hotspot_tracker = HotspotTracker(self, point) - if hotspot_tracker.hit: - self.hotspot_tracker = hotspot_tracker - self.hotspot_tracker.redraw_cell() - self.handled_last_mouse_down = True - if hotspot_tracker.is_for_context_menu(): - self.popup_context_menu(self.hotspot_tracker.row, event) - # once we're out of that call, we know the context menu is - # gone - hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - else: - self.handled_last_mouse_down = False - self.SuperClass.mouseDown_(self, event) - - def point_should_click(self, point, row): - """Should a click on a point result in a row-clicked signal? - - Subclasses can override if not every point should result in a click. - """ - return True - - def rightMouseDown_(self, event): - self.handleContextMenu_(event) - - def handleContextMenu_(self, event): - self.window().makeFirstResponder_(self) - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - column = self.columnAtPoint_(point) - row = self.rowAtPoint_(point) - if self.group_lines_enabled and column == 0: - self.selectAllItemsInGroupForRow_(row) - self.popup_context_menu(row, event) - - def selectAllItemsInGroupForRow_(self, row): - wrapper = wrappermap.wrapper(self) - infolist = wrapper.model - if (not isinstance(infolist, tablemodel.InfoListModel) or - infolist.get_grouping() is None): - return - - info, attrs, group_info = infolist[row] - select_range = NSMakeRange(row - group_info[0], group_info[1]) - index_set = NSIndexSet.indexSetWithIndexesInRange_(select_range) - self.selectRowIndexes_byExtendingSelection_(index_set, NO) - - def popup_context_menu(self, row, event): - selection = self.selectedRowIndexes() - if row != -1 and not selection.containsIndex_(row): - index_set = NSIndexSet.alloc().initWithIndex_(row) - self.selectRowIndexes_byExtendingSelection_(index_set, NO) - wrapper = wrappermap.wrapper(self) - if wrapper.context_menu_callback is not None: - menu_items = wrapper.context_menu_callback(wrapper) - menu = osxmenus.make_context_menu(menu_items) - NSMenu.popUpContextMenu_withEvent_forView_(menu, event, self) - - def mouseDragged_(self, event): - if self.hotspot_tracker is not None: - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - self.hotspot_tracker.update_position(point) - self.hotspot_tracker.update_hit() - else: - self.SuperClass.mouseDragged_(self, event) - - def mouseUp_(self, event): - if self.hotspot_tracker is not None: - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - self.hotspot_tracker.update_position(point) - self.hotspot_tracker.update_hit() - if self.hotspot_tracker.hit: - wrappermap.wrapper(self).send_hotspot_clicked() - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - else: - self.SuperClass.mouseUp_(self, event) - - def keyDown_(self, event): - mods = osxmenus.translate_event_modifiers(event) - if event.charactersIgnoringModifiers() == ' ' and len(mods) == 0: - # handle spacebar with no modifiers by sending the row-activated - # signal - wrapper = wrappermap.wrapper(self) - row = self.selectedRow() - if row >= 0: - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-activated', iter) - else: - self.SuperClass.keyDown_(self, event) - -class TableColumn(signals.SignalEmitter): - def __init__(self, title, renderer, header=None, **attrs): - signals.SignalEmitter.__init__(self) - self.create_signal('clicked') - self._column = MiroTableColumn.alloc().initWithIdentifier_(title) - self._column.set_attrs(attrs) - wrappermap.add(self._column, self) - header_cell = MiroTableHeaderCell.alloc().init() - self.custom_header = False - if header: - header_cell.set_widget(header) - self.custom_header = True - self._column.setHeaderCell_(header_cell) - self._column.headerCell().setStringValue_(title) - self._column.setEditable_(NO) - self._column.setResizingMask_(NSTableColumnNoResizing) - self.renderer = renderer - self.sort_order_ascending = True - self.sort_indicator_visible = False - self.do_horizontal_padding = True - self.min_width = self.max_width = None - renderer.setDataCell_(self._column) - - def set_do_horizontal_padding(self, horizontal_padding): - self.do_horizontal_padding = horizontal_padding - - def set_right_aligned(self, right_aligned): - if right_aligned: - self._column.headerCell().setAlignment_(NSRightTextAlignment) - else: - self._column.headerCell().setAlignment_(NSLeftTextAlignment) - - def set_min_width(self, width): - self.min_width = width - - def set_max_width(self, width): - self.max_width = width - - def set_width(self, width): - self._column.setWidth_(width) - - def get_width(self): - return self._column.width() - - def set_resizable(self, resizable): - mask = 0 - if resizable: - mask |= NSTableColumnUserResizingMask - self._column.setResizingMask_(mask) - - def set_sort_indicator_visible(self, visible): - self.sort_indicator_visible = visible - self._column.tableView().headerView().setNeedsDisplay_(True) - - def get_sort_indicator_visible(self): - return self.sort_indicator_visible - - def set_sort_order(self, ascending): - self.sort_order_ascending = ascending - self._column.tableView().headerView().setNeedsDisplay_(True) - - def get_sort_order_ascending(self): - return self.sort_order_ascending - - def set_index(self, index): - self.index = index - self.renderer.set_index(index) - -class MiroTableColumn(NSTableColumn): - def set_attrs(self, attrs): - self._attrs = attrs - - def attrs(self): - return self._attrs - -class MiroTableView(NSTableView): - SuperClass = NSTableView - for name, value in TableViewCommon.__dict__.items(): - locals()[name] = value - -class MiroTableHeaderView(NSTableHeaderView): - def initWithFrame_(self, frame): - # frame is not used - self = super(MiroTableHeaderView, self).initWithFrame_(frame) - self.selected = None - self.custom_header = False - return self - - def drawRect_(self, rect): - wrapper = wrappermap.wrapper(self.tableView()) - if self.selected: - self.selected.set_selected(False) - for column in wrapper.columns: - if column.sort_indicator_visible: - self.selected = column._column.headerCell() - self.selected.set_selected(True) - self.selected.set_ascending(column.sort_order_ascending) - break - NSTableHeaderView.drawRect_(self, rect) - if self.custom_header: - NSGraphicsContext.currentContext().saveGraphicsState() - # Draw the separator between the header and the contents. - context = DrawingContext(self, rect, rect) - context.set_line_width(1) - context.set_color((2 / 255.0, 2 / 255.0, 2 / 255.0)) - context.move_to(0, context.height - 0.5) - context.rel_line_to(context.width, 0) - context.stroke() - NSGraphicsContext.currentContext().restoreGraphicsState() - -class MiroTableHeaderCell(NSTableHeaderCell): - def init(self): - self = super(MiroTableHeaderCell, self).init() - self.layout_manager = LayoutManager() - self.button = None - return self - - def set_selected(self, selected): - self.button._enabled = selected - - def set_ascending(self, ascending): - self.button._ascending = ascending - - def set_widget(self, widget): - self.button = widget - - def drawWithFrame_inView_(self, frame, view): - if self.button is None: - # use the default behavior when set_widget hasn't been called - return NSTableHeaderCell.drawWithFrame_inView_(self, frame, view) - - NSGraphicsContext.currentContext().saveGraphicsState() - drawing_rect = NSMakeRect(frame.origin.x, frame.origin.y, - frame.size.width, frame.size.height) - context = DrawingContext(view, drawing_rect, drawing_rect) - context.style = self.make_drawing_style(frame, view) - self.layout_manager.reset() - columns = wrappermap.wrapper(view.tableView()).columns - header_cells = [c._column.headerCell() for c in columns] - background_only = not self in header_cells - self.button.draw(context, self.layout_manager, background_only) - NSGraphicsContext.currentContext().restoreGraphicsState() - - def make_drawing_style(self, frame, view): - text_color = None - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight)): - text_color = NSColor.whiteColor() - return DrawingStyle(text_color=text_color) - -class CocoaSelectionOwnerMixin(SelectionOwnerMixin): - """Cocoa-specific methods for selection management. - - This subclass should not define any behavior. Methods that cannot be - completed in this widget state should raise WidgetActionError. - """ - - def _set_allow_multiple_select(self, allow): - self.tableview.setAllowsMultipleSelection_(allow) - - def _get_allow_multiple_select(self): - return self.tableview.allowsMultipleSelection() - - def _get_selected_iters(self): - selection = self.tableview.selectedRowIndexes() - selrows = tablemodel.list_from_nsindexset(selection) - return [self.model.iter_for_row(self.tableview, row) for row in selrows] - - def _get_selected_iter(self): - row = self.tableview.selectedRow() - if row == -1: - return None - return self.model.iter_for_row(self.tableview, row) - - def _get_selected_rows(self): - return [self.model[i] for i in self._get_selected_iters()] - - @property - def num_rows_selected(self): - return self.tableview.numberOfSelectedRows() - - def _is_selected(self, iter_): - row = self.row_of_iter(iter_) - return self.tableview.isRowSelected_(row) - - def _select(self, iter_): - row = self.row_of_iter(iter_) - index_set = NSIndexSet.alloc().initWithIndex_(row) - self.tableview.selectRowIndexes_byExtendingSelection_(index_set, YES) - - def _unselect(self, iter_): - self.tableview.deselectRow_(self.row_of_iter(iter_)) - - def _unselect_all(self): - self.tableview.deselectAll_(nil) - - def _iter_to_string(self, iter_): - return unicode(self.model.row_of_iter(self.tableview, iter_)) - - def _iter_from_string(self, row): - return self.model.iter_for_row(self.tableview, int(row)) - -class CocoaScrollbarOwnerMixin(ScrollbarOwnerMixin): - """Manages a TableView's scroll position.""" - def __init__(self): - ScrollbarOwnerMixin.__init__(self, _work_around_17153=True) - self.connect('place-in-scroller', self.on_place_in_scroller) - self.scroll_position = (0, 0) - self.clipview_notifications = None - self._position_set = False - - def _set_scroll_position(self, scroll_to): - """Restore a saved scroll position.""" - self.scroll_position = scroll_to - try: - scroller = self._scroller - except errors.WidgetNotReadyError: - return - self._position_set = True - clipview = scroller.contentView() - if not self.clipview_notifications: - self.clipview_notifications = NotificationForwarder.create(clipview) - # NOTE: intentional changes are BoundsChanged; bad changes are - # FrameChanged - clipview.setPostsFrameChangedNotifications_(YES) - self.clipview_notifications.connect(self.on_scroll_changed, - 'NSViewFrameDidChangeNotification') - # NOTE: scrollPoint_ just scrolls the point into view; we want to - # scroll the view so that the point becomes the origin - size = self.tableview.visibleRect().size - size = (size.width, size.height) - rect = NSMakeRect(scroll_to[0], scroll_to[1], size[0], size[1]) - self.tableview.scrollRectToVisible_(rect) - - def on_place_in_scroller(self, scroller): - # workaround for 17153.1 - if not self._position_set: - self._set_scroll_position(self.scroll_position) - - @property - def _manually_scrolled(self): - """Return whether the view has been scrolled explicitly by the user - since the last time it was set automatically. Ignores X coords. - """ - auto_y = self.scroll_position[1] - real_y = self.get_scroll_position()[1] - return abs(auto_y - real_y) > 5 - - def _get_item_area(self, iter_): - rect = self.tableview.rectOfRow_(self.row_of_iter(iter_)) - return NSRectToRect(rect) - - def _get_visible_area(self): - return NSRectToRect(self._scroller.contentView().documentVisibleRect()) - - def _get_scroll_position(self): - point = self._scroller.contentView().documentVisibleRect().origin - return NSPointToPoint(point) - - def on_scroll_changed(self, notification): - # we get this notification when the scroll position has been reset (when - # it should not have been); put it back - self.set_scroll_position(self.scroll_position) - # this notification also serves as the Cocoa equivalent to - # on_scroll_range_changed, which tells super when we may be ready to - # scroll to an iter - self.emit('scroll-range-changed') - - def set_scroller(self, scroller): - """For GTK; Cocoa tableview knows its enclosingScrollView""" - - @property - def _scroller(self): - """Return an NSScrollView or raise WidgetNotReadyError""" - scroller = self.tableview.enclosingScrollView() - if not scroller: - raise errors.WidgetNotReadyError('enclosingScrollView') - return scroller - -class SorterPadding(NSView): - # Why is this a Mac only widget? Because the wrappermap mechanism requires - # us to layout the widgets (so that we may call back to the portable API - # hooks of the widget. Since we only set the view component, this fake - # widget is never placed so the wrappermap mechanism fails to work. - # - # So far, this is okay because only the Mac uses custom headers. - def init(self): - self = super(SorterPadding, self).init() - image = Image(resources.path('images/headertoolbar.png')) - self.image = ImageSurface(image) - return self - - def isFlipped(self): - return YES - - def drawRect_(self, rect): - context = DrawingContext(self, self.bounds(), rect) - context.style = DrawingStyle() - self.image.draw(context, 0, 0, context.width, context.height) - # XXX this color doesn't take into account enable/disabled state - # of the sorting widgets. - edge = 72.0 / 255 - context.set_color((edge, edge, edge)) - context.set_line_width(1) - context.move_to(0.5, 0) - context.rel_line_to(0, context.height) - context.stroke() - -class TableView(CocoaSelectionOwnerMixin, CocoaScrollbarOwnerMixin, Widget): - """Displays data as a tabular list. TableView follows the GTK TreeView - widget fairly closely. - """ - - CREATES_VIEW = False - # Bit of a hack. We create several views. By setting CREATES_VIEW to - # False, we get to position the views manually. - - draws_selection = True - - def __init__(self, model, custom_headers=False): - Widget.__init__(self) - CocoaSelectionOwnerMixin.__init__(self) - CocoaScrollbarOwnerMixin.__init__(self) - self.create_signal('hotspot-clicked') - self.create_signal('row-clicked') - self.create_signal('row-activated') - self.create_signal('reallocate-columns') - self.model = model - self.columns = [] - self.drag_source = self.drag_dest = None - self.context_menu_callback = None - self.tableview = MiroTableView.alloc().init() - self.data_source = tablemodel.MiroTableViewDataSource.alloc() - types = (tablemodel.MIRO_DND_ITEM_LOCAL,) - self.tableview.registerForDraggedTypes_(types) - self.view = self.tableview - self.data_source.initWithModel_(self.model) - self.tableview.setDataSource_(self.data_source) - self.tableview.setVerticalMotionCanBeginDrag_(YES) - self.set_columns_draggable(False) - self.set_auto_resizes(False) - self.row_height_set = False - self.set_fixed_height(False) - self.auto_resizing = False - self.header_view = MiroTableHeaderView.alloc().initWithFrame_( - NSMakeRect(0, 0, 0, 0)) - self.tableview.setCornerView_(None) - self.custom_header = False - self.header_height = HEADER_HEIGHT - self.set_show_headers(True) - self.notifications = NotificationForwarder.create(self.tableview) - self.model_signal_ids = [ - self.model.connect_weak('row-changed', self.on_row_change), - self.model.connect_weak('structure-will-change', - self.on_model_structure_change), - ] - self.iters_to_update = [] - self.height_changed = self.reload_needed = False - self.old_selection = None - self._resizing = False - if custom_headers: - self._enable_custom_headers() - - def unset_model(self): - for signal_id in self.model_signal_ids: - self.model.disconnect(signal_id) - self.model = None - self.tableview.setDataSource_(None) - self.data_source = None - - def _enable_custom_headers(self): - self.custom_header = True - self.header_height = CUSTOM_HEADER_HEIGHT - self.header_view.custom_header = True - self.tableview.setCornerView_(SorterPadding.alloc().init()) - - def enable_album_view_focus_hack(self): - # this only matters on GTK - pass - - def focus(self): - if self.tableview.window() is not None: - self.tableview.window().makeFirstResponder_(self.tableview) - - def send_hotspot_clicked(self): - tracker = self.tableview.hotspot_tracker - self.emit('hotspot-clicked', tracker.name, tracker.iter) - - def on_row_change(self, model, iter): - self.iters_to_update.append(iter) - if not self.fixed_height: - self.height_changed = True - if self.tableview.hotspot_tracker is not None: - self.tableview.hotspot_tracker.update_hit() - - def on_model_structure_change(self, model): - self.will_need_reload() - self.cancel_hotspot_track() - - def will_need_reload(self): - if not self.reload_needed: - self.reload_needed = True - self.old_selection = self._get_selected_rows() - - def cancel_hotspot_track(self): - if self.tableview.hotspot_tracker is not None: - self.tableview.hotspot_tracker.redraw_cell() - self.tableview.hotspot_tracker = None - - def on_expanded(self, notification): - self.invalidate_size_request() - item = notification.userInfo()['NSObject'] - iter_ = self.model.iter_for_item[item] - self.emit('row-expanded', iter_, self.model.get_path(iter_)) - - def on_collapsed(self, notification): - self.invalidate_size_request() - item = notification.userInfo()['NSObject'] - iter_ = self.model.iter_for_item[item] - self.emit('row-collapsed', iter_, self.model.get_path(iter_)) - - def on_column_resize(self, notification): - if self.auto_resizing or self._resizing: - return - self._resizing = True - try: - column = notification.userInfo()['NSTableColumn'] - label = column.headerCell().stringValue() - self.emit('reallocate-columns', {label: column.width()}) - finally: - self._resizing = False - - def is_tree(self): - return isinstance(self.model, tablemodel.TreeTableModel) - - def set_row_expanded(self, iter, expanded): - """Expand or collapse the specified row. Succeeds or raises - WidgetActionError. - """ - item = iter.value() - if expanded: - self.tableview.expandItem_(item) - else: - self.tableview.collapseItem_(item) - if self.tableview.isItemExpanded_(item) != expanded: - raise errors.WidgetActionError( - "cannot expand iter. expandable: %r" % ( - self.tableview.isExpandable_(item),)) - self.invalidate_size_request() - - def is_row_expanded(self, iter): - return self.tableview.isItemExpanded_(iter.value()) - - def calc_size_request(self): - self.tableview.tile() - height = self.tableview.frame().size.height - if self._show_headers: - height += self.header_height - return self.calc_width(), height - - def viewport_repositioned(self): - self._do_layout() - - def viewport_created(self): - wrappermap.add(self.tableview, self) - self._do_layout() - self._add_views() - self.notifications.connect(self.on_selection_changed, - 'NSTableViewSelectionDidChangeNotification') - self.notifications.connect(self.on_column_resize, - 'NSTableViewColumnDidResizeNotification') - # scroll has been unset - self._position_set = False - - def remove_viewport(self): - if self.viewport is not None: - self._remove_views() - wrappermap.remove(self.tableview) - self.notifications.disconnect() - self.viewport = None - if self.clipview_notifications: - self.clipview_notifications.disconnect() - self.clipview_notifications = None - - def _should_place_header_view(self): - return self._show_headers and not self.parent_is_scroller - - def _add_views(self): - self.viewport.view.addSubview_(self.tableview) - if self._should_place_header_view(): - self.viewport.view.addSubview_(self.header_view) - - def _remove_views(self): - self.tableview.removeFromSuperview() - self.header_view.removeFromSuperview() - - def _do_layout(self): - x = self.viewport.placement.origin.x - y = self.viewport.placement.origin.y - width = self.viewport.get_width() - height = self.viewport.get_height() - if self._should_place_header_view(): - self.header_view.setFrame_(NSMakeRect(x, y, - width, self.header_height)) - self.tableview.setFrame_(NSMakeRect(x, y + self.header_height, - width, height - self.header_height)) - else: - self.header_view.setFrame_(NSMakeRect(x, y, - width, self.header_height)) - self.tableview.setFrame_(NSMakeRect(x, y, width, height)) - - if self.auto_resize: - self.auto_resizing = True - # ListView sizes itself in do_size_allocated; - # this is necessary for tablist and StandardView - columns = self.tableview.tableColumns() - if len(columns) == 1: - columns[0].setWidth_(self.viewport.area().size.width) - self.auto_resizing = False - self.queue_redraw() - - def calc_width(self): - if self.column_count() == 0: - return 0 - width = 0 - columns = self.tableview.tableColumns() - if self.auto_resize: - # Table auto-resizes, we can shrink to min-width for each column - width = sum(column.minWidth() for column in columns) - width += self.tableview.column_spacing * self.column_count() - else: - # Table doesn't auto-resize, the columns can't get smaller than - # their current width - width = sum(column.width() for column in columns) - return width - - def start_bulk_change(self): - # stop our model from emitting signals, which is slow if we're - # adding/removing/changing a bunch of rows. Instead, just reload the - # model afterwards. - self.will_need_reload() - self.cancel_hotspot_track() - self.model.freeze_signals() - - def model_changed(self): - if not self.row_height_set and self.fixed_height: - self.try_to_set_row_height() - self.model.thaw_signals() - size_changed = False - if self.reload_needed: - self.tableview.reloadData() - new_selection = self._get_selected_rows() - if new_selection != self.old_selection: - self.on_selection_changed(self.tableview) - self.old_selection = None - size_changed = True - elif self.iters_to_update: - if self.fixed_height or not self.height_changed: - # our rows don't change height, just update cell areas - if self.is_tree(): - for iter in self.iters_to_update: - self.tableview.reloadItem_(iter.value()) - else: - for iter in self.iters_to_update: - row = self.row_of_iter(iter) - rect = self.tableview.rectOfRow_(row) - self.tableview.setNeedsDisplayInRect_(rect) - else: - # our rows can change height inform Cocoa that their heights - # might have changed (this will redraw them) - index_set = NSMutableIndexSet.alloc().init() - for iter in self.iters_to_update: - try: - index_set.addIndex_(self.row_of_iter(iter)) - except LookupError: - # This happens when the iter's parent is unexpanded, - # just ignore. - pass - self.tableview.noteHeightOfRowsWithIndexesChanged_(index_set) - size_changed = True - else: - return - if size_changed: - self.invalidate_size_request() - self.height_changed = self.reload_needed = False - self.iters_to_update = [] - - def width_for_columns(self, width): - """If the table is width pixels big, how much width is available for - the table's columns. - """ - # XXX this used to do some calculation with the spacing of each column, - # but it doesn't appear like we need it to be that complicated anymore - # (see #18273) - return width - 2 - - def set_column_spacing(self, column_spacing): - self.tableview.column_spacing = column_spacing - - def set_row_spacing(self, row_spacing): - self.tableview.row_spacing = row_spacing - - def set_alternate_row_backgrounds(self, setting): - self.tableview.setUsesAlternatingRowBackgroundColors_(setting) - - def set_grid_lines(self, horizontal, vertical): - mask = 0 - if horizontal: - mask |= NSTableViewSolidHorizontalGridLineMask - if vertical: - mask |= NSTableViewSolidVerticalGridLineMask - self.tableview.setGridStyleMask_(mask) - - def set_gradient_highlight(self, setting): - self.tableview.gradientHighlight = setting - - def set_group_lines_enabled(self, enabled): - self.tableview.group_lines_enabled = enabled - self.queue_redraw() - - def set_group_line_style(self, color, width): - self.tableview.group_line_color = color + (1.0,) - self.tableview.group_line_width = width - self.queue_redraw() - - def get_tooltip(self, iter, column): - return None - - def add_column(self, column): - if not self.custom_header == column.custom_header: - raise ValueError('Column header does not match type ' - 'required by TableView') - self.columns.append(column) - self.tableview.addTableColumn_(column) - self._set_min_max_column_widths(column) - # Adding a column means that each row could have a different height. - # call noteNumberOfRowsChanged() to have OS X recalculate the heights - self.tableview.noteNumberOfRowsChanged() - self.invalidate_size_request() - self.try_to_set_row_height() - - def _set_min_max_column_widths(self, column): - if column.do_horizontal_padding: - spacing = self.tableview.column_spacing - else: - spacing = 0 - if column.min_width > 0: - column._column.setMinWidth_(column.min_width + spacing) - if column.max_width > 0: - column._column.setMaxWidth_(column.max_width + spacing) - - def column_count(self): - return len(self.tableview.tableColumns()) - - def remove_column(self, index): - column = self.columns.pop(index) - self.tableview.removeTableColumn_(column._column) - self.invalidate_size_request() - - def get_columns(self): - titles = [] - columns = self.tableview.tableColumns() - for column in columns: - titles.append(column.headerCell().stringValue()) - return titles - - def set_background_color(self, (red, green, blue)): - color = NSColor.colorWithDeviceRed_green_blue_alpha_(red, green, blue, - 1.0) - self.tableview.setBackgroundColor_(color) - - def set_show_headers(self, show): - self._show_headers = show - if show: - self.tableview.setHeaderView_(self.header_view) - else: - self.tableview.setHeaderView_(None) - if self.viewport is not None: - self._remove_views() - self._do_layout() - self._add_views() - self.invalidate_size_request() - self.queue_redraw() - - def is_showing_headers(self): - return self._show_headers - - def set_search_column(self, model_index): - pass - - def try_to_set_row_height(self): - if len(self.model) > 0: - first_iter = self.model.first_iter() - height = calc_row_height(self.tableview, self.model[first_iter]) - self.tableview.setRowHeight_(height) - self.row_height_set = True - - def set_auto_resizes(self, setting): - self.auto_resize = setting - - def set_columns_draggable(self, dragable): - self.tableview.setAllowsColumnReordering_(dragable) - - def set_fixed_height(self, fixed): - if fixed: - self.fixed_height = True - delegate_class = TableViewDelegate - self.row_height_set = False - self.try_to_set_row_height() - else: - self.fixed_height = False - delegate_class = VariableHeightTableViewDelegate - self.delegate = delegate_class.alloc().init() - self.tableview.setDelegate_(self.delegate) - self.tableview.reloadData() - - def row_of_iter(self, iter): - return self.model.row_of_iter(self.tableview, iter) - - def set_context_menu_callback(self, callback): - self.context_menu_callback = callback - - # disable the drag when the cells are constantly updating. Mac OS X - # deals badly with this.. - def set_volatile(self, volatile): - if volatile: - self.data_source.setDragSource_(None) - self.data_source.setDragDest_(None) - else: - self.data_source.setDragSource_(self.drag_source) - self.data_source.setDragDest_(self.drag_dest) - - def set_drag_source(self, drag_source): - self.drag_source = drag_source - self.data_source.setDragSource_(drag_source) - - def set_drag_dest(self, drag_dest): - self.drag_dest = drag_dest - if drag_dest is None: - self.data_source.setDragDest_(None) - else: - types = drag_dest.allowed_types() - self.data_source.setDragDest_(drag_dest) |