summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/dialog-multipaned.h
blob: 1d1bb50dd22b2b619916807b629a831ed00caba4 (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
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
// SPDX-License-Identifier: GPL-2.0-or-later

#ifndef INKSCAPE_UI_DIALOG_MULTIPANED_H
#define INKSCAPE_UI_DIALOG_MULTIPANED_H

/** @file
 * @brief A widget with multiple panes. Agnostic to type what kind of widgets panes contain.
 *
 * Authors: see git history
 *   Tavmjong Bah
 *
 * Copyright (c) 2020 Tavmjong Bah, Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <glibmm/refptr.h>
#include <gtkmm/enums.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/gesturedrag.h>
#include <gtkmm/orientable.h>
#include <gtkmm/widget.h>

namespace Inkscape {
namespace UI {
namespace Dialog {

/** A widget with multiple panes */

class MyDropZone;
class MyHandle;
class DialogMultipaned;

/* ============ DROPZONE  ============ */

/**
 * Dropzones are eventboxes at the ends of a DialogMultipaned where you can drop dialogs.
 */
class MyDropZone
    : public Gtk::Orientable
    , public Gtk::EventBox
{
public:
    MyDropZone(Gtk::Orientation orientation);
    ~MyDropZone() override;

    static void add_highlight_instances();
    static void remove_highlight_instances();

private:
    void set_size(int size);
    bool _active = false;
    void add_highlight();
    void remove_highlight();

    static std::list<MyDropZone *> _instances_list;
};

/* ============  HANDLE   ============ */

/**
 * Handles are event boxes that help with resizing DialogMultipaned' children.
 */
class MyHandle
    : public Gtk::Orientable
    , public Gtk::EventBox
{
public:
    MyHandle(Gtk::Orientation orientation, int size);
    ~MyHandle() override = default;

    bool on_enter_notify_event(GdkEventCrossing *crossing_event) override;
    void set_dragging(bool dragging);
private:
    bool on_leave_notify_event(GdkEventCrossing* crossing_event) override;
    bool on_button_press_event(GdkEventButton* button_event) override;
    bool on_button_release_event(GdkEventButton *event) override;
    bool on_motion_notify_event(GdkEventMotion* motion_event) override;
    void toggle_multipaned();
    void update_click_indicator(double x, double y);
    void show_click_indicator(bool show);
    bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
    Cairo::Rectangle get_active_click_zone();
    int _cross_size;
    Gtk::Widget *_child;
    void resize_handler(Gtk::Allocation &allocation);
    bool is_click_resize_active() const;
    bool _click = false;
    bool _click_indicator = false;
    bool _dragging = false;
};

/* ============ MULTIPANE ============ */

/*
 * A widget with multiple panes. Agnostic to type what kind of widgets panes contain.
 * Handles allow a user to resize children widgets. Drop zones allow adding widgets
 * at either end.
 */
class DialogMultipaned
    : public Gtk::Orientable
    , public Gtk::Container
{
public:
    DialogMultipaned(Gtk::Orientation orientation = Gtk::ORIENTATION_HORIZONTAL);
    ~DialogMultipaned() override;

    void prepend(Gtk::Widget *new_widget);
    void append(Gtk::Widget *new_widget);

    // Getters and setters
    Gtk::Widget *get_first_widget();
    Gtk::Widget *get_last_widget();
    std::vector<Gtk::Widget *> get_children() { return children; }
    void set_target_entries(const std::vector<Gtk::TargetEntry> &target_entries);
    bool has_empty_widget() { return (bool)_empty_widget; }

    // Signals
    sigc::signal<void, const Glib::RefPtr<Gdk::DragContext>> signal_prepend_drag_data();
    sigc::signal<void, const Glib::RefPtr<Gdk::DragContext>> signal_append_drag_data();
    sigc::signal<void> signal_now_empty();

    // UI functions
    void set_dropzone_sizes(int start, int end);
    void toggle_multipaned_children(bool show);
    void children_toggled();
    void ensure_multipaned_children();

protected:
    // Overrides
    Gtk::SizeRequestMode get_request_mode_vfunc() const override;
    void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override;
    void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override;
    void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override;
    void get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const override;
    void on_size_allocate(Gtk::Allocation &allocation) override;

    // Allow us to keep track of our widgets ourselves.
    void forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data) override;

    void on_add(Gtk::Widget *child) override;
    void on_remove(Gtk::Widget *child) override;

    // Signals
    sigc::signal<void, const Glib::RefPtr<Gdk::DragContext>> _signal_prepend_drag_data;
    sigc::signal<void, const Glib::RefPtr<Gdk::DragContext>> _signal_append_drag_data;
    sigc::signal<void> _signal_now_empty;

private:
    // We must manage children ourselves.
    std::vector<Gtk::Widget *> children;

    // Values used when dragging handle.
    int _handle = -1; // Child number of active handle
    int _drag_handle = -1;
    Gtk::Widget* _resizing_widget1 = nullptr;
    Gtk::Widget* _resizing_widget2 = nullptr;
    Gtk::Widget* _hide_widget1 = nullptr;
    Gtk::Widget* _hide_widget2 = nullptr;
    Gtk::Allocation start_allocation1;
    Gtk::Allocation start_allocationh;
    Gtk::Allocation start_allocation2;
    Gtk::Allocation allocation1;
    Gtk::Allocation allocationh;
    Gtk::Allocation allocation2;

    Glib::RefPtr<Gtk::GestureDrag> gesture;
    // Signal callbacks
    void on_drag_begin(double start_x, double start_y);
    void on_drag_end(double offset_x, double offset_y);
    void on_drag_update(double offset_x, double offset_y);
    void on_drag_data(const Glib::RefPtr<Gdk::DragContext> context, int x, int y,
                      const Gtk::SelectionData &selection_data, guint info, guint time);
    void on_prepend_drag_data(const Glib::RefPtr<Gdk::DragContext> context, int x, int y,
                              const Gtk::SelectionData &selection_data, guint info, guint time);
    void on_append_drag_data(const Glib::RefPtr<Gdk::DragContext> context, int x, int y,
                             const Gtk::SelectionData &selection_data, guint info, guint time);

    // Others
    Gtk::Widget *_empty_widget; // placeholder in an empty container
    void add_empty_widget();
    void remove_empty_widget();
    std::vector<sigc::connection> _connections;
};

} // namespace Dialog
} // namespace UI
} // namespace Inkscape

#endif // INKSCAPE_UI_DIALOG_MULTIPANED_H

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :