32 |
33 | #include "qaccordion/qaccordion.hpp"
34 |
35 | namespace Ui
36 | {
37 | class MainWindow;
38 | }
39 |
40 | class MainWindow : public QMainWindow
41 | {
42 | Q_OBJECT
43 |
44 | public:
45 | explicit MainWindow(QWidget *parent = nullptr);
46 | ~MainWindow() override;
47 |
48 | private:
49 | // lorem ipsum api url
50 | const char *const ipsumApi = "https://loripsum.net/api/1/short/code";
51 | // a random offline ipsum in case there is no network connection
52 | const char *const offlineIpsum =
53 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed plane "
54 | "dicit quod intellegit. Aufert enim sensus actionemque tollit omnem. "
55 | "Itaque ab his ordiamur. Restatis igitur vos;
"
56 | ""
57 | "Quod enim vituperabile est per se ipsum, id eo ipso vitium"
58 | "nominatum puto, vel etiam a vitio dictum vituperari."
59 | "Qui autem esse poteris, nisi te amor ipse ceperit?"
60 | " ";
61 |
62 | Ui::MainWindow *ui;
63 | // Keep pointers to our contetn frames
64 | QFrame *addCF;
65 | QFrame *insertCF;
66 | QFrame *removeCF;
67 | QFrame *moveCF;
68 |
69 | std::unique_ptr networkManager; /**< Network manager
70 | for ipsum api
71 | requests.*/
72 | std::queue labelIpsumQueue;
73 |
74 | void networkRequestFinished(QNetworkReply *reply);
75 |
76 | void contentPaneAdd(QAccordion *topAccordion);
77 | void contentPaneInsert(QAccordion *topAccordion);
78 | void contentPaneRemove(QAccordion *topAccordion);
79 | void contentPaneMove(QAccordion *topAccordion);
80 |
81 | void createIpsumLabel(QFrame *frame);
82 | };
83 |
84 | #endif // MAINWINDOW_H
85 |
--------------------------------------------------------------------------------
/demo/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 900
10 | 635
11 |
12 |
13 |
14 | qAccordion Demo
15 |
16 |
17 |
18 | :/qAccordionIcons/accordion_cc_grey.svg :/qAccordionIcons/accordion_cc_grey.svg
19 |
20 |
21 |
22 |
23 |
24 | true
25 |
26 |
27 |
28 | -
29 |
30 |
31 | Control Accordion
32 |
33 |
34 | -
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
53 |
54 |
55 |
56 | QAccordion
57 | QWidget
58 | qaccordion/qaccordion.hpp
59 | 1
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/doc/main_page.dox:
--------------------------------------------------------------------------------
1 | /**
2 | * @mainpage qAccordion - Cute Accordion redefined
3 | *
4 | * qAccordion provides an accordion widget for Qt 5. The widget is heavily
5 | * inspired by the [jQuery UI accordion control](https://jqueryui.com/accordion/).
6 | *
7 | * ## Dependencies
8 | *
9 | * This widget is for Qt 5 only. I highly recommend cmake for building it.
10 | *
11 | * ## Setting up qAccordion
12 | *
13 | * Using qAccordion in your project is possible in two ways.
14 | * 1. Add the source code to your project and use it directly.
15 | * 2. Compile a shared library using cmake and link your application with it.
16 | *
17 | * ## Usage
18 | *
19 | * You can add this widget to a GUI via the Qt Designer or programmatically but
20 | * you can not use the Designer to configure the widget.
21 | *
22 | * A simple usage example can be found in the README. A more detailed approach is
23 | * used in the demo application located in the test folder.
24 | *
25 | * ## License
26 | *
27 | * Copyright (C) 2015, 2017, 2020 Christian Rapp
28 | *
29 | * This program is free software: you can redistribute it and/or modify
30 | * it under the terms of the GNU General Public License as published by
31 | * the Free Software Foundation, either version 3 of the License, or
32 | * (at your option) any later version.
33 | *
34 | * This program is distributed in the hope that it will be useful,
35 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
36 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 | * GNU General Public License for more details.
38 | *
39 | * You should have received a copy of the GNU General Public License
40 | * along with this program. If not, see .
41 | *
42 | */
43 |
--------------------------------------------------------------------------------
/icons/accordion_cc_grey.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
--------------------------------------------------------------------------------
/icons/caret-bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crapp/qaccordion/9fcafc27a8ade4b4c7cf6358f38bf2ce9fea98fe/icons/caret-bottom.png
--------------------------------------------------------------------------------
/icons/caret-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crapp/qaccordion/9fcafc27a8ade4b4c7cf6358f38bf2ce9fea98fe/icons/caret-right.png
--------------------------------------------------------------------------------
/icons/noun_111558_cc.svg:
--------------------------------------------------------------------------------
1 | Created by Creative Stall from the Noun Project
--------------------------------------------------------------------------------
/icons/qaccordionicons.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | caret-bottom.png
4 | caret-right.png
5 | accordion_cc_grey.svg
6 |
7 |
8 |
--------------------------------------------------------------------------------
/include/qaccordion/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set (QACCORDION_HEADER
2 | "${CMAKE_CURRENT_SOURCE_DIR}/clickableframe.hpp"
3 | "${CMAKE_CURRENT_SOURCE_DIR}/contentpane.hpp"
4 | "${CMAKE_CURRENT_SOURCE_DIR}/qaccordion.hpp"
5 | PARENT_SCOPE
6 | )
7 |
--------------------------------------------------------------------------------
/include/qaccordion/clickableframe.hpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #ifndef CLICKABLEFRAME_HPP
18 | #define CLICKABLEFRAME_HPP
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include "qaccordion_config.hpp"
29 |
30 | /**
31 | * @brief The ClickableFrame class
32 | *
33 | * This class represents a clickable QFrame. It is used by a ContentPane. The
34 | * class is used internally.
35 | */
36 | class ClickableFrame : public QFrame
37 | {
38 | Q_OBJECT
39 | public:
40 | enum class ICON_POSITION { LEFT, RIGHT };
41 | enum class TRIGGER { NONE, SINGLECLICK, DOUBLECLICK, MOUSEOVER };
42 |
43 | const char *const CARRET_ICON_CLOSED =
44 | ":/qAccordionIcons/caret-right.png"; /**< Qt qrc "path" for the closed icon */
45 | const char *const CARRET_ICON_OPENED =
46 | ":/qAccordionIcons/caret-bottom.png"; /**< Qt qrc "path" for the opened icon */
47 |
48 | /**
49 | * @brief ClickableFrame constructor
50 | * @param header Header of the frame
51 | * @param parent Parent widget or 0
52 | * @param f Qt::WindowFlags
53 | */
54 | explicit ClickableFrame(QString header, QWidget *parent = nullptr,
55 | Qt::WindowFlags f = Qt::Widget);
56 |
57 | // TODO: Expose this function to the ContentPane api
58 | /**
59 | * @brief Change header trigger
60 | * @param tr
61 | *
62 | * @details
63 | * Set the trigger for the header. You may choose between single or double
64 | * mouseclick and mouse enter event. Or ClickableFrame::TRIGGER::NONE if you
65 | * want no interaction at all (you may still trigger the header
66 | * programmatically though).
67 | */
68 | void setTrigger(TRIGGER tr);
69 | /**
70 | * @brief Get the the trigger of the header
71 | * @return ClickableFrame::TRIGGER
72 | */
73 | TRIGGER getTrigger();
74 |
75 | /**
76 | * @brief Set the header string
77 | * @param header
78 | */
79 | void setHeader(QString header);
80 | /**
81 | * @brief Get the header string
82 | * @return QString
83 | */
84 | QString getHeader();
85 | /**
86 | * @brief Set the default stylesheet
87 | * @param stylesheet
88 | */
89 | void setNormalStylesheet(QString stylesheet);
90 | /**
91 | * @brief Get the default stylesheet
92 | * @return QString
93 | */
94 | QString getNormalStylesheet();
95 | /**
96 | * @brief Set mouseover stylesheet
97 | * @param stylesheet
98 | */
99 | void setHoverStylesheet(QString stylesheet);
100 | /**
101 | * @brief Get mouseover stylesheet
102 | * @return
103 | */
104 | QString getHoverStylesheet();
105 |
106 | signals:
107 | /**
108 | * @brief Signal that is emitted when the header is triggered
109 | * @param pos Currently unused
110 | */
111 | void triggered(QPoint pos);
112 |
113 | public slots:
114 |
115 | /**
116 | * @brief Set the header icon
117 | * @param icon
118 | *
119 | * @details
120 | * ContentPane will set an icon depending on its state (active or not).
121 | */
122 | void setIcon(const QPixmap &icon);
123 |
124 | /**
125 | * @brief Set icon position
126 | * @param pos
127 | */
128 | //void setIconPosition(ClickableFrame::ICON_POSITION pos);
129 |
130 | private:
131 | QLabel *iconLabel;
132 | QLabel *nameLabel;
133 |
134 | QString hoverStylesheet;
135 | QString normalStylesheet;
136 |
137 | QString header;
138 | QString tooltip;
139 |
140 | TRIGGER headerTrigger;
141 |
142 | void initFrame();
143 |
144 | protected:
145 | /**
146 | * @brief Reimplemented function to QMouseEvents
147 | * @param event
148 | */
149 | void mousePressEvent(QMouseEvent *event) override;
150 | void mouseDoubleClickEvent(QMouseEvent *event) override;
151 |
152 | /**
153 | * @brief Enter event for mouse over effects.
154 | * @param event
155 | */
156 | void enterEvent(QEvent *event) override;
157 | /**
158 | * @brief Leave effect for mouse over effects.
159 | * @param event
160 | */
161 | void leaveEvent(QEvent *event) override;
162 | };
163 |
164 | #endif // CLICKABLEFRAME_HPP
165 |
--------------------------------------------------------------------------------
/include/qaccordion/config.h.in:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #ifndef QACCORDION_CONFIG_H_IN
18 | #define QACCORDION_CONFIG_H_IN
19 |
20 | #define VERSION_MAJOR "@qAccordion_VERSION_MAJOR@"
21 | #define VERSION_MINOR "@qAccordion_VERSION_MINOR@"
22 | #define VERSION_PATCH "@qAccordion_VERSION_PATCH@"
23 |
24 | #ifdef __GNUC__
25 | #define ATTR_UNUSED __attribute__((unused))
26 | #else
27 | #define ATTR_UNUSED
28 | #endif
29 |
30 | #endif // QACCORDION_CONFIG_H_IN
31 |
--------------------------------------------------------------------------------
/include/qaccordion/contentpane.hpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #ifndef CONTENTPANE_HPP
18 | #define CONTENTPANE_HPP
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include
30 |
31 | #include "qaccordion_config.hpp"
32 |
33 | #include "clickableframe.hpp"
34 | // #include "qaccordion.hpp"
35 |
36 | /**
37 | * @brief Content Pane class
38 | *
39 | * @details
40 | * Content Panes are part of a QAccordion object. They represent a clickable
41 | * Header and can be expanded or retracted.
42 | *
43 | * When a ContentPane is created you have to provide a Header (ContentPane(QString, QWidget*))
44 | * and, if you want, a QFrame* (ContentPane(QString, QFrame*, QWidget*)) with the
45 | * content that should be displayed when the ContentPane is expanded.
46 | *
47 | * Managing the content is pretty straight forward use getContentFrame() and setContentFrame()
48 | * to manage the content. You are absolutely free to do anything what you like with
49 | * this QFrame. Just keep in mind there is a maximumHeight for the container in which
50 | * the content frame is placed (currently 150). If your content exceeds this height
51 | * you have to either increase it with setMaximumHeight() or add a [QScrollArea](http://doc.qt.io/qt-5/qscrollarea.html)
52 | * to the content frame. You may use setContainerFrameStyle() to change the frame
53 | * style of the container.
54 | *
55 | * The Header can be changed after the creation of the ContentPane with setHeader().
56 | * Additionally you can set a tooltip, a standard stylesheet and a mouseover
57 | * stylesheet, the frame style and the icons.
58 | * @sa
59 | * setHeaderTooltip(), setHeaderStylesheet(), setHeaderHoverStylesheet(),
60 | * setHeaderFrameStyle(), setHeaderIconActive(), setHeaderIconInActive()
61 | *
62 | * @details
63 | * The animation speed is influenceable setAnimationDuration().
64 | */
65 | class ContentPane : public QWidget
66 | {
67 | Q_OBJECT
68 | public:
69 | /**
70 | * @brief ContentPane constructor
71 | * @param header The Header of the content pane
72 | * @param parent Parent widget or 0
73 | */
74 | explicit ContentPane(QString header, QWidget *parent = nullptr);
75 | /**
76 | * @brief ContentPane constructor
77 | * @param header The Header of the content pane
78 | * @param content Content to display when expanded
79 | * @param parent Parent widget or 0
80 | */
81 | explicit ContentPane(QString header, QFrame *content, QWidget *parent = nullptr);
82 |
83 | /**
84 | * @brief Check if this Content pane is active
85 | * @return boolean True if active
86 | */
87 | bool getActive() const;
88 |
89 | /**
90 | * @brief Get the content frame of the content pane
91 | * @return QFrame*
92 | *
93 | * @details
94 | * Use this method to get a pointer to a QFrame where you can place
95 | * your content. The widget lifetime is managed by qAccordion.
96 | */
97 | QFrame *getContentFrame();
98 | /**
99 | * @brief Set the content frame
100 | * @param content QFrame* with your content
101 | *
102 | * Set / change the content frame of this content pane with \p content. The
103 | * lifecycle
104 | */
105 | void setContentFrame(QFrame *content);
106 |
107 | /**
108 | * @brief Get the maximum height of the content pane container frame
109 | * @return int
110 | */
111 | int getMaximumHeight();
112 | /**
113 | * @brief Set the maximum height of the content pane container.
114 | * @param maxHeight
115 | *
116 | * @details
117 | * Every content pane has a container frame that defines the visible maximum
118 | * height. With this function you can change this setting.
119 | *
120 | @note
121 | * This setting does not set the maximum height of the content frame. It is
122 | * the users responsibilty to make sure everything in the content frame is
123 | * visible. This can be influenced by either changing the maximum height of the
124 | * container or for example by adding a [QScrollArea](http://doc.qt.io/qt-5.5/qscrollarea.html).
125 | */
126 | void setMaximumHeight(int maxHeight);
127 |
128 | void setTrigger(ClickableFrame::TRIGGER tr);
129 | ClickableFrame::TRIGGER getTrigger();
130 |
131 | /**
132 | * @brief Set the header of the content pane
133 | * @param header
134 | */
135 | void setHeader(QString header);
136 | /**
137 | * @brief Return the header of the content pane
138 | * @return QString
139 | */
140 | QString getHeader();
141 | /**
142 | * @brief Set the header icon that will be displayed when the content pane is active
143 | * @param icon Path to image
144 | *
145 | * @details
146 | * Set the header icon for active content pane state. Provide a path to a
147 | * supported image file. If the file does not exist or has an unsupported
148 | * format the icon will not be changed.
149 | * @sa
150 | * Supported [image formats](http://doc.qt.io/qt-5/qimagereader.html#supportedImageFormats) by QImageReader.
151 | */
152 | void setHeaderIconActive(const QString &icon);
153 | /**
154 | * @brief Set the header icon that will be displayed when the content pane is active
155 | * @param icon QPixmap that should be used
156 | *
157 | * @details
158 | * This is an overloaded method of setHeaderIconActive(QString), which allows
159 | * you directly pass a QPixmap object.
160 | */
161 | void setHeaderIconActive(const QPixmap &icon);
162 | /**
163 | * @brief Get the header icon for active state
164 | * @return QPixmap
165 | */
166 | QPixmap getHeaderIconActive();
167 | /**
168 | * @brief et the header icon that will be displayed when the content pane is inactive
169 | * @param icon Path to image
170 | *
171 | * @details
172 | * Set the header icon for inactive content pane state. Provide a path to a
173 | * supported image file. If the file does not exist or has an unsupported
174 | * format the icon will not be changed.
175 | * @sa
176 | * Supported [image formats](http://doc.qt.io/qt-5/qimagereader.html#supportedImageFormats) by QImageReader.
177 | */
178 | void setHeaderIconInActive(const QString &icon);
179 | /**
180 | * @brief setHeaderIconInActive
181 | * @param icon
182 | * @sa setHeaderIconInActive(QString)
183 | */
184 | void setHeaderIconInActive(const QPixmap &icon);
185 | /**
186 | * @brief Get the header for inactive state
187 | * @return QPixmap
188 | */
189 | QPixmap getHeaderIconInActive();
190 | /**
191 | * @brief Set header tooltip
192 | * @param tooltip String to show as tooltip
193 | *
194 | * @details
195 | * Set a string as header tooltip that will be shown when the mouse hovers
196 | * over the header area.
197 | */
198 | void setHeaderTooltip(const QString &tooltip);
199 | /**
200 | * @brief Get the header tooltip
201 | * @return Tooltip as QString
202 | *
203 | * @details
204 | * Get the header tooltip as QString.
205 | */
206 | QString getHeaderTooltip();
207 | /**
208 | * @brief Set a stylesheet for the header frame
209 | * @param stylesheet CSS Style Sheet as string
210 | *
211 | * @details
212 | * You can use [Cascading Style Sheets](http://doc.qt.io/qt-5/stylesheet.html) as supported by Qt to
213 | * style the header. This is the standard style sheet. You may also set a
214 | * style for mouse over with setHeaderHoverStylesheet().
215 | */
216 | void setHeaderStylesheet(QString stylesheet);
217 | /**
218 | * @brief Get the current header style sheet
219 | * @return CSS string
220 | *
221 | * @details
222 | * Get the css of the content pane header as QString.
223 | */
224 | QString getHeaderStylesheet();
225 | /**
226 | * @brief Set a stylesheet for the header frame when the mouse hovers over it
227 | * @param stylesheet CSS Style Sheet as string
228 | *
229 | * @details
230 | * Set a \p stylesheet for the header for a special effect when the mouse
231 | * hovers over it.
232 | * @sa
233 | * setHeaderStylesheet() for additional details.
234 | */
235 | void setHeaderHoverStylesheet(QString stylesheet);
236 | /**
237 | * @brief Get the mouse over header style sheet
238 | * @return CSS Style Sheet as string
239 | *
240 | * @details
241 | * Returns the mouse over header style sheet.
242 | */
243 | QString getHeaderHoverStylesheet();
244 | /**
245 | * @brief Set the header frame style
246 | * @param style
247 | *
248 | * @details
249 | * The style is the bitwise OR between a frame shape and a frame shadow style.
250 | * See the [Qt Documentation](http://doc.qt.io/qt-5/qframe.html#setFrameStyle) for additional details.
251 | */
252 | void setHeaderFrameStyle(int style);
253 | /**
254 | * @brief Get the header frame style
255 | * @return int
256 | *
257 | * @sa
258 | * setHeaderFrameStyle() for additional information.
259 | */
260 | int getHeaderFrameStyle();
261 | /**
262 | * @brief Set the container frame style
263 | * @param style
264 | *
265 | * @sa
266 | * setHeaderFrameStyle() for additional information
267 | */
268 | void setContainerFrameStyle(int style);
269 | /**
270 | * @brief Get the container frame style
271 | * @return int
272 | *
273 | * @sa
274 | * setHeaderFrameStyle() for additional information
275 | */
276 | int getContainerFrameStyle();
277 | /**
278 | * @brief Set the duration for the open and close animation
279 | * @param duration Duration in milliseconds
280 | *
281 | * @details
282 | * Set the duration of the QPropertyAnimation in milliseconds.
283 | */
284 | void setAnimationDuration(uint duration);
285 | /**
286 | * @brief Get the duration of the open, close animation.
287 | * @return Duration in milliseconds
288 | */
289 | uint getAnimationDuration();
290 |
291 | signals:
292 |
293 | /**
294 | * @brief Clicked signal is emitted when the header is clicked
295 | */
296 | void clicked();
297 | /**
298 | * @brief Signal will be emitted after the open animation finished
299 | */
300 | void isActive();
301 | /**
302 | * @brief Signal will be emitted after the close animation finished
303 | */
304 | void isInactive();
305 |
306 | public slots:
307 |
308 | /**
309 | * @brief Slot that is called when the header has been triggered
310 | * @param pos Currently unused
311 | *
312 | * @details
313 | * This slot is used to notify the ContentPane widget that the header
314 | * has been triggered. You can use this slot yourself to open or close the
315 | * ContentPane. Check the state of the pane before with getActive() as this
316 | * slot might not do what you intended to do (for example you want to close
317 | * the ContentPane. If it is already inactive and you call this slot without
318 | * checking the state it will be opened).
319 | */
320 | void headerTriggered(QPoint pos);
321 |
322 | private:
323 | // yeah we are friends. this is important to keep openContentPane and
324 | // closeContentPane private
325 | friend class QAccordion;
326 |
327 | ClickableFrame *header;
328 | QFrame *container;
329 | QFrame *content;
330 |
331 | QPixmap headerIconActive;
332 | QPixmap headerIconInActive;
333 |
334 | int headerFrameStyle;
335 | int contentPaneFrameStyle;
336 | int containerAnimationMaxHeight;
337 |
338 | bool active;
339 |
340 | std::unique_ptr openAnimation;
341 | std::unique_ptr closeAnimation;
342 |
343 | void initDefaults(QString header);
344 | void initHeaderFrame(QString header);
345 | void initContainerContentFrame();
346 | void initAnimations();
347 |
348 | private slots:
349 |
350 | /**
351 | * @brief Open the content pane
352 | *
353 | * @details
354 | * This will open the content pane if it is currently closed.
355 | * @warning
356 | * Currently there is no inbuild mechanism to close an already open
357 | * Content Pane when you open another one programmatically. Meaning you have
358 | * to take care of this yourself.
359 | */
360 | void openContentPane();
361 | /**
362 | * @brief Close the content pane
363 | *
364 | * @details
365 | * This will close the content pane if it is currently open.
366 | */
367 | void closeContentPane();
368 |
369 | protected:
370 | /**
371 | * @brief paintEvent Reimplement paintEvent to use stylesheets in derived Widgets
372 | * @param event
373 | */
374 | void paintEvent(ATTR_UNUSED QPaintEvent *event) override;
375 | };
376 |
377 | #endif // CONTENTPANE_HPP
378 |
--------------------------------------------------------------------------------
/include/qaccordion/qaccordion.hpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #ifndef QACCORDION_HPP
18 | #define QACCORDION_HPP
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | #include
31 | #include
32 | #include
33 | #include
34 |
35 | #include "qaccordion_config.hpp"
36 |
37 | #include "contentpane.hpp"
38 |
39 | class ContentPane;
40 |
41 | /**
42 | * @brief QAccordion base class
43 | *
44 | * @details
45 | * This class is the basis of the qAccordion widget. If you want to add a
46 | * accordion widget to your gui then you have to create an object of this class.
47 | *
48 | * Managing content panes is simpel:
49 | * * You can either add (addContentPane()) or insert (insertContentPane()) new ContentPanes.
50 | * * Use removeContentPane() to remove existing ContentPanes.
51 | * * moveContentPane() allows you to change the order of the ContentPanes.
52 | * * To change a ContentPane in place use swapContentPane()
53 | *
54 | * @note
55 | * Many of the mentioned functions are overloaded, provided for convenience.
56 | *
57 | * @details
58 | * The signal numberOfContentPanesChanged() is emitted whenever the number of
59 | * content panes changed.
60 | *
61 | * In case of an error you may get a more detailed error description with
62 | * getError().
63 | *
64 | * @warning
65 | * Currently Headers have to be unique
66 | *
67 | */
68 | class QAccordion : public QWidget
69 | {
70 | Q_OBJECT
71 | public:
72 | /**
73 | * @brief QAccordion constructor
74 | * @param parent Optionally provide a parent widget
75 | */
76 | explicit QAccordion(QWidget *parent = nullptr);
77 |
78 | /**
79 | * @brief Returns the number of content panes
80 | * @return int
81 | */
82 | int numberOfContentPanes() const;
83 |
84 | /**
85 | * @brief Add a new content Pane
86 | * @param header Header of the content pane
87 | * @return Content pane index
88 | *
89 | * @details
90 | * Use this method to add a new content pane with the Header header.
91 | * The method will return the index of the new content pane, or -1 if
92 | * the pane was not added because \p header already exists.
93 | */
94 | int addContentPane(QString header);
95 | /**
96 | * @brief Add a new content Pane
97 | * @param header Header of the content pane
98 | * @param contentFrame The content of the pane
99 | * @return Content pane index
100 | *
101 | * @details
102 | * This is an overloaded method of addContentPane(QString), that
103 | * allows you to provide your own content frame.
104 | * @warning
105 | * Headers have to be unique
106 | */
107 | int addContentPane(QString header, QFrame *contentFrame);
108 | /**
109 | * @brief Add content pane
110 | * @param cpane New content pane to add
111 | * @return Content pane index
112 | *
113 | * @details
114 | * This is an overloaded method of addContentPane(QString), that
115 | * allows you to provide your own content pane.
116 | */
117 | int addContentPane(ContentPane *cpane);
118 | /**
119 | * @brief Insert content pane
120 | * @param index Index of the content pane
121 | * @param header Header of the content pane
122 | * @return bool True if insert was successfull
123 | *
124 | * @details
125 | * You can use this method to insert a new content pane at given index with
126 | * \p header defining the Header. An empty content pane will be created that
127 | * you can get with getContentPane().
128 | *
129 | * Returns true if the insert was successfull.
130 | */
131 | bool insertContentPane(uint index, QString header);
132 | /**
133 | * @brief Insert content pane
134 | * @param index Index of the content pane
135 | * @param header Header of the content pane
136 | * @param contentFrame Content frame of the content pane
137 | * @return bool True if insert was successfull
138 | *
139 | * @details
140 | * This is an overloaded method of insertContentPane(uint, QString).
141 | * Use this method when you already created a content frame that you want to
142 | * insert.
143 | * @warning
144 | * Headers have to be unique
145 | */
146 | bool insertContentPane(uint index, QString header, QFrame *contentFrame);
147 | /**
148 | * @brief Insert content pane
149 | * @param index Index of the content pane
150 | * @param cpane Content Pane to insert
151 | * @return bool True if insert was successfull
152 | *
153 | * @details
154 | * This is an overloaded method of insertContentPane(uint, QString).
155 | * Use this method when you already created a content pane that you want to
156 | * insert.
157 | */
158 | bool insertContentPane(uint index, ContentPane *cpane);
159 |
160 | /**
161 | * @brief Swap the content pane
162 | * @param index Index of the content pane to swap
163 | * @param cpane New content pane
164 | * @return bool True if swap was successfull
165 | *
166 | * @details
167 | * With this method you can replace an existing content pane at \p index with
168 | * a new one \p cpane.
169 | *
170 | * Returns true if the swap was successfull.
171 | *
172 | * The old content pane will be __deleted__.
173 | */
174 | bool swapContentPane(uint index, ContentPane *cpane);
175 |
176 | /**
177 | * @brief Remove a content pane
178 | * @param deleteObject Delete the object and free memory
179 | * @param index Index of the content pane
180 | * @return bool
181 | *
182 | * @details
183 | * Remove a content pane at \p index. If \p deleteObject is \p true the
184 | * object will be deleted. Otherwise it is up to the user to
185 | * free the allocated memory.
186 | *
187 | * Returns true if the pane was removed and false otherwise.
188 | */
189 | bool removeContentPane(bool deleteObject, uint index);
190 | /**
191 | * @brief Remove a content pane
192 | * @param deleteObject Delete the object and free memory
193 | * @param header Header of the content pane
194 | * @return bool
195 | *
196 | * @details
197 | * This is an overloaded method of removeContentPane(bool, uint).
198 | */
199 | bool removeContentPane(bool deleteObject, QString header);
200 | /**
201 | * @brief Remove a content pane
202 | * @param deleteObject Delete the object and free memory
203 | * @param contentframe Content frame of the content pane
204 | * @return bool
205 | *
206 | * @details
207 | * This is an overloaded method of removeContentPane(bool, uint).
208 | */
209 | bool removeContentPane(bool deleteObject, QFrame *contentframe);
210 | /**
211 | * @brief Remove a content pane
212 | * @param deleteObject Delete the object and free memory
213 | * @param contentPane The content pane to remove
214 | * @return bool
215 | *
216 | * @details
217 | * This is an overloaded method of removeContentPane(bool, uint).
218 | */
219 | bool removeContentPane(bool deleteObject, ContentPane *contentPane);
220 |
221 | /**
222 | * @brief Move content pane
223 | * @param currentIndex The current index of the content pane.
224 | * @param newIndex The new index of the current pane
225 | * @return bool
226 | *
227 | * @details
228 | * Moves a content from \p currentIndex to \p newIndex. Returns true if the
229 | * content pane was moved, false otherwise.
230 | */
231 | bool moveContentPane(uint currentIndex, uint newIndex);
232 |
233 | /**
234 | * @brief Get content pane
235 | * @param index Index of the content pane
236 | * @return QFrame* or nullptr
237 | *
238 | * @details
239 | * Get a content pane (QFrame*) with \p index. This method will return a
240 | * __nullptr__ if the content pane does not exist.
241 | */
242 | ContentPane *getContentPane(uint index);
243 |
244 | /**
245 | * @brief Get the index of a content pane
246 | * @param header Header of the Content Pane
247 | * @return Index of the content pane
248 | *
249 | * @details
250 | * Get the index of a ContentPane with \p header. This method will return
251 | * -1 if a ContentPane with this header does not exist.
252 | */
253 | int getContentPaneIndex(QString header) const;
254 | /**
255 | * @brief Get the index of a content pane
256 | * @param contentFrame Content Frame
257 | * @return Index of the content pane
258 | *
259 | * @details
260 | * This is an overloaded function of getContentPaneIndex(QString)
261 | */
262 | int getContentPaneIndex(QFrame *contentFrame) const;
263 | /**
264 | * @brief Get the index of a content pane
265 | * @param contentPane ContentPane*
266 | * @return Index of the content pane
267 | *
268 | * @details
269 | * This is an overloaded function of getContentPaneIndex(QString)
270 | */
271 | int getContentPaneIndex(ContentPane *contentPane) const;
272 |
273 | /**
274 | * @brief Get the index of the active ContentPane
275 | * @return Vector with indexes of all active ContentPanes
276 | *
277 | * @details
278 | * This method will fill a vector with the index of all active ContentPanes.
279 | * The vector will be empty if no ContentPane is active
280 | */
281 | void getActiveContentPaneIndex(std::vector &indexVector) const;
282 |
283 | /**
284 | * @brief Get the number of content panes
285 | * @return Number of content panes
286 | */
287 | int getNumberOfContentPanes() const;
288 |
289 | /**
290 | * @brief Allow multiple ContentPane to be open
291 | * @param status
292 | *
293 | * @details
294 | * This option allows you to open several ContentPane at the same time.
295 | * @note
296 | * Default value for this option is \p false.
297 | */
298 | void setMultiActive(bool status);
299 | /**
300 | * @brief Check status of multiActive
301 | * @return bool
302 | *
303 | * @sa
304 | * setMultiActive()
305 | */
306 | bool getMultiActive() const;
307 |
308 | /**
309 | * @brief If collapsible is true you can close all ContentPanes
310 | * @param status
311 | *
312 | * @details
313 | * With the collapsible option you can control if one content pane has to be
314 | * open and can't be closed.
315 | */
316 | void setCollapsible(bool status);
317 | /**
318 | * @brief Get collapsible status
319 | * @return bool
320 | * @sa
321 | * setCollapsible()
322 | */
323 | bool getCollapsible() const;
324 |
325 | /**
326 | * @brief Get error string
327 | * @return Error string
328 | *
329 | * @details
330 | * Call this method after a function call failed for a detailed error
331 | * description.
332 | */
333 | QString getError();
334 |
335 | signals:
336 | /**
337 | * @brief Signals the new number of content panes
338 | * @param number Number of content panes
339 | *
340 | * @details
341 | * Signal will be emitted if the number of content panes changes
342 | */
343 | void numberOfContentPanesChanged(int number);
344 |
345 | public slots:
346 |
347 | private:
348 | std::vector contentPanes;
349 |
350 | QSpacerItem *spacer;
351 |
352 | QString errorString;
353 |
354 | bool multiActive;
355 | bool collapsible;
356 |
357 | int internalAddContentPane(QString header, QFrame *cframe = nullptr,
358 | ContentPane *cpane = nullptr);
359 | bool internalInsertContentPane(uint index, QString header,
360 | QFrame *contentFrame = nullptr,
361 | ContentPane *cpane = nullptr);
362 | bool internalRemoveContentPane(bool deleteOject, int index = -1,
363 | QString name = "",
364 | QFrame *contentFrame = nullptr,
365 | ContentPane *cpane = nullptr);
366 | int findContentPaneIndex(QString name = "", QFrame *cframe = nullptr,
367 | ContentPane *cpane = nullptr) const;
368 |
369 | bool checkIndexError(uint index, bool sizeIndexAllowed,
370 | const QString &errMessage);
371 | void handleClickedSignal(ContentPane *cpane);
372 |
373 | private slots:
374 | void numberOfPanesChanged(int number);
375 |
376 | protected:
377 | /**
378 | * @brief paintEvent Reimplement paintEvent to use stylesheets in derived Widgets
379 | * @param event
380 | */
381 | void paintEvent(ATTR_UNUSED QPaintEvent *event) override;
382 | };
383 |
384 | #endif // QACCORDION_HPP
385 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Find includes in corresponding build directories
2 | set(CMAKE_INCLUDE_CURRENT_DIR ON)
3 | # Instruct CMake to run moc automatically when needed.
4 | set(CMAKE_AUTOMOC ON)
5 |
6 | # Find the QtWidgets library. This has dependencies on QtGui and QtCore!
7 | find_package(Qt5Widgets 5.2 REQUIRED)
8 | message(STATUS "Found Qt version ${Qt5Widgets_VERSION_STRING}")
9 |
10 | if(QACCORDION_EXTERNAL)
11 | set(base_path ${CMAKE_SOURCE_DIR}/external/qaccordion/)
12 | message(STATUS
13 | "Using qAccordion external config with base path: ${base_path}")
14 | # provide the current build path to parent scope. add this to include
15 | # directories in parent projects
16 | set(EXTERNAL_INCLUDE
17 | "${CMAKE_CURRENT_BINARY_DIR}"
18 | PARENT_SCOPE
19 | )
20 | else()
21 | set(base_path ${CMAKE_SOURCE_DIR})
22 | endif()
23 |
24 | # configure a header file to pass some of the CMake settings
25 | # to the source code
26 | configure_file(
27 | ${base_path}/include/qaccordion/config.h.in
28 | ${CMAKE_CURRENT_BINARY_DIR}/qaccordion_config.hpp
29 | )
30 |
31 | set(QACCORDION_SOURCE
32 | ${CMAKE_CURRENT_SOURCE_DIR}/clickableframe.cpp
33 | ${CMAKE_CURRENT_SOURCE_DIR}/contentpane.cpp
34 | ${CMAKE_CURRENT_SOURCE_DIR}/qaccordion.cpp
35 | )
36 |
37 | set(QACCORDION_ICON_RESOURCE "${base_path}/icons/qaccordionicons.qrc")
38 | # add resource files so they can be compiled into the binary
39 | qt5_add_resources(ICON_RESOURCE_ADDED ${QACCORDION_ICON_RESOURCE})
40 |
41 | # we are building a shared library
42 | add_library(qaccordion SHARED
43 | ${QACCORDION_HEADER}
44 | ${QACCORDION_SOURCE}
45 | ${ICON_RESOURCE_ADDED})
46 |
47 | target_link_libraries(qaccordion Qt5::Widgets)
48 |
49 | set_property(TARGET qaccordion PROPERTY CXX_STANDARD_REQUIRED ON)
50 | set_property(TARGET qaccordion PROPERTY CXX_STANDARD 14)
51 |
52 | target_include_directories(qaccordion
53 | PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
54 | PUBLIC ${base_path}/include
55 | )
56 |
57 | if(QACCORDION_BUILD_DEMO)
58 | # generate ui_*.h files
59 | qt5_wrap_ui(accordion_demo_FORMS ${DEMO_UI})
60 | find_package(Qt5Network REQUIRED)
61 | add_executable(accordion_demo
62 | ${DEMO_HEADER} ${DEMO_SOURCE} ${accordion_demo_FORMS})
63 | target_link_libraries(accordion_demo qaccordion Qt5::Widgets Qt5::Network)
64 | set_property(TARGET accordion_demo PROPERTY CXX_STANDARD_REQUIRED ON)
65 | set_property(TARGET accordion_demo PROPERTY CXX_STANDARD 14)
66 | endif()
67 |
68 | install(TARGETS qaccordion DESTINATION lib)
69 | install(FILES ${base_path}/include/qaccordion/clickableframe.hpp
70 | DESTINATION include/qaccordion)
71 | install(FILES ${base_path}/include/qaccordion/contentpane.hpp
72 | DESTINATION include/qaccordion)
73 | install(FILES ${base_path}/include/qaccordion/qaccordion.hpp
74 | DESTINATION include/qaccordion)
75 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qaccordion_config.hpp
76 | DESTINATION include/qaccordion)
77 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qaccordion_export.hpp
78 | DESTINATION include/qaccordion)
79 |
80 |
--------------------------------------------------------------------------------
/src/clickableframe.cpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #include "qaccordion/clickableframe.hpp"
18 |
19 | ClickableFrame::ClickableFrame(QString header, QWidget *parent,
20 | Qt::WindowFlags f)
21 | : iconLabel(nullptr),
22 | nameLabel(nullptr),
23 | header(std::move(header)),
24 | QFrame(parent, f)
25 | {
26 | this->setAttribute(Qt::WA_Hover, true);
27 | this->headerTrigger = TRIGGER::SINGLECLICK;
28 | this->setCursor(Qt::PointingHandCursor);
29 | QColor background = this->palette().color(QPalette::ColorRole::Background);
30 | QColor lighter = background.lighter(110);
31 | this->normalStylesheet = "";
32 | this->hoverStylesheet = "background-color: " + lighter.name() + ";";
33 | this->initFrame();
34 | }
35 |
36 | void ClickableFrame::setTrigger(ClickableFrame::TRIGGER tr)
37 | {
38 | this->headerTrigger = tr;
39 | if (tr != TRIGGER::NONE) {
40 | this->setCursor(Qt::PointingHandCursor);
41 | } else {
42 | this->setCursor(Qt::ForbiddenCursor);
43 | }
44 | }
45 |
46 | ClickableFrame::TRIGGER ClickableFrame::getTrigger()
47 | {
48 | return this->headerTrigger;
49 | }
50 |
51 | void ClickableFrame::setHeader(QString header)
52 | {
53 | this->header = std::move(header);
54 | this->nameLabel->setText(this->header);
55 | }
56 |
57 | QString ClickableFrame::getHeader() { return this->header; }
58 |
59 | void ClickableFrame::setIcon(const QPixmap &icon)
60 | {
61 | this->iconLabel->setPixmap(icon);
62 | }
63 |
64 | //void ClickableFrame::setIconPosition(ClickableFrame::ICON_POSITION pos) {}
65 |
66 | void ClickableFrame::setNormalStylesheet(QString stylesheet)
67 | {
68 | this->normalStylesheet = std::move(stylesheet);
69 | this->setStyleSheet(this->normalStylesheet);
70 | }
71 |
72 | QString ClickableFrame::getNormalStylesheet() { return this->normalStylesheet; }
73 |
74 | void ClickableFrame::setHoverStylesheet(QString stylesheet)
75 | {
76 | this->hoverStylesheet = std::move(stylesheet);
77 | }
78 |
79 | QString ClickableFrame::getHoverStylesheet() { return this->hoverStylesheet; }
80 |
81 | void ClickableFrame::initFrame()
82 | {
83 | this->setSizePolicy(QSizePolicy::Policy::Preferred,
84 | QSizePolicy::Policy::Fixed);
85 | this->setLayout(new QHBoxLayout());
86 |
87 | this->iconLabel = new QLabel();
88 | this->iconLabel->setPixmap(QPixmap(this->CARRET_ICON_CLOSED));
89 | this->layout()->addWidget(this->iconLabel);
90 |
91 | this->nameLabel = new QLabel();
92 | nameLabel->setText(this->header);
93 | this->layout()->addWidget(nameLabel);
94 |
95 | dynamic_cast(this->layout())->addStretch();
96 |
97 | this->setStyleSheet(this->normalStylesheet);
98 | }
99 |
100 | void ClickableFrame::mousePressEvent(QMouseEvent *event)
101 | {
102 | if (this->headerTrigger == TRIGGER::SINGLECLICK) {
103 | emit this->triggered(event->pos());
104 | event->accept();
105 | } else {
106 | event->ignore();
107 | }
108 | }
109 |
110 | void ClickableFrame::mouseDoubleClickEvent(QMouseEvent *event)
111 | {
112 | if (this->headerTrigger == TRIGGER::DOUBLECLICK) {
113 | emit this->triggered(event->pos());
114 | event->accept();
115 | } else {
116 | event->ignore();
117 | }
118 | }
119 |
120 | // TODO: No Stylesheet change when TRIGGER::NONE?
121 | void ClickableFrame::enterEvent(ATTR_UNUSED QEvent *event)
122 | {
123 | if (this->headerTrigger != TRIGGER::NONE) {
124 | this->setStyleSheet(this->hoverStylesheet);
125 | }
126 | }
127 |
128 | void ClickableFrame::leaveEvent(ATTR_UNUSED QEvent *event)
129 | {
130 | if (this->headerTrigger != TRIGGER::NONE) {
131 | this->setStyleSheet(this->normalStylesheet);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/contentpane.cpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #include "qaccordion/contentpane.hpp"
18 |
19 | ContentPane::ContentPane(QString header, QWidget *parent) : QWidget(parent)
20 | {
21 | this->content = nullptr;
22 |
23 | this->initDefaults(std::move(header));
24 | }
25 |
26 | ContentPane::ContentPane(QString header, QFrame *content, QWidget *parent)
27 | : content(content), QWidget(parent)
28 | {
29 | this->initDefaults(std::move(header));
30 | }
31 |
32 | bool ContentPane::getActive() const { return this->active; }
33 |
34 | QFrame *ContentPane::getContentFrame() { return this->content; }
35 |
36 | void ContentPane::setContentFrame(QFrame *content)
37 | {
38 | this->container->layout()->removeWidget(this->content);
39 | if (this->content != nullptr) {
40 | delete (this->content);
41 | }
42 | this->content = content;
43 | dynamic_cast(this->container->layout())
44 | ->insertWidget(0, this->content);
45 | }
46 |
47 | int ContentPane::getMaximumHeight() { return this->container->maximumHeight(); }
48 |
49 | void ContentPane::setMaximumHeight(int maxHeight)
50 | {
51 | this->containerAnimationMaxHeight = maxHeight;
52 |
53 | if (this->getActive()) {
54 | this->container->setMaximumHeight(this->containerAnimationMaxHeight);
55 | }
56 | this->openAnimation->setEndValue(this->containerAnimationMaxHeight);
57 | this->closeAnimation->setStartValue(this->containerAnimationMaxHeight);
58 | }
59 |
60 | void ContentPane::setTrigger(ClickableFrame::TRIGGER tr)
61 | {
62 | this->header->setTrigger(tr);
63 | }
64 |
65 | ClickableFrame::TRIGGER ContentPane::getTrigger()
66 | {
67 | return this->header->getTrigger();
68 | }
69 |
70 | void ContentPane::setHeader(QString header)
71 | {
72 | this->header->setHeader(std::move(header));
73 | }
74 |
75 | QString ContentPane::getHeader() { return this->header->getHeader(); }
76 |
77 | void ContentPane::setHeaderIconActive(const QString &icon)
78 | {
79 | QPixmap pic(icon);
80 | if (!pic.isNull()) {
81 | this->headerIconActive = pic;
82 | if (this->getActive()) {
83 | this->header->setIcon(this->headerIconActive);
84 | }
85 | }
86 | }
87 |
88 | void ContentPane::setHeaderIconActive(const QPixmap &icon)
89 | {
90 | if (!icon.isNull()) {
91 | this->headerIconActive = icon;
92 | if (this->getActive()) {
93 | this->header->setIcon(this->headerIconActive);
94 | }
95 | }
96 | }
97 |
98 | QPixmap ContentPane::getHeaderIconActive() { return this->headerIconActive; }
99 |
100 | void ContentPane::setHeaderIconInActive(const QString &icon)
101 | {
102 | QPixmap pic(icon);
103 | if (!pic.isNull()) {
104 | this->headerIconInActive = pic;
105 | if (!this->getActive()) {
106 | this->header->setIcon(this->headerIconInActive);
107 | }
108 | }
109 | }
110 |
111 | void ContentPane::setHeaderIconInActive(const QPixmap &icon)
112 | {
113 | if (!icon.isNull()) {
114 | this->headerIconInActive = icon;
115 | if (!this->getActive()) {
116 | this->header->setIcon(this->headerIconInActive);
117 | }
118 | }
119 | }
120 |
121 | QPixmap ContentPane::getHeaderIconInActive() { return this->headerIconInActive; }
122 |
123 | void ContentPane::setHeaderTooltip(const QString &tooltip)
124 | {
125 | this->header->setToolTip(tooltip);
126 | }
127 |
128 | QString ContentPane::getHeaderTooltip() { return this->header->toolTip(); }
129 |
130 | void ContentPane::setHeaderStylesheet(QString stylesheet)
131 | {
132 | this->header->setNormalStylesheet(std::move(stylesheet));
133 | }
134 |
135 | QString ContentPane::getHeaderStylesheet()
136 | {
137 | return this->header->getNormalStylesheet();
138 | }
139 |
140 | void ContentPane::setHeaderHoverStylesheet(QString stylesheet)
141 | {
142 | this->header->setHoverStylesheet(std::move(stylesheet));
143 | }
144 |
145 | QString ContentPane::getHeaderHoverStylesheet()
146 | {
147 | return this->header->getHoverStylesheet();
148 | }
149 |
150 | void ContentPane::setHeaderFrameStyle(int style)
151 | {
152 | this->header->setFrameStyle(style);
153 | }
154 |
155 | int ContentPane::getHeaderFrameStyle() { return this->header->frameStyle(); }
156 |
157 | void ContentPane::setContainerFrameStyle(int style)
158 | {
159 | this->container->setFrameStyle(style);
160 | }
161 |
162 | int ContentPane::getContainerFrameStyle()
163 | {
164 | return this->container->frameStyle();
165 | }
166 |
167 | void ContentPane::openContentPane()
168 | {
169 | if (this->getActive()) {
170 | return;
171 | }
172 | this->openAnimation->start();
173 | this->header->setIcon(this->headerIconActive);
174 | this->active = true;
175 | }
176 |
177 | void ContentPane::closeContentPane()
178 | {
179 | if (!this->getActive()) {
180 | return;
181 | }
182 | this->closeAnimation->start();
183 | this->header->setIcon(this->headerIconInActive);
184 | this->active = false;
185 | }
186 |
187 | void ContentPane::initDefaults(QString header)
188 | {
189 | this->active = false;
190 |
191 | this->headerFrameStyle = QFrame::Shape::StyledPanel | QFrame::Shadow::Raised;
192 | this->contentPaneFrameStyle =
193 | QFrame::Shape::StyledPanel | QFrame::Shadow::Plain;
194 | this->containerAnimationMaxHeight = 150;
195 | // TODO: Why do I need to set the vertial policy to Maximum? from the api
196 | // documentation Minimum would make more sens :/
197 | this->setSizePolicy(QSizePolicy::Policy::Preferred,
198 | QSizePolicy::Policy::Maximum);
199 |
200 | this->setLayout(new QVBoxLayout());
201 | this->layout()->setSpacing(1);
202 | this->layout()->setContentsMargins(QMargins());
203 |
204 | this->initHeaderFrame(std::move(header));
205 | this->initContainerContentFrame();
206 | this->initAnimations();
207 | }
208 |
209 | void ContentPane::initHeaderFrame(QString header)
210 | {
211 | this->header = new ClickableFrame(std::move(header));
212 | this->header->setFrameStyle(this->headerFrameStyle);
213 | // init the icons
214 | this->setHeaderIconActive(this->header->CARRET_ICON_OPENED);
215 | this->setHeaderIconInActive(this->header->CARRET_ICON_CLOSED);
216 | this->layout()->addWidget(this->header);
217 |
218 | QObject::connect(this->header, &ClickableFrame::triggered, this,
219 | &ContentPane::headerTriggered);
220 | }
221 |
222 | void ContentPane::initContainerContentFrame()
223 | {
224 | this->container = new QFrame();
225 | this->container->setLayout(new QVBoxLayout());
226 | this->container->setFrameStyle(this->contentPaneFrameStyle);
227 | this->container->setMaximumHeight(0);
228 | this->container->setSizePolicy(QSizePolicy::Policy::Preferred,
229 | QSizePolicy::Policy::Preferred);
230 | this->layout()->addWidget(this->container);
231 |
232 | if (this->content == nullptr) {
233 | this->content = new QFrame();
234 | }
235 |
236 | this->container->layout()->addWidget(this->content);
237 | this->container->layout()->setSpacing(0);
238 | this->container->layout()->setContentsMargins(QMargins());
239 | }
240 |
241 | void ContentPane::initAnimations()
242 | {
243 | this->openAnimation = std::make_unique();
244 | this->closeAnimation = std::make_unique();
245 | // TODO: Currently these animations only animate maximumHeight. This leads to
246 | // different behaviour depending on whether the Accordion Widget is placed
247 | // inside a QScollWidget or not. Maybe we also need to animate minimumHeight
248 | // as well to get the same effect.
249 | // TODO: Lots of boilerplate code here
250 | this->openAnimation->setTargetObject(this->container);
251 | this->openAnimation->setPropertyName("maximumHeight");
252 | this->closeAnimation->setTargetObject(this->container);
253 | this->closeAnimation->setPropertyName("maximumHeight");
254 |
255 | this->openAnimation->setDuration(300);
256 | this->closeAnimation->setDuration(300);
257 | this->openAnimation->setStartValue(0);
258 | this->closeAnimation->setStartValue(this->containerAnimationMaxHeight);
259 | this->openAnimation->setEndValue(this->containerAnimationMaxHeight);
260 | this->closeAnimation->setEndValue(0);
261 | this->openAnimation->setEasingCurve(
262 | QEasingCurve(QEasingCurve::Type::Linear));
263 | this->closeAnimation->setEasingCurve(
264 | QEasingCurve(QEasingCurve::Type::Linear));
265 | }
266 |
267 | void ContentPane::headerTriggered(ATTR_UNUSED QPoint pos)
268 | {
269 | emit this->clicked();
270 | }
271 |
272 | void ContentPane::paintEvent(ATTR_UNUSED QPaintEvent *event)
273 | {
274 | QStyleOption o;
275 | o.initFrom(this);
276 | QPainter p(this);
277 | style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
278 | }
279 |
--------------------------------------------------------------------------------
/src/qaccordion.cpp:
--------------------------------------------------------------------------------
1 | // This file is part of qAccordion. An Accordion widget for Qt
2 | // Copyright © 2015, 2017, 2020 Christian Rapp <0x2a at posteo dot org>
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | #include "qaccordion/qaccordion.hpp"
18 |
19 | QAccordion::QAccordion(QWidget *parent) : QWidget(parent)
20 | {
21 | // make sure our resource file gets initialized
22 | Q_INIT_RESOURCE(qaccordionicons);
23 |
24 | this->multiActive = false;
25 | this->collapsible = true;
26 |
27 | // set our basic layout
28 | this->setLayout(new QVBoxLayout());
29 |
30 | // add a stretch to the end so all content panes are at the top
31 | dynamic_cast(this->layout())->addStretch();
32 | this->layout()->setSpacing(1);
33 | this->layout()->setContentsMargins(QMargins());
34 | // TODO: Do we need to keep a pointer to the spacer?
35 | this->spacer = dynamic_cast(this->layout()->itemAt(0));
36 |
37 | // seome things we want to do if the number of panes change
38 | QObject::connect(this, &QAccordion::numberOfContentPanesChanged, this,
39 | &QAccordion::numberOfPanesChanged);
40 | }
41 |
42 | int QAccordion::numberOfContentPanes() const { return this->contentPanes.size(); }
43 |
44 | int QAccordion::addContentPane(QString header)
45 | {
46 | return this->internalAddContentPane(std::move(header));
47 | }
48 |
49 | int QAccordion::addContentPane(QString header, QFrame *contentFrame)
50 | {
51 | return this->internalAddContentPane(std::move(header), contentFrame);
52 | }
53 |
54 | int QAccordion::addContentPane(ContentPane *cpane)
55 | {
56 | return this->internalAddContentPane("", nullptr, cpane);
57 | }
58 |
59 | bool QAccordion::insertContentPane(uint index, QString header)
60 | {
61 | return this->internalInsertContentPane(index, std::move(header));
62 | }
63 |
64 | bool QAccordion::insertContentPane(uint index, QString header,
65 | QFrame *contentFrame)
66 | {
67 | return this->internalInsertContentPane(index, std::move(header),
68 | contentFrame);
69 | }
70 |
71 | bool QAccordion::insertContentPane(uint index, ContentPane *cpane)
72 | {
73 | return this->internalInsertContentPane(index, "", nullptr, cpane);
74 | }
75 |
76 | bool QAccordion::swapContentPane(uint index, ContentPane *cpane)
77 | {
78 | if (this->checkIndexError(index, false,
79 | "Can not swap content pane at index " +
80 | QString::number(index) +
81 | ". Index out of range.")) {
82 | return false;
83 | }
84 |
85 | if (this->findContentPaneIndex("", nullptr, cpane) != -1) {
86 | this->errorString =
87 | "Can not swap content pane as new pane is already "
88 | "managed by accordion widget";
89 | return false;
90 | }
91 |
92 | // remove the old content pane from the accordion layout
93 | dynamic_cast(this->layout())
94 | ->removeWidget(this->contentPanes.at(index));
95 | delete this->contentPanes.at(index);
96 |
97 | // add the new content pane to the appropriate vector
98 | this->contentPanes.at(index) = cpane;
99 |
100 | // add the new content pane to the layout
101 | dynamic_cast(this->layout())
102 | ->insertWidget(index, this->contentPanes.at(index));
103 |
104 | return true;
105 | }
106 |
107 | bool QAccordion::removeContentPane(bool deleteObject, uint index)
108 | {
109 | return this->internalRemoveContentPane(deleteObject, index);
110 | }
111 |
112 | bool QAccordion::removeContentPane(bool deleteObject, QString header)
113 | {
114 | return this->internalRemoveContentPane(deleteObject, -1, std::move(header));
115 | }
116 |
117 | bool QAccordion::removeContentPane(bool deleteObject, QFrame *contentframe)
118 | {
119 | return this->internalRemoveContentPane(deleteObject, -1, "", contentframe);
120 | }
121 |
122 | bool QAccordion::removeContentPane(bool deleteObject, ContentPane *contentPane)
123 | {
124 | return this->internalRemoveContentPane(deleteObject, -1, "", nullptr,
125 | contentPane);
126 | }
127 |
128 | bool QAccordion::moveContentPane(uint currentIndex, uint newIndex)
129 | {
130 | if (this->checkIndexError(currentIndex, false,
131 | "Can not move from " +
132 | QString::number(currentIndex) +
133 | ". Index out of range.") ||
134 | this->checkIndexError(newIndex, false,
135 | "Can not move to " + QString::number(newIndex) +
136 | ". Index out of range.")) {
137 | return false;
138 | }
139 |
140 | auto *layout = dynamic_cast(this->layout());
141 | // get the pane we want to move
142 | ContentPane *movePane = this->contentPanes.at(currentIndex);
143 |
144 | // remove the widget from the layout and insert it at the new position
145 | layout->removeWidget(movePane);
146 | layout->insertWidget(newIndex, movePane);
147 |
148 | // keep our vector synchronized
149 | this->contentPanes.erase(this->contentPanes.begin() + currentIndex);
150 | this->contentPanes.insert(this->contentPanes.begin() + newIndex, movePane);
151 |
152 | return true;
153 | }
154 |
155 | ContentPane *QAccordion::getContentPane(uint index)
156 | {
157 | try {
158 | return this->contentPanes.at(index);
159 | } catch (const std::out_of_range &ex) {
160 | qDebug() << Q_FUNC_INFO << "Can not return Content Pane: " << ex.what();
161 | this->errorString = "Can not return Content Pane: " + QString(ex.what());
162 | return nullptr;
163 | }
164 | }
165 |
166 | int QAccordion::getContentPaneIndex(QString header) const
167 | {
168 | return this->findContentPaneIndex(std::move(header));
169 | }
170 |
171 | int QAccordion::getContentPaneIndex(QFrame *contentFrame) const
172 | {
173 | return this->findContentPaneIndex("", contentFrame);
174 | }
175 |
176 | int QAccordion::getContentPaneIndex(ContentPane *contentPane) const
177 | {
178 | return this->findContentPaneIndex("", nullptr, contentPane);
179 | }
180 |
181 | void QAccordion::getActiveContentPaneIndex(std::vector &indexVector) const
182 | {
183 | // first of all make sure it is empty
184 | indexVector.clear();
185 | std::for_each(this->contentPanes.begin(), this->contentPanes.end(),
186 | [&indexVector, this](ContentPane *pane) {
187 | if (pane->getActive()) {
188 | indexVector.push_back(
189 | this->findContentPaneIndex("", nullptr, pane));
190 | }
191 | });
192 | }
193 |
194 | int QAccordion::getNumberOfContentPanes() const { return this->contentPanes.size(); }
195 |
196 | void QAccordion::setMultiActive(bool status) { this->multiActive = status; }
197 |
198 | bool QAccordion::getMultiActive() const { return this->multiActive; }
199 |
200 | void QAccordion::setCollapsible(bool status) { this->collapsible = status; }
201 |
202 | bool QAccordion::getCollapsible() const { return this->collapsible; }
203 |
204 | QString QAccordion::getError() { return this->errorString; }
205 |
206 | int QAccordion::internalAddContentPane(QString header, QFrame *cframe,
207 | ContentPane *cpane)
208 | {
209 | if (this->findContentPaneIndex(header, cframe, cpane) != -1) {
210 | this->errorString = "Can not add content pane as it already exists";
211 | return -1;
212 | }
213 |
214 | if (cpane == nullptr) {
215 | if (cframe != nullptr) {
216 | cpane = new ContentPane(std::move(header), cframe);
217 | } else {
218 | cpane = new ContentPane(std::move(header));
219 | }
220 | }
221 | dynamic_cast(this->layout())
222 | ->insertWidget(this->layout()->count() - 1, cpane);
223 | this->contentPanes.push_back(cpane);
224 |
225 | // manage the clicked signal in a lambda expression
226 | QObject::connect(cpane, &ContentPane::clicked,
227 | [this, cpane]() { this->handleClickedSignal(cpane); });
228 |
229 | emit numberOfContentPanesChanged(this->contentPanes.size());
230 |
231 | return static_cast(this->contentPanes.size() - 1);
232 | }
233 |
234 | bool QAccordion::internalInsertContentPane(uint index, QString header,
235 | QFrame *contentFrame,
236 | ContentPane *cpane)
237 | {
238 | if (this->checkIndexError(index, true,
239 | "Can not insert Content Pane at index " +
240 | QString::number(index) +
241 | ". Index out of range")) {
242 | return false;
243 | }
244 |
245 | if (this->findContentPaneIndex(header, contentFrame, cpane) != -1) {
246 | return false;
247 | }
248 |
249 | if (cpane == nullptr) {
250 | if (contentFrame != nullptr) {
251 | cpane = new ContentPane(std::move(header), contentFrame);
252 | } else {
253 | cpane = new ContentPane(std::move(header));
254 | }
255 | }
256 |
257 | dynamic_cast(this->layout())->insertWidget(index, cpane);
258 |
259 | this->contentPanes.insert(this->contentPanes.begin() + index, cpane);
260 |
261 | // manage the clicked signal in a lambda expression
262 | QObject::connect(cpane, &ContentPane::clicked,
263 | [this, cpane]() { this->handleClickedSignal(cpane); });
264 |
265 | emit numberOfContentPanesChanged(this->contentPanes.size());
266 |
267 | return true;
268 | }
269 |
270 | bool QAccordion::internalRemoveContentPane(bool deleteOject, int index,
271 | QString name, QFrame *contentFrame,
272 | ContentPane *cpane)
273 | {
274 | if (index != -1 &&
275 | this->checkIndexError(index, false,
276 | "Can not remove content pane at index " +
277 | QString::number(index) +
278 | ". Index out of range")) {
279 | return false;
280 | }
281 |
282 | if (index == -1) {
283 | index = this->findContentPaneIndex(std::move(name), contentFrame, cpane);
284 | if (index == -1) {
285 | this->errorString =
286 | "Can not remove content pane as it is not part "
287 | "of the accordion widget";
288 | return false;
289 | }
290 | }
291 |
292 | dynamic_cast(this->layout())
293 | ->removeWidget(this->contentPanes.at(index));
294 |
295 | // only delete the object if user wants to.
296 | if (deleteOject) {
297 | delete this->contentPanes.at(index);
298 | this->contentPanes.at(index) = nullptr;
299 | }
300 |
301 | this->contentPanes.erase(this->contentPanes.begin() + index);
302 |
303 | emit numberOfContentPanesChanged(this->contentPanes.size());
304 |
305 | return true;
306 | }
307 |
308 | int QAccordion::findContentPaneIndex(QString name, QFrame *cframe,
309 | ContentPane *cpane) const
310 | {
311 | // simple method that finds the index of a content by Header, content frame
312 | // or content pane.
313 | int index = -1;
314 | if (name != "") {
315 | auto result = std::find_if(
316 | this->contentPanes.begin(), this->contentPanes.end(),
317 | [&name](ContentPane *pane) { return pane->getHeader() == name; });
318 | if (result != std::end(this->contentPanes)) {
319 | // get the index by subtracting begin iterator from result
320 | // iterator
321 | // TODO: Is this cast really necessary?
322 | index = static_cast(result - this->contentPanes.begin());
323 | }
324 | }
325 | if (cframe != nullptr) {
326 | auto result =
327 | std::find_if(this->contentPanes.begin(), this->contentPanes.end(),
328 | [cframe](ContentPane *cpane) {
329 | return cpane->getContentFrame() == cframe;
330 | });
331 | if (result != std::end(this->contentPanes)) {
332 | index = static_cast(result - this->contentPanes.begin());
333 | }
334 | }
335 | if (cpane != nullptr) {
336 | auto result = std::find(this->contentPanes.begin(),
337 | this->contentPanes.end(), cpane);
338 | if (result != std::end(this->contentPanes)) {
339 | index = static_cast(result - this->contentPanes.begin());
340 | }
341 | }
342 | return index;
343 | }
344 |
345 | bool QAccordion::checkIndexError(uint index, bool sizeIndexAllowed,
346 | const QString &errMessage)
347 | {
348 | // sizeIndexAllowed is only used by inserting. If there is one pane you will
349 | // be able to insert a new one before and after.
350 | // FIXME: Actually there seem to be some bugs hidden here. User may now for
351 | // example delete index 0 even if there isn't any content pane. I think we
352 | // excluded checking 0 because of inserting.
353 | // Update, I removed the 0 exclusion in the second if statement. Really a
354 | // fix??
355 | if (sizeIndexAllowed) {
356 | if (index != 0 && index > this->contentPanes.size()) {
357 | qDebug() << Q_FUNC_INFO << errMessage;
358 | this->errorString = errMessage;
359 | return true;
360 | }
361 | } else {
362 | if (index >= this->contentPanes.size()) {
363 | qDebug() << Q_FUNC_INFO << errMessage;
364 | this->errorString = errMessage;
365 | return true;
366 | }
367 | }
368 | return false;
369 | }
370 |
371 | void QAccordion::handleClickedSignal(ContentPane *cpane)
372 | {
373 | // if the clicked content pane is open we simply close it and return
374 | if (cpane->getActive()) {
375 | // if collapsible and multiActive are false we are not allowed to close
376 | // this pane
377 | if (!this->collapsible && !this->multiActive) {
378 | return;
379 | }
380 | // when multiActive is true we have to check if there is any other open
381 | // cpane. if so we can close this one
382 | std::vector activePanes;
383 | if (!this->collapsible) {
384 | this->getActiveContentPaneIndex(activePanes);
385 | if (activePanes.size() == 1) {
386 | return; // only one active --> good bye :)
387 | }
388 | }
389 | cpane->closeContentPane();
390 | return;
391 | }
392 | // if it is not open we will open it and search our vector for other
393 | // panes that are already open.
394 | // TODO: Is it really necessary to search for more than one open cpane?
395 | if (!cpane->getActive()) {
396 | // check if multiActive is allowed
397 | if (!this->getMultiActive()) {
398 | std::for_each(this->contentPanes.begin(), this->contentPanes.end(),
399 | [](ContentPane *pane) {
400 | if (pane->getActive()) {
401 | pane->closeContentPane();
402 | }
403 | });
404 | }
405 | cpane->openContentPane();
406 | }
407 | }
408 |
409 | void QAccordion::numberOfPanesChanged(int number)
410 | {
411 | // automatically open contentpane if we have only one and collapsible is
412 | // false
413 | if (number == 1 && !this->collapsible) {
414 | this->contentPanes.at(0)->openContentPane();
415 | }
416 | }
417 |
418 | void QAccordion::paintEvent(ATTR_UNUSED QPaintEvent *event)
419 | {
420 | QStyleOption o;
421 | o.initFrom(this);
422 | QPainter p(this);
423 | style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
424 | }
425 |
--------------------------------------------------------------------------------