This file is indexed.

/usr/share/pyshared/etk/docking/docklayout.py is in python-etk.docking 0.2-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
# -*- coding: utf-8 -*-
# vim:sw=4:et:ai

# Copyright © 2010 etk.docking Contributors
#
# 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
import sys
from collections import namedtuple
from logging import getLogger

from simplegeneric import generic

import gobject
import gtk
import gtk.gdk as gdk
import itertools
from weakref import WeakKeyDictionary

from .dnd import DRAG_TARGET_ITEM_LIST, Placeholder
from .dockframe import DockFrame
from .dockpaned import DockPaned
from .dockgroup import DockGroup
from .dockitem import DockItem
from .docksettings import settings
from .util import flatten

# On OSX/X11 Utility windows are above all windows,
# even if the app is not the active app.
PROVIDE_FLOATING_WINDOW_HINTS = sys.platform != 'darwin'

MAGIC_BORDER_SIZE = 10

DragData = namedtuple('DragData', 'drop_widget leave received')


class DockLayout(gobject.GObject):
    """
    Manage a dock layout.

    For this to work the toplevel widget in the layout hierarchy should be a
    DockFrame. The DockFrame is registered with the DockLayout. After that
    sophisticated drag-and-drop functionality is present.

    NB. When items are closed, the item-closed signal is emitted. The item is *not*
    destroyed, though.
    """

    __gtype_name__ = 'EtkDockLayout'
    __gsignals__ = {
        'item-closed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                      (gobject.TYPE_OBJECT, gobject.TYPE_OBJECT)),
        'item-selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                      (gobject.TYPE_OBJECT, gobject.TYPE_OBJECT)),
    }

    def __init__(self):
        gobject.GObject.__init__(self)

        # Initialize logging
        self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self))))

        self.frames = set()
        self._signal_handlers = {} # Map widget -> set([signals, ...])

        self._focused_item = None
        self._focused_group = None
        self._focus_data = WeakKeyDictionary() # Map item -> last focused widget

        self._drag_data = None

    def add(self, frame):
        assert isinstance(frame, DockFrame)
        self.frames.add(frame)
        self.add_signal_handlers(frame)

    def remove(self, frame):
        self.remove_signal_handlers(frame)
        self.frames.remove(frame)

    def get_main_frames(self):
        """
        Get the frames that are non-floating (the main frames).
        """
        return (f for f in self.frames \
                if not (isinstance(f.get_parent(), gtk.Window) \
                    and f.get_parent().get_transient_for()) )

    def get_floating_frames(self):
        """
        Get the floating frames. Floating frames have a gtk.Window as parent that is
        transient for some other window.
        """
        return (f for f in self.frames \
                if isinstance(f.get_parent(), gtk.Window) \
                    and f.get_parent().get_transient_for() )

    def get_widgets(self, name):
        """
        Get a set of widgets based on their name.
        """
        return filter(lambda w: w.get_name() == name,
                      itertools.chain.from_iterable(flatten(frame) for frame in self.frames))

    def _get_signals(self, widget):
        """
        Get a list of signals to be registered for a specific widget.
        """
        if isinstance(widget, DockPaned):
            signals = (('item-added', self.on_widget_add),
                       ('item-removed', self.on_widget_remove))
        elif isinstance(widget, DockGroup):
            signals = (('item-added', self.on_widget_add),
                       ('item-removed', self.on_widget_remove),
                       ('item-selected', self.on_dockgroup_item_selected))
        elif isinstance(widget, DockItem):
            signals = (('close', self.on_dockitem_close),)
        elif isinstance(widget, gtk.Container):
            signals = (('add', self.on_widget_add),
                       ('remove', self.on_widget_remove))
        else:
            signals = ()


        return signals + (('drag-motion', self.on_widget_drag_motion),
                          ('drag-leave', self.on_widget_drag_leave),
                          ('drag-drop', self.on_widget_drag_drop),
                          ('drag-data-received', self.on_widget_drag_data_received),
                          ('drag-end', self.on_widget_drag_end),
                          ('drag-failed', self.on_widget_drag_failed),
                          ('notify::is-focus', self.on_widget_is_focus))

    def add_signal_handlers(self, widget):
        """
        Set up signal handlers for layout and child widgets. Also group state is changed
        from selected to prelight, in order to have one focused widget.
        """
        if self._signal_handlers.get(widget):
            return

        signals = set()
        drag_dest = widget.drag_dest_get_target_list()

        if not drag_dest:
            widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [DRAG_TARGET_ITEM_LIST],
                                 gdk.ACTION_MOVE)
        elif DRAG_TARGET_ITEM_LIST not in drag_dest:
            widget.drag_dest_set_target_list(drag_dest + [DRAG_TARGET_ITEM_LIST])

        # Use instance methods here, so layout can do additional bookkeeping
        for name, callback in self._get_signals(widget):
            try:
                signals.add(widget.connect(name, callback))
            except TypeError, e:
                self.log.debug(e)

        self._signal_handlers[widget] = signals

        if isinstance(widget, gtk.Container):
            widget.foreach(self.add_signal_handlers)

        # Ensure SELECTED state is only for the selected item
        if isinstance(widget, DockGroup):
            widget.set_tab_state(gtk.STATE_PRELIGHT)

    def remove_signal_handlers(self, widget):
        """
        Remove signal handlers.
        """
        try:
            signals = self._signal_handlers[widget]
        except KeyError:
            pass # No signals
        else:
            for s in signals:
                widget.disconnect(s)

            del self._signal_handlers[widget]

            # TODO: widget.drag_dest_set_target_list(drag_dest - [DRAG_TARGET_ITEM_LIST])??

            if isinstance(widget, gtk.Container):
                widget.foreach(self.remove_signal_handlers)

    def update_floating_window_title(self, widget):
        frame = widget.get_ancestor(DockFrame)

        if frame in self.get_floating_frames():
            frame.get_toplevel().set_title(
                ', '.join(
                    map(lambda w: w.title,
                        filter(lambda w: isinstance(w, DockItem),
                            flatten(frame)))))

    def do_item_closed(self, group, item):
        """
        If an item is closed, perform maintenance cleanup.
        """
        if settings[item].auto_remove:
            cleanup(group, self)

    def do_item_selected(self, group, item):
        # Use this callback to grey out the selection on all but the active selection?
        self._focused_item = item

        if not (group is self._focused_group and group.get_tab_state() != gtk.STATE_PRELIGHT):
            if self._focused_group:
                self._focused_group.set_tab_state(gtk.STATE_PRELIGHT)

            self._focused_group = group
            group.set_tab_state(gtk.STATE_SELECTED)

    def on_widget_add(self, container, widget):
        """
        Deal with new elements being added to the layout or it's children.
        """
        if isinstance(widget, gtk.Container):
            self.add_signal_handlers(widget)

        self.update_floating_window_title(container)

    def on_widget_remove(self, container, widget):
        """
        Remove signals from containers and subcontainers.
        """
        if isinstance(widget, gtk.Container):
            self.remove_signal_handlers(widget)

        self.update_floating_window_title(container)

    def on_widget_drag_motion(self, widget, context, x, y, timestamp):
        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            context.docklayout = self
            drag_data = drag_motion(widget, context, x, y, timestamp)

            old_drop_widget = self._drag_data and self._drag_data.drop_widget
            new_drop_widget = drag_data and drag_data.drop_widget

            if new_drop_widget is not old_drop_widget:
                self.on_widget_drag_leave(widget, context, timestamp)
                self._drag_data = drag_data

    def on_widget_drag_leave(self, widget, context, timestamp):
        # Note: when dropping, drag-leave is invoked before drag-drop
        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            drag_data = self._drag_data

            if drag_data and drag_data.leave:
                self.log.debug('on widget drag leave %s' % drag_data.leave)
                drag_data.leave(drag_data.drop_widget)

    def on_widget_drag_drop(self, widget, context, x, y, timestamp):
        self.log.debug('drag_drop %s %s %s %s', context, x, y, timestamp)

        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            drag_data = self._drag_data

            if drag_data and drag_data.drop_widget:
                target = gdk.atom_intern(DRAG_TARGET_ITEM_LIST[0])
                drag_data.drop_widget.drag_get_data(context, target, timestamp)
                return True

            # act as if drag failed:
            source = context.get_source_widget()
            source.emit('drag-failed', context, 1)
            cleanup(source, self)

        return False


    def on_widget_drag_data_received(self, widget, context, x, y, selection_data, info, timestamp):
        '''
        Execute the received handler using the received handler retrieved in the
        drag_drop event handler.
        '''
        self.log.debug('drag_data_received %s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp))

        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            drag_data = self._drag_data
            assert drag_data.received

            try:
                drag_data.received(selection_data, info)
            finally:
                self._drag_data = None

    def on_widget_drag_end(self, widget, context):
        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            context.docklayout = self
            return drag_end(widget, context)

    def on_widget_drag_failed(self, widget, context, result):
        if DRAG_TARGET_ITEM_LIST[0] in context.targets:
            context.docklayout = self
            return drag_failed(widget, context, result)

    def on_widget_is_focus(self, widget, pspec):
        """
        The input focus moved to another widget.
        """
        if isinstance(widget, DockItem):
            item = widget
        else:
            item = widget.get_ancestor(DockItem)

        if item:
            self._focus_data[item] = widget

            if item is not self._focused_item:
                group = item.get_parent()
                self.emit('item-selected', group, item)

    def on_dockitem_close(self, item):
        group = item.get_parent()
        self.emit('item-closed', group, item)
        cleanup(group, self)

    def on_dockgroup_item_selected(self, group, item):
        """
        An item is selected by clicking on a tab.
        """
        focus_child = self._focus_data.get(item)

        if focus_child:
            # item-selected is emited by is-focus handler
            focus_child.set_property('has-focus', True)

        self.emit('item-selected', group, item)

################################################################################
# Placement
################################################################################

def add_new_group_left(widget, new_group):
    add_new_group_before(widget, new_group, gtk.ORIENTATION_HORIZONTAL)

def add_new_group_right(widget, new_group):
    add_new_group_after(widget, new_group, gtk.ORIENTATION_HORIZONTAL)

def add_new_group_above(widget, new_group):
    add_new_group_before(widget, new_group, gtk.ORIENTATION_VERTICAL)

def add_new_group_below(widget, new_group):
    add_new_group_after(widget, new_group, gtk.ORIENTATION_VERTICAL)

def add_new_group_before(widget, new_group, orientation):
    """
    Create a new DockGroup and place it before `widget`. The DockPaned that will hold
    both groups will have the defined orientation.
    """
    position = widget.get_parent().get_children().index(widget)
    add_new_group(widget, new_group, orientation, position)
    return new_group

def add_new_group_after(widget, new_group, orientation):
    """
    Create a new DockGroup and place it after `widget`. The DockPaned that will hold
    both groups will have the defined orientation.
    """
    position = widget.get_parent().get_children().index(widget) + 1
    add_new_group(widget, new_group, orientation, position)
    return new_group

def add_new_group(widget, new_group, orientation, position):
    """
    Place a `new_group` next to `widget` in a
    """
    # Parameters: orientation, position (left/top: None, right/bottom: 1),
    # current_child, (paned) current_position, (paned) weight

    parent = widget.get_parent()
    assert parent

    try:
        current_position = parent.item_num(widget)
        weight = parent.child_get_property(widget, 'weight')
    except AttributeError:
        current_position = None

    if isinstance(parent, DockPaned) and orientation == parent.get_orientation():
        new_paned = parent
    else:
        new_paned = new(DockPaned)
        new_paned.set_orientation(orientation)

        # Current_child will always be a DockGroup or DockPaned with opposite orientation
        parent.remove(widget)

        if current_position is not None:
            # used for DockPaned
            parent.insert_item(new_paned, position=current_position, weight=weight)
        else:
            # Used for DockFrame
            parent.add(new_paned)

        new_paned.insert_item(widget, weight=0.5)
        widget.queue_resize()

    #new_group = new(DockGroup, source, context.docklayout)
    new_paned.insert_item(new_group, position, weight=0.5)

    new_paned.show()
    new_group.show()

    return new_group

def _window_delete_handler(window, event):
    map(lambda i: i.close(),
        filter(lambda i: isinstance(i, DockItem), flatten(window)))
    return False

def add_new_group_floating(new_group, layout, size=None, pos=None):
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)

    if pos:
        window.move(*pos)

    window.set_resizable(True)
    window.set_skip_taskbar_hint(True)
    if PROVIDE_FLOATING_WINDOW_HINTS:
        window.set_type_hint(gdk.WINDOW_TYPE_HINT_UTILITY)
    window.set_transient_for(layout.get_main_frames().next().get_toplevel())

    if size:
        window.set_size_request(*size)

    window.connect('delete-event', _window_delete_handler)
    frame = new(DockFrame)
    window.add(frame)
    frame.add(new_group)
    window.show()
    frame.show()
    new_group.show()
    layout.add(frame)

    return frame

