1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""miro.plat.frontends.widgets.layout -- Widgets that handle laying out other
32
We basically follow GTK's packing model. Widgets are packed into vboxes,
33
hboxes or other container widgets. The child widgets request a minimum size,
34
and the container widgets allocate space for their children. Widgets may get
35
more size then they requested in which case they have to deal with it. In
36
rare cases, widgets may get less size then they requested in which case they
37
should just make sure they don't throw an exception or segfault.
39
Check out the GTK tutorial for more info.
45
from Foundation import *
46
from objc import YES, NO, nil, signature, loadBundle
49
from miro.plat.frontends.widgets import tableview
50
from miro.plat.frontends.widgets import wrappermap
51
from miro.plat.frontends.widgets import viewport
52
from miro.plat.frontends.widgets.base import Container, Bin, FlippedView
53
from miro.plat.frontends.widgets.helpers import NotificationForwarder
54
from miro.util import Matrix
56
rbSplitViewBundlePath = '%s/RBSplitView.framework' % NSBundle.mainBundle().privateFrameworksPath()
57
loadBundle('RBSplitView', globals(), bundle_path=rbSplitViewBundlePath)
59
def _extra_space_iter(extra_length, count):
60
"""Utility function to allocate extra space left over in containers."""
63
extra_space, leftover = divmod(extra_length, count)
67
yield extra_space + leftover
72
"""Utility class to store how we are packing a single widget."""
74
def __init__(self, widget, expand, padding):
77
self.padding = padding
80
"""Base class for HBox and VBox. """
83
def __init__(self, spacing=0):
84
self.spacing = spacing
85
Container.__init__(self)
86
self.packing_start = []
90
def packing_both(self):
91
return itertools.chain(self.packing_start, self.packing_end)
93
def get_children(self):
94
for packing in self.packing_both():
96
children = property(get_children)
98
# Internally Boxes use a (length, breadth) coordinate system. length and
99
# breadth will be either x or y depending on which way the box is
100
# oriented. The subclasses must provide methods to translate between the
101
# 2 coordinate systems.
103
def translate_size(self, size):
104
"""Translate a (width, height) tulple to (length, breadth)."""
105
raise NotImplementedError()
107
def untranslate_size(self, size):
108
"""Reverse the work of translate_size."""
109
raise NotImplementedError()
111
def make_child_rect(self, position, length):
112
"""Create a rect to position a child with."""
113
raise NotImplementedError()
115
def pack_start(self, child, expand=False, padding=0):
116
self.packing_start.append(BoxPacking(child, expand, padding))
118
self.expand_count += 1
119
self.child_added(child)
121
def pack_end(self, child, expand=False, padding=0):
122
self.packing_end.append(BoxPacking(child, expand, padding))
124
self.expand_count += 1
125
self.child_added(child)
127
def _remove_from_packing(self, child):
128
for i in xrange(len(self.packing_start)):
129
if self.packing_start[i].widget is child:
130
return self.packing_start.pop(i)
131
for i in xrange(len(self.packing_end)):
132
if self.packing_end[i].widget is child:
133
return self.packing_end.pop(i)
134
raise LookupError("%s not found" % child)
136
def remove(self, child):
137
packing = self._remove_from_packing(child)
139
self.expand_count -= 1
140
self.child_removed(child)
142
def translate_widget_size(self, widget):
143
return self.translate_size(widget.get_size_request())
145
def calc_size_request(self):
147
for packing in self.packing_both():
148
child_length, child_breadth = \
149
self.translate_widget_size(packing.widget)
150
length += child_length
152
length += packing.padding * 2 # Need to pad on both sides
153
breadth = max(breadth, child_breadth)
154
spaces = max(0, len(self.packing_start) + len(self.packing_end) - 1)
155
length += spaces * self.spacing
156
return self.untranslate_size((length, breadth))
158
def place_children(self):
159
request_length, request_breadth = self.translate_widget_size(self)
160
ps = self.viewport.placement.size
161
total_length, dummy = self.translate_size((ps.width, ps.height))
162
total_extra_space = total_length - request_length
163
extra_space_iter = _extra_space_iter(total_extra_space,
165
start_end = self._place_packing_list(self.packing_start,
167
if self.expand_count == 0 and total_extra_space > 0:
168
# account for empty space after the end of pack_start list and
169
# before the pack_end list.
170
self.draw_empty_space(start_end, total_extra_space)
171
start_end += total_extra_space
172
self._place_packing_list(reversed(self.packing_end), extra_space_iter,
175
def draw_empty_space(self, start, length):
176
empty_rect = self.make_child_rect(start, length)
177
my_view = self.viewport.view
178
opaque_view = my_view.opaqueAncestor()
179
if opaque_view is not None:
180
empty_rect2 = opaque_view.convertRect_fromView_(empty_rect, my_view)
181
opaque_view.setNeedsDisplayInRect_(empty_rect2)
183
def _place_packing_list(self, packing_list, extra_space_iter, position):
184
for packing in packing_list:
185
child_length, child_breadth = \
186
self.translate_widget_size(packing.widget)
188
child_length += extra_space_iter.next()
189
if packing.padding: # space before
190
self.draw_empty_space(position, packing.padding)
191
position += packing.padding
192
child_rect = self.make_child_rect(position, child_length)
193
if packing.padding: # space after
194
self.draw_empty_space(position, packing.padding)
195
position += packing.padding
196
packing.widget.place(child_rect, self.viewport.view)
197
position += child_length
199
self.draw_empty_space(position, self.spacing)
200
position += self.spacing
204
Container.enable(self)
205
for mem in self.children:
209
Container.disable(self)
210
for mem in self.children:
214
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
215
def translate_size(self, size):
216
return (size[1], size[0])
218
def untranslate_size(self, size):
219
return (size[1], size[0])
221
def make_child_rect(self, position, length):
222
placement = self.viewport.placement
223
return NSMakeRect(placement.origin.x, placement.origin.y + position,
224
placement.size.width, length)
227
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
228
def translate_size(self, size):
229
return (size[0], size[1])
231
def untranslate_size(self, size):
232
return (size[0], size[1])
234
def make_child_rect(self, position, length):
235
placement = self.viewport.placement
236
return NSMakeRect(placement.origin.x + position, placement.origin.y,
237
length, placement.size.height)
239
class Alignment(Bin):
240
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
243
def __init__(self, xalign=0.0, yalign=0.0, xscale=0.0, yscale=0.0,
244
top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
250
self.top_pad = top_pad
251
self.bottom_pad = bottom_pad
252
self.left_pad = left_pad
253
self.right_pad = right_pad
254
if self.child is not None:
255
self.place_children()
257
def set(self, xalign=0.0, yalign=0.0, xscale=0.0, yscale=0.0):
262
if self.child is not None:
263
self.place_children()
265
def set_padding(self, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
266
self.top_pad = top_pad
267
self.bottom_pad = bottom_pad
268
self.left_pad = left_pad
269
self.right_pad = right_pad
270
if self.child is not None:
271
self.place_children()
273
def vertical_pad(self):
274
return self.top_pad + self.bottom_pad
276
def horizontal_pad(self):
277
return self.left_pad + self.right_pad
279
def calc_size_request(self):
281
child_width, child_height = self.child.get_size_request()
282
return (child_width + self.horizontal_pad(),
283
child_height + self.vertical_pad())
287
def calc_size(self, requested, total, scale):
288
extra_width = max(0, total - requested)
289
return requested + int(round(extra_width * scale))
291
def calc_position(self, size, total, align):
292
return int(round((total - size) * align))
294
def place_children(self):
295
if self.child is None:
298
total_width = self.viewport.placement.size.width
299
total_height = self.viewport.placement.size.height
300
total_width -= self.horizontal_pad()
301
total_height -= self.vertical_pad()
302
request_width, request_height = self.child.get_size_request()
304
child_width = self.calc_size(request_width, total_width, self.xscale)
305
child_height = self.calc_size(request_height, total_height, self.yscale)
306
child_x = self.calc_position(child_width, total_width, self.xalign)
307
child_y = self.calc_position(child_height, total_height, self.yalign)
308
child_x += self.left_pad
309
child_y += self.top_pad
311
my_origin = self.viewport.area().origin
312
child_rect = NSMakeRect(my_origin.x + child_x, my_origin.y + child_y, child_width, child_height)
313
self.child.place(child_rect, self.viewport.view)
314
# Make sure the space not taken up by our child is redrawn.
315
self.viewport.queue_redraw()
317
class DetachedWindowHolder(Alignment):
319
Alignment.__init__(self, bottom_pad=16, xscale=1.0, yscale=1.0)
321
class SplitterDelegate(NSObject):
323
def initWithSplitter_(self, splitter):
324
self = super(SplitterDelegate, self).init()
325
self.splitter = splitter
326
self.normalColor = NSColor.colorWithDeviceWhite_alpha_(64.0/255.0, 1.0)
327
self.disabledColor = NSColor.colorWithDeviceWhite_alpha_(135.0/255.0, 1.0)
330
@signature("{_NSRect={_NSPoint=ff}{_NSSize=ff}}@:@{_NSRect={_NSPoint=ff}{_NSSize=ff}}i")
331
def splitView_cursorRect_forDivider_(self, sender, rect, divider):
337
@signature("i@:@{_NSPoint=ff}@")
338
def splitView_dividerForPoint_inSubview_(self, sender, point, subview):
339
left_width = self.splitter.left_view.bounds().size.width
340
if (subview.identifier() == 'left' and point.x >= left_width-3) or (subview.identifier() == 'right' and point.x-left_width <= 3):
344
@signature("v@:@@{_NSRect={_NSPoint=ff}{_NSSize=ff}}{_NSRect={_NSPoint=ff}{_NSSize=ff}}")
345
def splitView_changedFrameOfSubview_from_to_(self, sender, subview, before, after):
346
if subview.identifier() == 'left':
347
self.splitter.place_left_children()
349
self.splitter.place_right_children()
352
def splitView_wasResizedFrom_to_(self, sender, before, after):
353
sender.adjustSubviewsExcepting_(self.splitter.left_view);
355
@signature("{_NSRect={_NSPoint=ff}{_NSSize=ff}}@:@{_NSRect={_NSPoint=ff}{_NSSize=ff}}@@{_NSRect={_NSPoint=ff}{_NSSize=ff}}")
356
def splitView_willDrawDividerInRect_betweenView_andView_withProposedRect_(self, sender, dividerRect, leading, trailing, imageRect):
357
if self.splitter.view.window().isMainWindow():
358
self.normalColor.set()
360
self.disabledColor.set()
361
NSRectFill(dividerRect)
364
@signature("i@:@i@@i")
365
def splitView_shouldResizeWindowForDivider_betweenView_andView_willGrow_(self, sender, divider, leading, trailing, grow):
366
return (NSApp().currentEvent().modifierFlags() & NSAlternateKeyMask != 0)
368
class MiroSplitSubview(RBSplitSubview):
372
class Splitter(Container):
373
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
375
Container.__init__(self)
377
self.view = RBSplitView.alloc().initWithFrame_(NSRect((0,0), (800,600)))
378
self.view.setVertical_(YES)
380
self.delegate = SplitterDelegate.alloc().initWithSplitter_(self)
381
self.view.setDelegate_(self.delegate)
384
self.left_view = MiroSplitSubview.alloc().init()
385
self.left_view.setIdentifier_('left')
386
self.view.addSubview_atPosition_(self.left_view, 0)
389
self.right_view = MiroSplitSubview.alloc().init()
390
self.right_view.setIdentifier_('right')
391
self.view.addSubview_atPosition_(self.right_view, 1)
393
divider = NSImage.alloc().initWithSize_(NSSize(1.0, 1.0))
395
NSColor.clearColor().set()
396
NSRectFill(NSRect((0.0, 0.0), (1.0, 1.0)))
397
divider.unlockFocus()
398
divider.setFlipped_(YES)
399
self.view.setDivider_(divider)
401
def get_children(self):
404
children.append(self.left)
406
children.append(self.right)
409
def calc_size_request(self):
412
for child in self.get_children():
413
child_width, child_height = child.get_size_request()
415
height = max(height, child_height)
418
def place_left_children(self):
419
if self.left is not None:
420
self.left.place(self.left_view.bounds(), self.left_view)
422
def place_right_children(self):
423
if self.right is not None:
424
self.right.place(self.right_view.bounds(), self.right_view)
426
def place_children(self):
427
self.place_left_children()
428
self.place_right_children()
430
def set_left(self, widget):
431
"""Set the left child widget."""
434
self.set_min_left_width()
435
self.child_changed(old_left, self.left)
437
def set_right(self, widget):
438
"""Set the right child widget. """
439
old_right = self.right
441
self.set_min_right_width()
442
self.child_changed(old_right, self.right)
444
def set_left_width(self, width):
446
self.left_view.setHidden_(YES)
448
self.left_view.setHidden_(NO)
449
self.left_view.setDimension_(width)
450
self.place_children()
452
def get_left_width(self):
453
return self.left_view.frame().size.width
455
def set_right_width(self, width):
456
self.right_view.setDimension_(width)
457
self.place_children()
459
def set_min_left_width(self):
460
min_width, _ = self.left.get_size_request()
461
self.left_view.setMinDimension_andMaxDimension_(min_width, 600)
463
def set_min_right_width(self):
464
min_width, _ = self.right.get_size_request()
465
self.right_view.setMinDimension_andMaxDimension_(min_width, 4000)
467
def remove_left(self):
468
"""Remove the left child widget."""
471
self.child_removed(old_left)
473
def remove_right(self):
474
"""Remove the right child widget."""
475
old_right = self.right
477
self.child_removed(old_right)
479
class _TablePacking(object):
480
"""Utility class to help with packing Table widgets."""
481
def __init__(self, widget, column, row, column_span, row_span):
485
self.column_span = column_span
486
self.row_span = row_span
488
def column_indexes(self):
489
return range(self.column, self.column + self.column_span)
491
def row_indexes(self):
492
return range(self.row, self.row + self.row_span)
494
class Table(Container):
495
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
498
def __init__(self, columns, rows):
499
Container.__init__(self)
500
self._cells = Matrix(columns, rows)
501
self._children = [] # List of _TablePacking objects
502
self._children_sorted = True
504
self.columns = columns
505
self.row_spacing = self.column_spacing = 0
507
def _ensure_children_sorted(self):
508
if not self._children_sorted:
509
def cell_area(table_packing):
510
return table_packing.column_span * table_packing.row_span
511
self._children.sort(key=cell_area)
512
self._children_sorted = True
514
def get_children(self):
515
return [cell.widget for cell in self._children]
516
children = property(get_children)
518
def calc_size_request(self):
519
self._ensure_children_sorted()
520
self._calc_dimensions()
521
return self.total_width, self.total_height
523
def _calc_dimensions(self):
524
self.column_widths = [0] * self.columns
525
self.row_heights = [0] * self.rows
527
for tp in self._children:
528
child_width, child_height = tp.widget.get_size_request()
529
# recalc the width of the child's columns
530
self._recalc_dimension(child_width, self.column_widths,
532
# recalc the height of the child's rows
533
self._recalc_dimension(child_height, self.row_heights,
536
self.total_width = (self.column_spacing * (self.columns - 1) +
537
sum(self.column_widths))
538
self.total_height = (self.row_spacing * (self.rows - 1) +
539
sum(self.row_heights))
541
def _recalc_dimension(self, child_size, size_array, positions):
542
current_size = sum(size_array[p] for p in positions)
543
child_size_needed = child_size - current_size
544
if child_size_needed > 0:
545
iter = _extra_space_iter(child_size_needed, len(positions))
547
size_array[p] += iter.next()
549
def place_children(self):
550
column_positions = [0]
551
for width in self.column_widths[:-1]:
552
column_positions.append(width + column_positions[-1])
554
for height in self.row_heights[:-1]:
555
row_positions.append(height + row_positions[-1])
557
my_x= self.viewport.placement.origin.x
558
my_y = self.viewport.placement.origin.y
559
for tp in self._children:
560
x = my_x + column_positions[tp.column]
561
y = my_y + row_positions[tp.row]
562
width = sum(self.column_widths[i] for i in tp.column_indexes())
563
height = sum(self.row_heights[i] for i in tp.row_indexes())
564
rect = NSMakeRect(x, y, width, height)
565
tp.widget.place(rect, self.viewport.view)
567
def pack(self, widget, column, row, column_span=1, row_span=1):
568
tp = _TablePacking(widget, column, row, column_span, row_span)
569
for c in tp.column_indexes():
570
for r in tp.row_indexes():
571
if self._cells[c, r]:
572
raise ValueError("Cell %d x %d is already taken" % (c, r))
573
self._cells[column, row] = widget
574
self._children.append(tp)
575
self._children_sorted = False
576
self.child_added(widget)
578
def remove(self, child):
579
for i in xrange(len(self._children)):
580
if self._children[i].widget is child:
581
self._children.remove(i)
584
raise ValueError("%s is not a child of this Table" % child)
585
self._cells.remove(child)
586
self.child_removed(widget)
588
def set_column_spacing(self, spacing):
589
self.column_spacing = spacing
590
self.invalidate_size_request()
592
def set_row_spacing(self, spacing):
593
self.row_spacing = spacing
594
self.invalidate_size_request()
596
def enable(self, row=None, column=None):
597
Container.enable(self)
598
if row != None and column != None:
599
if self._cells[column, row]:
600
self._cells[column, row].enable()
602
for mem in self._cells.row(row):
605
for mem in self._cells.column(column):
608
for mem in self._cells:
611
def disable(self, row=None, column=None):
612
Container.disable(self)
613
if row != None and column != None:
614
if self._cells[column, row]:
615
self._cells[column, row].disable()
617
for mem in self._cells.row(row):
618
if mem: mem.disable()
620
for mem in self._cells.column(column):
621
if mem: mem.disable()
623
for mem in self._cells:
624
if mem: mem.disable()
627
"""See https://develop.participatoryculture.org/trac/democracy/wiki/WidgetAPI for a description of the API for this class."""
628
def __init__(self, horizontal, vertical):
630
self.view = NSScrollView.alloc().init()
631
self.view.setAutohidesScrollers_(YES)
632
self.view.setHasHorizontalScroller_(horizontal)
633
self.view.setHasVerticalScroller_(vertical)
634
self.document_view = FlippedView.alloc().init()
635
self.view.setDocumentView_(self.document_view)
636
self.view.setAutohidesScrollers_(True)
638
def set_has_borders(self, has_border):
639
self.view.setBorderType_(NSBezelBorder)
641
def set_background_color(self, color):
642
self.view.setBackgroundColor_(self.make_color(color))
644
def add(self, child):
645
child.parent_is_scroller = True
649
child.parent_is_scroller = False
652
def calc_size_request(self):
655
if not self.view.hasHorizontalScroller():
656
width = self.child.get_size_request()[0]
657
if not self.view.hasVerticalScroller():
658
height = self.child.get_size_request()[1]
659
# Add a little room for the scrollbars
660
if self.view.hasHorizontalScroller():
662
if self.view.hasVerticalScroller():
668
def place_children(self):
669
if self.child is not None:
670
child_width, child_height = self.child.get_size_request()
671
child_width = max(child_width, self.view.contentView().frame().size.width)
672
frame = NSRect(NSPoint(0,0), NSSize(child_width, child_height))
673
if isinstance(self.child, tableview.TableView) and self.child.is_showing_headers():
674
# Hack to allow the content of a table view to scroll, but not
676
self.child.place(frame, self.document_view)
677
self.view.setDocumentView_(self.child.tableview)
679
self.child.place(frame, self.document_view)
680
self.document_view.setFrame_(frame)
681
self.document_view.setNeedsDisplay_(YES)
682
self.view.setNeedsDisplay_(YES)
684
class ExpanderView(FlippedView):
686
self = super(ExpanderView, self).init()
687
self.label_rect = None
688
self.content_view = None
689
self.button = NSButton.alloc().init()
690
self.button.setState_(NSOffState)
691
self.button.setTitle_("")
692
self.button.setBezelStyle_(NSDisclosureBezelStyle)
693
self.button.setButtonType_(NSPushOnPushOffButton)
694
self.button.sizeToFit()
695
self.addSubview_(self.button)
696
self.button.setTarget_(self)
697
self.button.setAction_('buttonChanged:')
698
self.content_view = FlippedView.alloc().init()
701
def buttonChanged_(self, button):
702
if button.state() == NSOnState:
703
self.addSubview_(self.content_view)
705
self.content_view.removeFromSuperview()
707
wrappermap.wrapper(self).invalidate_size_request()
709
def mouseDown_(self, event):
710
pass # Just need to respond to the selector so we get mouseUp_
712
def mouseUp_(self, event):
713
position = event.locationInWindow()
714
window_label_rect = self.convertRect_toView_(self.label_rect, None)
715
if NSPointInRect(position, window_label_rect):
716
self.button.setNextState()
717
self.buttonChanged_(self.button)
724
def __init__(self, child):
730
self.view = ExpanderView.alloc().init()
731
self.button = self.view.button
732
self.button.setFrameOrigin_(NSPoint(self.BUTTON_PAD_LEFT,
733
self.BUTTON_PAD_TOP))
734
self.content_view = self.view.content_view
736
def remove_viewport(self):
737
Bin.remove_viewport(self)
738
if self.label is not None:
739
self.label.remove_viewport()
741
def set_spacing(self, spacing):
742
self.spacing = spacing
744
def set_label(self, widget):
745
if self.label is not None:
746
self.label.remove_viewport()
748
self.children_changed()
750
def set_expanded(self, expanded):
752
self.button.setState_(NSOnState)
754
self.button.setState_(NSOffState)
755
self.view.buttonChanged_(self.button)
757
def calc_top_size(self):
758
width = self.button.bounds().size.width
759
height = self.button.bounds().size.height
760
if self.label is not None:
761
label_width, label_height = self.label.get_size_request()
762
width += self.LABEL_SPACING + label_width
763
height = max(height, label_height)
764
width += self.BUTTON_PAD_LEFT
765
height += self.BUTTON_PAD_TOP
768
def calc_size_request(self):
769
width, height = self.calc_top_size()
770
if self.child is not None and self.button.state() == NSOnState:
771
child_width, child_height = self.child.get_size_request()
772
width = max(width, child_width)
773
height += self.spacing + child_height
776
def place_children(self):
777
top_width, top_height = self.calc_top_size()
779
label_width, label_height = self.label.get_size_request()
780
button_width = self.button.bounds().size.width
781
label_x = self.BUTTON_PAD_LEFT + button_width + self.LABEL_SPACING
782
label_rect = NSMakeRect(label_x, self.BUTTON_PAD_TOP,
783
label_width, label_height)
784
self.label.place(label_rect, self.viewport.view)
785
self.view.label_rect = label_rect
787
size = self.viewport.area().size
788
child_rect = NSMakeRect(0, 0, size.width, size.height -
790
self.content_view.setFrame_(NSMakeRect(0, top_height, size.width,
791
size.height - top_height))
792
self.child.place(child_rect, self.content_view)
795
class TabViewDelegate(NSObject):
796
def tabView_willSelectTabViewItem_(self, tab_view, tab_view_item):
798
wrapper = wrappermap.wrapper(tab_view)
800
pass # The NSTabView hasn't been placed yet, don't worry about it.
802
wrapper.place_child_with_item(tab_view_item)
804
class TabContainer(Container):
806
Container.__init__(self)
808
self.item_to_child = {}
809
self.view = NSTabView.alloc().init()
810
self.view.setAllowsTruncatedLabels_(NO)
811
self.delegate = TabViewDelegate.alloc().init()
812
self.view.setDelegate_(self.delegate)
814
def append_tab(self, child_widget, label, image):
815
item = NSTabViewItem.alloc().init()
816
item.setLabel_(label)
817
item.setView_(FlippedView.alloc().init())
818
self.view.addTabViewItem_(item)
819
self.children.append(child_widget)
820
self.child_added(child_widget)
821
self.item_to_child[item] = child_widget
823
def select_tab(self, index):
824
self.view.selectTabViewItemAtIndex_(index)
826
def place_children(self):
827
self.place_child_with_item(self.view.selectedTabViewItem())
829
def place_child_with_item(self, tab_view_item):
830
child = self.item_to_child[tab_view_item]
831
child_view = tab_view_item.view()
832
content_rect =self.view.contentRect()
833
child_view.setFrame_(content_rect)
834
child.place(child_view.bounds(), child_view)
836
def calc_size_request(self):
837
tab_size = self.view.minimumSize()
838
# make sure there's enough room for the tabs, plus a little extra
839
# space to make things look good
840
max_width = tab_size.width + 60
842
for child in self.children:
843
width, height = child.get_size_request()
844
max_width = max(width, max_width)
845
max_height = max(height, max_height)
846
max_height += tab_size.height
848
return max_width, max_height