├── README.md ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── piemenu ├── QtPieItem ├── QtPieMenu ├── qtpieaction.cpp ├── qtpieaction.h ├── qtpieitem.cpp ├── qtpieitem.h ├── qtpiemenu.cpp ├── qtpiemenu.h ├── sectormenu.cpp └── sectormenu.h ├── screenshot ├── screenshot_1.png └── screenshot_2.png └── test-piemenu.pro /README.md: -------------------------------------------------------------------------------- 1 | Qt PieMenu Widgets 2 | ================== 3 | 4 | The qtpiemenu : [Qt5ExtLib](https://github.com/sintegrial/Qt5ExtLib) 5 | 6 | 7 | create Sector menu on Qt 5.11.x 8 | 9 | ### ScreenShot 10 | ![Main](https://github.com/mola/QtPieMenu/raw/master/screenshot/screenshot_1.png) 11 | ![Main](https://github.com/mola/QtPieMenu/raw/master/screenshot/screenshot_2.png) 12 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | #include "piemenu/QtPieMenu" 6 | #include "piemenu/sectormenu.h" 7 | 8 | MainWindow::MainWindow(QWidget *parent): 9 | QMainWindow(parent), 10 | ui(new Ui::MainWindow) 11 | { 12 | ui->setupUi(this); 13 | root = new SectorMenu("root", this); 14 | 15 | SectorMenu *child1 = new SectorMenu("Hammer", root); 16 | root->insertItem(child1); 17 | // root->setItemIcon(tr("Hammer"), 0); 18 | 19 | SectorMenu *child11 = new SectorMenu("Merge", child1); 20 | child1->insertItem(child11); 21 | child11->insertItem(tr("C"), this, SLOT(ignoreAction())); 22 | child11->insertItem(tr("B"), this, SLOT(ignoreAction())); 23 | child11->insertItem(tr("A"), this, SLOT(ignoreAction())); 24 | 25 | SectorMenu *child12 = new SectorMenu("Split", child1); 26 | child1->insertItem(child12); 27 | child12->insertItem(tr("A"), this, SLOT(ignoreAction())); 28 | child12->insertItem(tr("B"), this, SLOT(ignoreAction())); 29 | child12->insertItem(tr("C"), this, SLOT(ignoreAction())); 30 | 31 | root->insertItem("Left", this, SLOT(ignoreAction())); 32 | 33 | 34 | SectorMenu *child13 = new SectorMenu("Down", root); 35 | child13->insertItem(tr("Q1"), this, SLOT(ignoreAction())); 36 | child13->insertItem(tr("Q2"), this, SLOT(ignoreAction())); 37 | child13->insertItem(tr("Q3"), this, SLOT(ignoreAction())); 38 | root->insertItem(child13); 39 | 40 | root->insertItem("Right", this, SLOT(ignoreAction())); 41 | } 42 | 43 | MainWindow::~MainWindow() 44 | { 45 | delete ui; 46 | } 47 | 48 | void MainWindow::ignoreAction() 49 | { 50 | } 51 | 52 | void MainWindow::contextMenuEvent(QContextMenuEvent *event) 53 | { 54 | root->popup(event->globalPos()); 55 | } 56 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | class SectorMenu; 7 | namespace Ui 8 | { 9 | class MainWindow; 10 | } 11 | 12 | class MainWindow: public QMainWindow 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit MainWindow(QWidget *parent = nullptr); 18 | 19 | ~MainWindow(); 20 | 21 | private slots: 22 | void ignoreAction(); 23 | 24 | private: 25 | Ui::MainWindow *ui; 26 | SectorMenu *root; 27 | 28 | // QWidget interface 29 | 30 | protected: 31 | void contextMenuEvent(QContextMenuEvent *event); 32 | }; 33 | 34 | #endif // MAINWINDOW_H 35 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | MainWindow 3 | 4 | 5 | 6 | 0 7 | 0 8 | 400 9 | 300 10 | 11 | 12 | 13 | MainWindow 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /piemenu/QtPieItem: -------------------------------------------------------------------------------- 1 | #include "qtpieitem.h" 2 | -------------------------------------------------------------------------------- /piemenu/QtPieMenu: -------------------------------------------------------------------------------- 1 | #include "qtpiemenu.h" 2 | -------------------------------------------------------------------------------- /piemenu/qtpieaction.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | #include "qtpiemenu.h" 48 | #include "qtpieaction.h" 49 | 50 | /*! \internal 51 | 52 | Constructs a QtPieAction. The \a title argument is the human 53 | readable title of this action, such as "New", "Open" or "Select". 54 | 55 | The \a receiver is a QObject in which the signal or slot \a member 56 | will be called when this item is activated. 57 | */ 58 | QtPieAction::QtPieAction(const QString &text, 59 | QObject *receiver, const char *member) 60 | : QtPieItem(text) 61 | { 62 | connect(this, SIGNAL(activated()), receiver, member); 63 | } 64 | 65 | /*! \internal 66 | 67 | Constructs a QtPieAction. The \a title argument is the human 68 | readable text label of this action, such as "New", "Open" or "Select". 69 | The QIcon \a icons will be displayed together with the text, 70 | or by itself if \a title is empty. 71 | 72 | The \a receiver is a QObject in which the signal or slot \a member 73 | will be called when this item is activated. 74 | */ 75 | QtPieAction::QtPieAction(const QIcon &icons, const QString &text, 76 | QObject *receiver, const char *member) 77 | : QtPieItem(icons, text) 78 | { 79 | connect(this, SIGNAL(activated()), receiver, member); 80 | } 81 | 82 | /*! \internal 83 | 84 | Returns the type of QtPieItem, in this case always Action. 85 | */ 86 | int QtPieAction::type() const 87 | { 88 | return QtPieMenu::Action; 89 | } 90 | 91 | /*! \internal 92 | 93 | This function is called by the QtPieMenu parent when the item that 94 | holds this QtPieAction is activated. 95 | */ 96 | void QtPieAction::activate() 97 | { 98 | emit activated(); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /piemenu/qtpieaction.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | #ifndef QTPIEACTION_H 48 | #define QTPIEACTION_H 49 | 50 | #include "qtpieitem.h" 51 | 52 | #include 53 | #include 54 | #include 55 | 56 | class QtPieAction: public QtPieItem 57 | { 58 | Q_OBJECT 59 | 60 | public: 61 | QtPieAction(const QString &text, 62 | QObject *receiver, const char *member); 63 | 64 | QtPieAction(const QIcon &icon, const QString &text, 65 | QObject *receiver, const char *member); 66 | 67 | void activate(); 68 | 69 | Q_SIGNALS: 70 | 71 | void activated(); 72 | 73 | protected: 74 | int type() const; 75 | }; 76 | 77 | #endif // QTPIEACTION_H 78 | -------------------------------------------------------------------------------- /piemenu/qtpieitem.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | #include "qtpieitem.h" 48 | #include "qtpiemenu.h" 49 | 50 | #include 51 | #include 52 | 53 | /*! 54 | \class QtPieItem 55 | \brief The QtPieItem class is the base class for QtPieMenu. 56 | 57 | It has a very simple interface, providing the base implementation 58 | of functions common for QtPieMenu and QtPieAction. 59 | 60 | The text of an item is set with setText() and retrieved with 61 | text(). Similarly, setIcon() and icon() set and get the icon set 62 | associated with the item. 63 | 64 | QtPieMenu uses weight() to determine how much space an item should 65 | claim on a pie. The weights of the items are compared so that an 66 | item with a weight of 2 claims twice as much space as one of 67 | weight 1. Use setWeight() to set the weight of an item. 68 | 69 | type() returns the type of the item. This is used by QtPieMenu to 70 | determine wheter an item triggers a simple action or whether it 71 | pops up a submenu. 72 | */ 73 | 74 | /*! 75 | Constructs a QtPieItem. The \a title and \a weight 76 | arguments are stored as member data. The \a parent argument is 77 | passed to QWidget's constructor. 78 | */ 79 | QtPieItem::QtPieItem(const QString &title, unsigned int weight, QWidget *parent) 80 | : QWidget(parent, Qt::Popup), t(title), e(true), w(weight) 81 | { 82 | setText(title); 83 | } 84 | 85 | /*! 86 | Destructs a QtPieItem. 87 | */ 88 | QtPieItem::~QtPieItem() 89 | { 90 | } 91 | 92 | /*! 93 | Constructs a QtPieItem. The \a icon, \a text and \a weight arguments are 94 | stored as member data. The \a parent argument is passed to QWidget's 95 | constructor. 96 | */ 97 | QtPieItem::QtPieItem(const QIcon &icon, const QString &text, unsigned int weight, QWidget *parent) 98 | : QWidget(parent, Qt::Popup), i(icon), e(true), w(weight) 99 | { 100 | setText(text); 101 | } 102 | 103 | /*! 104 | Sets the item's text to \a text. 105 | */ 106 | void QtPieItem::setText(const QString &text) 107 | { 108 | t = text; 109 | if (text.length() > t.replace(QRegExp(QLatin1String("<[^>/]*br[^>]*>")), " ").length() || 110 | text.length() > t.replace(QRegExp(QLatin1String("<[^>/]*p[^>]*>")), " ").length() || 111 | text.length() > t.remove(QRegExp(QLatin1String("<[^>]+>"))).length()) 112 | qWarning("QtPieItem::setText: HTML support not implemented until Qt 4.1. All tags removed from text."); 113 | 114 | } 115 | 116 | /*! 117 | Returns the title of this QtPieItem. 118 | */ 119 | QString QtPieItem::text() const 120 | { 121 | return t; 122 | } 123 | 124 | /*! 125 | Sets the item's icon to \a icon. 126 | */ 127 | void QtPieItem::setIcon(const QIcon &icon) 128 | { 129 | i = icon; 130 | } 131 | 132 | /*! 133 | Returns a reference to the icon for this item. 134 | */ 135 | QIcon QtPieItem::icon() const 136 | { 137 | return i; 138 | } 139 | 140 | /*! 141 | Sets the item's weight to \a weight. 142 | */ 143 | void QtPieItem::setWeight(int weight) 144 | { 145 | w = weight; 146 | } 147 | 148 | /*! 149 | Returns the weight of the item. 150 | */ 151 | int QtPieItem::weight() const 152 | { 153 | return w; 154 | } 155 | 156 | /*! 157 | Returns \e true if this menu is enabled, otherwise returns \e false. 158 | */ 159 | bool QtPieItem::isEnabled() const 160 | { 161 | return e; 162 | } 163 | 164 | /*! 165 | Enables this item if \a enabled is \e true, otherwise disables 166 | this item. 167 | */ 168 | void QtPieItem::setEnabled(bool enabled) 169 | { 170 | e = enabled; 171 | } 172 | 173 | /*! 174 | Returns the type of node as a QtPieItem::Type. Valid values are 175 | Invalid, Action and SubMenu. 176 | */ 177 | int QtPieItem::type() const 178 | { 179 | return QtPieMenu::Invalid; 180 | } 181 | -------------------------------------------------------------------------------- /piemenu/qtpieitem.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | // depot/addons/main/widgets/qtpiemenu/src/qtpieitem.h#3 - edit change 282028 (text) 48 | #ifndef QTPIEITEM_H 49 | #define QTPIEITEM_H 50 | 51 | #include 52 | #include 53 | #include 54 | 55 | #if defined (Q_WS_WIN) 56 | # if !defined (QT_QTPIEMENU_EXPORT) && !defined (QT_QTPIEMENU_IMPORT) 57 | # define QT_QTPIEMENU_EXPORT 58 | # elif defined (QT_QTPIEMENU_IMPORT) 59 | # if defined (QT_QTPIEMENU_EXPORT) 60 | # undef QT_QTPIEMENU_EXPORT 61 | # endif 62 | # define QT_QTPIEMENU_EXPORT __declspec(dllimport) 63 | # elif defined (QT_QTPIEMENU_EXPORT) 64 | # undef QT_QTPIEMENU_EXPORT 65 | # define QT_QTPIEMENU_EXPORT __declspec(dllexport) 66 | # endif 67 | #else 68 | # define QT_QTPIEMENU_EXPORT 69 | #endif 70 | 71 | class QT_QTPIEMENU_EXPORT QtPieItem: public QWidget 72 | { 73 | Q_OBJECT 74 | 75 | public: 76 | QtPieItem(const QString &text = QString(), unsigned int weight = 1, QWidget *parent = 0); 77 | 78 | QtPieItem(const QIcon &icon, const QString &title = QString(), unsigned int weight = 1, QWidget *parent = 0); 79 | 80 | virtual ~QtPieItem(); 81 | 82 | void setText(const QString &text); 83 | 84 | QString text() const; 85 | 86 | void setIcon(const QIcon &icon); 87 | 88 | QIcon icon() const; 89 | 90 | void setWeight(int weight); 91 | 92 | int weight() const; 93 | 94 | void setEnabled(bool enabled = true); 95 | 96 | bool isEnabled() const; 97 | 98 | friend class QtPieMenu; 99 | 100 | virtual int type() const; 101 | 102 | private: 103 | QString t; 104 | QIcon i; 105 | bool e; 106 | unsigned int w; 107 | }; 108 | 109 | #endif // QTPIEITEM_H 110 | -------------------------------------------------------------------------------- /piemenu/qtpiemenu.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | #include "qtpieitem.h" 48 | #include "qtpiemenu.h" 49 | #include "qtpieaction.h" 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | 70 | const double PI = 3.14159265358979323846264338327950288419717; 71 | const double TWOPI = 2.0 * PI; 72 | enum MouseButtonStatus 73 | { 74 | Pressed, 75 | Released 76 | }; 77 | 78 | static MouseButtonStatus mouseButtonStatus = Released; 79 | 80 | /*! \class QtPieMenu qtpiemenu.h 81 | 82 | \brief The QtPieMenu class provides a pie menu popup widget. 83 | 84 | A pie menu is a popup menu that is usually invoked as a context 85 | menu, and that supports several forms of navigation. 86 | 87 | Using conventional navigation, menu items can be highlighted 88 | simply by moving the mouse over them, or by single-clicking them, 89 | or by using the keyboard's arrow keys. Menu items can be chosen 90 | (invoking a submenu or the menu item's action), by clicking a 91 | highlighted menu item, or by pressing \key{Enter}. 92 | 93 | Pie menus can also be navigated using \e{gestures}. 94 | Gesture navigation is where a menu item is chosen as the result of 95 | moving the mouse in a particular sequence of movements, for 96 | example, up-left-down, without the user having to actually 97 | read the menu item text. 98 | 99 | The user can cancel a pie menu in the conventional way by clicking 100 | outside of the pie menu, or by clicking the center of the pie (the 101 | "cancel zone"), or by pressing \key{Esc}. 102 | 103 | Pie menus are faster to navigate with the mouse than conventional 104 | menus because all the menu items are available at an equal 105 | distance from the origin of the mouse pointer. 106 | 107 | The circular layout and the length of the longest menu text 108 | imposes a natural limit on how many items can be displayed at a 109 | time. For this reason, submenus are very commonly used. 110 | 111 | Use popup() to pop up the pie menu. Note that QtPieMenu is 112 | different from QPopupMenu in that it pops up with the mouse cursor 113 | in the center of the menu (i.e. over the cancel zone), instead of 114 | having the cursor at the top left corner. 115 | 116 | Pie menus can only be used as popup menus; they cannot be used as 117 | normal widgets. 118 | 119 | A pie menu contains of a list of items, where each item is 120 | displayed as a slice of a pie. Items are added with insertItem(), 121 | and are displayed with a richtext text label, an icon or with both. 122 | The items are laid out automatically, starting from the top at 123 | index 0 and going counter clockwise. Each item can either pop up a 124 | submenu, or perform an action when activated. To get an item at 125 | a particular position, use itemAt(). 126 | 127 | When inserting action items, you specify a receiver and a slot. 128 | The slot is invoked in the receiver when the action is activated. 129 | In the following example, a pie menu is created with three items: 130 | "Open" and "Close" are actions, and "New" pops up a submenu. The 131 | submenu has two items, "New project" and "New dialog", which are 132 | both actions. 133 | 134 | \code 135 | Editor::Editor(QWidget *parent, const char *name) 136 | : QTextEdit(parent) 137 | { 138 | setObjectName(name); 139 | 140 | // Create a root menu and insert two action items. 141 | QtPieMenu *rootMenu = new QtPieMenu(tr("Root menu"), this); 142 | rootMenu->insertItem(tr("Open"), storage, SLOT(open())); 143 | rootMenu->insertItem(tr("Close"), storage, SLOT(close()); 144 | 145 | // Now create a submenu and insert two action items. 146 | QtPieMenu *subMenu = new QtPieMenu(tr("New"), rootMenu); 147 | subMenu->insertItem(tr("New project"), formEditor, SLOT(newProject())); 148 | subMenu->insertItem(tr("New dialog"), formEditor, SLOT(newDialog())); 149 | 150 | // Finally add the submenu to the root menu. 151 | rootMenu->insertItem(subMenu); 152 | } 153 | \endcode 154 | 155 | By default, each slice of the pie takes up an equal amount of 156 | space. Sometimes it can be useful to have certain slices take up 157 | more space than others. QtPieItem::setItemWeight() changes an item's 158 | space weighting, which by default is 1. QtPieMenu uses the 159 | weightings when laying out the pie slices. Each slice gets a share 160 | of the size proportional to its weight. 161 | 162 | \img weighted.png 163 | 164 | At the center of a pie menu is a cancel zone, which cancels the 165 | menu if the user clicks in it. The radius of the whole pie menu 166 | and of the cancel zone are set in the constructor, or with 167 | setOuterRadius() and setInnerRadius(). 168 | 169 | Any shape or layout of items is possible by subclassing QtPieMenu 170 | and reimplementing paintEvent(), indexAt(), generateMask() and 171 | reposition(). 172 | 173 | \img qtpiemenu.png 174 | */ 175 | 176 | /*! \enum QtPieMenu::ActivateReason 177 | 178 | This enum describes the reason for the activation of an item in a 179 | pie menu. 180 | 181 | \value MousePress The activation was caused by a mouse button press. 182 | \value MouseRelease The activation was caused by a mouse button release. 183 | \value KeyPress The activation was caused by a key press. 184 | \value Hovering The activation was caused by hovering the mouse pointer 185 | over an item. 186 | \value Dragging The activation was caused by dragging. 187 | 188 | \sa activateItem() 189 | */ 190 | 191 | /*! \enum QtPieMenu::ItemType 192 | 193 | \value Invalid The item has an invalid type or doesn't exist; for 194 | example if QtPiePenu::itemType() is called with an invalid 195 | index. 196 | 197 | \value SubMenu Activating the item will pop up a submenu. 198 | \value Action Activating this item will trigger an action. 199 | */ 200 | 201 | /*! \fn QtPieMenu::activated() 202 | 203 | This signal is emitted when a pie menu action item is activated, 204 | causing the popup menu to hide. It is used internally by 205 | QtPieMenu. Most users will find activated(int) more useful. 206 | */ 207 | 208 | /*! \fn QtPieMenu::activated(int i) 209 | 210 | \overload 211 | 212 | This signal is emitted when the action item at index position \a i 213 | in the pie menu's list of menu items is activated. 214 | 215 | \sa highlighted() 216 | */ 217 | 218 | /*! \fn QtPieMenu::highlighted(int i) 219 | 220 | This signal is emitted when the item at index position \a i in the 221 | pie menu's list of menu items is highlighted. 222 | 223 | \sa activated() 224 | */ 225 | 226 | /*! \fn QtPieMenu::canceled() 227 | 228 | This signal is emitted when the pie menu is canceled. Only the 229 | menu that emits this signal is canceled; any parent menus are 230 | still shown. 231 | 232 | \sa canceledAll() 233 | */ 234 | 235 | /*! \fn QtPieMenu::canceledAll() 236 | 237 | This signal is emitted when the pie menu is canceled in such a way 238 | that the entire menu (including submenus) hides. For example, this 239 | would happen if the user clicked somewhere outside all the pie 240 | menus. 241 | 242 | \sa canceled() 243 | */ 244 | 245 | /*! \fn QtPieMenu::aboutToShow() 246 | 247 | This signal is emitted just before the pie menu is displayed. You 248 | can connect it to any slot that sets up the menu contents (e.g. to 249 | ensure that the correct items are enabled). 250 | 251 | \sa aboutToHide(), insertItem(), removeItemAt() 252 | */ 253 | 254 | /*! 255 | \fn void QtPieMenu::aboutToHide() 256 | 257 | This signal is emitted just before the pie menu is hidden after it 258 | has been displayed. 259 | 260 | \warning Do not open a widget in a slot connected to this signal. 261 | 262 | \sa aboutToShow(), insertItem(), removeItemAt() 263 | */ 264 | 265 | /*! 266 | Constructs a pie menu. The \a innerRadius argument is the radius 267 | of the cancel zone at the center of the pie. The \a outerRadius is 268 | the radius of the outer edge of the pie. 269 | 270 | If this pie menu is a submenu, the richtext \a text argument is 271 | displayed by its parent menu. 272 | 273 | The \a parent argument is passed on to QWidget's 274 | constructor. The object name is set to the \a name argument. 275 | */ 276 | QtPieMenu::QtPieMenu(const QString &text, QWidget *parent, const char *name, 277 | uint innerRadius, uint outerRadius): 278 | QtPieItem(text, 1, parent), 279 | pix(outerRadius * 2 + 1, outerRadius * 2 + 1), hoverTimer(this), 280 | innerRad(innerRadius), outerRad(outerRadius) 281 | { 282 | init(name); 283 | } 284 | 285 | /*! 286 | Constructs a pie menu. The \a innerRadius argument is the radius 287 | of the cancel zone at the center of the pie. The \a outerRadius is 288 | the radius of the outer edge of the pie. 289 | 290 | If this pie menu is a submenu, the \a icon argument is displayed 291 | by its parent menu. 292 | 293 | The \a parent argument is passed on to QWidget's 294 | constructor. The object name is set to the \a name argument. 295 | */ 296 | QtPieMenu::QtPieMenu(const QIcon &icon, QWidget *parent, const char *name, 297 | uint innerRadius, uint outerRadius): 298 | QtPieItem(icon, QString(), 1, parent), 299 | pix(outerRadius * 2 + 1, outerRadius * 2 + 1), hoverTimer(this), 300 | innerRad(innerRadius), outerRad(outerRadius) 301 | { 302 | init(name); 303 | } 304 | 305 | /*! 306 | Constructs a pie menu. The \a innerRadius argument is the radius 307 | of the cancel zone at the center of the pie. The \a outerRadius is 308 | the radius of the outer edge of the pie. 309 | 310 | If this pie menu is a submenu, the \a icon and richtext \a text 311 | arguments are displayed by its parent menu. 312 | 313 | The \a parent argument is passed on to QWidget's 314 | constructor. The object name is set to the \a name argument. 315 | */ 316 | QtPieMenu::QtPieMenu(const QIcon &icon, const QString &text, QWidget *parent, 317 | const char *name, uint innerRadius, uint outerRadius): 318 | QtPieItem(icon, text, 1, parent), 319 | pix(outerRadius * 2 + 1, outerRadius * 2 + 1), hoverTimer(this), 320 | innerRad(innerRadius), outerRad(outerRadius) 321 | { 322 | init(name); 323 | } 324 | 325 | /*! \internal 326 | 327 | Creates the pie mask and initializes the pie menu. 328 | */ 329 | void QtPieMenu::init(const QString &name) 330 | { 331 | #if defined (QTSOLUTIONS_BUILDEVAL) 332 | void checkEval(); 333 | checkEval(); 334 | #endif 335 | hasMask = false; 336 | setMouseTracking(true); 337 | 338 | hItem = -2; 339 | shownSubMenu = -1; 340 | shaded = false; 341 | ignoreNextMouseReleaseIfNotDragging = false; 342 | lastActivatedItem = -1; 343 | syncMenu = false; 344 | 345 | setObjectName(name); 346 | 347 | connect(&hoverTimer, SIGNAL(timeout()), SLOT(hoverAlert())); 348 | } 349 | 350 | /*! 351 | Destructs the pie menu. 352 | */ 353 | QtPieMenu::~QtPieMenu() 354 | { 355 | deleteItems(); 356 | } 357 | 358 | /*! 359 | Sets the text of the pie menu item at position \a index to \a 360 | text. Currently QtPieMenu only supports plain text, and HTML tags will be stripped 361 | from \a text. 362 | 363 | \code 364 | pieMenu->setItemText("Undo", 0); 365 | \endcode 366 | 367 | \sa itemText() setItemIcon() 368 | */ 369 | void QtPieMenu::setItemText(const QString &text, int index) 370 | { 371 | if (!itemAt(index)) 372 | { 373 | qWarning("QtPieMenu::setItemText(\"%s\", %i) index of of range", text.toLatin1().constData(), index); 374 | 375 | return; 376 | } 377 | 378 | itemAt(index)->setText(text); 379 | } 380 | 381 | /*! 382 | Returns the text of the pie menu item at position \a index. The 383 | string may be a rich text string. 384 | 385 | \sa setItemText() itemIcon() 386 | */ 387 | QString QtPieMenu::itemText(int index) const 388 | { 389 | if (!itemAt(index)) 390 | { 391 | qWarning("QtPieMenu::itemText(%i) index of of range", index); 392 | 393 | return ""; 394 | } 395 | 396 | return itemAt(index)->text(); 397 | } 398 | 399 | /*! 400 | Sets the icon of the pie menu item at position \a index to \a 401 | icon. 402 | 403 | \sa itemIcon() setItemText() 404 | */ 405 | void QtPieMenu::setItemIcon(const QIcon &icon, int index) 406 | { 407 | if (!itemAt(index)) 408 | { 409 | qWarning("QtPieMenu::setItemIcon(%p, %i) index of of range", &icon, index); 410 | 411 | return; 412 | } 413 | 414 | itemAt(index)->setIcon(icon); 415 | } 416 | 417 | /*! 418 | Returns the icon set of the pie menu item at position \a index. If 419 | no icon has been set an invalid icon is returned. 420 | 421 | \sa setItemIcon() itemText() 422 | */ 423 | QIcon QtPieMenu::itemIcon(int index) const 424 | { 425 | if (!itemAt(index)) 426 | { 427 | qWarning("QtPieMenu::itemIcon(%i) index of of range", index); 428 | 429 | return QIcon(); 430 | } 431 | 432 | return itemAt(index)->icon(); 433 | } 434 | 435 | /*! 436 | Sets the relative weight of the pie menu item at position \a index 437 | to \a weight. The weight of an item decides how big its slice of 438 | the pie menu will be. By default, all pie menu items have a weight 439 | of 1. If one item has a weight of 2, it will be twice as big as 440 | the other items. 441 | 442 | \img weighted.png 443 | 444 | The pie menu in this image has three pie menu items with icons 445 | and no text. The item at position 0 has a weight of 2, and the 446 | rest have the default weight of 1. 447 | 448 | \sa itemWeight() 449 | */ 450 | void QtPieMenu::setItemWeight(int weight, int index) 451 | { 452 | if (!itemAt(index)) 453 | { 454 | qWarning("QtPieMenu::setItemWeight(%i, %i) index of of range", weight, index); 455 | 456 | return; 457 | } 458 | 459 | itemAt(index)->setWeight(weight); 460 | } 461 | 462 | /*! 463 | Returns the weight of the pie menu item at position \a index. 464 | 465 | \sa setItemWeight() 466 | */ 467 | int QtPieMenu::itemWeight(int index) const 468 | { 469 | if (!itemAt(index)) 470 | { 471 | qWarning("QtPieMenu::itemWeight(%i) index of of range", index); 472 | 473 | return 0; 474 | } 475 | 476 | return itemAt(index)->weight(); 477 | } 478 | 479 | /*! 480 | If \a enabled is true, this function enables the pie menu item at 481 | position \a index; otherwise the item is disabled. When an item is 482 | disabled, it will not respond to mouse clicks or releases. All 483 | all items are enabled by default. 484 | 485 | \sa isItemEnabled() 486 | */ 487 | void QtPieMenu::setItemEnabled(bool enabled, int index) 488 | { 489 | if (!itemAt(index)) 490 | { 491 | qWarning("QtPieMenu::setItemEnabled(%s, %i) index of of range", enabled ? "true" : "false", index); 492 | 493 | return; 494 | } 495 | 496 | itemAt(index)->setEnabled(enabled); 497 | } 498 | 499 | /*! 500 | Returns true if the pie menu item at position \a index is enabled; 501 | otherwise returns false. 502 | 503 | \sa setItemEnabled() 504 | */ 505 | bool QtPieMenu::isItemEnabled(int index) const 506 | { 507 | if (!itemAt(index)) 508 | { 509 | qWarning("QtPieMenu::isItemEnabled(%i) index of of range", index); 510 | 511 | return false; 512 | } 513 | 514 | return itemAt(index)->isEnabled(); 515 | } 516 | 517 | /*! \internal 518 | 519 | Returns \e true if this is the top level menu, otherwise returns 520 | \e false. 521 | */ 522 | bool QtPieMenu::isTopLevelMenu() const 523 | { 524 | return !parent()->inherits("QtPieMenu"); 525 | } 526 | 527 | /*! \property QtPieMenu::innerRadius 528 | 529 | \brief the inner radius of the pie menu 530 | 531 | The radius in pixels of the cancel zone (in the center) of the pie 532 | menu. Setting the radius to 0 disables the cancel zone. 533 | 534 | \sa outerRadius 535 | */ 536 | void QtPieMenu::setInnerRadius(int r) 537 | { 538 | innerRad = r; 539 | } 540 | 541 | int QtPieMenu::innerRadius() const 542 | { 543 | return innerRad; 544 | } 545 | 546 | /*! \property QtPieMenu::outerRadius 547 | 548 | \brief the outer radius of the pie menu 549 | 550 | The outer radius of the pie menu in pixels. 551 | 552 | \sa innerRadius 553 | */ 554 | void QtPieMenu::setOuterRadius(int r) 555 | { 556 | outerRad = r; 557 | pix = pix.scaled(outerRad * 2 + 1, outerRad * 2 + 1); 558 | } 559 | 560 | int QtPieMenu::outerRadius() const 561 | { 562 | return outerRad; 563 | } 564 | 565 | /*! \internal 566 | 567 | Returns the type of QtPieItem. In this case, always returns SubMenu. 568 | */ 569 | int QtPieMenu::type() const 570 | { 571 | return SubMenu; 572 | } 573 | 574 | /*! 575 | Displays the pie menu with the center of the pie at the global 576 | position \a pos. 577 | 578 | To translate a widget's contents coordinates into global 579 | coordinates, use QWidget::mapToGlobal(). 580 | */ 581 | void QtPieMenu::popup(const QPoint &pos) 582 | { 583 | hItem = indexAt(mapFromGlobal(QCursor::pos())); 584 | lastMousePos = pos; 585 | lastActivatedItem = -1; 586 | 587 | shaded = false; 588 | 589 | // Force whole pie menu to be inside desktop. 590 | int x = pos.x() - sizeHint().width() / 2; 591 | int y = pos.y() - sizeHint().height() / 2; 592 | const QRect screenGeometry = qApp->desktop()->screenGeometry(qApp->desktop()->screenNumber(pos)); 593 | 594 | if (x < screenGeometry.left()) 595 | { 596 | x = screenGeometry.left(); 597 | } 598 | 599 | if (y < screenGeometry.top()) 600 | { 601 | y = screenGeometry.top(); 602 | } 603 | 604 | if (x + sizeHint().width() > screenGeometry.right()) 605 | { 606 | x = screenGeometry.right() - sizeHint().width(); 607 | } 608 | 609 | if (y + sizeHint().height() > screenGeometry.bottom()) 610 | { 611 | y = screenGeometry.bottom() - sizeHint().height(); 612 | } 613 | 614 | move(x, y); 615 | reposition(); 616 | show(); 617 | } 618 | 619 | /* 620 | Synchronously executes this pie menu. 621 | 622 | Pops up the pie menu with its center at the global position \a 623 | pos. (To translate a widget's local coordinates into global 624 | coordinates, use QWidget::mapToGlobal().) 625 | 626 | The return value is the index position of the selected item in 627 | either the popup menu or one of its submenus, or -1 if no item is 628 | selected (normally because the user pressed Esc). 629 | 630 | Note that all signals are emitted as usual. If you connect a menu 631 | item to a slot and call the menu's exec(), you get the result both 632 | via the signal-slot connection and in the return value of exec(). 633 | 634 | Common usage is to position the popup at the current mouse position: 635 | 636 | \code 637 | exec(QCursor::pos()); 638 | \endcode 639 | */ 640 | 641 | /* 642 | int QtPieMenu::exec(const QPoint &pos) 643 | { 644 | syncMenu = true; 645 | QGuardedPtr that = this; 646 | popup(pos); 647 | qApp->enter_loop(); 648 | if (that) { 649 | syncMenu = false; 650 | return lastActivatedItem; 651 | } else { 652 | return -1; 653 | } 654 | } 655 | */ 656 | 657 | /*! 658 | \overload 659 | 660 | Adds the submenu \a item to this pie menu at position \a index. 661 | The items are ordered by their ordinal position, starting from the 662 | top of the pie and counting counter clockwise. 663 | 664 | \code 665 | QtPieMenu *subMenu = new QtPieMenu("Undo", this, SLOT(undo())); 666 | rootMenu->insertItem(subMenu); 667 | \endcode 668 | */ 669 | void QtPieMenu::insertItem(QtPieMenu *item, int index) 670 | { 671 | // Out of bounds indexes are treated as appends. 672 | if ((index < 0) || (index > items.size())) 673 | { 674 | index = items.size(); 675 | } 676 | 677 | items.insert(index, item); 678 | 679 | connect(item, SIGNAL(activated()), SLOT(subMenuSelected())); 680 | connect(item, SIGNAL(canceled()), SLOT(subMenuCanceled())); 681 | connect(item, SIGNAL(canceledAll()), SLOT(allCanceled())); 682 | } 683 | 684 | /*! 685 | \overload 686 | 687 | Adds an action item to this pie menu at position \a index. The 688 | \a text is used as the item's text, or the text that is 689 | displayed on this item's slice in the pie. The \a receiver gets 690 | notified through the signal or slot in \a member when the item is 691 | activated. 692 | 693 | \code 694 | rootMenu->insertItem("Save", this, SLOT(save())); 695 | \endcode 696 | 697 | The items are ordered by their ordinal position, starting from the 698 | top of the pie and going counter clockwise. 699 | */ 700 | void QtPieMenu::insertItem(const QString &text, QObject *receiver, 701 | const char *member, int index) 702 | { 703 | QtPieAction *action = new QtPieAction(text, receiver, member); 704 | 705 | // Out of bounds indexes are treated as appends. 706 | if ((index < 0) || (index > int(items.size()))) 707 | { 708 | index = items.size(); 709 | } 710 | 711 | items.insert(index, action); 712 | } 713 | 714 | /*! 715 | Adds an action item to this pie menu at position \a index. The \a 716 | icon is displayed on this item's slice in the pie. The \a receiver 717 | gets notified through the signal or slot in \a member when the 718 | item is activated. 719 | 720 | \code 721 | QIcon icon(QPixmap("save.png")); 722 | rootMenu->insertItem(icon, this, SLOT(save())); 723 | \endcode 724 | 725 | The items are ordered by their ordinal position, starting from the 726 | top of the pie and going counter clockwise. 727 | */ 728 | void QtPieMenu::insertItem(const QIcon &icon, QObject *receiver, const char *member, int index) 729 | { 730 | QtPieAction *action = new QtPieAction(icon, QString(), receiver, member); 731 | 732 | // Out of bounds indexes are treated as appends. 733 | if ((index < 0) || (index > int(items.size()))) 734 | { 735 | index = items.size(); 736 | } 737 | 738 | items.insert(index, action); 739 | } 740 | 741 | /*! 742 | \overload 743 | 744 | Adds an action item to this pie menu at position \a index. The 745 | \a icons and \a text are used as the item's text 746 | and icon, or the text and icon that is displayed on this item's 747 | slice in the pie. The \a receiver gets notified through the signal 748 | or slot in \a member when the item is activated. 749 | 750 | \code 751 | QIcon icon(QPixmap("save.png")); 752 | rootMenu->insertItem(icon, "Save", this, SLOT(save())); 753 | \endcode 754 | 755 | The items are ordered by their ordinal position, starting from the 756 | top of the pie and going counter clockwise. 757 | */ 758 | void QtPieMenu::insertItem(const QIcon &icons, const QString &text, 759 | QObject *receiver, const char *member, int index) 760 | { 761 | QtPieAction *action = new QtPieAction(icons, text, receiver, member); 762 | 763 | // Out of bounds indexes are treated as appends. 764 | if ((index < 0) || (index > int(items.size()))) 765 | { 766 | index = items.size(); 767 | } 768 | 769 | items.insert(index, action); 770 | } 771 | 772 | /*! 773 | Returns a pointer to the item at position \a index if there is 774 | one; otherwise returns 0. This is useful if weights, texts or 775 | icons need to be changed at run-time. 776 | 777 | \code 778 | pieMenu->itemAt(0)->setText("Delete"); 779 | \endcode 780 | */ 781 | QtPieItem * QtPieMenu::itemAt(int index) const 782 | { 783 | // Out of bounds indexes are treated as appends. 784 | if ((index < 0) || (index > int(items.size()))) 785 | { 786 | return 0; 787 | } 788 | 789 | return (static_cast(this))->items[index]; 790 | } 791 | 792 | /*! 793 | Returns the number of items in this pie menu. 794 | */ 795 | int QtPieMenu::count() const 796 | { 797 | return items.size(); 798 | } 799 | 800 | /*! 801 | Removes all the items from the pie menu. 802 | */ 803 | void QtPieMenu::clear() 804 | { 805 | hideShownSubMenu(); 806 | 807 | deleteItems(); 808 | 809 | hItem = -2; 810 | } 811 | 812 | /*! 813 | If the item at position \a index is a submenu, a pointer to the 814 | corresponding QtPieMenu is returned; otherwise returns 0. 815 | */ 816 | QtPieMenu * QtPieMenu::subMenuAt(int index) const 817 | { 818 | QtPieItem *item = (static_cast(this))->items[index]; 819 | 820 | if (item && (item->type() == SubMenu)) 821 | { 822 | return static_cast(item); 823 | } 824 | 825 | return 0; 826 | } 827 | 828 | /*! 829 | Removes the item at position \a index. 830 | */ 831 | void QtPieMenu::removeItemAt(int index) 832 | { 833 | if ((index >= int(items.size())) || (index < 0)) 834 | { 835 | qWarning("Attempt to remove item %i from QtPieMenu with %i items.", 836 | index, items.size()); 837 | 838 | return; 839 | } 840 | 841 | deleteItems(index); 842 | } 843 | 844 | /*! 845 | Highlights the item at position \a index, then schedules a display 846 | update. 847 | */ 848 | void QtPieMenu::setHighlightedItem(int index) 849 | { 850 | hItem = index; 851 | update(); 852 | } 853 | 854 | /*! \reimp 855 | */ 856 | QSize QtPieMenu::sizeHint() const 857 | { 858 | return QSize(outerRad * 2 + 1, outerRad * 2 + 1); 859 | } 860 | 861 | /*! 862 | Draws the outline of the pie menu using the bitmap \a mask. This 863 | is used as a QWidget mask. 864 | 865 | When subclassing QtPieMenu, this function should be reimplemented 866 | if the shape of the new menu is different from QtPieMenu's shape. 867 | */ 868 | void QtPieMenu::generateMask(QBitmap *mask) 869 | { 870 | QPainter p(mask); 871 | 872 | p.setPen(Qt::color1); 873 | p.setBrush(Qt::color1); 874 | p.drawEllipse(mask->rect().adjusted(0, 0, -1, -1)); 875 | } 876 | 877 | /*! 878 | Repositions a pie menu. This function is called in popup() after 879 | the pie menu's initial position has been set. Reimplement this 880 | function when subclassing QtPieMenu to gain finer control of where 881 | submenus are positioned when popped up. 882 | 883 | The default implementation does nothing. 884 | */ 885 | void QtPieMenu::reposition() 886 | { 887 | } 888 | 889 | /*! \reimp 890 | 891 | Notes on keyboard navigation: Move selection clockwise with the 892 | keys \key{Right}, \key{Down} and \key{Tab}. Move counter clockwise 893 | with \key{Left}, \key{Up} and \key{Backtab}. To select an item, 894 | use \key{Return}, \key{Enter} or \key{Space}. To cancel a menu, 895 | use \key{Backspace} or \key{Escape}. 896 | */ 897 | void QtPieMenu::keyPressEvent(QKeyEvent *e) 898 | { 899 | switch (e->key()) 900 | { 901 | case Qt::Key_Right: 902 | case Qt::Key_Down: 903 | case Qt::Key_Tab: 904 | --hItem; 905 | 906 | if (hItem < 0) 907 | { 908 | hItem += items.size(); 909 | } 910 | 911 | update(); 912 | break; 913 | case Qt::Key_Left: 914 | case Qt::Key_Up: 915 | case Qt::Key_Backtab: 916 | ++hItem; 917 | 918 | if (hItem >= char(items.size())) 919 | { 920 | hItem -= items.size(); 921 | } 922 | 923 | update(); 924 | break; 925 | case Qt::Key_Return: 926 | case Qt::Key_Enter: 927 | case Qt::Key_Space: 928 | motion = 0; 929 | activateItem(KeyPress); 930 | 931 | if (shownSubMenu != -1) 932 | { 933 | ((QtPieMenu *)shownItemPtr())->setHighlightedItem(0); 934 | } 935 | 936 | break; 937 | case Qt::Key_Escape: 938 | case Qt::Key_Backspace: 939 | { 940 | emit aboutToHide(); 941 | hide(); 942 | } 943 | break; 944 | default: 945 | break; 946 | } 947 | } 948 | 949 | /*! \internal 950 | 951 | Handles mouse presses. A mouse press always activates or cancels a 952 | submenu. 953 | */ 954 | void QtPieMenu::mousePressEvent(QMouseEvent *e) 955 | { 956 | hoverTimer.stop(); 957 | 958 | mouseButtonStatus = Pressed; 959 | lastMousePos = mapFromGlobal(e->globalPos()); 960 | 961 | double mousexdist = lastMousePos.x() - rect().center().x(); 962 | double mouseydist = lastMousePos.y() - rect().center().y(); 963 | double mouserad = sqrt(mousexdist * mousexdist + mouseydist * mouseydist); 964 | double mouseAngle = acos(mousexdist / mouserad); 965 | 966 | if (mouseydist >= 0) 967 | { 968 | mouseAngle = TWOPI - mouseAngle; 969 | } 970 | 971 | hItem = indexAt(lastMousePos); 972 | 973 | if (shaded && (shownSubMenu != hItem)) 974 | { 975 | hideShownSubMenu(); 976 | } 977 | 978 | activateItem(MousePress); 979 | motion = 0; 980 | } 981 | 982 | /*! \internal 983 | 984 | Handles mouse releases. A mouse release will activate a menu in 985 | dragging mode. 986 | */ 987 | void QtPieMenu::mouseReleaseEvent(QMouseEvent *e) 988 | { 989 | hoverTimer.stop(); 990 | 991 | // Solve the case where a submenu is canceled by clicking inside 992 | // the cancel zone, and the next mouse release comes to us. 993 | if (ignoreNextMouseReleaseIfNotDragging) 994 | { 995 | ignoreNextMouseReleaseIfNotDragging = false; 996 | 997 | if (!dragging()) 998 | { 999 | mouseButtonStatus = Released; 1000 | 1001 | return; 1002 | } 1003 | } 1004 | 1005 | lastMousePos = mapFromGlobal(e->globalPos()); 1006 | hoverTimer.stop(); 1007 | 1008 | if (!shaded) 1009 | { 1010 | activateItem(MouseRelease | (dragging() ? Dragging : 0)); 1011 | } 1012 | 1013 | mouseButtonStatus = Released; 1014 | motion = 0; 1015 | } 1016 | 1017 | /*! \internal 1018 | 1019 | Handles mouse move events. Mouse tracking is enabled, so we get 1020 | these continuously. Monitors mouse position, distance from center 1021 | and angle, and uses these to decide the section that is currently 1022 | benieth the cursor, and wether we are crossing a section boundary. 1023 | */ 1024 | void QtPieMenu::mouseMoveEvent(QMouseEvent *e) 1025 | { 1026 | lastMousePos = mapFromGlobal(e->globalPos()); 1027 | double mouseRad = radiusAt(lastMousePos); 1028 | QtPieMenu *parentMenu = qobject_cast(parent()); 1029 | 1030 | if (parentMenu && (mouseRad > outerRad)) 1031 | { 1032 | parentMenu->mouseMoveEvent(e); // pos() will be wrong, but is not used 1033 | return; 1034 | } 1035 | 1036 | ++motion; 1037 | mouseButtonStatus = (e->buttons()) != 0 ? Pressed : Released; 1038 | 1039 | // Detect changes in highlighting. The logics are as follows: If 1040 | // the mouse pointer is outside the cancel zone and inside the 1041 | // pie's outer radius, then highlight the section that the mouse 1042 | // pointer is hovering over. The exception is that we also 1043 | // highlight the section if the mouse pointer is outside the pie, 1044 | // if a submenu is shown. In all other cases, no item is 1045 | // highlighted. 1046 | int prevHighlightedItem = hItem; 1047 | 1048 | if ((mouseRad >= innerRad) && ((shownSubMenu != -1) || ((mouseRad <= outerRad) || dragging()))) 1049 | { 1050 | hItem = indexAt(lastMousePos); 1051 | } 1052 | else 1053 | { 1054 | if (mouseRad < innerRad) 1055 | { 1056 | hItem = -1; 1057 | } 1058 | else 1059 | { 1060 | hItem = -2; 1061 | } 1062 | } 1063 | 1064 | // If highlight changes, order an update. 1065 | if (prevHighlightedItem != hItem) 1066 | { 1067 | emit highlighted(hItem); 1068 | update(); 1069 | 1070 | // And in this case, if we are in dragging mode, start the 1071 | // timer so that in case the highlighted item is a submenu, it 1072 | // will pop up in 0.25 seconds. If the mouse hovers over an 1073 | // action item, any shown submenu is closed. 1074 | hoverTimer.setSingleShot(true); 1075 | hoverTimer.start(250); 1076 | } 1077 | 1078 | // In dragging mode, always order an activation of a submenu if 1079 | // the mouse has moved far enough away from the pie. This allows 1080 | // more snappy response as we do not have to wait 250ms. 1081 | if (highlightedItemPtr() && (highlightedItemPtr()->type() == SubMenu) && (mouseRad > outerRad) && dragging()) 1082 | { 1083 | hoverTimer.stop(); 1084 | activateItem(Hovering | Dragging); 1085 | } 1086 | } 1087 | 1088 | /*! \internal 1089 | 1090 | When our pie menu is shown, we reset the motion and anything we 1091 | know about items. 1092 | */ 1093 | void QtPieMenu::showEvent(QShowEvent *e) 1094 | { 1095 | emit aboutToShow(); 1096 | 1097 | if (!hasMask) 1098 | { 1099 | // Create mask. 1100 | QBitmap mask(pix.size()); 1101 | mask.fill(Qt::color0); 1102 | generateMask(&mask); 1103 | pix.setMask(mask); 1104 | setMask(mask); 1105 | hasMask = true; 1106 | } 1107 | 1108 | // motion is used to detect dragging. 1109 | motion = 0; 1110 | 1111 | // Determine what item in this menu should be highlighted from the 1112 | // current mouse position. With quick gestures, the mouse cursor 1113 | // may already have moved and stopped at a new item before we have 1114 | // shown this submenu. We need to highlight the relevant item. 1115 | lastMousePos = mapFromGlobal(QCursor::pos()); 1116 | double rad = radiusAt(lastMousePos); 1117 | 1118 | if ((rad >= innerRad) && (rad <= outerRad)) 1119 | { 1120 | hItem = angleToIndex(angleAt(lastMousePos), items.size()); 1121 | } 1122 | else 1123 | { 1124 | if (rad < innerRad) 1125 | { 1126 | hItem = -1; 1127 | } 1128 | else 1129 | { 1130 | hItem = -2; 1131 | } 1132 | } 1133 | 1134 | QWidget::showEvent(e); 1135 | } 1136 | 1137 | /*! \internal 1138 | 1139 | */ 1140 | void QtPieMenu::hideEvent(QHideEvent *e) 1141 | { 1142 | if (lastActivatedItem == -1) 1143 | { 1144 | emit canceled(); 1145 | } 1146 | 1147 | hItem = -2; 1148 | hideShownSubMenu(); 1149 | QWidget::hideEvent(e); 1150 | 1151 | /*if (syncMenu) 1152 | qApp->exit_loop();*/ 1153 | } 1154 | 1155 | /*! \internal 1156 | 1157 | A slot which is called by the timer. Triggers a menu activation if 1158 | we are not shaded. 1159 | */ 1160 | void QtPieMenu::hoverAlert() 1161 | { 1162 | activateItem(Hovering | (dragging() ? Dragging : 0)); 1163 | } 1164 | 1165 | /*! 1166 | Activates an item. The reason for the activation is given in \a 1167 | reason. Activating an item either causes an action to be 1168 | activated, a submenu to popup, or the menu to be canceled. 1169 | 1170 | \sa QtPieMenu::ActivateReason 1171 | */ 1172 | void QtPieMenu::activateItem(int reason) 1173 | { 1174 | // Ignore stuff that happens when the menu is not shown. This is 1175 | // to avoid submenus from popping up after a parent menu has been 1176 | // closed. 1177 | if (!isVisible()) 1178 | { 1179 | return; 1180 | } 1181 | 1182 | // As a menu is activated, the timer must be stopped or we might 1183 | // get two activations. 1184 | hoverTimer.stop(); 1185 | 1186 | double mouseRad = radiusAt(lastMousePos); 1187 | bool isInCancelZone = mouseRad < innerRad; 1188 | bool isOutsidePie = mouseRad > outerRad; 1189 | 1190 | if (reason & Dragging) 1191 | { 1192 | // Don't cancel the menu if hovering over the cancel zone. 1193 | if (isInCancelZone) 1194 | { 1195 | if (shownSubMenu != -1) 1196 | { 1197 | // Hide shown submenu 1198 | hideShownSubMenu(); 1199 | } 1200 | 1201 | // Restart the timer. If the pointer moves out of the 1202 | // cancel zone, we still want to detect hovering. 1203 | if (reason & Hovering) 1204 | { 1205 | hoverTimer.setSingleShot(true); 1206 | hoverTimer.start(250); 1207 | } 1208 | 1209 | return; 1210 | } 1211 | 1212 | // If we're hovering over an item, or if this activation was 1213 | // caused by a mouse release, then activate. 1214 | } 1215 | else if (!(reason & KeyPress)) 1216 | { 1217 | // A mouse release can never activate a menu when not in 1218 | // dragging mode. 1219 | if (reason & MouseRelease) 1220 | { 1221 | return; 1222 | } 1223 | 1224 | // Cancel the menu if we got a MousePress in the cancel zone 1225 | // or outside the pie. Any other activity in the cancel zone 1226 | // when not in dragging mode is ignored. 1227 | if (isInCancelZone || isOutsidePie) 1228 | { 1229 | if (reason & Hovering && (shownSubMenu != -1)) 1230 | { 1231 | // Hide shown submenu 1232 | hideShownSubMenu(); 1233 | } 1234 | 1235 | if (reason & MousePress) 1236 | { 1237 | if (!isInCancelZone) 1238 | { 1239 | emit canceledAll(); 1240 | } 1241 | 1242 | emit aboutToHide(); 1243 | hide(); 1244 | } 1245 | 1246 | return; 1247 | } 1248 | } 1249 | 1250 | // ### Investigate this special case further. It should not 1251 | // ### happen. 1252 | if (hItem < 0) 1253 | { 1254 | return; 1255 | } 1256 | 1257 | // Ignore all operations on disabled items. 1258 | if (!items.at(hItem)->isEnabled()) 1259 | { 1260 | return; 1261 | } 1262 | 1263 | int acthItem = hItem; 1264 | 1265 | // Show new child 1266 | if (highlightedItemPtr()->type() == SubMenu) 1267 | { 1268 | // Avoid flickering 1269 | if (acthItem == shownSubMenu) 1270 | { 1271 | return; 1272 | } 1273 | 1274 | QtPieMenu *pie = (QtPieMenu *)highlightedItemPtr(); 1275 | 1276 | // Hide shown submenu 1277 | hideShownSubMenu(); 1278 | 1279 | emit activated(acthItem); 1280 | 1281 | shaded = true; 1282 | 1283 | double angle; 1284 | 1285 | if (count() == 1) 1286 | { 1287 | // If there is only one pie menu item, and the parent does 1288 | // not inherit QtPieMenu (qualifies as the rool level pie 1289 | // menu), then the submenu should open straight north. If 1290 | // this is not the root menu however, the submenu should 1291 | // open on the same axis that intersects both the parent's 1292 | // center and this menu's center, at the point where it 1293 | // intersects with this menu's outer radius. 1294 | if (!parent()->inherits("QtPieMenu")) 1295 | { 1296 | angle = 0; 1297 | } 1298 | else 1299 | { 1300 | QtPieMenu *parentPie = (QtPieMenu *)parent(); 1301 | QPoint ppos = mapToGlobal(parentPie->pos()); 1302 | ppos.setX(ppos.x() + parentPie->width() / 2); 1303 | ppos.setY(ppos.y() + parentPie->height() / 2); 1304 | 1305 | QPoint opos = mapToGlobal(pos()); 1306 | opos.setX(opos.x() + width() / 2); 1307 | opos.setY(opos.y() + height() / 2); 1308 | 1309 | double xxdist = (double)(opos.x() - ppos.x()); 1310 | double yydist = (double)(opos.y() - ppos.y()); 1311 | double rrad = sqrt(xxdist * xxdist + yydist * yydist); 1312 | angle = acos(xxdist / rrad); 1313 | 1314 | if (yydist > 0) 1315 | { 1316 | angle = TWOPI - angle; 1317 | } 1318 | } 1319 | } 1320 | else 1321 | { 1322 | double a = indexToAngle(acthItem, count(), true); 1323 | double b = indexToAngle(acthItem + 1, count(), true); 1324 | 1325 | if (b > a) 1326 | { 1327 | angle = (a + b) / 2; 1328 | } 1329 | else 1330 | { 1331 | angle = (a + b + TWOPI) / 2; 1332 | } 1333 | } 1334 | 1335 | double b = ((double)(outerRadius() + pie->outerRadius())) * cos(angle) * 0.90; 1336 | double c = -(double)(outerRadius() + pie->outerRadius()) * sin(angle) * 0.90; 1337 | 1338 | hoverTimer.stop(); 1339 | 1340 | // If hovering in non-dragging mode, show the submenu outside 1341 | // the main menu. 1342 | shownSubMenu = acthItem; 1343 | pie->popup(mapToGlobal(QPoint(rect().center().x() + (int)b, 1344 | rect().center().y() + (int)c))); 1345 | 1346 | update(); 1347 | } 1348 | else 1349 | { 1350 | if (shownSubMenu != -1) 1351 | { 1352 | // Hide shown submenu 1353 | hideShownSubMenu(); 1354 | } 1355 | 1356 | // Hovering never activates an action 1357 | if (reason & Hovering) 1358 | { 1359 | return; 1360 | } 1361 | 1362 | emit activated(acthItem); 1363 | QtPieAction *pie = (QtPieAction *)highlightedItemPtr(); 1364 | pie->activate(); 1365 | 1366 | lastActivatedItem = acthItem; 1367 | subMenuSelected(); 1368 | } 1369 | } 1370 | 1371 | /*! \internal 1372 | 1373 | Draw the pie menu and blit it onto the screen. 1374 | */ 1375 | void QtPieMenu::paintEvent(QPaintEvent *) 1376 | { 1377 | QPainter p(&pix); 1378 | QRadialGradient gradientBack(rect().center().x(), rect().center().y() - 10, rect().width() / 2.0); // diagonal gradient from top-left to bottom-right 1379 | 1380 | gradientBack.setColorAt(0, QColor(174, 184, 191)); 1381 | gradientBack.setColorAt(0.25, QColor(220, 220, 220)); 1382 | gradientBack.setColorAt(0.85, QColor(239, 238, 239)); 1383 | gradientBack.setColorAt(1, QColor(190, 190, 190)); 1384 | 1385 | QRadialGradient gradientLight(rect().center().x(), rect().center().y() - 10, rect().width() / 2.0); // diagonal gradient from top-left to bottom-right 1386 | gradientLight.setColorAt(0, QColor(253, 99, 70)); 1387 | gradientLight.setColorAt(1, QColor(253, 184, 92)); 1388 | 1389 | p.setRenderHint(QPainter::Antialiasing, true); 1390 | QRect adjustedRect = pix.rect().adjusted(0, 0, -1, -1); 1391 | 1392 | // Special case: 0 or 1 items 1393 | if (items.size() < 2) 1394 | { 1395 | if ((hItem == 0) && items.at(0)->isEnabled()) 1396 | { 1397 | p.setBrush(palette().highlight()); 1398 | } 1399 | else 1400 | { 1401 | p.setBrush(palette().background()); 1402 | } 1403 | 1404 | if (shaded) 1405 | { 1406 | p.setBrush(QBrush(p.brush().color().dark(125))); 1407 | } 1408 | 1409 | p.fillRect(rect(), gradientBack); 1410 | // p.fillRect(rect(), p.brush()); 1411 | } 1412 | 1413 | // Draw the pie with borders and selected slice. 1414 | if (items.size() > 1) 1415 | { 1416 | for (int i = 0; i < items.size(); ++i) 1417 | { 1418 | if ((i == hItem) && items.at(i)->isEnabled()) 1419 | { 1420 | // p.setBrush(QBrush(palette().highlight())); 1421 | p.setBrush(gradientLight); 1422 | } 1423 | else 1424 | { 1425 | // p.setBrush(QBrush(palette().background())); 1426 | p.setBrush(gradientBack); 1427 | } 1428 | 1429 | p.setPen(palette().mid().color()); 1430 | 1431 | if (shaded) 1432 | { 1433 | p.setPen(QPen(p.pen().color().dark(125))); 1434 | p.setBrush(QBrush(p.brush().color().dark(125))); 1435 | } 1436 | 1437 | double a = indexToAngle(i, items.size(), true); 1438 | double b = indexToAngle(i + 1, items.size(), true); 1439 | 1440 | if (b < a) 1441 | { 1442 | b += TWOPI; 1443 | } 1444 | 1445 | double size = b - a; 1446 | 1447 | if (size < 0) 1448 | { 1449 | size += TWOPI; 1450 | } 1451 | 1452 | int startAngle = (int)((a * 360.0 * 16.0) / TWOPI); 1453 | 1454 | p.drawPie(adjustedRect, startAngle, (int)((size * 360.0 * 16.0) / TWOPI)); 1455 | } 1456 | } 1457 | 1458 | QColor background; 1459 | QColor dark = palette().dark().color(); 1460 | QColor light = palette().light().color(); 1461 | 1462 | if (hItem == -1) 1463 | { 1464 | background = palette().highlight().color(); 1465 | } 1466 | else 1467 | { 1468 | background = palette().background().color(); 1469 | } 1470 | 1471 | if (shaded) 1472 | { 1473 | background = background.dark(125); 1474 | dark = dark.dark(125); 1475 | light = light.dark(125); 1476 | } 1477 | 1478 | // Shading 1479 | QRect innerRect(adjustedRect.center().x() - innerRad, adjustedRect.center().y() - innerRad, 1480 | innerRad * 2 + 1, innerRad * 2 + 1); 1481 | 1482 | p.setPen(Qt::NoPen); 1483 | p.setBrush(background); 1484 | 1485 | p.drawPie(innerRect, 0, 360 * 16); 1486 | 1487 | light.setAlpha(128); 1488 | p.setPen(QPen(light, 1)); 1489 | p.setBrush(Qt::NoBrush); 1490 | p.drawArc(innerRect, 225 * 16, 180 * 16); 1491 | 1492 | p.setPen(QPen(dark, 1)); 1493 | p.drawArc(innerRect, 45 * 16, 180 * 16); 1494 | 1495 | p.setPen(QPen(light, 1)); 1496 | p.setBrush(Qt::NoBrush); 1497 | p.drawArc(adjustedRect, 45 * 16, 180 * 16); 1498 | p.setPen(QPen(dark, 1)); 1499 | p.drawArc(adjustedRect, 225 * 16, 180 * 16); 1500 | 1501 | // Draw cancel zone 1502 | if (innerRad > 0) 1503 | { 1504 | innerRect.setLeft(innerRect.left() + 1); 1505 | innerRect.setRight(innerRect.right() - 1); 1506 | innerRect.setTop(innerRect.top() + 1); 1507 | innerRect.setBottom(innerRect.bottom() - 1); 1508 | 1509 | p.setPen(QPen(dark, 1)); 1510 | p.drawArc(innerRect, 225 * 16, 180 * 16); 1511 | p.setPen(QPen(light, 1)); 1512 | p.drawArc(innerRect, 45 * 16, 180 * 16); 1513 | p.setPen(QPen(dark, 1)); 1514 | 1515 | p.setBrush(background); 1516 | 1517 | p.drawArc(innerRect, 225 * 16, 180 * 16); 1518 | p.setPen(QPen(light, 1)); 1519 | p.drawArc(innerRect, 45 * 16, 180 * 16); 1520 | } 1521 | 1522 | QRect r = innerRect; 1523 | innerRect.setLeft(r.center().x() + ((r.left() - r.center().x()) / 3) * 1); 1524 | innerRect.setRight(r.center().x() + ((r.right() - r.center().x()) / 3) * 1); 1525 | innerRect.setTop(r.center().y() + ((r.top() - r.center().y()) / 3) * 1); 1526 | innerRect.setBottom(r.center().y() + ((r.bottom() - r.center().y()) / 3) * 1); 1527 | 1528 | QColor text = hItem == -1 ? palette().highlightedText().color() : palette().text().color(); 1529 | 1530 | if (shaded) 1531 | { 1532 | text = text.dark(125); 1533 | } 1534 | 1535 | p.setPen(QPen(text, 2)); 1536 | p.drawLine(innerRect.topLeft(), innerRect.bottomRight()); 1537 | p.drawLine(innerRect.topRight(), innerRect.bottomLeft()); 1538 | 1539 | // Distance from edge of pie to tip of arrow. 1540 | int h1 = 2; 1541 | 1542 | // Height of arrow. 1543 | int h2 = 6; 1544 | 1545 | // Width of arrow in radians. 1546 | double w = 0.1; 1547 | 1548 | // Draw little arrows on the edge of the items that hide a 1549 | // submenu. 1550 | int i; 1551 | 1552 | for (i = 0; i < items.size(); ++i) 1553 | { 1554 | if (!items.at(i) || (items.at(i)->type() != SubMenu)) 1555 | { 1556 | continue; 1557 | } 1558 | 1559 | if (i == hItem) 1560 | { 1561 | p.setBrush(palette().highlightedText()); 1562 | 1563 | // Smooth edges of the arrow by drawing with a pen that 1564 | // has the average color of the foreground and the 1565 | // background. 1566 | QColor c = palette().highlightedText().color(); 1567 | QColor d = palette().highlight().color(); 1568 | p.setPen(QColor((c.red() + d.red()) / 2, 1569 | (c.green() + d.green()) / 2, 1570 | (c.blue() + d.blue()) / 2)); 1571 | } 1572 | else 1573 | { 1574 | p.setBrush(palette().text()); 1575 | 1576 | QColor c = palette().text().color(); 1577 | QColor d = palette().background().color(); 1578 | p.setPen(QColor((c.red() + d.red()) / 2, 1579 | (c.green() + d.green()) / 2, 1580 | (c.blue() + d.blue()) / 2)); 1581 | } 1582 | 1583 | if (shaded) 1584 | { 1585 | p.setPen(QPen(p.pen().color().dark(125))); 1586 | p.setBrush(QBrush(p.brush().color().dark(125))); 1587 | } 1588 | 1589 | double angle; 1590 | 1591 | if (count() == 1) 1592 | { 1593 | // If there is only one pie menu item, and the parent does 1594 | // not inherit QtPieMenu (qualifies as the rool level pie 1595 | // menu), then the arrow should be drawn straight 1596 | // north. If this is not the root menu however, the arrow 1597 | // should be drawn on the same axis that intersects both 1598 | // the parent's center and this menu's center, at the 1599 | // point where it intersects with this menu's outer 1600 | // radius. 1601 | if (!parent()->inherits("QtPieMenu")) 1602 | { 1603 | angle = 0; 1604 | } 1605 | else 1606 | { 1607 | QtPieMenu *parentPie = (QtPieMenu *)parent(); 1608 | QPoint ppos = mapToGlobal(parentPie->pos()); 1609 | ppos.setX(ppos.x() + parentPie->width() / 2); 1610 | ppos.setY(ppos.y() + parentPie->height() / 2); 1611 | 1612 | QPoint opos = mapToGlobal(pos()); 1613 | opos.setX(opos.x() + width() / 2); 1614 | opos.setY(opos.y() + height() / 2); 1615 | 1616 | double xxdist = (double)(opos.x() - ppos.x()); 1617 | double yydist = (double)(opos.y() - ppos.y()); 1618 | double rrad = sqrt(xxdist * xxdist + yydist * yydist); 1619 | angle = acos(xxdist / rrad); 1620 | 1621 | if (yydist > 0) 1622 | { 1623 | angle = TWOPI - angle; 1624 | } 1625 | } 1626 | } 1627 | else 1628 | { 1629 | double a = indexToAngle(i, count(), true); 1630 | double b = indexToAngle(i + 1, count(), true); 1631 | 1632 | if (b > a) 1633 | { 1634 | angle = (a + b) / 2; 1635 | } 1636 | else 1637 | { 1638 | angle = (a + b + TWOPI) / 2; 1639 | } 1640 | } 1641 | 1642 | double trad = (double)(outerRad - h1); 1643 | double tb = trad * cos(angle); 1644 | double tc = -trad *sin(angle); 1645 | double lrad = (double)(outerRad - h2); 1646 | double lb = lrad * cos(angle + w); 1647 | double lc = -lrad *sin(angle + w); 1648 | double rrad = (double)(outerRad - h2); 1649 | double rb = rrad * cos(angle - w); 1650 | double rc = -rrad *sin(angle - w); 1651 | QPolygon ar(3); 1652 | ar.setPoint(0, pix.rect().center().x() + (int)tb, pix.rect().center().y() + (int)tc); 1653 | ar.setPoint(1, pix.rect().center().x() + (int)rb, pix.rect().center().y() + (int)rc); 1654 | ar.setPoint(2, pix.rect().center().x() + (int)lb, pix.rect().center().y() + (int)lc); 1655 | 1656 | p.drawPolygon(ar); 1657 | } 1658 | 1659 | // Draw the text and/or icon on each slice. 1660 | for (i = 0; i < items.size(); ++i) 1661 | { 1662 | QString text = items.at(i)->text(); 1663 | double angle1 = indexToAngle(i, items.size(), true); 1664 | double angle2 = indexToAngle(i + 1, items.size(), true); 1665 | 1666 | if (angle2 < angle1) 1667 | { 1668 | angle2 += TWOPI; 1669 | } 1670 | 1671 | double angle = (angle1 + angle2) / 2.0; 1672 | double rad = (double)(innerRad + outerRad) / 2.0; 1673 | double b = rad * cos(angle); 1674 | double c = -rad *sin(angle); 1675 | QFontMetrics fontMetrics = p.fontMetrics(); 1676 | QRect boundingRect; 1677 | 1678 | if (!text.isEmpty()) 1679 | { 1680 | boundingRect = fontMetrics.boundingRect(text); 1681 | } 1682 | 1683 | int textWidth = boundingRect.width(); 1684 | QPixmap icon = items.at(i)->icon().pixmap(QSize(32, 32), 1685 | QIcon::Normal, 1686 | QIcon::On); 1687 | int x = pix.rect().center().x(); 1688 | int y = pix.rect().center().y(); 1689 | 1690 | // Draw icon 1691 | if (!items.at(i)->icon().isNull()) 1692 | { 1693 | QRect r(x + int(b) - (icon.width() + textWidth) / 2, 1694 | y + int(c) - icon.height() / 2, 1695 | icon.width(), icon.height()); 1696 | 1697 | if (shaded) 1698 | { 1699 | // Only paint every second pixel. 1700 | QBitmap bitmap(icon.width(), icon.height()); 1701 | QPainter mapper(&bitmap); 1702 | int xorer = 0; 1703 | 1704 | for (int b = 0; b < icon.height(); ++b) 1705 | { 1706 | xorer = b & 1; 1707 | 1708 | for (int a = 0; a < icon.width(); ++a) 1709 | { 1710 | mapper.setPen(xorer++ & 1 ? Qt::color1 : Qt::color0); 1711 | mapper.drawPoint(a, b); 1712 | } 1713 | } 1714 | 1715 | icon.setMask(bitmap); 1716 | } 1717 | 1718 | p.drawPixmap(r, icon); 1719 | } 1720 | 1721 | // Draw text 1722 | if (!text.isEmpty()) 1723 | { 1724 | QBrush pen; 1725 | QBrush brush; 1726 | 1727 | if (i == hItem) 1728 | { 1729 | if (highlightedItemPtr()->isEnabled()) 1730 | { 1731 | pen = palette().highlightedText(); 1732 | brush = palette().highlight(); 1733 | } 1734 | else 1735 | { 1736 | pen = palette().mid(); 1737 | brush = palette().background(); 1738 | } 1739 | } 1740 | else 1741 | { 1742 | if (items.at(i)->isEnabled()) 1743 | { 1744 | pen = palette().text(); 1745 | } 1746 | else 1747 | { 1748 | pen = palette().mid(); 1749 | } 1750 | 1751 | brush = palette().background(); 1752 | } 1753 | 1754 | if (shaded) 1755 | { 1756 | pen = QBrush(pen.color().dark(125)); 1757 | brush = QBrush(brush.color().dark(125)); 1758 | } 1759 | 1760 | // Draw text horizontally in center of slice. 1761 | double angle1 = indexToAngle(i, items.size(), true); 1762 | double angle2 = indexToAngle(i + 1, items.size(), true); 1763 | 1764 | if (angle2 < angle1) 1765 | { 1766 | angle2 += TWOPI; 1767 | } 1768 | 1769 | double angle = (angle1 + angle2) / 2.0; 1770 | double rad = (double)(innerRad + outerRad) / 2.0; 1771 | double b = rad * cos(angle); 1772 | double c = -rad *sin(angle); 1773 | 1774 | // Draw rich text, always centered. 1775 | QRect r(x + (int)b - (icon.width() + textWidth) / 2 + icon.width(), 1776 | y + (int)c - boundingRect.height() / 2, textWidth, boundingRect.height()); 1777 | 1778 | p.setPen(pen.color()); 1779 | p.setBrush(brush); 1780 | p.drawText(r.left(), r.top() + boundingRect.height(), text); 1781 | } 1782 | } 1783 | 1784 | QPainter p2(this); 1785 | p2.drawPixmap(QPoint(0, 0), pix); 1786 | } 1787 | 1788 | /*! \internal 1789 | 1790 | */ 1791 | void QtPieMenu::subMenuSelected() 1792 | { 1793 | emit activated(); 1794 | emit aboutToHide(); 1795 | 1796 | hide(); 1797 | } 1798 | 1799 | /*! \internal 1800 | 1801 | This slot is called when a submenu has been canceled. This pie 1802 | menu is unshaded, and an update is ordered. 1803 | */ 1804 | void QtPieMenu::subMenuCanceled() 1805 | { 1806 | shownSubMenu = -1; 1807 | 1808 | // reset motion counter. 1809 | motion = 0; 1810 | hItem = -2; 1811 | 1812 | // prevent delayed popup. 1813 | hoverTimer.stop(); 1814 | 1815 | shaded = false; 1816 | 1817 | // When a submenu is canceled, the following mouse release will 1818 | // go to us. 1819 | ignoreNextMouseReleaseIfNotDragging = true; 1820 | 1821 | update(); 1822 | } 1823 | 1824 | /*! \internal 1825 | 1826 | This slot is called when the whole menu has been canceled. This 1827 | pie menu is hidden, and an update is ordered. 1828 | */ 1829 | void QtPieMenu::allCanceled() 1830 | { 1831 | shownSubMenu = -1; 1832 | 1833 | motion = 0; 1834 | hItem = -2; 1835 | 1836 | // prevent delayed popup. 1837 | hoverTimer.stop(); 1838 | 1839 | shaded = false; 1840 | 1841 | ignoreNextMouseReleaseIfNotDragging = true; 1842 | 1843 | emit aboutToHide(); 1844 | hide(); 1845 | emit canceledAll(); 1846 | 1847 | update(); 1848 | } 1849 | 1850 | /*! \internal 1851 | 1852 | Given the item index \a item (ranging from 0 and up) and the total 1853 | number of items \a total, calculate the angle in radians from the 1854 | base [0,0] - [1,0] line of the pie to the end of the \a item 1855 | section. 1856 | */ 1857 | double QtPieMenu::indexToAngle(int item, int total, bool shifted) 1858 | { 1859 | // Wrap index. 1860 | item %= total; 1861 | 1862 | // Sum up all the weights, and find the base angle, or the angle 1863 | // of an item of weight 1. 1864 | double totalWeight = 0; 1865 | 1866 | if (items.size() != 0) 1867 | { 1868 | for (int i = 0; i < items.size(); ++i) 1869 | { 1870 | totalWeight += items.at(i)->weight(); 1871 | } 1872 | } 1873 | else 1874 | { 1875 | totalWeight = total; 1876 | } 1877 | 1878 | double baseAngle = TWOPI / (double)totalWeight; 1879 | 1880 | // Find the start angle of the first item. 1881 | double rad = PI / 2; 1882 | 1883 | if (shifted) 1884 | { 1885 | if (!items.isEmpty()) 1886 | { 1887 | rad -= ((double)items.at(0)->weight() * baseAngle) / 2.0; 1888 | } 1889 | else 1890 | { 1891 | rad -= baseAngle / 2.0; 1892 | } 1893 | } 1894 | 1895 | while (rad < 0) 1896 | { 1897 | rad += TWOPI; 1898 | } 1899 | 1900 | // Add the base angle multiplied by the items' weights until we 1901 | // reach the item we're queried for. 1902 | for (int index = 0; index < item; ++index) 1903 | { 1904 | if (index >= items.size()) 1905 | { 1906 | rad += baseAngle; 1907 | } 1908 | else 1909 | { 1910 | rad += double(items.at(index)->weight()) * baseAngle; 1911 | } 1912 | } 1913 | 1914 | // Adjust accordingly. 1915 | while (rad > TWOPI) 1916 | { 1917 | rad -= TWOPI; 1918 | } 1919 | 1920 | return rad; 1921 | } 1922 | 1923 | /*! \internal 1924 | 1925 | An inverse of indexToAngle(): Given an angle in radians from the 1926 | base [0,0] - [1,0] line of the pie, find the section in which this 1927 | angle resides. 1928 | */ 1929 | int QtPieMenu::angleToIndex(double angle, int total) 1930 | { 1931 | if (total == 0) 1932 | { 1933 | return -1; 1934 | } 1935 | 1936 | // Sum up all the weights, and find the base angle, or the angle 1937 | // of an item of weight 1. 1938 | double totalWeight = 0; 1939 | 1940 | for (int i = 0; i < items.size(); ++i) 1941 | { 1942 | totalWeight += items.at(i)->weight(); 1943 | } 1944 | 1945 | double baseAngle = TWOPI / (double)totalWeight; 1946 | 1947 | // Find the start angle of the first item. 1948 | double rad = PI / 2; 1949 | 1950 | if (!items.isEmpty()) 1951 | { 1952 | rad -= (double(items.at(0)->weight()) * baseAngle) / 2.0; 1953 | } 1954 | 1955 | while (rad < 0) 1956 | { 1957 | rad += TWOPI; 1958 | } 1959 | 1960 | // Add the base angle multiplied by the items' weights until we 1961 | // reach the item we're queried for. 1962 | double oldrad = rad; 1963 | 1964 | for (int index = 0; index < items.size(); ++index) 1965 | { 1966 | rad += double(items.at(index)->weight()) * baseAngle; 1967 | 1968 | if ((angle > oldrad) && (angle <= rad)) 1969 | { 1970 | return index; 1971 | } 1972 | else if ((angle + TWOPI > oldrad) && (angle + TWOPI <= rad)) 1973 | { 1974 | return index; 1975 | } 1976 | 1977 | oldrad = rad; 1978 | } 1979 | 1980 | return -1; 1981 | } 1982 | 1983 | /*! 1984 | Returns the index of the item that is highlighted when the cursor 1985 | is at position \a pos. 1986 | 1987 | This function is reimplemented if a subclass of QtPieMenu provides 1988 | a new item layout. 1989 | */ 1990 | int QtPieMenu::indexAt(const QPoint &pos) 1991 | { 1992 | return angleToIndex(angleAt(pos), items.size()); 1993 | } 1994 | 1995 | /*! 1996 | Returns the distance from \a pos to the center of the pie menu. 1997 | */ 1998 | double QtPieMenu::radiusAt(const QPoint &pos) const 1999 | { 2000 | double mousexdist = pos.x() - rect().center().x(); 2001 | double mouseydist = pos.y() - rect().center().y(); 2002 | 2003 | return sqrt(mousexdist * mousexdist + mouseydist * mouseydist); 2004 | } 2005 | 2006 | /*! 2007 | With origin set to the center of the pie, this function returns the 2008 | angle in radians between the line that starts at (0,0) and ends at 2009 | (1,0) and the line that starts at (0,0) and ends at \a pos. 2010 | */ 2011 | double QtPieMenu::angleAt(const QPoint &pos) const 2012 | { 2013 | double mousexdist = pos.x() - rect().center().x(); 2014 | double mouseydist = pos.y() - rect().center().y(); 2015 | double mouserad = sqrt(mousexdist * mousexdist + mouseydist * mouseydist); 2016 | double angle = acos(mousexdist / mouserad); 2017 | 2018 | if (mouseydist >= 0) 2019 | { 2020 | angle = TWOPI - angle; 2021 | } 2022 | 2023 | return angle; 2024 | } 2025 | 2026 | /*! \internal 2027 | 2028 | If the mouse is being dragged with the right mouse button down, 2029 | returns \e true. Otherwise returns \e false. 2030 | */ 2031 | bool QtPieMenu::dragging() const 2032 | { 2033 | return mouseButtonStatus == Pressed && motion > 6; 2034 | } 2035 | 2036 | /*! \internal 2037 | 2038 | If a submenu is currently shown, hide it. 2039 | */ 2040 | void QtPieMenu::hideShownSubMenu() 2041 | { 2042 | if (shownSubMenu != -1) 2043 | { 2044 | QtPieMenu *menu = static_cast(items[shownSubMenu]); 2045 | 2046 | if (!menu) 2047 | { 2048 | // ### Dead code 2049 | return; 2050 | } 2051 | 2052 | emit menu->aboutToHide(); 2053 | menu->hide(); 2054 | 2055 | shownSubMenu = -1; 2056 | shaded = false; 2057 | update(); 2058 | } 2059 | } 2060 | 2061 | /*! 2062 | Returns the index of the item that is currently selected, or -1 if 2063 | no item is selected. 2064 | */ 2065 | int QtPieMenu::highlightedItem() 2066 | { 2067 | return hItem; 2068 | } 2069 | 2070 | /*! \internal 2071 | Deletes the object at index \a index in the internal list. If \a index is -1 2072 | then all objects in the list will be deleted. 2073 | */ 2074 | void QtPieMenu::deleteItems(int index) 2075 | { 2076 | if (index >= 0) 2077 | { 2078 | delete items[index]; 2079 | items.removeAt(index); 2080 | } 2081 | else 2082 | { 2083 | for (int i = 0; i < items.size(); ++i) 2084 | { 2085 | delete items[i]; 2086 | } 2087 | 2088 | items.clear(); 2089 | } 2090 | } 2091 | 2092 | /*! \internal 2093 | Returns a pointer to the item that is currently selected, or 0 if 2094 | none is selected. 2095 | */ 2096 | QtPieItem * QtPieMenu::highlightedItemPtr() 2097 | { 2098 | if (hItem < 0) 2099 | { 2100 | return 0; 2101 | } 2102 | else 2103 | { 2104 | return items[hItem]; 2105 | } 2106 | } 2107 | 2108 | /*! \internal 2109 | 2110 | Returns a pointer to the item that is currently selected, or 0 if 2111 | none is selected. 2112 | */ 2113 | QtPieItem * QtPieMenu::shownItemPtr() 2114 | { 2115 | if (shownSubMenu == -1) 2116 | { 2117 | return 0; 2118 | } 2119 | else 2120 | { 2121 | return items[shownSubMenu]; 2122 | } 2123 | } 2124 | 2125 | /*! 2126 | Returns true if the menu is shaded (inactive); otherwise returns false. 2127 | */ 2128 | bool QtPieMenu::isShaded() const 2129 | { 2130 | return shaded; 2131 | } 2132 | 2133 | /*! 2134 | Returns the type of the item at position \a index, or 2135 | QtPieItem::Invalid if there is no item at position \a index. 2136 | */ 2137 | int QtPieMenu::itemType(int index) const 2138 | { 2139 | if (!itemAt(index)) 2140 | { 2141 | return Invalid; 2142 | } 2143 | 2144 | return (ItemType)itemAt(index)->type(); 2145 | } 2146 | -------------------------------------------------------------------------------- /piemenu/qtpiemenu.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 4 | ** All rights reserved. 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) 6 | ** 7 | ** This file is part of a Qt Solutions component. 8 | ** 9 | ** Commercial Usage 10 | ** Licensees holding valid Qt Commercial licenses may use this file in 11 | ** accordance with the Qt Solutions Commercial License Agreement provided 12 | ** with the Software or, alternatively, in accordance with the terms 13 | ** contained in a written agreement between you and Nokia. 14 | ** 15 | ** GNU Lesser General Public License Usage 16 | ** Alternatively, this file may be used under the terms of the GNU Lesser 17 | ** General Public License version 2.1 as published by the Free Software 18 | ** Foundation and appearing in the file LICENSE.LGPL included in the 19 | ** packaging of this file. Please review the following information to 20 | ** ensure the GNU Lesser General Public License version 2.1 requirements 21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 22 | ** 23 | ** In addition, as a special exception, Nokia gives you certain 24 | ** additional rights. These rights are described in the Nokia Qt LGPL 25 | ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this 26 | ** package. 27 | ** 28 | ** GNU General Public License Usage 29 | ** Alternatively, this file may be used under the terms of the GNU 30 | ** General Public License version 3.0 as published by the Free Software 31 | ** Foundation and appearing in the file LICENSE.GPL included in the 32 | ** packaging of this file. Please review the following information to 33 | ** ensure the GNU General Public License version 3.0 requirements will be 34 | ** met: http://www.gnu.org/copyleft/gpl.html. 35 | ** 36 | ** Please note Third Party Software included with Qt Solutions may impose 37 | ** additional restrictions and it is the user's responsibility to ensure 38 | ** that they have met the licensing requirements of the GPL, LGPL, or Qt 39 | ** Solutions Commercial license and the relevant license of the Third 40 | ** Party Software they are using. 41 | ** 42 | ** If you are unsure which license is appropriate for your use, please 43 | ** contact Nokia at qt-info@nokia.com. 44 | ** 45 | ****************************************************************************/ 46 | 47 | // depot/addons/main/widgets/qtpiemenu/src/qtpiemenu.h#31 - edit change 282028 (text) 48 | #ifndef QTPIEMENU_H 49 | #define QTPIEMENU_H 50 | 51 | #include "qtpieitem.h" 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | class QPainter; 63 | class QtPieMenu; 64 | class QBitmap; 65 | class QPaintEvent; 66 | class QMouseEvent; 67 | class QShowEvent; 68 | class QHideEvent; 69 | class QKeyEvent; 70 | 71 | #if defined (Q_WS_WIN) 72 | # if !defined (QT_QTPIEMENU_EXPORT) && !defined (QT_QTPIEMENU_IMPORT) 73 | # define QT_QTPIEMENU_EXPORT 74 | # elif defined (QT_QTPIEMENU_IMPORT) 75 | # if defined (QT_QTPIEMENU_EXPORT) 76 | # undef QT_QTPIEMENU_EXPORT 77 | # endif 78 | # define QT_QTPIEMENU_EXPORT __declspec(dllimport) 79 | # elif defined (QT_QTPIEMENU_EXPORT) 80 | # undef QT_QTPIEMENU_EXPORT 81 | # define QT_QTPIEMENU_EXPORT __declspec(dllexport) 82 | # endif 83 | #else 84 | # define QT_QTPIEMENU_EXPORT 85 | #endif 86 | 87 | class QT_QTPIEMENU_EXPORT QtPieMenu: public QtPieItem 88 | { 89 | Q_OBJECT 90 | Q_PROPERTY(int innerRadius READ innerRadius WRITE setInnerRadius) 91 | Q_PROPERTY(int outerRadius READ outerRadius WRITE setOuterRadius) 92 | 93 | public: 94 | QtPieMenu(const QString &title, QWidget *parent = 0, 95 | const char *name = 0, 96 | uint innerRad = 15, uint outerRad = 75); 97 | 98 | QtPieMenu(const QIcon &icon, QWidget *parent = 0, 99 | const char *name = 0, 100 | uint innerRad = 15, uint outerRad = 75); 101 | 102 | QtPieMenu(const QIcon &icon, const QString &title, 103 | QWidget *parent = 0, const char *name = 0, 104 | uint innerRad = 15, uint outerRad = 75); 105 | 106 | virtual ~QtPieMenu(); 107 | 108 | void setItemText(const QString &text, int index); 109 | 110 | QString itemText(int index) const; 111 | 112 | void setItemIcon(const QIcon &icon, int index); 113 | 114 | QIcon itemIcon(int index) const; 115 | 116 | void setItemWeight(int weight, int index); 117 | 118 | int itemWeight(int index) const; 119 | 120 | void setItemEnabled(bool enabled, int index); 121 | 122 | bool isItemEnabled(int index) const; 123 | 124 | void popup(const QPoint &pos); 125 | 126 | int count() const; 127 | 128 | virtual int indexAt(const QPoint &pos); 129 | 130 | void insertItem(const QIcon &icon, 131 | QObject *receiver, const char *member, int index = -1); 132 | 133 | void insertItem(const QString &text, 134 | QObject *receiver, const char *member, int index = -1); 135 | 136 | void insertItem(const QIcon &icon, const QString &text, 137 | QObject *receiver, const char *member, int index = -1); 138 | 139 | void insertItem(QtPieMenu *item, int index = -1); 140 | 141 | QtPieMenu * subMenuAt(int index) const; 142 | 143 | void removeItemAt(int index); 144 | 145 | void clear(); 146 | 147 | void setHighlightedItem(int index); 148 | 149 | int highlightedItem(); 150 | 151 | void setInnerRadius(int r); 152 | 153 | int innerRadius() const; 154 | 155 | void setOuterRadius(int r); 156 | 157 | int outerRadius() const; 158 | 159 | bool isShaded() const; 160 | 161 | enum ItemType 162 | { 163 | Invalid, 164 | SubMenu, 165 | Action 166 | }; 167 | 168 | int itemType(int index) const; 169 | 170 | QSize sizeHint() const; 171 | 172 | Q_SIGNALS: 173 | 174 | void activated(); 175 | 176 | void activated(int); 177 | 178 | void highlighted(int); 179 | 180 | void canceled(); 181 | 182 | void canceledAll(); 183 | 184 | void aboutToShow(); 185 | 186 | void aboutToHide(); 187 | 188 | protected Q_SLOTS: 189 | void hoverAlert(); 190 | 191 | void subMenuCanceled(); 192 | 193 | void subMenuSelected(); 194 | 195 | void allCanceled(); 196 | 197 | protected: 198 | virtual void deleteItems(int index = -1); 199 | 200 | virtual void generateMask(QBitmap *mask); 201 | 202 | virtual void reposition(); 203 | 204 | double radiusAt(const QPoint &pos) const; 205 | 206 | double angleAt(const QPoint &pos) const; 207 | 208 | double indexToAngle(int item, int total, bool shifted = false); 209 | 210 | int angleToIndex(double rad, int total); 211 | 212 | void init(const QString &name); 213 | 214 | void paintEvent(QPaintEvent *); 215 | 216 | void mouseMoveEvent(QMouseEvent *); 217 | 218 | void mousePressEvent(QMouseEvent *); 219 | 220 | void mouseReleaseEvent(QMouseEvent *); 221 | 222 | void keyPressEvent(QKeyEvent *); 223 | 224 | void showEvent(QShowEvent *); 225 | 226 | void hideEvent(QHideEvent *); 227 | 228 | enum ActivateReason 229 | { 230 | MousePress = 0x01, 231 | MouseRelease = 0x02, 232 | KeyPress = 0x04, 233 | Hovering = 0x08, 234 | Dragging = 0x10 235 | }; 236 | 237 | void activateItem(int reason); 238 | 239 | bool isTopLevelMenu() const; 240 | 241 | bool dragging() const; 242 | 243 | void hideShownSubMenu(); 244 | 245 | int type() const; 246 | 247 | QPixmap pix; 248 | 249 | private: 250 | QtPieItem* highlightedItemPtr(); 251 | 252 | QtPieItem* shownItemPtr(); 253 | 254 | QtPieItem* itemAt(int index) const; 255 | 256 | QList items; 257 | 258 | int motion; 259 | QTimer hoverTimer; 260 | QPoint lastMousePos; 261 | int innerRad, outerRad; 262 | int hItem; 263 | int shownSubMenu; 264 | bool shaded; 265 | bool ignoreNextMouseReleaseIfNotDragging; 266 | int lastActivatedItem; 267 | bool syncMenu; 268 | bool hasMask; 269 | }; 270 | 271 | #endif 272 | -------------------------------------------------------------------------------- /piemenu/sectormenu.cpp: -------------------------------------------------------------------------------- 1 | #include "sectormenu.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | SectorMenu::SectorMenu(const QString &title, 8 | QWidget *parent): 9 | QtPieMenu(title, parent) 10 | { 11 | int depth = menuDepth(); 12 | 13 | setInnerRadius(depth * 65); 14 | setOuterRadius((depth + 1) * 65); 15 | setFixedSize(2 * outerRadius() + 1, 16 | 2 * outerRadius() + 1); 17 | 18 | 19 | gradientBack = new QRadialGradient(rect().center().x(), rect().center().y() - 10, rect().width() / 2.0); // diagonal gradient from top-left to bottom-right 20 | gradientBack->setColorAt(0, QColor(174, 184, 191)); 21 | gradientBack->setColorAt(0.25, QColor(230, 230, 230)); 22 | gradientBack->setColorAt(0.90, QColor(239, 238, 239)); 23 | gradientBack->setColorAt(1, QColor(220, 220, 220)); 24 | 25 | gradientLight = new QRadialGradient(rect().center().x(), rect().center().y() - 10, rect().width() / 2.0); // diagonal gradient from top-left to bottom-right 26 | gradientLight->setColorAt(0, QColor(253, 99, 70)); 27 | gradientLight->setColorAt(1, QColor(253, 184, 92)); 28 | } 29 | 30 | int SectorMenu::menuDepth() const 31 | { 32 | const QObject *pie = this; 33 | int depth = 0; 34 | 35 | while (pie->parent() 36 | && pie->parent()->inherits("SectorMenu")) 37 | { 38 | pie = pie->parent(); 39 | ++depth; 40 | } 41 | 42 | return depth; 43 | } 44 | 45 | void SectorMenu::generateMask(QBitmap *mask) 46 | { 47 | if (menuDepth() == 0) 48 | { 49 | startAngle = 45 * 16; 50 | arcLength = 360 * 16; 51 | } 52 | else 53 | { 54 | SectorMenu *parentPie = (SectorMenu *)parent(); 55 | 56 | for (int i = 0; i < parentPie->count(); ++i) 57 | { 58 | if (parentPie->subMenuAt(i) == this) 59 | { 60 | arcLength = parentPie->arcLength / parentPie->count(); 61 | startAngle = (45 * 16) + (arcLength * i); 62 | break; 63 | } 64 | } 65 | } 66 | 67 | QPainter painter(mask); 68 | painter.setRenderHint(QPainter::Antialiasing, true); 69 | 70 | painter.setPen(QPen(Qt::color1, 2)); 71 | painter.setBrush(Qt::color1); 72 | painter.drawPie(0, 0, 73 | outerRadius() * 2, outerRadius() * 2, 74 | startAngle, arcLength); 75 | 76 | if (innerRadius() > 0) 77 | { 78 | // painter.fillRect(rect(), Qt::color0); 79 | QPoint center = rect().center(); 80 | painter.setPen(Qt::color0); 81 | painter.setBrush(Qt::color0); 82 | painter.drawPie(center.x() - innerRadius(), 83 | center.y() - innerRadius(), 84 | innerRadius() * 2, 85 | innerRadius() * 2, 86 | 0, 360 * 16); 87 | } 88 | } 89 | 90 | int SectorMenu::indexAt(const QPoint &pos) 91 | { 92 | if (count() == 0) 93 | { 94 | return -1; 95 | } 96 | 97 | int sliceSize = arcLength / count(); 98 | 99 | if (sliceSize == 0) 100 | { 101 | return -1; 102 | } 103 | 104 | int angle = (int)((angleAt(pos) * 360.0 * 16.0) / (2.0 * M_PI)); 105 | 106 | if (angle - startAngle < 0) 107 | { 108 | angle += 16 * 360; 109 | } 110 | 111 | angle -= startAngle; 112 | 113 | int sector = angle / sliceSize; 114 | 115 | if ((sector < 0) || (sector >= count())) 116 | { 117 | return -1; 118 | } 119 | 120 | return sector; 121 | } 122 | 123 | void SectorMenu::reposition() 124 | { 125 | if (menuDepth() > 0) 126 | { 127 | SectorMenu *parentPie = (SectorMenu *)parent(); 128 | QPoint center = parentPie->geometry().center(); 129 | move(center.x() - outerRadius(), 130 | center.y() - outerRadius()); 131 | } 132 | } 133 | 134 | void SectorMenu::paintEvent(QPaintEvent *) 135 | { 136 | QPainter painter(this); 137 | 138 | painter.setRenderHint(QPainter::Antialiasing, true); 139 | 140 | QFontMetrics metrics(font()); 141 | QPoint center = rect().center(); 142 | int sliceSize = arcLength / count(); 143 | 144 | for (int i = 0; i < count(); ++i) 145 | { 146 | if (i == highlightedItem()) 147 | { 148 | painter.setPen(palette().highlightedText().color()); 149 | painter.setBrush(*gradientLight); 150 | } 151 | else 152 | { 153 | painter.setPen(palette().foreground().color()); 154 | painter.setBrush(*gradientBack); 155 | } 156 | 157 | painter.drawPie(0, 0, 158 | outerRadius() * 2, 159 | outerRadius() * 2, 160 | startAngle + sliceSize * i, 161 | sliceSize); 162 | 163 | // painter.setPen(palette().foreground().color()); 164 | // painter.setBrush(Qt::darkGray); 165 | 166 | // painter.drawPie(0, 0, 167 | // outerRadius() * 2, 168 | // outerRadius() * 2, 169 | // startAngle + sliceSize * i, 170 | // sliceSize); 171 | 172 | double rad = (innerRadius() + outerRadius()) 173 | / 2.0; 174 | double slice = sliceSize * M_PI / (360.0 * 8.0); 175 | double angle = startAngle * M_PI / (360.0 * 8.0); 176 | angle += slice * (i + 0.5); 177 | 178 | int x = (int)(cos(angle) * rad); 179 | int y = (int)(-sin(angle) * rad); 180 | 181 | painter.drawText(center.x() + x 182 | - metrics.width(itemText(i)) / 2, 183 | center.y() + y, itemText(i)); 184 | } 185 | 186 | painter.setPen(palette().foreground().color()); 187 | painter.setBrush(Qt::darkGray); 188 | painter.drawEllipse(center, 15, 15); 189 | 190 | painter.setPen(palette().foreground().color()); 191 | painter.setBrush(*gradientBack); 192 | painter.drawEllipse(center, 10, 10); 193 | } 194 | -------------------------------------------------------------------------------- /piemenu/sectormenu.h: -------------------------------------------------------------------------------- 1 | #ifndef SECTORMENU_H 2 | #define SECTORMENU_H 3 | 4 | #include "qtpiemenu.h" 5 | 6 | class SectorMenu: public QtPieMenu 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | SectorMenu(const QString &title, QWidget *parent); 12 | 13 | int indexAt(const QPoint &pos); 14 | 15 | protected: 16 | void generateMask(QBitmap *mask); 17 | 18 | void reposition(); 19 | 20 | void paintEvent(QPaintEvent *event); 21 | 22 | int menuDepth() const; 23 | 24 | private: 25 | int startAngle; 26 | int arcLength; 27 | QRadialGradient *gradientCenter; 28 | QRadialGradient *gradientBack; 29 | QRadialGradient *gradientLight; 30 | }; 31 | 32 | #endif // SECTORMENU_H 33 | -------------------------------------------------------------------------------- /screenshot/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mola/QtPieMenu/0705adf6cb46c8095781858691e86d1209cd2044/screenshot/screenshot_1.png -------------------------------------------------------------------------------- /screenshot/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mola/QtPieMenu/0705adf6cb46c8095781858691e86d1209cd2044/screenshot/screenshot_2.png -------------------------------------------------------------------------------- /test-piemenu.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-11-10T13:28:20 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui widgets 8 | 9 | TARGET = test-piemenu 10 | TEMPLATE = app 11 | 12 | # The following define makes your compiler emit warnings if you use 13 | # any feature of Qt which has been marked as deprecated (the exact warnings 14 | # depend on your compiler). Please consult the documentation of the 15 | # deprecated API in order to know how to port your code away from it. 16 | DEFINES += QT_DEPRECATED_WARNINGS 17 | 18 | # You can also make your code fail to compile if you use deprecated APIs. 19 | # In order to do so, uncomment the following line. 20 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 21 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 22 | 23 | CONFIG += c++11 24 | 25 | SOURCES += \ 26 | main.cpp \ 27 | mainwindow.cpp \ 28 | piemenu/qtpieaction.cpp \ 29 | piemenu/qtpieitem.cpp \ 30 | piemenu/qtpiemenu.cpp \ 31 | piemenu/sectormenu.cpp 32 | 33 | HEADERS += \ 34 | mainwindow.h \ 35 | piemenu/qtpieaction.h \ 36 | piemenu/QtPieItem \ 37 | piemenu/qtpieitem.h \ 38 | piemenu/QtPieMenu \ 39 | piemenu/qtpiemenu.h \ 40 | piemenu/sectormenu.h 41 | 42 | FORMS += \ 43 | mainwindow.ui 44 | 45 | # Default rules for deployment. 46 | qnx: target.path = /tmp/$${TARGET}/bin 47 | else: unix:!android: target.path = /opt/$${TARGET}/bin 48 | !isEmpty(target.path): INSTALLS += target 49 | --------------------------------------------------------------------------------