################################################################################
# Drag and Drop behaviour
################################################################################

def _propagate_to_parent(func, widget, context, x, y, timestamp):
    '''
    Common function to propagate calls to a parent widget.
    '''
    parent = widget.get_parent()

    if parent:
        # Should not use get_pointer as it's not testable.
        px, py = widget.translate_coordinates(parent, x, y)
        return func(parent, context, px, py, timestamp)
    else:
        return None

def with_magic_borders(func):
    '''
    decorator for handlers that have sensitive borders, as items may be dropped
    on the parent item as well.
    '''
    def func_with_magic_borders(widget, context, x, y, timestamp):
        # Always ensure we check the parent class:
        handled = _propagate_to_parent(magic_borders, widget, context, x, y, timestamp)
        return handled or func(widget, context, x, y, timestamp)

    func_with_magic_borders.__doc__ = func.__doc__
    return func_with_magic_borders

@generic
def magic_borders(widget, context, x, y, timestamp):
    '''
    :returns: DragData if the parent widget handled the event

    This method is used to find out if (in case an item is dragged on the border of
    a widget, the parent is eager to take that event instead. This, for example,
    can be used to place items above or below each other in not-yet existing paned
    sections.
    '''
    pass

@generic
def drag_motion(widget, context, x, y, timestamp):
    '''
    :param context: the gdk.DragContext
    :param x: the X position of the drop
    :param y: the Y position of the drop
    :param timestamp: the time of the drag event
    :returns: a tuple (widget, callback) to be called when leaving the
    item (drag_leave event) if the cursor is in a drop zone.

    The do_drag_motion() signal handler is executed when the drag operation
    moves over a drop target widget. The handler must determine if the
    cursor position is in a drop zone or not. If it is not in a drop zone,
    it should return False and no further processing is necessary. Otherwise,
    the handler should return True. In this case, the handler is responsible
    for providing the necessary information for displaying feedback to the
    user, by calling the gdk.DragContext.drag_status() method. If the
    decision to accept or reject the drop can't be made based solely on
    the cursor position and the type of the data, the handler may inspect
    the dragged data by calling the drag_get_data() method and defer the
    gdk.DragContext.drag_status() method call to the do_drag_data_received()
    signal handler.

    Note::
        There is no do_drag_enter() signal handler. The drag receiver has
        to keep track of any do_drag_motion() signals received since the
        last do_drag_leave() signal. The first do_drag_motion() signal
        received after a do_drag_leave() signal should be treated as an
        "enter" signal. Upon an "enter", the handler will typically
        highlight the drop site with the drag_highlight() method.

    drag_data_received(widget, context, x, y, selection_data, info, timestamp):

    The do_drag_data_received() signal handler is executed when the drag
    destination receives the data from the drag operation. If the data was
    received in order to determine whether the drop will be accepted, the
    handler is expected to call the gdk.DragContext.drag_status() method
    and not finish the drag. If the data was received in response to a
    do_drag_drop() signal (and this is the last target to be received),
    the handler for this signal is expected to process the received data
    and then call the gdk.DragContext.finish() method, setting the success
    parameter to True if the data was processed successfully.
    '''
    return _propagate_to_parent(drag_motion, widget, context, x, y, timestamp)

