summaryrefslogtreecommitdiffstats
path: root/js/ui/xdndHandler.js
blob: 3a4880b419ad1913cefd6657cb1deeef02e909f2 (plain)
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
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported XdndHandler */

const { Clutter } = imports.gi;
const Signals = imports.misc.signals;

const DND = imports.ui.dnd;
const Main = imports.ui.main;

var XdndHandler = class extends Signals.EventEmitter {
    constructor() {
        super();

        // Used to display a clone of the cursor window when the
        // window group is hidden (like it happens in the overview)
        this._cursorWindowClone = null;

        // Used as a drag actor in case we don't have a cursor window clone
        this._dummy = new Clutter.Actor({ width: 1, height: 1, opacity: 0 });
        Main.uiGroup.add_actor(this._dummy);
        this._dummy.hide();

        var dnd = global.backend.get_dnd();
        dnd.connect('dnd-enter', this._onEnter.bind(this));
        dnd.connect('dnd-position-change', this._onPositionChanged.bind(this));
        dnd.connect('dnd-leave', this._onLeave.bind(this));
    }

    // Called when the user cancels the drag (i.e release the button)
    _onLeave() {
        global.window_group.disconnectObject(this);
        if (this._cursorWindowClone) {
            this._cursorWindowClone.destroy();
            this._cursorWindowClone = null;
        }

        this.emit('drag-end');
    }

    _onEnter() {
        global.window_group.connectObject('notify::visible',
            this._onWindowGroupVisibilityChanged.bind(this), this);

        this.emit('drag-begin', global.get_current_time());
    }

    _onWindowGroupVisibilityChanged() {
        if (!global.window_group.visible) {
            if (this._cursorWindowClone)
                return;

            let windows = global.get_window_actors();
            let cursorWindow = windows[windows.length - 1];

            // FIXME: more reliable way?
            if (!cursorWindow.get_meta_window().is_override_redirect())
                return;

            const constraintPosition = new Clutter.BindConstraint({
                coordinate: Clutter.BindCoordinate.POSITION,
                source: cursorWindow,
            });

            this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow });
            Main.uiGroup.add_actor(this._cursorWindowClone);

            // Make sure that the clone has the same position as the source
            this._cursorWindowClone.add_constraint(constraintPosition);
        } else {
            if (!this._cursorWindowClone)
                return;

            this._cursorWindowClone.destroy();
            this._cursorWindowClone = null;
        }
    }

    _onPositionChanged(obj, x, y) {
        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);

        // Make sure that the cursor window is on top
        if (this._cursorWindowClone)
            Main.uiGroup.set_child_above_sibling(this._cursorWindowClone, null);

        let dragEvent = {
            x,
            y,
            dragActor: this._cursorWindowClone ?? this._dummy,
            source: this,
            targetActor: pickedActor,
        };

        for (let i = 0; i < DND.dragMonitors.length; i++) {
            let motionFunc = DND.dragMonitors[i].dragMotion;
            if (motionFunc) {
                let result = motionFunc(dragEvent);
                if (result != DND.DragMotionResult.CONTINUE)
                    return;
            }
        }

        while (pickedActor) {
            if (pickedActor._delegate && pickedActor._delegate.handleDragOver) {
                let [r_, targX, targY] = pickedActor.transform_stage_point(x, y);
                let result = pickedActor._delegate.handleDragOver(this,
                                                                  dragEvent.dragActor,
                                                                  targX,
                                                                  targY,
                                                                  global.get_current_time());
                if (result != DND.DragMotionResult.CONTINUE)
                    return;
            }
            pickedActor = pickedActor.get_parent();
        }
    }
};