1 | /* |
2 | This file is part of the KDE project. |
3 | |
4 | Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr> |
5 | Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com> |
6 | |
7 | Permission is hereby granted, free of charge, to any person obtaining a |
8 | copy of this software and associated documentation files (the "Software"), |
9 | to deal in the Software without restriction, including without limitation |
10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, |
11 | and/or sell copies of the Software, and to permit persons to whom the |
12 | Software is furnished to do so, subject to the following conditions: |
13 | |
14 | The above copyright notice and this permission notice shall be included in |
15 | all copies or substantial portions of the Software. |
16 | |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
23 | DEALINGS IN THE SOFTWARE. |
24 | */ |
25 | |
26 | #include "menuwidget.h" |
27 | |
28 | #include <QMenu> |
29 | #include <QDesktopWidget> |
30 | #include <QGraphicsView> |
31 | #include <QGraphicsLinearLayout> |
32 | |
33 | #include <KWindowSystem> |
34 | #include <KDebug> |
35 | #include <KApplication> |
36 | |
37 | MenuWidget::(QGraphicsView *view) : |
38 | QGraphicsWidget(), |
39 | m_mouseTimer(new QTimer(this)), |
40 | m_actionTimer(new QTimer(this)), |
41 | m_view(view), |
42 | m_layout(new QGraphicsLinearLayout(this)), |
43 | m_currentButton(0), |
44 | m_contentBottomMargin(0), |
45 | m_mousePosition(-1, -1), |
46 | m_visibleMenu(0), |
47 | m_menu(0) |
48 | { |
49 | connect(m_actionTimer, SIGNAL(timeout()), SLOT(slotUpdateActions())); |
50 | connect(m_mouseTimer, SIGNAL(timeout()), SLOT(slotCheckActiveItem())); |
51 | } |
52 | |
53 | MenuWidget::() |
54 | { |
55 | while (!m_buttons.isEmpty()) { |
56 | delete m_buttons.front(); |
57 | m_buttons.pop_front(); |
58 | } |
59 | } |
60 | |
61 | void MenuWidget::(QMenu *) |
62 | { |
63 | if (m_menu) { |
64 | disconnect(m_menu, SIGNAL(destroyed()), this, SLOT(slotMenuDestroyed())); |
65 | m_menu->removeEventFilter(this); |
66 | } |
67 | if (menu) { |
68 | if (m_mouseTimer->isActive()) { |
69 | m_mouseTimer->stop(); |
70 | } |
71 | m_visibleMenu = 0; |
72 | m_menu = menu; |
73 | connect(m_menu, SIGNAL(destroyed()), SLOT(slotMenuDestroyed()), Qt::UniqueConnection); |
74 | m_menu->installEventFilter(this); |
75 | slotUpdateActions(); |
76 | } |
77 | } |
78 | |
79 | void MenuWidget::() |
80 | { |
81 | MenuButton* button = 0; |
82 | |
83 | if (!m_menu) { |
84 | return; |
85 | } |
86 | |
87 | foreach (QAction* action, m_menu->actions()) |
88 | { |
89 | button = createButton(action); |
90 | if (button) { |
91 | m_layout->addItem(button); |
92 | button->setMenu(action->menu()); |
93 | m_buttons << button; |
94 | } |
95 | } |
96 | |
97 | //Assume all buttons have same margins |
98 | if (button) { |
99 | m_contentBottomMargin = button->bottomMargin(); |
100 | } |
101 | } |
102 | |
103 | bool MenuWidget::(QObject* object, QEvent* event) |
104 | { |
105 | bool filtered; |
106 | if (object == m_menu) { |
107 | filtered = menuEventFilter(event); |
108 | } else { |
109 | filtered = subMenuEventFilter(static_cast<QMenu*>(object), event); |
110 | } |
111 | return filtered ? true : QGraphicsWidget::eventFilter(object, event); |
112 | } |
113 | |
114 | bool MenuWidget::(QEvent* event) |
115 | { |
116 | switch (event->type()) { |
117 | case QEvent::ActionAdded: |
118 | case QEvent::ActionRemoved: |
119 | case QEvent::ActionChanged: |
120 | // Try to limit layout updates |
121 | m_actionTimer->start(500); |
122 | break; |
123 | default: |
124 | break; |
125 | } |
126 | return false; |
127 | } |
128 | |
129 | bool MenuWidget::(QObject* object, QEvent* event) |
130 | { |
131 | QMenu * = static_cast<QMenu*>(object); |
132 | |
133 | if (event->type() == QEvent::KeyPress) { |
134 | menu->removeEventFilter(this); |
135 | QApplication::sendEvent(menu, event); |
136 | menu->installEventFilter(this); |
137 | if (!event->isAccepted()) { |
138 | QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); |
139 | switch (keyEvent->key()) { |
140 | case Qt::Key_Left: |
141 | showLeftRightMenu(false); |
142 | break; |
143 | case Qt::Key_Right: |
144 | showLeftRightMenu(true); |
145 | break; |
146 | case Qt::Key_Escape: |
147 | menu->hide(); |
148 | break; |
149 | default: |
150 | break; |
151 | } |
152 | } |
153 | return true; |
154 | } |
155 | return false; |
156 | } |
157 | |
158 | void MenuWidget::() |
159 | { |
160 | m_menu = 0; |
161 | m_visibleMenu = 0; |
162 | m_currentButton = 0; |
163 | } |
164 | |
165 | void MenuWidget::() |
166 | { |
167 | MenuButton* buttonBelow = 0; |
168 | QPoint pos = m_view->mapFromGlobal(QCursor::pos()); |
169 | QGraphicsItem* item = m_view->itemAt(pos); |
170 | |
171 | if (pos == m_mousePosition) { |
172 | return; |
173 | } else { |
174 | m_mousePosition = pos; |
175 | } |
176 | |
177 | if (item) { |
178 | buttonBelow = qobject_cast<MenuButton*>(item->toGraphicsObject()); |
179 | } |
180 | |
181 | if (!buttonBelow) { |
182 | return; |
183 | } |
184 | |
185 | if (buttonBelow != m_currentButton) { |
186 | if (m_currentButton && m_currentButton->nativeWidget()) { |
187 | m_currentButton->nativeWidget()->setDown(false); |
188 | m_currentButton->setHovered(false); |
189 | } |
190 | m_currentButton = buttonBelow; |
191 | if (m_currentButton->nativeWidget()) { |
192 | m_currentButton->nativeWidget()->setDown(true); |
193 | } |
194 | m_visibleMenu = showMenu(); |
195 | } |
196 | } |
197 | |
198 | void MenuWidget::() |
199 | { |
200 | if (m_currentButton && m_currentButton->nativeWidget()) { |
201 | m_currentButton->nativeWidget()->setDown(false); |
202 | } |
203 | |
204 | if (m_mouseTimer->isActive()) { |
205 | m_mouseTimer->stop(); |
206 | } |
207 | m_visibleMenu = 0; |
208 | emit aboutToHide(); |
209 | } |
210 | |
211 | void MenuWidget::() |
212 | { |
213 | m_currentButton = qobject_cast<MenuButton*>(sender()); |
214 | |
215 | if (m_currentButton && m_currentButton->nativeWidget()) { |
216 | m_currentButton->nativeWidget()->setDown(true); |
217 | } |
218 | m_visibleMenu = showMenu(); |
219 | // Start auto navigation after click |
220 | if (!m_mouseTimer->isActive()) |
221 | m_mouseTimer->start(100); |
222 | } |
223 | |
224 | void MenuWidget::() |
225 | { |
226 | if (m_visibleMenu) { |
227 | return; // Later |
228 | } |
229 | |
230 | m_actionTimer->stop(); |
231 | m_currentButton = 0; |
232 | foreach (MenuButton *button, m_buttons) { |
233 | disconnect(button, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); |
234 | m_layout->removeItem(button); |
235 | button->hide(); |
236 | m_buttons.removeOne(button); |
237 | delete button; |
238 | } |
239 | initLayout(); |
240 | // Menu may be empty on application startup |
241 | // slotUpdateActions will be called later by eventFilter() |
242 | if (m_menu && m_menu->actions().length()) { |
243 | emit needResize(); |
244 | } |
245 | } |
246 | |
247 | void MenuWidget::(QAction *action) |
248 | { |
249 | if (!m_menu) { |
250 | return; |
251 | } |
252 | |
253 | m_currentButton = m_buttons.first(); |
254 | |
255 | if (action) { |
256 | QMenu *; |
257 | int i = 0; |
258 | foreach (MenuButton *button, m_buttons) { |
259 | menu = m_menu->actions()[i]->menu(); |
260 | if (menu && menu == action->menu()) { |
261 | m_currentButton = button; |
262 | break; |
263 | } |
264 | if (++i >= m_menu->actions().length()) { |
265 | break; |
266 | } |
267 | } |
268 | } |
269 | m_currentButton->nativeWidget()->animateClick(); |
270 | } |
271 | |
272 | void MenuWidget::() |
273 | { |
274 | if (m_mouseTimer->isActive()) { |
275 | m_mouseTimer->stop(); |
276 | } |
277 | QGraphicsWidget::hide(); |
278 | } |
279 | |
280 | MenuButton* MenuWidget::(QAction *action) |
281 | { |
282 | if( action->isSeparator() || !action->menu() || !action->isVisible()) { |
283 | return 0; |
284 | } |
285 | |
286 | action->setShortcut(QKeySequence()); |
287 | MenuButton *button = new MenuButton(this); |
288 | button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); |
289 | button->setText(action->text()); |
290 | connect(button, SIGNAL(clicked()), SLOT(slotButtonClicked())); |
291 | return button; |
292 | } |
293 | |
294 | QMenu* MenuWidget::() |
295 | { |
296 | QMenu * = 0; |
297 | |
298 | if (m_visibleMenu) { |
299 | disconnect(m_visibleMenu, SIGNAL(aboutToHide()), this, SLOT(slotMenuAboutToHide())); |
300 | m_visibleMenu->hide(); |
301 | } |
302 | |
303 | if (m_currentButton && m_menu) { |
304 | menu = m_currentButton->menu(); |
305 | } |
306 | |
307 | // Last chance to get menu |
308 | // Some applications like Firefox have empties menus on layout updates |
309 | // They should populate this menu later but in fact, they use another object |
310 | // So, we check here directly the button name, may fail with menubar with buttons with same name (test apps) |
311 | if (menu && menu->actions().length() == 0) { |
312 | foreach (QAction *action, m_menu->actions()) { |
313 | if (action->text() == m_currentButton->text()) { |
314 | menu = action->menu(); |
315 | break; |
316 | } |
317 | } |
318 | } |
319 | |
320 | if (menu) { |
321 | QPoint globalPos = m_view->mapToGlobal(QPoint(0,0)); |
322 | QPointF parentPos = m_currentButton->mapFromParent(QPoint(0,0)); |
323 | QRect screen = KApplication::desktop()->screenGeometry(); |
324 | int x = globalPos.x() - parentPos.x(); |
325 | int y = globalPos.y() + m_currentButton->size().height() - parentPos.y(); |
326 | |
327 | menu->popup(QPoint(x, y)); |
328 | |
329 | // Fix offscreen menu |
330 | if (menu->size().height() + y > screen.height() + screen.y()) { |
331 | y = globalPos.y() - parentPos.y() - menu->size().height(); |
332 | if (menu->size().width() + x > screen.width() + screen.x()) |
333 | x = screen.width() + screen.x() - menu->size().width(); |
334 | else if (menu->size().width() + x < screen.x()) |
335 | x = screen.x(); |
336 | menu->move(x, y); |
337 | } |
338 | |
339 | connect(menu, SIGNAL(aboutToHide()), this, SLOT(slotMenuAboutToHide())); |
340 | |
341 | installEventFilterForAll(menu, this); |
342 | } |
343 | return menu; |
344 | } |
345 | |
346 | void MenuWidget::(bool next) |
347 | { |
348 | if (!m_currentButton) { |
349 | return; |
350 | } |
351 | |
352 | int index = m_buttons.indexOf(m_currentButton); |
353 | if (index == -1) { |
354 | kWarning() << "Couldn't find button!" ; |
355 | return; |
356 | } |
357 | if (next) { |
358 | index = (index + 1) % m_buttons.count(); |
359 | } else { |
360 | index = (index == 0 ? m_buttons.count() : index) - 1; |
361 | } |
362 | |
363 | if (m_currentButton && m_currentButton->nativeWidget()) { |
364 | m_currentButton->nativeWidget()->setDown(false); |
365 | } |
366 | m_currentButton = m_buttons.at(index); |
367 | if (m_currentButton && m_currentButton->nativeWidget()) { |
368 | m_currentButton->nativeWidget()->setDown(true); |
369 | } |
370 | m_visibleMenu = showMenu(); |
371 | } |
372 | |
373 | void MenuWidget::(QMenu *, QObject *object) |
374 | { |
375 | if (!menu) { |
376 | return; |
377 | } |
378 | |
379 | menu->installEventFilter(this); |
380 | |
381 | foreach (QAction *action, menu->actions()) { |
382 | if (action->menu()) |
383 | installEventFilterForAll(action->menu(), object); |
384 | } |
385 | } |
386 | |
387 | #include "menuwidget.moc" |