@generic
def cleanup(widget, layout):
    '''
    :param widget: widget that may require cleanup.

    Perform cleanup action for a widget. For example after a drag-and-drop
    operation or when a dock-item is closed.
    '''
    parent = widget.get_parent()
    return parent and cleanup(parent, layout)

@generic
def drag_end(widget, context):
    '''
    :param context: the gdk.DragContext

    The do_drag_end() signal handler is executed when the drag operation is
    completed. A typical reason to use this signal handler is to undo things
    done in the do_drag_begin() handler.
    '''
    parent = widget.get_parent()
    return parent and drag_end(parent, context)

@generic
def drag_failed(widget, context, result):
    '''
    :param context: the gdk.DragContext
    :param result: the result of the drag operation
    :returns: True if the failed drag operation has been already handled.

    The do_drag_failed() signal handler is executed on the drag source when
    a drag has failed. The handler may hook custom code to handle a failed
    DND operation based on the type of error. It returns True if the
    failure has been already handled (not showing the default
    "drag operation failed" animation), otherwise it returns False.
    '''
    parent = widget.get_parent()
    return parent and drag_failed(parent, context, result)

################################################################################
# DockGroup
################################################################################

def new(class_, old=None, layout=None):
    '''
    Create a new Widget. Set the group name if required.
    '''
    new = class_()

    if old and settings[old].inherit_settings:
        new.set_name(old.get_name())
    return new

