BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
AccordionWidget.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/coregui/Views/AccordionWidget/AccordionWidget.cpp
6 //! @brief Implements AccordionWidget class
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
15 // This file is part of qAccordion. An Accordion widget for Qt
16 // Copyright (C) 2015 Christian Rapp <0x2a at posteo dot org>
17 //
18 // This program is free software: you can redistribute it and/or modify
19 // it under the terms of the GNU General Public License as published by
20 // the Free Software Foundation, either version 3 of the License, or
21 // (at your option) any later version.
22 //
23 // This program is distributed in the hope that it will be useful,
24 // but WITHOUT ANY WARRANTY; without even the implied warranty of
25 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 // GNU General Public License for more details.
27 //
28 // You should have received a copy of the GNU General Public License
29 // along with this program. If not, see <http://www.gnu.org/licenses/>.
30 
32 #include <QDebug>
33 #include <stdexcept>
34 
35 AccordionWidget::AccordionWidget(QWidget* parent) : QWidget(parent)
36 {
37  // make sure our resource file gets initialized
38  Q_INIT_RESOURCE(accordionwidgeticons);
39 
40  this->multiActive = true;
41  this->collapsible = true;
42 
43  // set our basic layout
44  this->setLayout(new QVBoxLayout());
45 
46  // add a stretch to the end so all content panes are at the top
47  dynamic_cast<QVBoxLayout*>(this->layout())->addStretch();
48  this->layout()->setSpacing(1);
49  this->layout()->setContentsMargins(QMargins());
50  // TODO: Do we need to keep a pointer to the spacer?
51  this->spacer = dynamic_cast<QSpacerItem*>(this->layout()->itemAt(0));
52 
53  // seome things we want to do if the number of panes change
54  QObject::connect(this, &AccordionWidget::numberOfContentPanesChanged, this,
56 }
57 
59 {
60  return static_cast<int>(contentPanes.size());
61 }
62 
64 {
65  return this->internalAddContentPane(std::move(header));
66 }
67 
68 int AccordionWidget::addContentPane(QString header, QFrame* contentFrame)
69 {
70  return this->internalAddContentPane(std::move(header), contentFrame);
71 }
72 
74 {
75  return this->internalAddContentPane("", nullptr, cpane);
76 }
77 
78 bool AccordionWidget::insertContentPane(uint index, QString header)
79 {
80  return this->internalInsertContentPane(index, header);
81 }
82 
83 bool AccordionWidget::insertContentPane(uint index, QString header, QFrame* contentFrame)
84 {
85  return this->internalInsertContentPane(index, header, contentFrame);
86 }
87 
89 {
90  return this->internalInsertContentPane(index, "", nullptr, cpane);
91 }
92 
94 {
95  if (this->checkIndexError(index, false,
96  "Can not swap content pane at index " + QString::number(index)
97  + ". Index out of range.")) {
98  return false;
99  }
100 
101  if (this->findContentPaneIndex("", nullptr, cpane) != -1) {
102  this->errorString = "Can not swap content pane as new pane is already "
103  "managed by accordion widget";
104  return false;
105  }
106 
107  // remove the old content pane from the accordion layout
108  dynamic_cast<QVBoxLayout*>(this->layout())->removeWidget(this->contentPanes.at(index));
109  delete this->contentPanes.at(index);
110 
111  // add the new content pane to the appropriate vector
112  this->contentPanes.at(index) = cpane;
113 
114  // add the new content pane to the layout
115  dynamic_cast<QVBoxLayout*>(this->layout())->insertWidget(index, this->contentPanes.at(index));
116 
117  return true;
118 }
119 
120 bool AccordionWidget::removeContentPane(bool deleteObject, uint index)
121 {
122  return this->internalRemoveContentPane(deleteObject, index);
123 }
124 
125 bool AccordionWidget::removeContentPane(bool deleteObject, QString header)
126 {
127  return this->internalRemoveContentPane(deleteObject, -1, header);
128 }
129 
130 bool AccordionWidget::removeContentPane(bool deleteObject, QFrame* contentframe)
131 {
132  return this->internalRemoveContentPane(deleteObject, -1, "", contentframe);
133 }
134 
135 bool AccordionWidget::removeContentPane(bool deleteObject, ContentPane* contentPane)
136 {
137  return this->internalRemoveContentPane(deleteObject, -1, "", nullptr, contentPane);
138 }
139 
140 bool AccordionWidget::moveContentPane(uint currentIndex, uint newIndex)
141 {
142  if (this->checkIndexError(currentIndex, false,
143  "Can not move from " + QString::number(currentIndex)
144  + ". Index out of range.")
145  || this->checkIndexError(newIndex, false,
146  "Can not move to " + QString::number(newIndex)
147  + ". Index out of range.")) {
148  return false;
149  }
150 
151  QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(this->layout());
152  // get the pane we want to move
153  ContentPane* movePane = this->contentPanes.at(currentIndex);
154 
155  // remove the widget from the layout and insert it at the new position
156  layout->removeWidget(movePane);
157  layout->insertWidget(newIndex, movePane);
158 
159  // keep our vector synchronized
160  this->contentPanes.erase(this->contentPanes.begin() + currentIndex);
161  this->contentPanes.insert(this->contentPanes.begin() + newIndex, movePane);
162 
163  return true;
164 }
165 
167 {
168  try {
169  return this->contentPanes.at(index);
170  } catch (const std::out_of_range& ex) {
171  qDebug() << Q_FUNC_INFO << "Can not return Content Pane: " << ex.what();
172  this->errorString = "Can not return Content Pane: " + QString(ex.what());
173  return nullptr;
174  }
175 }
176 
178 {
179  return this->findContentPaneIndex(header);
180 }
181 
182 int AccordionWidget::getContentPaneIndex(QFrame* contentFrame)
183 {
184  return this->findContentPaneIndex("", contentFrame);
185 }
186 
188 {
189  return this->findContentPaneIndex("", nullptr, contentPane);
190 }
191 
192 void AccordionWidget::getActiveContentPaneIndex(std::vector<int>& indexVector)
193 {
194  // first of all make sure it is empty
195  indexVector.clear();
196  std::for_each(this->contentPanes.begin(), this->contentPanes.end(),
197  [&indexVector, this](ContentPane* pane) {
198  if (pane->getActive()) {
199  indexVector.push_back(this->findContentPaneIndex("", nullptr, pane));
200  }
201  });
202 }
203 
205 {
206  this->multiActive = status;
207 }
208 
210 {
211  return this->multiActive;
212 }
213 
215 {
216  this->collapsible = status;
217 }
218 
220 {
221  return this->collapsible;
222 }
223 
225 {
226  return this->errorString;
227 }
228 
229 int AccordionWidget::internalAddContentPane(QString header, QFrame* cframe, ContentPane* cpane)
230 {
231  if (this->findContentPaneIndex(header, cframe, cpane) != -1) {
232  this->errorString = "Can not add content pane as it already exists";
233  return -1;
234  }
235 
236  if (cpane == nullptr) {
237  if (cframe != nullptr) {
238  cpane = new ContentPane(std::move(header), cframe);
239  } else {
240  cpane = new ContentPane(std::move(header));
241  }
242  }
243  dynamic_cast<QVBoxLayout*>(this->layout())->insertWidget(this->layout()->count() - 1, cpane);
244  this->contentPanes.push_back(cpane);
245 
246  // manage the clicked signal in a lambda expression
247  QObject::connect(cpane, &ContentPane::clicked,
248  [this, cpane]() { this->handleClickedSignal(cpane); });
249 
251 
252  return numberOfContentPanes() - 1;
253 }
254 
255 bool AccordionWidget::internalInsertContentPane(uint index, QString header, QFrame* contentFrame,
256  ContentPane* cpane)
257 {
258  if (this->checkIndexError(index, true,
259  "Can not insert Content Pane at index " + QString::number(index)
260  + ". Index out of range")) {
261  return false;
262  }
263 
264  if (this->findContentPaneIndex(header, contentFrame, cpane) != -1) {
265  return false;
266  }
267 
268  if (cpane == nullptr) {
269  if (contentFrame != nullptr) {
270  cpane = new ContentPane(std::move(header), contentFrame);
271  } else {
272  cpane = new ContentPane(std::move(header));
273  }
274  }
275 
276  dynamic_cast<QVBoxLayout*>(this->layout())->insertWidget(index, cpane);
277 
278  this->contentPanes.insert(this->contentPanes.begin() + index, cpane);
279 
280  // manage the clicked signal in a lambda expression
281  QObject::connect(cpane, &ContentPane::clicked,
282  [this, cpane]() { this->handleClickedSignal(cpane); });
283 
285 
286  return true;
287 }
288 
289 bool AccordionWidget::internalRemoveContentPane(bool deleteOject, int index, QString name,
290  QFrame* contentFrame, ContentPane* cpane)
291 {
292  if (index != -1
293  && this->checkIndexError(index, false,
294  "Can not remove content pane at index " + QString::number(index)
295  + ". Index out of range")) {
296  return false;
297  }
298 
299  if (index == -1) {
300  index = this->findContentPaneIndex(std::move(name), contentFrame, cpane);
301  if (index == -1) {
302  this->errorString = "Can not remove content pane as it is not part "
303  "of the accordion widget";
304  return false;
305  }
306  }
307 
308  dynamic_cast<QVBoxLayout*>(this->layout())->removeWidget(this->contentPanes.at(index));
309 
310  // only delete the object if user wants to.
311  if (deleteOject) {
312  delete this->contentPanes.at(index);
313  this->contentPanes.at(index) = nullptr;
314  }
315 
316  this->contentPanes.erase(this->contentPanes.begin() + index);
317 
319 
320  return true;
321 }
322 
323 int AccordionWidget::findContentPaneIndex(QString name, QFrame* cframe, ContentPane* cpane)
324 {
325  // simple method that finds the index of a content by Header, content frame
326  // or content pane.
327  int index = -1;
328  if (name != "") {
329  auto result =
330  std::find_if(this->contentPanes.begin(), this->contentPanes.end(),
331  [&name](ContentPane* pane) { return pane->getHeader() == name; });
332  if (result != std::end(this->contentPanes)) {
333  // get the index by subtracting begin iterator from result
334  // iterator
335  // TODO: Is this cast really necessary?
336  index = static_cast<int>(result - this->contentPanes.begin());
337  }
338  }
339  if (cframe != nullptr) {
340  auto result = std::find_if(
341  this->contentPanes.begin(), this->contentPanes.end(),
342  [cframe](ContentPane* cpane) { return cpane->getContentFrame() == cframe; });
343  if (result != std::end(this->contentPanes)) {
344  index = static_cast<int>(result - this->contentPanes.begin());
345  }
346  }
347  if (cpane != nullptr) {
348  auto result = std::find(this->contentPanes.begin(), this->contentPanes.end(), cpane);
349  if (result != std::end(this->contentPanes)) {
350  index = static_cast<int>(result - this->contentPanes.begin());
351  }
352  }
353  return index;
354 }
355 
356 bool AccordionWidget::checkIndexError(uint index, bool sizeIndexAllowed, const QString& errMessage)
357 {
358  // sizeIndexAllowed is only used by inserting. If there is one pane you will
359  // be able to insert a new one before and after.
360  // FIXME: Actually there seem to be some bugs hidden here. User may now for
361  // example delete index 0 even if there isn't any content pane. I think we
362  // excluded checking 0 because of inserting.
363  // Update, I removed the 0 exclusion in the second if statement. Really a
364  // fix??
365  if (sizeIndexAllowed) {
366  if (index != 0 && static_cast<int>(index) > numberOfContentPanes()) {
367  qDebug() << Q_FUNC_INFO << errMessage;
368  this->errorString = errMessage;
369  return true;
370  }
371  } else {
372  if (static_cast<int>(index) >= numberOfContentPanes()) {
373  qDebug() << Q_FUNC_INFO << errMessage;
374  this->errorString = errMessage;
375  return true;
376  }
377  }
378  return false;
379 }
380 
382 {
383  // if the clicked content pane is open we simply close it and return
384  if (cpane->getActive()) {
385  // if collapsible and multiActive are false we are not allowed to close
386  // this pane
387  if (!this->collapsible && !this->multiActive) {
388  return;
389  }
390  // when multiActive is true we have to check if there is any other open
391  // cpane. if so we can close this one
392  std::vector<int> activePanes;
393  if (!this->collapsible) {
394  this->getActiveContentPaneIndex(activePanes);
395  if (activePanes.size() == 1)
396  return; // only one active --> good bye :)
397  }
398  cpane->closeContentPane();
399  return;
400  }
401  // if it is not open we will open it and search our vector for other
402  // panes that are already open.
403  // TODO: Is it really necessary to search for more than one open cpane?
404  if (!cpane->getActive()) {
405  // check if multiActive is allowed
406  if (!this->getMultiActive()) {
407  std::for_each(this->contentPanes.begin(), this->contentPanes.end(),
408  [](ContentPane* pane) {
409  if (pane->getActive())
410  pane->closeContentPane();
411  });
412  }
413  cpane->openContentPane();
414  }
415 }
416 
418 {
419  // automatically open contentpane if we have only one and collapsible is
420  // false
421  if (number == 1 && this->collapsible == false) {
422  this->contentPanes.at(0)->openContentPane();
423  }
424 }
425 
426 void AccordionWidget::paintEvent(ATTR_UNUSED QPaintEvent* event)
427 {
428  QStyleOption o;
429  o.initFrom(this);
430  QPainter p(this);
431  style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
432 }
#define ATTR_UNUSED
Defines ContentPane class.
bool getCollapsible()
Get collapsible status.
ContentPane * getContentPane(uint index)
Get content pane.
bool moveContentPane(uint currentIndex, uint newIndex)
Move content pane.
bool removeContentPane(bool deleteObject, uint index)
Remove a content pane.
void paintEvent(ATTR_UNUSED QPaintEvent *event)
paintEvent Reimplement paintEvent to use stylesheets in derived Widgets
bool checkIndexError(uint index, bool sizeIndexAllowed, const QString &errMessage)
int addContentPane(QString header)
Add a new content Pane.
void handleClickedSignal(ContentPane *cpane)
bool insertContentPane(uint index, QString header)
Insert content pane.
void numberOfPanesChanged(int number)
void setCollapsible(bool status)
If collapsible is true you can close all ContentPanes.
void numberOfContentPanesChanged(int number)
Signals the new number of content panes.
int findContentPaneIndex(QString name="", QFrame *cframe=nullptr, ContentPane *cpane=nullptr)
std::vector< ContentPane * > contentPanes
bool internalRemoveContentPane(bool deleteOject, int index=-1, QString name="", QFrame *contentFrame=nullptr, ContentPane *cpane=nullptr)
QString getError()
Get error string.
AccordionWidget(QWidget *parent=0)
QAccordion constructor.
QSpacerItem * spacer
bool swapContentPane(uint index, ContentPane *cpane)
Swap the content pane.
void getActiveContentPaneIndex(std::vector< int > &indexVector)
Get the index of the active ContentPane.
bool getMultiActive()
Check status of multiActive.
int getContentPaneIndex(QString header)
Get the index of a content pane.
bool internalInsertContentPane(uint index, QString header, QFrame *contentFrame=nullptr, ContentPane *cpane=nullptr)
void setMultiActive(bool status)
Allow multiple ContentPane to be open.
int internalAddContentPane(QString header, QFrame *cframe=nullptr, ContentPane *cpane=nullptr)
int numberOfContentPanes()
Returns the number of content panes.
Content Pane class.
Definition: ContentPane.h:75
void clicked()
Clicked signal is emitted when the header is clicked.
void closeContentPane()
Close the content pane.
void openContentPane()
Open the content pane.
bool getActive()
Check if this Content pane is active.
Definition: ContentPane.cpp:48
QString const & name(EShape k)
Definition: particles.cpp:21