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 "LayersWidget.h"
18
19 #include <cmath>
20
21 #include <QCheckBox>
22 #include <QHBoxLayout>
23 #include <QLabel>
24 #include <QLineEdit>
25 #include <QPushButton>
26 #include <QScrollArea>
27 #include <QVBoxLayout>
28
29 #include "Layer.h"
30 #include "Scene.h"
31
32 namespace impl_
33 {
34
LayerWidget(int index)35 LayerWidget::LayerWidget(int index) :
36 index_(index),
37 isActive_(false)
38 {
39 visibilityCheckBox_ = new QCheckBox();
40 visibilityCheckBox_->setCheckState(Qt::Checked);
41 visibilityCheckBox_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
42 connect(visibilityCheckBox_, SIGNAL(stateChanged(int)), this, SLOT(onVisibilityCheckBoxStateChanged_(int)));
43 connect(visibilityCheckBox_, SIGNAL(clicked(bool)), this, SLOT(onVisibilityCheckBoxClicked_(bool)));
44
45 nameLabel_ = new QLabel();
46 nameLabel_->setMinimumHeight(30);
47
48 nameLineEdit_ = new QLineEdit();
49 nameLineEdit_->setMinimumHeight(30);
50 nameLineEdit_->hide();
51 connect(nameLineEdit_, &QLineEdit::editingFinished, this, &LayerWidget::onNameLineEditEditingFinished_);
52
53 QHBoxLayout * layout = new QHBoxLayout();
54 layout->addWidget(visibilityCheckBox_);
55 layout->addWidget(nameLabel_);
56 layout->addWidget(nameLineEdit_);
57 setLayout(layout);
58
59 setAutoFillBackground(true);
60 updateBackground_();
61 }
62
~LayerWidget()63 LayerWidget::~LayerWidget()
64 {
65
66 }
67
index() const68 int LayerWidget::index() const
69 {
70 return index_;
71 }
72
isActive() const73 bool LayerWidget::isActive() const
74 {
75 return isActive_;
76 }
77
setActive(bool b)78 void LayerWidget::setActive(bool b)
79 {
80 if (b != isActive_)
81 {
82 isActive_ = b;
83 updateBackground_();
84 if (isActive_)
85 {
86 emit activated(index());
87 }
88 }
89 }
90
visibility() const91 bool LayerWidget::visibility() const
92 {
93 return visibilityCheckBox_->isChecked();
94 }
95
setVisibility(bool b)96 void LayerWidget::setVisibility(bool b)
97 {
98 if (b != visibility())
99 {
100 visibilityCheckBox_->setChecked(b);
101 // Note: we don't emit a signal here, as it will be emitted
102 // in onVisibilityCheckBoxStateChanged_.
103 }
104 }
105
name() const106 QString LayerWidget::name() const
107 {
108 return nameLabel_->text();
109 }
110
setName(const QString & newName)111 bool LayerWidget::setName(const QString& newName)
112 {
113 // Abord name editing if any
114 abortNameEditing_();
115
116 // Set new name if different form current name
117 if (newName != name())
118 {
119 nameLabel_->setText(newName);
120 emit nameChanged(index());
121 return true;
122 }
123 else
124 {
125 return false;
126 }
127 }
128
startNameEditing()129 void LayerWidget::startNameEditing()
130 {
131 startNameEditing_(NameEditingReason_::ExternalRequest);
132 }
133
mousePressEvent(QMouseEvent *)134 void LayerWidget::mousePressEvent(QMouseEvent*)
135 {
136 if (!isActive_)
137 {
138 setActive(true);
139 emit checkpoint();
140 }
141 }
142
mouseDoubleClickEvent(QMouseEvent *)143 void LayerWidget::mouseDoubleClickEvent(QMouseEvent*)
144 {
145 startNameEditing_(NameEditingReason_::DoubleClick);
146 }
147
onVisibilityCheckBoxClicked_(bool)148 void LayerWidget::onVisibilityCheckBoxClicked_(bool)
149 {
150 emit checkpoint();
151 }
152
onVisibilityCheckBoxStateChanged_(int)153 void LayerWidget::onVisibilityCheckBoxStateChanged_(int)
154 {
155 emit visibilityChanged(index());
156 }
157
onNameLineEditEditingFinished_()158 void LayerWidget::onNameLineEditEditingFinished_()
159 {
160 finishNameEditing_();
161 }
162
startNameEditing_(NameEditingReason_ reason)163 void LayerWidget::startNameEditing_(NameEditingReason_ reason)
164 {
165 if (!nameLineEdit_->isVisible())
166 {
167 nameEditingReason_ = reason;
168 nameLineEdit_->setText(name());
169 nameLabel_->hide();
170 nameLineEdit_->show();
171 nameLineEdit_->selectAll();
172 nameLineEdit_->setFocus();
173 }
174 }
175
abortNameEditing_()176 void LayerWidget::abortNameEditing_()
177 {
178 if (nameLineEdit_->isVisible())
179 {
180 nameLineEdit_->hide();
181 nameLabel_->show();
182 }
183 }
184
finishNameEditing_()185 void LayerWidget::finishNameEditing_()
186 {
187 if (nameLineEdit_->isVisible())
188 {
189 QString newName = nameLineEdit_->text();
190
191 nameLineEdit_->hide();
192 nameLabel_->show();
193
194 bool changed = setName(newName);
195 if (changed && nameEditingReason_ == NameEditingReason_::DoubleClick)
196 {
197 // We only emit checkpoint if the user action causing the scene to
198 // change is initiated from this LayerWidget. In other words, the
199 // widget responsible for starting a user action is the widget
200 // responsible for calling checkpoint.
201 emit checkpoint();
202 }
203
204 if (nameEditingReason_ == NameEditingReason_::ExternalRequest)
205 {
206 emit nameEditingFinished(index());
207 }
208 }
209 }
210
updateBackground_()211 void LayerWidget::updateBackground_()
212 {
213 QPalette::ColorRole backgroundRole = isActive() ? QPalette::Highlight : QPalette::Base;
214 setBackgroundRole(backgroundRole);
215 }
216
217 } // namespace impl_
218
LayersWidget(Scene * scene)219 LayersWidget::LayersWidget(Scene * scene) :
220 scene_(scene),
221 numVisibleLayerWidgets_(0),
222 activeLayerWidget_(nullptr)
223 {
224 // VBoxLayout with all the individual LayerWidget instances
225 layerListLayout_ = new QVBoxLayout();
226 layerListLayout_->setContentsMargins(0,0,0,0);
227 layerListLayout_->setSpacing(0);
228
229 // Create one LayerWidget right now. It will be hidden shortly after if the
230 // scene has in fact no layers.
231 //
232 // This is required because for some reason, LayerWidgets won't show up if
233 // none exist before layerListLayout_ is added to the scrollArea. I suspect
234 // this to be a bug of Qt.
235 //
236 createNewLayerWidget_();
237
238 // Wrap the layerListLayout_ into yet another VBoxLayout.
239 // We need this because:
240 // 1. We need scrollArea->setWidgetResizable(true) to enable horizontal stretching
241 // of the LayerWidget items, so that the background color takes all the
242 // horizontal space when selected.
243 // 2. Unfortunately, as a side effect, this enables vertical stretching too, which results
244 // in ugly vertical stretching of all the LayerWidget items.
245 // 3. So we add a QSpacerItem to "eat" all the remaining space below layerListLayout_.
246 QVBoxLayout * layerListLayout2 = new QVBoxLayout();
247 layerListLayout2->setContentsMargins(0,0,0,0);
248 layerListLayout2->setSpacing(0);
249 layerListLayout2->addLayout(layerListLayout_);
250 layerListLayout2->addStretch();
251
252 // Put the vbox layout in a scrollarea
253 QScrollArea * scrollArea = new QScrollArea();
254 scrollArea->setWidgetResizable(true);
255 QWidget * layerList = new QWidget();
256 layerList->setLayout(layerListLayout2);
257 scrollArea->setWidget(layerList);
258
259 // Set background color for scrollarea
260 scrollArea->setBackgroundRole(QPalette::Base);
261 scrollArea->setAutoFillBackground(true);
262
263 // Create buttons
264 QPushButton * newLayerButton = new QPushButton(tr("New"));
265 QPushButton * moveLayerUpButton = new QPushButton(tr("Move Up"));
266 QPushButton * moveLayerDownButton = new QPushButton(tr("Move Down"));
267 QPushButton * deleteLayerButton = new QPushButton(tr("Delete"));
268 connect(newLayerButton, &QPushButton::clicked, this, &LayersWidget::onNewLayerClicked_);
269 connect(moveLayerUpButton, &QPushButton::clicked, this, &LayersWidget::onMoveLayerUpClicked_);
270 connect(moveLayerDownButton, &QPushButton::clicked, this, &LayersWidget::onMoveLayerDownClicked_);
271 connect(deleteLayerButton, &QPushButton::clicked, this, &LayersWidget::onDeleteLayerClicked_);
272 QHBoxLayout * buttonsLayout = new QHBoxLayout();
273 buttonsLayout->addWidget(newLayerButton);
274 buttonsLayout->addWidget(moveLayerUpButton);
275 buttonsLayout->addWidget(moveLayerDownButton);
276 buttonsLayout->addStretch();
277 buttonsLayout->addWidget(deleteLayerButton);
278
279 // Add scrollarea to this widget
280 QVBoxLayout * layout = new QVBoxLayout();
281 layout->addWidget(scrollArea);
282 layout->addLayout(buttonsLayout);
283 setLayout(layout);
284
285 // Connect to scene
286 updateUiFromScene_();
287 connect(scene_, SIGNAL(layerAttributesChanged()), this, SLOT(onSceneLayerAttributesChanged_()));
288 }
289
~LayersWidget()290 LayersWidget::~LayersWidget()
291 {
292
293 }
294
scene() const295 Scene * LayersWidget::scene() const
296 {
297 return scene_;
298 }
299
onLayerWidgetActivated_(int index)300 void LayersWidget::onLayerWidgetActivated_(int index)
301 {
302 scene()->setActiveLayer(numVisibleLayerWidgets_ - 1 - index);
303 }
304
onLayerWidgetVisibilityChanged_(int index)305 void LayersWidget::onLayerWidgetVisibilityChanged_(int index)
306 {
307 if (0 <= index && index < numVisibleLayerWidgets_) {
308 int j = numVisibleLayerWidgets_ - 1 - index;
309 bool visibility = layerWidgets_[index]->visibility();
310 scene()->layer(j)->setVisible(visibility);
311 }
312 }
313
onLayerWidgetNameChanged_(int index)314 void LayersWidget::onLayerWidgetNameChanged_(int index)
315 {
316 if (0 <= index && index < numVisibleLayerWidgets_) {
317 int j = numVisibleLayerWidgets_ - 1 - index;
318 QString name = layerWidgets_[index]->name();
319 scene()->layer(j)->setName(name);
320 }
321 }
322
onLayerWidgetNameEditingFinished_(int)323 void LayersWidget::onLayerWidgetNameEditingFinished_(int)
324 {
325 scene()->emitCheckpoint();
326 }
327
onLayerWidgetCheckpoint_()328 void LayersWidget::onLayerWidgetCheckpoint_()
329 {
330 scene()->emitCheckpoint();
331 }
332
onNewLayerClicked_()333 void LayersWidget::onNewLayerClicked_()
334 {
335 // Create layer. This should indirectly create the corresponding
336 // LayerWidget, unless using asynchronous signals/slots.
337 Layer * layer = scene()->createLayer(tr("New Layer"));
338
339 // Enter name editing mode. We need to check in case of asynchronous
340 // signals/slots.
341 //
342 if (activeLayerWidget_)
343 {
344 int j = numVisibleLayerWidgets_ - 1 - activeLayerWidget_->index();
345 if (scene()->layer(j) == layer)
346 {
347 activeLayerWidget_->startNameEditing();
348 // Checkpoint will be emitted in onLayerWidgetNameEditingFinished_
349 }
350 }
351 else
352 {
353 // This is not supposed to happen
354 scene()->emitCheckpoint();
355 }
356 }
357
onDeleteLayerClicked_()358 void LayersWidget::onDeleteLayerClicked_()
359 {
360 scene()->destroyActiveLayer();
361 scene()->emitCheckpoint();
362 }
363
onMoveLayerUpClicked_()364 void LayersWidget::onMoveLayerUpClicked_()
365 {
366 scene()->moveActiveLayerUp();
367 scene()->emitCheckpoint();
368 }
369
onMoveLayerDownClicked_()370 void LayersWidget::onMoveLayerDownClicked_()
371 {
372 scene()->moveActiveLayerDown();
373 scene()->emitCheckpoint();
374 }
375
onSceneLayerAttributesChanged_()376 void LayersWidget::onSceneLayerAttributesChanged_()
377 {
378 updateUiFromScene_();
379 }
380
updateUiFromScene_()381 void LayersWidget::updateUiFromScene_()
382 {
383 // Show as many existing LayerWidgets as necessary
384 int numLayers = scene()->numLayers();
385 int numLayerWidgets = layerWidgets_.size();
386 int newNumVisibleLayerWidgets = std::min(numLayers, numLayerWidgets);
387 for (int i = numVisibleLayerWidgets_; i < newNumVisibleLayerWidgets; ++i) {
388 layerWidgets_[i]->show();
389 ++numVisibleLayerWidgets_;
390 }
391
392 // Create as many new LayerWidgets as necessary
393 for (int i = numVisibleLayerWidgets_; i < numLayers; ++i) {
394 createNewLayerWidget_();
395 }
396
397 // Hide superfluous LayerWidgets
398 for (int i = numLayers; i < numVisibleLayerWidgets_; ++i) {
399 layerWidgets_[i]->hide();
400 }
401 numVisibleLayerWidgets_ = numLayers;
402
403 // Set LayerWidgets names and visibility
404 for (int i = 0; i < numVisibleLayerWidgets_; ++i) {
405 int j = numVisibleLayerWidgets_ - 1 - i;
406 bool visibility= scene()->layer(j)->isVisible();
407 QString name = scene()->layer(j)->name();
408 layerWidgets_[i]->setVisibility(visibility);
409 layerWidgets_[i]->setName(name);
410 }
411
412 // Set active LayerWidget
413 int jActive = scene()->activeLayerIndex();
414 int iActive = numVisibleLayerWidgets_ - 1 - jActive;
415 if (activeLayerWidget_ && activeLayerWidget_->index() != iActive) {
416 activeLayerWidget_->setActive(false);
417 activeLayerWidget_ = nullptr;
418 }
419 if (0 <= iActive && iActive < numVisibleLayerWidgets_) {
420 activeLayerWidget_ = layerWidgets_[iActive];
421 activeLayerWidget_->setActive(true);
422 }
423 }
424
425 // Precondition: all LayerWidgets are visible
createNewLayerWidget_()426 void LayersWidget::createNewLayerWidget_()
427 {
428 impl_::LayerWidget * layerWidget = new impl_::LayerWidget(layerWidgets_.size());
429 ++numVisibleLayerWidgets_;
430 layerWidgets_.push_back(layerWidget);
431 layerListLayout_->addWidget(layerWidget);
432 connect(layerWidget, &impl_::LayerWidget::activated, this, &LayersWidget::onLayerWidgetActivated_);
433 connect(layerWidget, &impl_::LayerWidget::visibilityChanged, this, &LayersWidget::onLayerWidgetVisibilityChanged_);
434 connect(layerWidget, &impl_::LayerWidget::nameChanged, this, &LayersWidget::onLayerWidgetNameChanged_);
435 connect(layerWidget, &impl_::LayerWidget::nameEditingFinished, this, &LayersWidget::onLayerWidgetNameEditingFinished_);
436 connect(layerWidget, &impl_::LayerWidget::checkpoint, this, &LayersWidget::onLayerWidgetCheckpoint_);
437 }
438