1 // Copyright (C) 2012-2019 The VPaint Developers.
2 // See the COPYRIGHT file at the top-level directory of this distribution
3 // and at https://github.com/dalboris/vpaint/blob/master/COPYRIGHT
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #include "Global.h"
18 
19 #include "DevSettings.h"
20 #include "Scene.h"
21 #include "View.h"
22 #include "MainWindow.h"
23 #include "SettingsDialog.h"
24 #include "DevSettings.h"
25 #include "ColorSelector.h"
26 #include "Timeline.h"
27 
28 #include <QDebug>
29 #include <QApplication>
30 #include <QKeyEvent>
31 #include <QHBoxLayout>
32 #include <QPushButton>
33 #include <QToolBar>
34 #include <QMenu>
35 #include <QAction>
36 #include <QSettings>
37 #include <QStatusBar>
38 #include <QDir>
39 
40 // -------- Initialization --------
41 
42 Global * global_ = 0;
global()43 Global * global() { return global_; }
initialize(MainWindow * w)44 void Global::initialize(MainWindow * w) { global_ = new Global(w); }
45 
Global(MainWindow * w)46 Global::Global(MainWindow * w) :
47     toolMode_(SELECT),
48     toolBar_(0),
49     toolModeToolBar_(0),
50     isScalingCorner_(false),
51     isScalingEdge_(false),
52     isRotating_(false),
53     isDragAndDropping_(false),
54     isDraggingPivot_(false),
55     xSceneCursorPos_(0),
56     ySceneCursorPos_(0),
57     currentDisplayMode_(ILLUSTRATION),
58     switchToDisplayMode_(OUTLINE),
59     otherDisplayMode_(ILLUSTRATION_OUTLINE),
60     mainWindow_(w),
61     preferences_(),
62     preferencesDialog_(0),
63     settings_(0),
64     documentDir_(QDir::home())
65 {
66     // Color selectors
67     currentColor_ = new ColorSelector();
68     currentColor_->setToolTip(tr("Current color (C)"));
69     currentColor_->setStatusTip(tr("Click to open the color selector"));
70 
71     snapThreshold_ = new SpinBox();
72     snapThreshold_->setCaption(tr(" snap threshold "));
73 
74     sculptRadius_ = new SpinBox();
75     sculptRadius_->setCaption(tr(" sculpt radius "));
76 
77     // Event filter
78     QCoreApplication::instance()->installEventFilter(this);
79 
80     // Status bar help
81     statusBarHelp_ = new QLabel();
82     statusBarHelp_->setText("Find help here.");
83     w->statusBar()->addWidget(statusBarHelp_);
84     connect(this, SIGNAL(keyboardModifiersChanged()), this, SLOT(updateStatusBarHelp()));
85 
86 }
87 
deleteIsolatedVertices()88 bool Global::deleteIsolatedVertices()
89 {
90     return true;
91 }
92 
deleteShortEdges()93 bool Global::deleteShortEdges()
94 {
95     return true;
96 }
97 
keyboardModifiers()98 Qt::KeyboardModifiers Global::keyboardModifiers()
99 {
100     return keyboardModifiers_;
101 }
102 
updateModifiers()103 void Global::updateModifiers()
104 {
105     Qt::KeyboardModifiers keyboardModifiers = QGuiApplication::queryKeyboardModifiers();
106     if(keyboardModifiers_ != keyboardModifiers)
107     {
108         keyboardModifiers_ = keyboardModifiers;
109         emit keyboardModifiersChanged();
110     }
111 }
112 
eventFilter(QObject *,QEvent * event)113 bool Global::eventFilter(QObject * /*watched*/, QEvent * event)
114 {
115     // Every single event delivered by Qt go through this method first before
116     // going to its target object, so keep it as lightweight as possible
117 
118     // It is used as a convenient way to fix a few event behaviours that were
119     // not quite right out of the box.
120 
121     // --------------------- Detect modifier key presses --------------
122 
123     // Detect modifier key presses (Shift, Ctrl, Alt, etc.) and update application
124     // state accordingly (e.g., indicate which modifiers are pressed in the status bar, or
125     // redraw the scene, since highlighting color depends on which modifiers are pressed)
126 
127     // If a modifier is pressed or released, update the modifier state, and emit a signal
128     // if this state has changed
129     // If a modifier is pressed or released, update the modifier state, and emit a signal
130     // if this state has changed
131     if(event->type() == QEvent::KeyPress ||
132        event->type() == QEvent::KeyRelease)
133     {
134         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
135         if(keyEvent)
136         {
137             // Workaround for Mac delete key
138             // This is needed because of a bug in QT 5 that has not been resolved as of 5.5.0
139 #ifdef Q_OS_MAC
140             if(keyEvent->key() == Qt::Key_Backspace)
141             {
142                 scene()->smartDelete();
143             }
144 #endif
145             if(keyEvent->key() == Qt::Key_Shift ||
146                keyEvent->key() == Qt::Key_Alt ||
147                keyEvent->key() == Qt::Key_Meta ||
148                keyEvent->key() == Qt::Key_AltGr ||
149                keyEvent->key() == Qt::Key_Control)
150             {
151                 updateModifiers();
152             }
153         }
154 
155         // Continue normal processing of the event
156         return false;
157     }
158     else if(event->type() == QEvent::FocusIn )
159     {
160         updateModifiers();
161 
162         // Continue normal processing of the event
163         return false;
164     }
165 
166     // --------------------- Resolve shortcut overloads --------------
167 
168     // Resolve shortcut overloads
169     else if(event->type() == QEvent::Shortcut)
170     {
171         QShortcutEvent * shortcutEvent = static_cast<QShortcutEvent *>(event);
172 
173         if(shortcutEvent->isAmbiguous())
174         {
175             QKeySequence key = shortcutEvent->key();
176             resolveAmbiguousShortcuts(key);
177 
178             // Stop processing of the event
179             return true;
180 
181         }
182         else
183         {
184             // Continue normal processing of the event
185             return false;
186         }
187     }
188 
189     // --------------------- Keep standard behaviour --------------
190 
191     // Continue normal processing of the event
192     return false;
193 }
194 
resolveAmbiguousShortcuts(const QKeySequence & key)195 void Global::resolveAmbiguousShortcuts(const QKeySequence & key)
196 {
197     qDebug() << "Ambiguous shortcut:" << key;
198 }
199 
sceneCursorPos() const200 Eigen::Vector2d Global::sceneCursorPos() const
201 {
202     return Eigen::Vector2d(xSceneCursorPos_, ySceneCursorPos_);
203 }
204 
setSceneCursorPos(const Eigen::Vector2d & pos)205 void Global::setSceneCursorPos(const Eigen::Vector2d & pos)
206 {
207     xSceneCursorPos_ = pos[0];
208     ySceneCursorPos_ = pos[1];
209 }
210 
toolModeToolBar() const211 QToolBar * Global::toolModeToolBar() const
212 {
213     return toolModeToolBar_;
214 }
215 
toolBar() const216 QToolBar * Global::toolBar() const
217 {
218     return toolBar_;
219 }
220 
createToolBars()221 void Global::createToolBars()
222 {
223     // ----- Tool modes -----
224 
225     // Create toolbar
226     toolBar_ = mainWindow()->addToolBar(tr("Toolbar"));
227     toolBar_->setOrientation(Qt::Vertical);
228     toolBar_->setMovable(false);
229     mainWindow()->addToolBar(Qt::LeftToolBarArea, toolBar_);
230 
231     // Set toolbar size
232     int iconWidth = 32;
233     toolBar_->setIconSize(QSize(iconWidth,iconWidth));
234     currentColor_->setIconSize(QSize(iconWidth,iconWidth));
235     currentColor_->updateIcon();
236 
237     // Create actions (exclusive checkable)
238     QActionGroup * actionGroup = new QActionGroup(this);
239     for(int i=0; i<NUMBER_OF_TOOL_MODES; i++)
240     {
241         toolModeActions[i] = new ToolModeAction(static_cast<ToolMode>(i), actionGroup);
242         toolModeActions[i]->setCheckable(true);
243         toolModeActions[i]->setShortcutContext(Qt::ApplicationShortcut);
244         toolBar_->addAction(toolModeActions[i]);
245         connect(toolModeActions[i], SIGNAL(triggered(Global::ToolMode)),
246                               this, SLOT(setToolMode(Global::ToolMode)));
247     }
248 
249     // Select
250     toolModeActions[SELECT]->setText(tr("Select and move (F1)"));
251     toolModeActions[SELECT]->setIcon(QIcon(":/images/select.png"));
252     toolModeActions[SELECT]->setStatusTip(tr("Select objects, move objects, glue objects together, and split curves."));
253     toolModeActions[SELECT]->setShortcut(QKeySequence(Qt::Key_F1));
254 
255     // Sketch
256     toolModeActions[SKETCH]->setText(tr("Sketch (F2)"));
257     toolModeActions[SKETCH]->setIcon(QIcon(":/images/sketch.png"));
258     toolModeActions[SKETCH]->setStatusTip(tr("Sketch curves."));
259     toolModeActions[SKETCH]->setShortcut(QKeySequence(Qt::Key_F2));
260 
261     // Paint
262     toolModeActions[PAINT]->setText(tr("Paint (F3)"));
263     toolModeActions[PAINT]->setIcon(QIcon(":/images/paint.png"));
264     toolModeActions[PAINT]->setStatusTip(tr("Paint an empty space or an existing object."));
265     toolModeActions[PAINT]->setShortcut(QKeySequence(Qt::Key_F3));
266 
267     // Sculpt
268     toolModeActions[SCULPT]->setText(tr("Sculpt (F4)"));
269     toolModeActions[SCULPT]->setIcon(QIcon(":/images/sculpt.png"));
270     toolModeActions[SCULPT]->setStatusTip(tr("Sculpt curves."));
271     toolModeActions[SCULPT]->setShortcut(QKeySequence(Qt::Key_F4));
272 
273     // ----- Color selectors -----
274 
275     // Colors
276     colorSelectorAction_ = toolBar_->addWidget(currentColor_);
277     colorSelectorAction_->setText(tr("Color"));
278     colorSelectorAction_->setToolTip(tr("Color (C)"));
279     colorSelectorAction_->setStatusTip(tr("Click to open the color selector"));
280     colorSelectorAction_->setShortcut(QKeySequence(Qt::Key_C));
281     colorSelectorAction_->setShortcutContext(Qt::ApplicationShortcut);
282     connect(colorSelectorAction_, SIGNAL(triggered()), currentColor_, SLOT(click()));
283 
284     // ----- Tool Options -----
285 
286     toolModeToolBar_ = new QToolBar("Action Bar");
287     toolModeToolBar_->setIconSize(QSize(200,iconWidth));
288     toolModeToolBar_->setMovable(false);
289     mainWindow()->addToolBar(toolModeToolBar_);
290 
291     // ---------------------   Color   ------------------------
292 
293     actionChangeColor_ = new QAction(this);
294     actionChangeColor_->setText(tr("Change color"));
295     actionChangeColor_->setIcon(QIcon(":/images/change-color.png"));
296     actionChangeColor_->setStatusTip(tr("Change the color of the selected cells"));
297     //actionChangeColor_->setShortcut(QKeySequence(Qt::Key_C));
298     actionChangeColor_->setShortcutContext(Qt::ApplicationShortcut);
299     mainWindow()->addAction(actionChangeColor_);
300     connect(actionChangeColor_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(changeColor()));
301 
302     // ---------------------   Edges   ------------------------
303 
304     actionChangeEdgeWidth_ = new QAction(this);
305     actionChangeEdgeWidth_->setText(tr("Change edge width (W)"));
306     actionChangeEdgeWidth_->setIcon(QIcon(":/images/change-width.png"));
307     actionChangeEdgeWidth_->setStatusTip(tr("Change the width of the selected edges"));
308     actionChangeEdgeWidth_->setShortcut(QKeySequence(Qt::Key_W));
309     actionChangeEdgeWidth_->setShortcutContext(Qt::ApplicationShortcut);
310     mainWindow()->addAction(actionChangeEdgeWidth_);
311     connect(actionChangeEdgeWidth_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(changeEdgeWidth()));
312 
313 
314     // ---------------------   Faces   ------------------------
315 
316     actionCreateFace_ = new QAction(this);
317     actionCreateFace_->setText(tr("Create Face (F)"));
318     actionCreateFace_->setIcon(QIcon(":/images/create-face.png"));
319     actionCreateFace_->setStatusTip(tr("Create a face whose boundary is the selected edges"));
320     actionCreateFace_->setShortcut(QKeySequence(Qt::Key_F));
321     actionCreateFace_->setShortcutContext(Qt::ApplicationShortcut);
322     mainWindow()->addAction(actionCreateFace_);
323     connect(actionCreateFace_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(createFace()));
324 
325     actionAddCycles_ = new QAction(this);
326     actionAddCycles_->setText(tr("Add Holes (H)"));
327     actionAddCycles_->setIcon(QIcon(":/images/add-cycles.png"));
328     actionAddCycles_->setStatusTip(tr("Add holes to the selected face, whose boundaries are the selected edges"));
329     actionAddCycles_->setShortcut(QKeySequence(Qt::Key_H));
330     actionAddCycles_->setShortcutContext(Qt::ApplicationShortcut);
331     mainWindow()->addAction(actionAddCycles_);
332     connect(actionAddCycles_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(addCyclesToFace()));
333 
334     actionRemoveCycles_ = new QAction(this);
335     actionRemoveCycles_->setText(tr("Remove Holes (Ctrl+H)"));
336     actionRemoveCycles_->setIcon(QIcon(":/images/remove-cycles.png"));
337     actionRemoveCycles_->setStatusTip(tr("Remove holes from the selected face, whose boundaries are the selected edges"));
338     actionRemoveCycles_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_H));
339     actionRemoveCycles_->setShortcutContext(Qt::ApplicationShortcut);
340     mainWindow()->addAction(actionRemoveCycles_);
341     connect(actionRemoveCycles_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(removeCyclesFromFace()));
342 
343     // ---------------------   Topological operations   ------------------------
344 
345     actionGlue_ = new QAction(this);
346     actionGlue_->setText(tr("Glue"));
347     actionGlue_->setToolTip(tr("Glue (G)"));
348     actionGlue_->setIcon(QIcon(":/images/glue.png"));
349     actionGlue_->setStatusTip(tr("Glue two endpoints or two curves together"));
350     actionGlue_->setShortcut(QKeySequence(Qt::Key_G));
351     actionGlue_->setShortcutContext(Qt::ApplicationShortcut);
352     mainWindow()->addAction(actionGlue_);
353     connect(actionGlue_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(glue()));
354 
355     actionUnglue_ = new QAction(this);
356     actionUnglue_->setText(tr("Explode"));
357     actionUnglue_->setToolTip(tr("Explode (E)"));
358     actionUnglue_->setIcon(QIcon(":/images/unglue.png"));
359     actionUnglue_->setStatusTip(tr("Duplicate the selected objects to disconnect adjacent curves and surfaces"));
360     //actionUnglue_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_G));
361     actionUnglue_->setShortcut(QKeySequence(Qt::Key_E));
362     actionUnglue_->setShortcutContext(Qt::ApplicationShortcut);
363     mainWindow()->addAction(actionUnglue_);
364     connect(actionUnglue_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(unglue()));
365 
366     actionUncut_ = new QAction(this);
367     actionUncut_->setText(tr("Simplify"));
368     actionUncut_->setToolTip(tr("Simplify (Backspace)"));
369     actionUncut_->setIcon(QIcon(":/images/simplify.png"));
370     actionUncut_->setStatusTip(tr("Simplify the selected objects, by merging curves and surfaces together"));
371     actionUncut_->setShortcut(QKeySequence(Qt::Key_Backspace));
372     actionUncut_->setShortcutContext(Qt::ApplicationShortcut);
373     mainWindow()->addAction(actionUncut_);
374     connect(actionUncut_, SIGNAL(triggered()), mainWindow()->scene(), SLOT(uncut()));
375 
376     // Desired icon size
377     double sideLength = 40;
378 
379     // ---------------------   Shared actions/options   ------------------------
380 
381     // None
382 
383     // ---------------------   Select options   ------------------------
384 
385     toolModeToolBar_->addAction(actionChangeColor_);
386     toolModeToolBar_->addAction(actionChangeEdgeWidth_);
387     separatorSelect1_ = toolModeToolBar_->addSeparator();
388     toolModeToolBar_->addAction(actionCreateFace_);
389     toolModeToolBar_->addAction(actionAddCycles_);
390     toolModeToolBar_->addAction(actionRemoveCycles_);
391     separatorSelect2_ = toolModeToolBar_->addSeparator();
392 
393     toolModeToolBar_->widgetForAction(actionChangeColor_)->setFixedSize(sideLength,sideLength);
394     toolModeToolBar_->widgetForAction(actionChangeEdgeWidth_)->setFixedSize(sideLength,sideLength);
395     toolModeToolBar_->widgetForAction(actionCreateFace_)->setFixedSize(sideLength,sideLength);
396     toolModeToolBar_->widgetForAction(actionAddCycles_)->setFixedSize(sideLength,sideLength);
397     toolModeToolBar_->widgetForAction(actionRemoveCycles_)->setFixedSize(sideLength,sideLength);
398 
399     toolModeToolBar_->addAction(actionGlue_);
400     toolModeToolBar_->addAction(actionUnglue_);
401     toolModeToolBar_->addAction(actionUncut_);
402     toolModeToolBar_->widgetForAction(actionGlue_)->setFixedSize(sideLength+20,sideLength);
403     toolModeToolBar_->widgetForAction(actionUnglue_)->setFixedSize(sideLength+20,sideLength);
404     toolModeToolBar_->widgetForAction(actionUncut_)->setFixedSize(sideLength+20,sideLength);
405 
406 
407     // ---------------------   Sketch options   ------------------------
408 
409     // Tablet pressure
410     actionUseTabletPressure_ = new QAction(this);
411     actionUseTabletPressure_->setCheckable(true);
412     actionUseTabletPressure_->setChecked(true);
413     toolModeToolBar_->addAction(actionUseTabletPressure_);
414     toolModeToolBar_->widgetForAction(actionUseTabletPressure_)->setFixedSize(sideLength,sideLength);
415     actionUseTabletPressure_->setText(tr("Toggle stylus pressure"));
416     actionUseTabletPressure_->setIcon(QIcon(":/images/pressure.png"));
417     actionUseTabletPressure_->setStatusTip(tr("Enable or disable stylus pressure (only for users with a pen tablet)"));
418     //actionUseTabletPressure_->setShortcut(QKeySequence(Qt::Key_Backspace));
419     actionUseTabletPressure_->setShortcutContext(Qt::ApplicationShortcut);
420     mainWindow()->addAction(actionUseTabletPressure_);
421     connect(actionUseTabletPressure_, SIGNAL(triggered()), this, SLOT(toggleStylusPressure()));
422 
423     // Edge width
424     edgeWidth_ = new SpinBox();
425     edgeWidth_->setCaption(tr(" pen width "));
426     edgeWidth_->setValue(settings().edgeWidth());
427     actionEdgeWidth_ = toolModeToolBar_->addWidget(edgeWidth_);
428     connect(edgeWidth_, SIGNAL(valueChanged(double)), this, SLOT(setEdgeWidth_(double)));
429 
430     // Separator
431     separatorSketch1_ = toolModeToolBar_->addSeparator();
432 
433     // Planar map mode
434     actionPlanarMapMode_ = new QAction(this);
435     actionPlanarMapMode_->setCheckable(true);
436     actionPlanarMapMode_->setChecked(true);
437     toolModeToolBar_->addAction(actionPlanarMapMode_);
438     toolModeToolBar_->widgetForAction(actionPlanarMapMode_)->setFixedSize(110,sideLength);
439     actionPlanarMapMode_->setText(tr("Toggle intersections"));
440     actionPlanarMapMode_->setIcon(QIcon(":/images/planar-map-on.png"));
441     actionPlanarMapMode_->setStatusTip(tr("When intersections are enabled, the sketched curve automatically splits existing curves and surfaces."));
442     //actionPlanarMapMode_->setShortcut(QKeySequence(Qt::Key_Backspace));
443     actionPlanarMapMode_->setShortcutContext(Qt::ApplicationShortcut);
444     mainWindow()->addAction(actionPlanarMapMode_);
445     connect(actionPlanarMapMode_, SIGNAL(triggered()), this, SLOT(togglePlanarMapMode()));
446 
447     // Separator
448     separatorSketch2_ = toolModeToolBar_->addSeparator();
449 
450     // Snapping
451     actionSnapMode_ = new QAction(this);
452     actionSnapMode_->setCheckable(true);
453     actionSnapMode_->setChecked(true);
454     toolModeToolBar_->addAction(actionSnapMode_);
455     toolModeToolBar_->widgetForAction(actionSnapMode_)->setFixedSize(110,sideLength);
456     actionSnapMode_->setText(tr("Toggle snapping"));
457     actionSnapMode_->setIcon(QIcon(":/images/snapping-on.png"));
458     actionSnapMode_->setStatusTip(tr("When snapping is enabled, the sketched curve is automatically glued to existing curves."));
459     //actionSnapMode_->setShortcut(QKeySequence(Qt::Key_Backspace));
460     actionSnapMode_->setShortcutContext(Qt::ApplicationShortcut);
461     mainWindow()->addAction(actionSnapMode_);
462     connect(actionSnapMode_, SIGNAL(triggered()), this, SLOT(toggleSnapping()));
463 
464     // Edge width
465     actionSnapThreshold_ = toolModeToolBar_->addWidget(snapThreshold_);
466 
467     // ---------------------   Sculpt options   ------------------------
468 
469     actionSculptRadius_ = toolModeToolBar_->addWidget(sculptRadius_);
470 
471     // ---------------------   Cut options   ------------------------
472 
473     // Set default Tool Mode
474     setToolMode(SKETCH);
475 }
476 
displayMode() const477 Global::DisplayMode Global::displayMode() const
478 {
479     return currentDisplayMode_;
480 }
481 
activeView() const482 View * Global::activeView() const
483 {
484     return mainWindow()->activeView();
485 }
486 
hoveredView() const487 View * Global::hoveredView() const
488 {
489     return mainWindow()->hoveredView();
490 }
491 
activeTime() const492 Time Global::activeTime() const
493 {
494     return activeView()->activeTime();
495 }
496 
timeline() const497 Timeline * Global::timeline() const
498 {
499     return mainWindow()->timeline();
500 }
501 
setDisplayMode(Global::DisplayMode mode)502 void Global::setDisplayMode(Global::DisplayMode mode)
503 {
504     if(currentDisplayMode_ == mode)
505     {
506         // do nothing
507     }
508     else
509     {
510         currentDisplayMode_  = mode;
511     }
512 }
513 
showCanvas() const514 bool Global::showCanvas() const
515 {
516     return mainWindow()->isShowCanvasChecked();
517 }
518 
togglePlanarMapMode()519 void Global::togglePlanarMapMode()
520 {
521     if(actionPlanarMapMode_->isChecked())
522     {
523         actionPlanarMapMode_->setText(tr("Disable intersections"));
524         actionPlanarMapMode_->setIcon(QIcon(":/images/planar-map-on.png"));
525     }
526     else
527     {
528         actionPlanarMapMode_->setText(tr("Enable intersections"));
529         actionPlanarMapMode_->setIcon(QIcon(":/images/planar-map-off.png"));
530     }
531 }
532 
toggleSnapping()533 void Global::toggleSnapping()
534 {
535     if(actionSnapMode_->isChecked())
536     {
537         actionSnapMode_->setText(tr("Disable snapping"));
538         actionSnapMode_->setIcon(QIcon(":/images/snapping-on.png"));
539         actionSnapThreshold_->setEnabled(true);
540     }
541     else
542     {
543         actionSnapMode_->setText(tr("Enable snapping"));
544         actionSnapMode_->setIcon(QIcon(":/images/snapping-off.png"));
545         actionSnapThreshold_->setEnabled(false);
546     }
547 }
548 
toggleStylusPressure()549 void Global::toggleStylusPressure()
550 {
551     // Nothing to do
552 }
553 
setScalingCorner(bool b)554 void Global::setScalingCorner(bool b)
555 {
556     isScalingCorner_ = b;
557     updateStatusBarHelp();
558 }
559 
setScalingEdge(bool b)560 void Global::setScalingEdge(bool b)
561 {
562     isScalingEdge_ = b;
563     updateStatusBarHelp();
564 }
565 
setRotating(bool b)566 void Global::setRotating(bool b)
567 {
568     isRotating_ = b;
569     updateStatusBarHelp();
570 }
571 
setDragAndDropping(bool b)572 void Global::setDragAndDropping(bool b)
573 {
574     isDragAndDropping_ = b;
575     updateStatusBarHelp();
576 }
577 
setDraggingPivot(bool b)578 void Global::setDraggingPivot(bool b)
579 {
580     isDraggingPivot_ = b;
581     updateStatusBarHelp();
582 }
583 
toolMode() const584 Global::ToolMode Global::toolMode() const
585 {
586     if(mainWindow()->isEditCanvasSizeVisible())
587     {
588         return EDIT_CANVAS_SIZE;
589     }
590     else
591     {
592         return toolMode_;
593     }
594 }
595 
setToolMode(Global::ToolMode mode)596 void Global::setToolMode(Global::ToolMode mode)
597 {
598     // Check consistency with action state
599     if(!toolModeActions[mode]->isChecked())
600         toolModeActions[mode]->setChecked(true);
601 
602     // Set member variable
603     toolMode_ = mode;
604 
605     // Hide everything
606     actionChangeColor_->setVisible(false);
607     actionChangeEdgeWidth_->setVisible(false);
608     actionCreateFace_->setVisible(false);
609     actionAddCycles_->setVisible(false);
610     actionRemoveCycles_->setVisible(false);
611 
612     toolModeToolBar_->removeAction(actionGlue_);
613     toolModeToolBar_->removeAction(actionUnglue_);
614     toolModeToolBar_->removeAction(actionUncut_);
615     actionPlanarMapMode_->setVisible(false);
616     actionSnapMode_->setVisible(false);
617     actionUseTabletPressure_->setVisible(false);
618     actionEdgeWidth_->setVisible(false);
619     actionSnapThreshold_->setVisible(false);
620     actionSculptRadius_->setVisible(false);
621     separatorSelect1_->setVisible(false);
622     separatorSelect2_->setVisible(false);
623     separatorSketch1_->setVisible(false);
624     separatorSketch2_->setVisible(false);
625 
626     // Desired icon size
627     double sideLength = 40;
628 
629     // Show relevant
630     switch(toolMode_)
631     {
632     case SELECT:
633         actionChangeColor_->setVisible(true);
634         actionChangeEdgeWidth_->setVisible(true);
635         actionCreateFace_->setVisible(true);
636         actionAddCycles_->setVisible(true);
637         actionRemoveCycles_->setVisible(true);
638         toolModeToolBar_->addAction(actionGlue_);
639         toolModeToolBar_->addAction(actionUnglue_);
640         toolModeToolBar_->addAction(actionUncut_);
641         toolModeToolBar_->widgetForAction(actionGlue_)->setFixedSize(sideLength+20,sideLength);
642         toolModeToolBar_->widgetForAction(actionUnglue_)->setFixedSize(sideLength+20,sideLength);
643         toolModeToolBar_->widgetForAction(actionUncut_)->setFixedSize(sideLength+20,sideLength);
644         separatorSelect1_->setVisible(true);
645         separatorSelect2_->setVisible(true);
646         break;
647     case SKETCH:
648         actionPlanarMapMode_->setVisible(true);
649         actionSnapMode_->setVisible(true);
650         actionUseTabletPressure_->setVisible(true);
651         actionSnapThreshold_->setVisible(true);
652         actionEdgeWidth_->setVisible(true);
653         separatorSketch1_->setVisible(true);
654         separatorSketch2_->setVisible(true);
655         break;
656     case SCULPT:
657         actionSculptRadius_->setVisible(true);
658         break;
659     case PAINT:
660         break;
661     default:
662         break;
663     }
664 
665     // Even when there's no icon, make it high enough
666     toolModeToolBar_->setMinimumHeight(50);
667 
668     // Update help
669     updateStatusBarHelp();
670 
671     // Update scene
672     mainWindow()->update();
673     mainWindow()->updatePicking();
674 }
675 
676 
updateStatusBarHelp()677 void Global::updateStatusBarHelp()
678 {
679     Qt::KeyboardModifiers keys = keyboardModifiers();
680     bool isCtrlDown = keys & Qt::ControlModifier;
681     bool isShiftDown = keys & Qt::ShiftModifier;
682     bool isAltDown = keys & Qt::AltModifier;
683 
684     QString message;
685     if(isCtrlDown || isShiftDown || isAltDown)
686     {
687         message += "[";
688         if(isCtrlDown)
689         {
690             message += QString(ACTION_MODIFIER_NAME_SHORT).toUpper();
691             if(isShiftDown || isAltDown)
692                 message += ",";
693         }
694         if(isShiftDown)
695         {
696             message += "SHIFT";
697             if(isAltDown)
698                 message += ",";
699         }
700 
701         if(isAltDown)
702         {
703             message += "ALT";
704         }
705         message += "] ";
706     }
707 
708     if (isScalingCorner_)
709     {
710         if(!isShiftDown)
711             message += "Hold SHIFT to preserve proportions. ";
712         if(!isAltDown)
713             message += "Hold ALT to scale relative to center/pivot. ";
714     }
715     else if (isScalingEdge_)
716     {
717         if(!isAltDown)
718             message += "Hold ALT to scale relative to center/pivot. ";
719     }
720     else if (isRotating_)
721     {
722         if(!isShiftDown)
723             message += "Hold SHIFT to rotate by 45° only. ";
724         if(!isAltDown)
725             message += "Hold ALT to rotate relative to opposite corner. ";
726     }
727     else if (isDragAndDropping_)
728     {
729         if(!isShiftDown)
730             message += "Hold SHIFT to constrain translation along 45° axes. ";
731     }
732     else if (isDraggingPivot_)
733     {
734         if(!isShiftDown)
735             message += "Hold SHIFT to snap to center and corners of bounding box. ";
736     }
737     else if(toolMode() == SELECT)
738     {
739         if(!isCtrlDown && !isShiftDown && !isAltDown) {
740             message += "Click to select highlighted object. Click on background to deselect all. Hold " + QString(ACTION_MODIFIER_NAME_SHORT).toUpper() + ", SHIFT, or ALT for more actions.";
741         }
742         else if(isCtrlDown && !isShiftDown && !isAltDown) {
743             message += "Click on curve to insert end point. Click on face to insert point-in-face.";
744         }
745         else if(!isCtrlDown && isShiftDown && !isAltDown) {
746             message += "Click to add highlighted object to the selection. Hold also ALT for different action.";
747         }
748         else if(!isCtrlDown && !isShiftDown && isAltDown) {
749             message += "Click to remove highlighted object from the selection. Hold also SHIFT for different action.";
750         }
751         else if(!isCtrlDown && isShiftDown && isAltDown) {
752             message += "Click to select unselected objects, or deselect selected objects.";
753         }
754         else {
755             message += "No action available for this combination of keyboard modifiers.";
756         }
757     }
758     else if(toolMode() == SKETCH)
759     {
760         if(!isCtrlDown && !isShiftDown && !isAltDown) {
761             message += "Hold left mouse button to draw a curve. " + QString(ACTION_MODIFIER_NAME_SHORT).toUpper() + ": Change pen width. ALT: Change snap threshold.";
762         }
763         else if(isCtrlDown && !isShiftDown && !isAltDown) {
764             message += "Hold left mouse button to change pen width.";
765         }
766         else if(!isCtrlDown && !isShiftDown && isAltDown) {
767             message += "Hold left mouse button to change snap threshold.";
768         }
769         else if(isCtrlDown && !isShiftDown && isAltDown) {
770             message += "Hold left mouse button to change both pen width and snap threshold.";
771         }
772         else {
773             message += "No action available for this combination of keyboard modifiers.";
774         }
775     }
776     else if(toolMode() == PAINT)
777     {
778         if(!isCtrlDown && !isShiftDown && !isAltDown) {
779             message += "Click on closed region delimited by curves to fill. Click on object to change color. Click on background to change background color.";
780         }
781         else {
782             message += "No action available for this combination of keyboard modifiers.";
783         }
784     }
785     else if(toolMode() == SCULPT)
786     {
787         if(!isCtrlDown && !isShiftDown && !isAltDown) {
788             message += "Hold left mouse button (LMB) to drag endpoint, or drag curve within radius. " + QString(ACTION_MODIFIER_NAME_SHORT).toUpper() + ": radius. SHIFT: smooth. ALT: thickness.";
789         }
790         else if(isCtrlDown && !isShiftDown && !isAltDown) {
791             message += "Hold LMB to change the radius of the sculpting tool. Note: radius not visible if cursor too far from curve.";
792         }
793         else if(!isCtrlDown && isShiftDown && !isAltDown) {
794             message += "Hold LMB to smooth curve within radius.";
795         }
796         else if(!isCtrlDown && !isShiftDown && isAltDown) {
797             message += "Hold LMB to change thickness of curve within radius. Trick: use a large radius to edit thickness of the whole curve.";
798         }
799         else {
800             message += "No action available for this combination of keyboard modifiers.";
801         }
802     }
803 
804     statusBarHelp_->setText(message);
805 }
806 
807 // Other getters
devSettings()808 DevSettings * Global::devSettings() { return DevSettings::instance(); }
mainWindow() const809 MainWindow * Global::mainWindow() const { return mainWindow_; }
settings()810 Settings & Global::settings() { return preferences_; }
scene() const811 Scene * Global::scene() const {return mainWindow()->scene();}
812 
edgeColor()813 QColor Global::edgeColor()
814 {
815     return currentColor_->color();
816 }
817 
faceColor()818 QColor Global::faceColor()
819 {
820     return currentColor_->color();
821 }
822 
useTabletPressure() const823 bool Global::useTabletPressure() const
824 {
825     return actionUseTabletPressure_->isChecked();
826 }
827 
edgeWidth() const828 double Global::edgeWidth() const
829 {
830     return edgeWidth_->value();
831 }
832 
setEdgeWidth_(double w)833 void Global::setEdgeWidth_(double w)
834 {
835     preferences_.setEdgeWidth(w);
836 }
837 
setEdgeWidth(double w)838 void Global::setEdgeWidth(double w)
839 {
840     if(edgeWidth() != w)
841     {
842         edgeWidth_->setValue(w);
843     }
844 
845     preferences_.setEdgeWidth(w);
846 }
847 
openPreferencesDialog()848 void Global::openPreferencesDialog()
849 {
850     // Create preferences dialog
851     if(!preferencesDialog_)
852     {
853         preferencesDialog_ = new SettingsDialog(mainWindow());
854         connect(preferencesDialog_, SIGNAL(preferencesChanged()), this, SLOT(updateWidgetValuesFromPreferences()));
855     }
856 
857     // Update and show references dialog
858     preferencesDialog_->go();
859 }
860 
updateWidgetValuesFromPreferences()861 void Global::updateWidgetValuesFromPreferences()
862 {
863     edgeWidth_->setValue(preferences_.edgeWidth());
864 }
865 
planarMapMode() const866 bool Global::planarMapMode() const
867 {
868     return actionPlanarMapMode_->isChecked();
869 }
870 
snapMode() const871 bool Global::snapMode() const
872 {
873     return actionSnapMode_->isChecked();
874 }
875 
snapThreshold() const876 double Global::snapThreshold() const
877 {
878     return snapThreshold_->value();
879 }
880 
setSnapThreshold(double newSnapThreshold)881 void Global::setSnapThreshold(double newSnapThreshold)
882 {
883     return snapThreshold_->setValue(newSnapThreshold);
884 }
885 
sculptRadius() const886 double Global::sculptRadius() const
887 {
888     return sculptRadius_->value();
889 }
890 
setSculptRadius(double newRadius)891 void Global::setSculptRadius(double newRadius)
892 {
893     return sculptRadius_->setValue(newRadius);
894 }
895 
readSettings()896 void Global::readSettings()
897 {
898     QSettings qsettings;
899 
900     // Geometry of the window
901     QSize size = qsettings.value("size", QSize(400, 400)).toSize();
902     QPoint pos = qsettings.value("pos", QPoint(200, 200)).toPoint();
903     mainWindow()->resize(size);
904     mainWindow()->move(pos);
905 
906     // User settings
907     settings().readFromDisk(qsettings);
908 
909     // Other settings
910     snapThreshold_->setValue( qsettings.value("tools-sketch-snapthreshold", 15.0).toDouble() );
911     sculptRadius_->setValue( qsettings.value("tools-sculpt-radius", 50.0).toDouble() );
912 }
913 
writeSettings()914 void Global::writeSettings()
915 {
916       QSettings qsettings;
917 
918       // Geometry of the window
919       qsettings.setValue("size", mainWindow()->size());
920       qsettings.setValue("pos", mainWindow()->pos());
921 
922       // User settings
923       settings().writeToDisk(qsettings);
924 
925       // Other settings
926       qsettings.setValue("tools-sketch-snapthreshold", snapThreshold_->value() );
927       qsettings.setValue("tools-sculpt-radius", sculptRadius_->value() );
928 }
929 
930 // ----- ToolModeAction -----
931 
ToolModeAction(Global::ToolMode mode,QObject * parent)932 ToolModeAction::ToolModeAction(Global::ToolMode mode, QObject * parent) :
933     QAction(parent),
934     toolMode(mode)
935 {
936     connect(this, SIGNAL(triggered()), this, SLOT(emitSpecializedTriggered()));
937 }
938 
emitSpecializedTriggered()939 void ToolModeAction::emitSpecializedTriggered()
940 {
941     emit triggered(toolMode);
942 }
943 
setDocumentDir(const QDir & dir)944 void Global::setDocumentDir(const QDir & dir)
945 {
946     documentDir_ = dir;
947 }
948 
documentDir() const949 QDir Global::documentDir() const
950 {
951     return documentDir_;
952 }
953