def dock_group_expose_highlight(self, event):
    try:
        tab = self._visible_tabs[self._drop_tab_index]
    except TypeError:
        a = event.area
    else:
        if tab is self._current_tab:
            a = event.area
        else:
            a = tab.area

    cr = self.window.cairo_create()
    cr.set_source_rgb(0, 0, 0)
    cr.set_line_width(1.0)
    cr.rectangle(a.x + 0.5, a.y + 0.5, a.width - 1, a.height - 1)
    cr.stroke()

def dock_group_highlight(self):
    if not hasattr(self, '_expose_event_id'):
        self.log.debug('attaching expose event')
        self._expose_event_id = self.connect_after('expose-event',
                                                   dock_group_expose_highlight)
    self.queue_draw()

def dock_unhighlight(self):
    self.queue_draw()

    try:
        self.disconnect(self._expose_event_id)
        del self._expose_event_id
    except AttributeError, e:
        self.log.debug(e, exc_info=True)

@drag_motion.when_type(DockGroup)
@with_magic_borders
def dock_group_drag_motion(self, context, x, y, timestamp):
    self.log.debug('dock_group_drag_motion: %s, %s, %s, %s' % (context, x, y, timestamp))

    # Insert the dragged tab before the tab under (x, y)
    drop_tab = self.get_tab_at_pos(x, y)
    self.log.debug('drop tab at (%d, %d) is %s', x, y, drop_tab)

    if drop_tab:
        self._drop_tab_index = self._visible_tabs.index(drop_tab)
    elif self._tabs:
        self._drop_tab_index = self._visible_tabs.index(self._current_tab)
    else:
        self._drop_tab_index = None

    dock_group_highlight(self)

    def dock_group_drag_data_received(selection_data, info):
        self.log.debug('%s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp))
        source = context.get_source_widget()
        assert source
        self.log.debug('Recieving item %s' % source.dragcontext.dragged_object)

        for item in reversed(source.dragcontext.dragged_object):
            self.insert_item(item, visible_position=self._drop_tab_index)

        context.finish(True, True, timestamp) # success, delete, time

    return DragData(self, dock_unhighlight, dock_group_drag_data_received)

@cleanup.when_type(DockGroup)
def dock_group_cleanup(self, layout):
    if not self.items and settings[self].auto_remove:
        parent = self.get_parent()
        self.log.debug('removing empty group')
        self.destroy()
        return parent and cleanup(parent, layout)

# Attached to drag *source*
@drag_end.when_type(DockGroup)
def dock_group_drag_end(self, context):
    cleanup(self, context.docklayout)

# Attached to drag *source*
@drag_failed.when_type(DockGroup)
def dock_group_drag_failed(self, context, result):
    global settings
    self.log.debug('%s, %s' % (context, result))
    if result == 1 and settings[self].can_float: #gtk.DRAG_RESULT_NO_TARGET
        if reduce(lambda a, b: a or b,
                  map(lambda i: settings[i].float_retain_size,
                      self.dragcontext.dragged_object)):
            size = (self.allocation.width, self.allocation.height)
        else:
            size = None

        layout = context.docklayout
        new_group = new(DockGroup, self, layout)
        add_new_group_floating(new_group, layout, size, self.get_pointer())

        for item in self.dragcontext.dragged_object:
            new_group.append_item(item)

    else:
        for item in self.dragcontext.dragged_object:
            self.insert_item(item, position=self._dragged_tab_index)
    return True

################################################################################
# DockPaned
################################################################################
def dock_paned_expose_highlight(self, event):
    try:
        handle = self._handles[self._drop_handle_index]
    except (AttributeError, IndexError, TypeError), e:
        self.log.error(e, exc_info=True)
        return
    else:
        a = handle.area

    cr = self.window.cairo_create()
    cr.set_source_rgb(0, 0, 0)
    cr.set_line_width(1.0)
    cr.rectangle(a.x + 0.5, a.y + 0.5, a.width - 1, a.height - 1)
    cr.stroke()

def dock_paned_highlight(self):
    if not hasattr(self, '_expose_event_id'):
        self.log.debug('attaching expose event')
        self._expose_event_id = self.connect_after('expose-event',
                                                   dock_paned_expose_highlight)
    self.queue_draw()

@drag_motion.when_type(DockPaned)
@with_magic_borders
def dock_paned_drag_motion(self, context, x, y, timestamp):
    self.log.debug('dock_paned_drag_motion: %s, %s, %s, %s' % (context, x, y, timestamp))

    handle = self._get_handle_at_pos(x, y)
    self.log.debug('handle at pos (%d, %d) is %s', x, y, handle)

    if handle:
        self._drop_handle_index = self._handles.index(handle)
    else:
        self._drop_handle_index = None

    dock_paned_highlight(self)

    def dock_paned_drag_data_received(selection_data, info):
        self.log.debug('%s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp))

        source = context.get_source_widget()

        self.log.debug('Recieving item %s' % source.dragcontext.dragged_object)
        # If on handle: create new DockGroup and add items
        new_group = new(DockGroup, source, context.docklayout)
        self.insert_item(new_group, self._drop_handle_index + 1)
        new_group.show()

        for item in source.dragcontext.dragged_object:
            new_group.insert_item(item)

        context.finish(True, True, timestamp) # success, delete, time

    return DragData(self, dock_unhighlight, dock_paned_drag_data_received)

@cleanup.when_type(DockPaned)
def dock_paned_cleanup(self, layout):
    if not len(self):
        parent = self.get_parent()
        self.log.debug('removing empty paned')
        self.destroy()
        return parent and cleanup(parent, layout)

    if len(self) == 1:
        parent = self.get_parent()
        child = self[0]

        if isinstance(parent, DockPaned):
            position = [c for c in parent].index(self)
            weight = parent.child_get_property(self, 'weight')
            self.remove(child)
            parent.remove(self)
            parent.insert_item(child, position=position, weight=weight)
            assert child.get_parent() is parent, (child.get_parent(), parent)
        else:
            #does not work: child.unparent()
            self.remove(child)
            parent.remove(self)
            parent.add(child)

        self.log.debug('removing empty paned - moved child to parent first')
        self.destroy()

# Attached to drag *source*
@drag_end.when_type(DockPaned)
def dock_paned_drag_end(self, context):
    cleanup(self, context.docklayout)

def dock_paned_magic_borders_leave(self):
    a = self.get_ancestor(DockFrame)
    if a:
        a.set_placeholder(None)

@magic_borders.when_type(DockPaned)
def dock_paned_magic_borders(self, context, x, y, timestamp):

    a = self.allocation

    def create_received(create):
        current_group = self.get_item_at_pos(x, y)
        assert current_group

        ca = current_group.allocation

        if x < MAGIC_BORDER_SIZE:
            allocation = (ca.x, ca.y, MAGIC_BORDER_SIZE, ca.height)
        elif a.width - x < MAGIC_BORDER_SIZE:
            allocation = (a.width - MAGIC_BORDER_SIZE, ca.y, MAGIC_BORDER_SIZE, ca.height)
        elif y < MAGIC_BORDER_SIZE:
            allocation = (ca.x, ca.y, ca.width, MAGIC_BORDER_SIZE)
        elif a.height - y < MAGIC_BORDER_SIZE:
            allocation = (ca.x, a.height - MAGIC_BORDER_SIZE, ca.width, MAGIC_BORDER_SIZE)

        frame = self.get_ancestor(DockFrame)
        fx, fy = self.translate_coordinates(frame, allocation[0], allocation[1])
        fa = frame.allocation
        allocation = (fa.x + fx, fa.y + fy, allocation[2], allocation[3])

        placeholder = Placeholder()
        frame.set_placeholder(placeholder)
        placeholder.size_allocate(allocation)
        placeholder.show()

        if create:
            if self.get_orientation() == gtk.ORIENTATION_HORIZONTAL:
                orientation = gtk.ORIENTATION_VERTICAL
            else:
                orientation = gtk.ORIENTATION_HORIZONTAL

            weight = self.child_get_property(current_group, 'weight')

            if min(x, y) < MAGIC_BORDER_SIZE:
                position = 0
            else:
                position = None

            def new_paned_and_group_receiver(selection_data, info):
                source = context.get_source_widget()
                assert source

                new_group = new(DockGroup, source, context.docklayout)
                add_new_group(current_group, new_group, orientation, position)

                self.log.debug('Recieving item %s' % source.dragcontext.dragged_object)

                for item in source.dragcontext.dragged_object:
                    new_group.append_item(item)

                context.finish(True, True, timestamp) # success, delete, time

            return new_paned_and_group_receiver

        else:
            def add_group_receiver(selection_data, info):
                source = context.get_source_widget()
                assert source
                new_group = new(DockGroup, source, context.docklayout)
                if min(x, y) < MAGIC_BORDER_SIZE:
                    position = 0
                else:
                    position = None
                self.insert_item(new_group, position)
                new_group.show()
                for item in source.dragcontext.dragged_object:
                    new_group.append_item(item)

                context.finish(True, True, timestamp) # success, delete, time

            return add_group_receiver

    if abs(min(y, a.height - y)) < MAGIC_BORDER_SIZE:
        received = create_received(self.get_orientation() == gtk.ORIENTATION_HORIZONTAL)
        return DragData(self, dock_paned_magic_borders_leave, received)
    elif abs(min(x, a.width - x)) < MAGIC_BORDER_SIZE:
        received = create_received(self.get_orientation() == gtk.ORIENTATION_VERTICAL)
        return DragData(self, dock_paned_magic_borders_leave, received)

    return None

################################################################################
# DockFrame
################################################################################
@cleanup.when_type(DockFrame)
def dock_frame_cleanup(self, layout):
    if not self.get_children():
        parent = self.get_parent()
        layout.remove(self)
        self.destroy()
        try:
            if parent.get_transient_for():
                parent.destroy()
        except AttributeError:
            self.log.error(' Not a transient top level widget')

@drag_end.when_type(DockFrame)
def dock_frame_drag_end(self, context):
    cleanup(self, context.docklayout)

def dock_frame_magic_borders_leave(self):
    self.set_placeholder(None)

@drag_motion.when_type(DockFrame)
@magic_borders.when_type(DockFrame)
def dock_frame_magic_borders(self, context, x, y, timestamp):
    '''
    Deal with drop events that are not accepted by any Paned. Provided the
    outermost n pixels are not used by the item itself, but propagate the event
    to the parent widget. This means that sometimes the event ends up in the
    "catch-all", the DockFrame.  The Frame should make sure a new DockPaned is
    created with the proper orientation and whatever's needed.
    '''
    a = self.allocation
    border = self.border_width

    if x - border < MAGIC_BORDER_SIZE:
        orientation = gtk.ORIENTATION_HORIZONTAL
        allocation = (a.x + border, a.y + border, MAGIC_BORDER_SIZE, a.height - border*2)
    elif a.width - x - border < MAGIC_BORDER_SIZE:
        orientation = gtk.ORIENTATION_HORIZONTAL
        allocation = (a.x + a.width - MAGIC_BORDER_SIZE - border, a.y + border, MAGIC_BORDER_SIZE, a.height - border*2)
    elif y - border < MAGIC_BORDER_SIZE:
        orientation = gtk.ORIENTATION_VERTICAL
        allocation = (a.x + border, a.y + border, a.width - border*2, MAGIC_BORDER_SIZE)
    elif a.height - y - border < MAGIC_BORDER_SIZE:
        orientation = gtk.ORIENTATION_VERTICAL
        allocation = (a.x + border, a.y + a.height - MAGIC_BORDER_SIZE - border, a.width - border*2, MAGIC_BORDER_SIZE)
    else:
        return None

    placeholder = Placeholder()

    self.set_placeholder(placeholder)
    placeholder.size_allocate(allocation)
    placeholder.show()

    current_child = self.get_children()[0]
    assert current_child

    if min(x - border, y - border) < MAGIC_BORDER_SIZE:
        position = 0
    else:
        position = None

    def new_paned_and_group_receiver(selection_data, info):
        source = context.get_source_widget()
        assert source

        new_group = new(DockGroup, source, context.docklayout)
        add_new_group(current_child, new_group, orientation, position)

        self.log.debug('Recieving item %s' % source.dragcontext.dragged_object)

        for item in source.dragcontext.dragged_object:
            new_group.append_item(item)

        context.finish(True, True, timestamp) # success, delete, time

    return DragData(self, dock_frame_magic_borders_leave, new_paned_and_group_receiver)