summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/canvas-grid.cpp
blob: 460b606f2c2cfa0d4dc55b309e4b976b463e1709 (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
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
// SPDX-License-Identifier: GPL-2.0-or-later

/*
 * Author:
 *   Tavmjong Bah
 *
 * Rewrite of code originally in desktop-widget.cpp.
 *
 * Copyright (C) 2020 Tavmjong Bah
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

// The scrollbars, and canvas are tightly coupled so it makes sense to have a dedicated
// widget to handle their interactions. The buttons are along for the ride. I don't see
// how to add the buttons easily via a .ui file (which would allow the user to put any
// buttons they want in their place).

#include <glibmm/i18n.h>
#include <gtkmm/enums.h>
#include <gtkmm/label.h>

#include "canvas-grid.h"

#include "desktop.h"        // Hopefully temp.
#include "desktop-events.h" // Hopefully temp.

#include "display/control/canvas-item-drawing.h" // sticky

#include "page-manager.h"

#include "ui/dialog/command-palette.h"
#include "ui/icon-loader.h"
#include "ui/widget/canvas.h"
#include "ui/widget/canvas-notice.h"
#include "ui/widget/ink-ruler.h"
#include "io/resource.h"

#include "widgets/desktop-widget.h"  // Hopefully temp.

namespace Inkscape {
namespace UI {
namespace Widget {

CanvasGrid::CanvasGrid(SPDesktopWidget *dtw)
{
    _dtw = dtw;
    set_name("CanvasGrid");

    // Canvas
    _canvas = std::make_unique<Inkscape::UI::Widget::Canvas>();
    _canvas->set_hexpand(true);
    _canvas->set_vexpand(true);
    _canvas->set_can_focus(true);
    _canvas->signal_event().connect(sigc::mem_fun(*this, &CanvasGrid::SignalEvent)); // TEMP

    // Command palette
    _command_palette = std::make_unique<Inkscape::UI::Dialog::CommandPalette>();

    // Notice overlay, note using unique_ptr will cause destruction race conditions
    _notice = CanvasNotice::create();

    // Canvas overlay
    _canvas_overlay.add(*_canvas);
    _canvas_overlay.add_overlay(*_command_palette->get_base_widget());
    _canvas_overlay.add_overlay(*_notice);

    // Horizontal Ruler
    _hruler = std::make_unique<Inkscape::UI::Widget::Ruler>(Gtk::ORIENTATION_HORIZONTAL);
    _hruler->add_track_widget(*_canvas);
    _hruler->set_hexpand(true);
    _hruler->show();
    // Tooltip/Unit set elsewhere

    // Vertical Ruler
    _vruler = std::make_unique<Inkscape::UI::Widget::Ruler>(Gtk::ORIENTATION_VERTICAL);
    _vruler->add_track_widget(*_canvas);
    _vruler->set_vexpand(true);
    _vruler->show();
    // Tooltip/Unit set elsewhere.

    // Guide Lock
    _guide_lock.set_name("LockGuides");
    _guide_lock.add(*Gtk::make_managed<Gtk::Image>("object-locked", Gtk::ICON_SIZE_MENU));
    // To be replaced by Gio::Action:
    _guide_lock.signal_toggled().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::update_guides_lock));
    _guide_lock.set_tooltip_text(_("Toggle lock of all guides in the document"));
    // Subgrid
    _subgrid.attach(_guide_lock,     0, 0, 1, 1);
    _subgrid.attach(*_vruler,        0, 1, 1, 1);
    _subgrid.attach(*_hruler,        1, 0, 1, 1);
    _subgrid.attach(_canvas_overlay, 1, 1, 1, 1);

    // Horizontal Scrollbar
    _hadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0);
    _hadj->signal_value_changed().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_adjustment_value_changed));
    _hscrollbar = Gtk::Scrollbar(_hadj, Gtk::ORIENTATION_HORIZONTAL);
    _hscrollbar.set_name("CanvasScrollbar");
    _hscrollbar.set_hexpand(true);

    // Vertical Scrollbar
    _vadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0);
    _vadj->signal_value_changed().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_adjustment_value_changed));
    _vscrollbar = Gtk::Scrollbar(_vadj, Gtk::ORIENTATION_VERTICAL);
    _vscrollbar.set_name("CanvasScrollbar");
    _vscrollbar.set_vexpand(true);

    // CMS Adjust (To be replaced by Gio::Action)
    _cms_adjust.set_name("CMS_Adjust");
    _cms_adjust.add(*Gtk::make_managed<Gtk::Image>("color-management", Gtk::ICON_SIZE_MENU));
    // Can't access via C++ API, fixed in Gtk4.
    gtk_actionable_set_action_name( GTK_ACTIONABLE(_cms_adjust.gobj()), "win.canvas-color-manage");
    _cms_adjust.set_tooltip_text(_("Toggle color-managed display for this document window"));

    // popover with some common display mode related options
    auto builder = Gtk::Builder::create_from_file(Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::UIS, "display-popup.glade"));
    _display_popup = builder;
    Gtk::Popover* popover;
    _display_popup->get_widget("popover", popover);
    Gtk::CheckButton* sticky_zoom;
    _display_popup->get_widget("zoom-resize", sticky_zoom);
    // To be replaced by Gio::Action:
    sticky_zoom->signal_toggled().connect([=](){ _dtw->sticky_zoom_toggled(); });
    _quick_actions.set_name("QuickActions");
    _quick_actions.set_popover(*popover);
    _quick_actions.set_image_from_icon_name("display-symbolic");
    _quick_actions.set_direction(Gtk::ARROW_LEFT);
    _quick_actions.set_tooltip_text(_("Display options"));

    // Main grid
    attach(_subgrid,       0, 0, 1, 2);
    attach(_hscrollbar,    0, 2, 1, 1);
    attach(_cms_adjust,    1, 2, 1, 1);
    attach(_quick_actions, 1, 0, 1, 1);
    attach(_vscrollbar,    1, 1, 1, 1);

    // For creating guides, etc.
    _hruler->signal_button_press_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_press_event),   _hruler.get(), true));
    _hruler->signal_button_release_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_release_event), _hruler.get(), true));
    _hruler->signal_motion_notify_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event),  _hruler.get(), true));

    // For creating guides, etc.
    _vruler->signal_button_press_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_press_event),   _vruler.get(), false));
    _vruler->signal_button_release_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_release_event), _vruler.get(), false));
    _vruler->signal_motion_notify_event().connect(
        sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event),  _vruler.get(), false));

    show_all();
}

CanvasGrid::~CanvasGrid()
{
    _page_modified_connection.disconnect();
    _page_selected_connection.disconnect();
    _sel_modified_connection.disconnect();
    _sel_changed_connection.disconnect();
    _document = nullptr;
    _notice = nullptr;
}

void CanvasGrid::on_realize() {
    // actions should be available now

    if (auto map = _dtw->get_action_map()) {
        auto set_display_icon = [=]() {
            Glib::ustring id;
            auto mode = _canvas->get_render_mode();
            switch (mode) {
                case RenderMode::NORMAL: id = "display";
                    break;
                case RenderMode::OUTLINE: id = "display-outline";
                    break;
                case RenderMode::OUTLINE_OVERLAY: id = "display-outline-overlay";
                    break;
                case RenderMode::VISIBLE_HAIRLINES: id = "display-enhance-stroke";
                    break;
                case RenderMode::NO_FILTERS: id = "display-no-filter";
                    break;
                default:
                    g_warning("Unknown display mode in canvas-grid");
                    break;
            }

            if (!id.empty()) {
                // if CMS is ON show alternative icons
                if (_canvas->get_cms_active()) {
                    id += "-alt";
                }
                _quick_actions.set_image_from_icon_name(id + "-symbolic");
            }
        };

        set_display_icon();

        // when display mode state changes, update icon
        auto cms_action = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(map->lookup_action("canvas-color-manage"));
        auto disp_action = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(map->lookup_action("canvas-display-mode"));

        if (cms_action && disp_action) {
            disp_action->signal_activate().connect([=](const Glib::VariantBase& state){ set_display_icon(); });
            cms_action-> signal_activate().connect([=](const Glib::VariantBase& state){ set_display_icon(); });
        }
        else {
            g_warning("No canvas-display-mode and/or canvas-color-manage action available to canvas-grid");
        }
    }
    else {
        g_warning("No action map available to canvas-grid");
    }

    parent_type::on_realize();
}

// TODO: remove when sticky zoom gets replaced by Gio::Action:
Gtk::ToggleButton* CanvasGrid::GetStickyZoom() {
    Gtk::CheckButton* sticky_zoom;
    _display_popup->get_widget("zoom-resize", sticky_zoom);
    return sticky_zoom;
}

// _dt2r should be a member of _canvas.
// get_display_area should be a member of _canvas.
void
CanvasGrid::UpdateRulers()
{
    auto prefs = Inkscape::Preferences::get();
    auto desktop = _dtw->desktop;
    auto document = desktop->getDocument();
    auto &pm = document->getPageManager();
    auto sel = desktop->getSelection();

    // Our connections to the document are handled with a lazy pattern to avoid
    // having to refactor the SPDesktopWidget class. We know UpdateRulers is
    // called in all situations when documents are loaded and replaced.
    if (document != _document) {
        _document = document;
        _page_selected_connection = pm.connectPageSelected([=](SPPage *) { UpdateRulers(); });
        _page_modified_connection = pm.connectPageModified([=](SPPage *) { UpdateRulers(); });
        _sel_modified_connection = sel->connectModified([=](Inkscape::Selection *, int) { UpdateRulers(); });
        _sel_changed_connection = sel->connectChanged([=](Inkscape::Selection *) { UpdateRulers(); });
    }

    Geom::Rect viewbox = desktop->get_display_area().bounds();
    Geom::Rect startbox = viewbox;
    if (prefs->getBool("/options/origincorrection/page", true)) {
        // Move viewbox according to the selected page's position (if any)
        startbox *= pm.getSelectedPageAffine().inverse();
    }

    // Scale and offset the ruler coordinates
    // Use an integer box to align the ruler to the grid and page.
    auto rulerbox = (startbox * Geom::Scale(_dtw->_dt2r));
    _hruler->set_range(rulerbox.left(), rulerbox.right());
    if (_dtw->desktop->is_yaxisdown()) {
        _vruler->set_range(rulerbox.top(), rulerbox.bottom());
    } else {
        _vruler->set_range(rulerbox.bottom(), rulerbox.top());
    }

    Geom::Point pos(_canvas->get_pos());
    auto scale = _canvas->get_affine();
    auto d2c = Geom::Translate(pos * scale.inverse()).inverse() * scale;
    auto pagebox = (pm.getSelectedPageRect() * d2c).roundOutwards();
    _hruler->set_page(pagebox.left(), pagebox.right());
    _vruler->set_page(pagebox.top(), pagebox.bottom());

    Geom::Rect selbox = Geom::IntRect(0, 0, 0, 0);
    if (auto bbox = sel->preferredBounds())
        selbox = (*bbox * d2c).roundOutwards();
    _hruler->set_selection(selbox.left(), selbox.right());
    _vruler->set_selection(selbox.top(), selbox.bottom());
}

void
CanvasGrid::ShowScrollbars(bool state)
{
    if (_show_scrollbars == state) return;
    _show_scrollbars = state;

    if (_show_scrollbars) {
        // Show scrollbars
        _hscrollbar.show();
        _vscrollbar.show();
        _cms_adjust.show();
        _cms_adjust.show_all_children();
        _quick_actions.show();
    } else {
        // Hide scrollbars
        _hscrollbar.hide();
        _vscrollbar.hide();
        _cms_adjust.hide();
        _quick_actions.hide();
    }
}

void
CanvasGrid::ToggleScrollbars()
{
    _show_scrollbars = !_show_scrollbars;
    ShowScrollbars(_show_scrollbars);

    // Will be replaced by actions
    auto prefs = Inkscape::Preferences::get();
    prefs->setBool("/fullscreen/scrollbars/state", _show_scrollbars);
    prefs->setBool("/window/scrollbars/state", _show_scrollbars);
}

void
CanvasGrid::ShowRulers(bool state)
{
    if (_show_rulers == state) return;
    _show_rulers = state;

    if (_show_rulers) {
        // Show rulers
        _hruler->show();
        _vruler->show();
        _guide_lock.show();
        _guide_lock.show_all_children();
    } else {
        // Hide rulers
        _hruler->hide();
        _vruler->hide();
        _guide_lock.hide();
    }
}

void
CanvasGrid::ToggleRulers()
{
    _show_rulers = !_show_rulers;
    ShowRulers(_show_rulers);

    // Will be replaced by actions
    auto prefs = Inkscape::Preferences::get();
    prefs->setBool("/fullscreen/rulers/state", _show_rulers);
    prefs->setBool("/window/rulers/state", _show_rulers);
}

void
CanvasGrid::ToggleCommandPalette()
{
    _command_palette->toggle();
}

void
CanvasGrid::showNotice(Glib::ustring const &msg, unsigned timeout)
{
    _notice->show(msg, timeout);
}

void
CanvasGrid::ShowCommandPalette(bool state)
{
    if (state) {
        _command_palette->open();
    }
    _command_palette->close();
}

// Update rulers on change of widget size, but only if allocation really changed.
void
CanvasGrid::on_size_allocate(Gtk::Allocation& allocation)
{
    Gtk::Grid::on_size_allocate(allocation);
    if (!(_allocation == allocation)) { // No != function defined!
        _allocation = allocation;
        UpdateRulers();
    }
}

// This belong in Canvas class
bool
CanvasGrid::SignalEvent(GdkEvent *event)
{
    if (event->type == GDK_BUTTON_PRESS) {
        _canvas->grab_focus();
        _command_palette->close();
    }

    if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) {
        _dtw->desktop->getCanvasDrawing()->set_sticky(event->button.state & GDK_SHIFT_MASK);
    }

    // Pass keyboard events back to the desktop root handler so TextTool can work
    if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)
        && !_canvas->get_current_canvas_item())
    {
        return sp_desktop_root_handler(event, _dtw->desktop);
    }
    
    return false;
}

// TODO Add actions so we can set shortcuts.
// * Sticky Zoom
// * CMS Adjust
// * Guide Lock


} // namespace Widget
} // namespace UI
} // namespace Inkscape

/*
  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 :