This repository has been archived by the owner on Nov 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
dockpaned.py
437 lines (359 loc) · 15.5 KB
/
dockpaned.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# -*- coding: utf-8 -*-
#
# Copyright © 2010 Dieter Verfaillie <dieterv@optionexplicit.be>
#
# This file is part of etk.docking.
#
# etk.docking is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# etk.docking is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with etk.docking. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division
from sys import maxint
from logging import getLogger
from math import floor
import gobject
import gtk
import gtk.gdk as gdk
from .util import _rect_overlaps
class _DockPanedHandle(object):
'''
Convenience class storing information about a handle.
'''
__slots__ = ['left', # item on the left side of this handle (_DockPanedItem)
'right', # item on the left side of this handle (_DockPanedItem)
'area'] # area, used for hit testing (gdk.Rectangle)
class _DockPanedItem(object):
'''
Convenience class storing information about a child.
'''
__slots__ = ['item', # child widget
'area', # area, used to calculate allocation (gdk.Rectangle)
'size', # percentual size used by this item (float)
'handler_id_visible'] # handler id for visible property notification signal
class DockPaned(gtk.Container):
'''
The etk.DockPaned class groups it's children in panes, either
horizontally or vertically.
'''
__gtype_name__ = 'EtkDockPaned'
__gproperties__ = {'handle-size': (gobject.TYPE_UINT,
'handle size',
'handle size',
0,
maxint,
4,
gobject.PARAM_READWRITE),
'orientation': (gobject.TYPE_UINT,
'handle size',
'handle size',
0,
1,
0,
gobject.PARAM_READWRITE)}
def __init__(self):
gtk.Container.__init__(self)
# Initialize logging
self.log = getLogger('<%s object at %s>' % (self.__gtype_name__, hex(id(self))))
# Internal housekeeping
self._children = []
self._visible_children = []
self._handles = []
self._handle_size = 4
self._orientation = gtk.ORIENTATION_HORIZONTAL
self._dragging = False
self._drag_pos = None
self._drag_handle = None
############################################################################
# GObject
############################################################################
def do_get_property(self, pspec):
if pspec.name == 'handle-size':
return self.get_handle_size()
elif pspec.name == 'orientation':
return self.get_orientation()
def do_set_property(self, pspec, value):
if pspec.name == 'handle-size':
self.set_handle_size(value)
elif pspec.name == 'orientation':
self.set_orientation(value)
def get_handle_size(self):
return self._handle_size
def set_handle_size(self, value):
self._handle_size = value
self.notify('handle-size')
def get_orientation(self):
return self._orientation
def set_orientation(self, value):
self._orientation = value
self.queue_resize()
self.notify('orientation')
############################################################################
# GtkWidget
############################################################################
def do_realize(self):
# Internal housekeeping
self.set_flags(self.flags() | gtk.REALIZED)
self.window = gdk.Window(self.get_parent_window(),
x = self.allocation.x,
y = self.allocation.y,
width = self.allocation.width,
height = self.allocation.height,
window_type = gdk.WINDOW_CHILD,
wclass = gdk.INPUT_OUTPUT,
event_mask = (gdk.EXPOSURE_MASK |
gdk.POINTER_MOTION_MASK |
gdk.BUTTON_PRESS_MASK |
gdk.BUTTON_RELEASE_MASK))
self.window.set_user_data(self)
self.style.attach(self.window)
self.style.set_background(self.window, gtk.STATE_NORMAL)
# Set parent window on all child widgets
for child in self._children:
child.item.set_parent_window(self.window)
def do_unrealize(self):
self.window.destroy()
gtk.Container.do_unrealize(self)
def do_map(self):
gtk.Container.do_map(self)
self.window.show()
def do_unmap(self):
self.window.hide()
gtk.Container.do_unmap(self)
def do_size_request(self, requisition):
# Calculate total size request
width = height = 0
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
w, h = child.item.size_request()
width += w
height = max(height, h)
width += (self._get_n_visible_items() - 1) * self._handle_size
elif self._orientation == gtk.ORIENTATION_VERTICAL:
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
w, h = child.item.size_request()
width = max(width, w)
height += h
height += (self._get_n_visible_items() - 1) * self._handle_size
requisition.width = width
requisition.height = height
def do_size_allocate(self, allocation):
self.allocation = allocation
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
# Keep track of these
self._visible_children = []
self._handles = []
# List visible children
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
self._visible_children.append(child)
# Allocate size and create handles
cx = cy = 0
for child in self._visible_children:
size = floor((allocation.width - (len(self._visible_children) - 1) * self._handle_size) * child.size)
child.area.x = cx
child.area.y = cy
child.area.width = max(child.item.size_request()[0], size)
child.area.height = allocation.height
cx += child.area.width
if child is self._visible_children[-1:][0]:
child.area.width = allocation.width - child.area.x
else:
handle = _DockPanedHandle()
handle.area = gdk.Rectangle()
handle.area.x = cx
handle.area.y = cy
handle.area.width = self._handle_size
handle.area.height = allocation.height
self._handles.append(handle)
cx += self._handle_size
child.item.size_allocate(child.area)
# Attach items to handles
for index, handle in enumerate(self._handles):
handle.left = self._visible_children[index]
handle.right = self._visible_children[index + 1]
elif self._orientation == gtk.ORIENTATION_VERTICAL:
#TODO: implement this
pass
if self.flags() & gtk.REALIZED:
self.window.move_resize(*allocation)
def do_expose_event(self, event):
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
self.propagate_expose(child.item, event)
return False
def do_button_press_event(self, event):
self._dragging = False
self._drag_handle = None
self._drag_pos = None
for handle in self._handles:
if _rect_overlaps(handle.area, event.x, event.y):
self._dragging = True
self._drag_handle = handle
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
self._drag_pos = event.x
elif self._orientation == gtk.ORIENTATION_VERTICAL:
self._drag_pos = event.y
def do_button_release_event(self, event):
if self._dragging:
self._dragging = False
self._drag_pos = None
self._drag_handle = None
def do_motion_notify_event(self, event):
if self._dragging:
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
width_delta = self.get_pointer()[0] - self._drag_pos
size_delta = width_delta / self.allocation.width
ri = self._drag_handle.right
li = self._drag_handle.left
if ri.area.width - width_delta >= ri.item.size_request()[0] and li.area.width + width_delta >= li.item.size_request()[0]:
li.size += size_delta
ri.size -= size_delta
self.queue_resize()
elif self._orientation == gtk.ORIENTATION_VERTICAL:
#TODO: implement this
pass
# Update drag position
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
self._drag_pos = event.x
elif self._orientation == gtk.ORIENTATION_VERTICAL:
self._drag_pos = event.y
else:
for handle in self._handles:
if _rect_overlaps(handle.area, event.x, event.y):
if self._orientation == gtk.ORIENTATION_HORIZONTAL:
cursor = gtk.gdk.Cursor(self.get_display(), gdk.SB_H_DOUBLE_ARROW)
elif self._orientation == gtk.ORIENTATION_VERTICAL:
cursor = gtk.gdk.Cursor(self.get_display(), gdk.SB_V_DOUBLE_ARROW)
break
else:
cursor = None
self.window.set_cursor(cursor)
############################################################################
# GtkContainer
############################################################################
def do_forall(self, internals, callback, data):
for child in self._children:
callback(child.item, data)
def do_add(self, widget):
# _DockPanedItem
child = _DockPanedItem()
child.item = widget
child.item.set_parent(self)
child.area = gdk.Rectangle()
child.size = None
child.handler_id_visible = child.item.connect('notify::visible', self._on_child_visibility_changed)
self._children.append(child)
if self.flags() & gtk.REALIZED:
child.item.set_parent_window(self.window)
if child.item.flags() & gtk.VISIBLE:
self._add_visible_item(child)
self.queue_resize()
def do_remove(self, widget):
# Get the _DockPanedItem associated with widget
for child in self._children:
if child.item is widget:
child.item.disconnect(child.handler_id_visible)
child.item.unparent()
self._children.remove(child)
if child.item.flags() & gtk.VISIBLE:
self._remove_visible_item(child)
self.queue_resize()
break
############################################################################
# EtkDockGroup
############################################################################
def _on_child_visibility_changed(self, gobject, pspec):
# Get the _DockPanedItem associated with gobject
for child in self._children:
if child.item is gobject:
if child.item.flags() & gtk.VISIBLE:
self._add_visible_item(child)
else:
self._remove_visible_item(child)
break
self.queue_resize()
def _add_visible_item(self, item):
n_visible_items = self._get_n_visible_items()
if item.size is None:
size_ok = []
size_nok = []
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
if child.size is None:
size_nok.append(child)
else:
size_ok.append(child)
for child in size_nok:
if n_visible_items > 1:
# Set a default size
child.size = 1.0 / n_visible_items
delta = child.size / (n_visible_items - 1)
# And the rest has to shrink...
for child in size_ok:
child.size -= delta
else:
child.size = 1.0
else:
if n_visible_items > 1:
delta = item.size / (n_visible_items - 1)
for child in self._children:
if child.item.flags() & gtk.VISIBLE and not item is child:
child.size -= delta
else:
item.size = 1.0
def _remove_visible_item(self, child):
n_visible_items = self._get_n_visible_items()
if n_visible_items > 1:
delta = child.size / n_visible_items
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
child.size += delta
elif n_visible_items == 1:
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
child.size = 1.0
break
#TODO: def append_item(self, item):
#TODO: def prepend_item(item, tab_label=None)
#TODO: def insert_item(item, tab_label=None, position=-1)
#TODO: def remove_item(self, item_num):
#TODO: def get_current_item(self):
#TODO: def get_nth_item(self, item_num):
def get_n_items(self):
'''
The get_n_items() method returns the number of items in the DockPaned.
'''
return len(self._children)
def _get_n_visible_items(self):
'''
The _get_n_visible_items() method returns the number of visible items
in the DockPaned. This method is used when self._visible_children has
not been calculated.
'''
n_items = 0
for child in self._children:
if child.item.flags() & gtk.VISIBLE:
n_items += 1
return n_items
def get_n_visible_items(self):
'''
The get_n_visible_items() method returns the number of visible items in
the DockPaned.
'''
return len(self._visible_children)
#TODO: def item_num(self, item):
#TODO: def set_current_item(self, item_num):
#TODO: def next_item()
#TODO: def prev_item()
#TODO: def reorder_item(item, position)