11#include <QQmlProperty>
13#include <QQuickRenderControl>
14#include <QQuickWindow>
17#include <KAcceleratorManager>
22QMenuProxy::QMenuProxy(
QObject *parent)
26 , m_placement(LeftPosedTopAlignedPopup)
27 , m_preferSeamlessEdges(false)
30 m_menu =
new QMenu(
nullptr);
40 Q_EMIT statusChanged();
45QMenuProxy::~QMenuProxy()
52 return QQmlListProperty<QMenuItem>(
this, &m_items);
55int QMenuProxy::actionCount()
const
57 return m_items.count();
60QMenuItem *QMenuProxy::action(
int index)
const
62 return m_items.at(index);
65QMenuProxy::Status QMenuProxy::status()
const
70QObject *QMenuProxy::visualParent()
const
72 return m_visualParent.data();
75void QMenuProxy::setVisualParent(
QObject *parent)
77 if (m_visualParent.data() ==
parent) {
84 action->setMenu(
nullptr);
90 action->setMenu(m_menu);
92 for (QMenuItem *item : std::as_const(m_items)) {
93 if (item->section()) {
94 if (!item->isVisible()) {
98 m_menu->addSection(item->text());
100 m_menu->addAction(item->action());
103 m_menu->updateGeometry();
107 Q_EMIT visualParentChanged();
110QWindow *QMenuProxy::transientParent()
112 if (!m_menu || !m_menu->windowHandle()) {
118void QMenuProxy::setTransientParent(
QWindow *parent)
120 if (!m_menu || !m_menu->windowHandle() ||
parent == m_menu->windowHandle()->transientParent()) {
124 m_menu->windowHandle()->setTransientParent(
parent);
125 Q_EMIT transientParentChanged();
128QMenuProxy::PopupPlacement QMenuProxy::placement()
const
133void QMenuProxy::setPlacement(QMenuProxy::PopupPlacement placement)
135 if (m_placement != placement) {
136 m_placement = placement;
138 Q_EMIT placementChanged();
142bool QMenuProxy::preferSeamlessEdges()
const
144 return m_preferSeamlessEdges;
147void QMenuProxy::setPreferSeamlessEdges(
bool request)
149 if (m_preferSeamlessEdges != request) {
150 m_preferSeamlessEdges = request;
152 Q_EMIT preferSeamlessEdgesChanged();
156int QMenuProxy::minimumWidth()
const
158 return m_menu->minimumWidth();
161void QMenuProxy::setMinimumWidth(
int width)
163 if (m_menu->minimumWidth() != width) {
164 m_menu->setMinimumWidth(width);
166 Q_EMIT minimumWidthChanged();
170int QMenuProxy::maximumWidth()
const
172 return m_menu->maximumWidth();
175void QMenuProxy::setMaximumWidth(
int width)
177 if (m_menu->maximumWidth() != width) {
178 m_menu->setMaximumWidth(width);
180 Q_EMIT maximumWidthChanged();
184void QMenuProxy::resetMaximumWidth()
186 setMaximumWidth(QWIDGETSIZE_MAX);
189bool QMenuProxy::event(
QEvent *event)
191 switch (event->type()) {
193 QChildEvent *ce =
static_cast<QChildEvent *
>(event);
196 if (mi && !m_items.contains(mi)) {
197 if (mi->separator()) {
198 m_menu->addSection(mi->text());
200 m_menu->addAction(mi->action());
208 QChildEvent *ce =
static_cast<QChildEvent *
>(event);
213 m_menu->removeAction(mi->action());
214 m_items.removeAll(mi);
226void QMenuProxy::clearMenuItems()
232void QMenuProxy::addMenuItem(
const QString &text)
234 QMenuItem *item =
new QMenuItem();
236 m_menu->addAction(item->action());
240void QMenuProxy::addMenuItem(QMenuItem *item, QMenuItem *before)
243 if (m_items.contains(item)) {
244 m_menu->removeAction(item->action());
245 m_items.removeAll(item);
248 m_menu->insertAction(before->action(), item->action());
250 const int index = m_items.indexOf(before);
253 m_items.insert(index, item);
258 }
else if (!m_items.contains(item)) {
259 m_menu->addAction(item->action());
263 removeMenuItem(item);
267void QMenuProxy::addSection(
const QString &text)
269 m_menu->addSection(text);
272void QMenuProxy::removeMenuItem(QMenuItem *item)
278 m_menu->removeAction(item->action());
279 m_items.removeOne(item);
282void QMenuProxy::itemTriggered(
QAction *action)
284 for (
int i = 0; i < m_items.count(); ++i) {
285 QMenuItem *item = m_items.at(i);
286 if (item->action() == action) {
294void QMenuProxy::rebuildMenu()
298 for (QMenuItem *item : std::as_const(m_items)) {
299 if (item->section()) {
300 if (!item->isVisible()) {
304 m_menu->addSection(item->text());
306 m_menu->addAction(item->action());
307 if (item->action()->
menu()) {
316 m_menu->adjustSize();
326 QPoint relativePos = pos + offset;
327 return renderWindow->mapToGlobal(relativePos);
329 return quickWindow->mapToGlobal(pos);
336void QMenuProxy::open(
int x,
int y)
338 QQuickItem *parentItem =
nullptr;
340 if (m_visualParent) {
352 QPointF posLocal = parentItem->mapToScene(QPointF(x, y));
354 QPoint posGlobal = mapToGlobalUsingRenderWindowOfItem(parentItem, posLocal);
356 setupSeamlessEdges(std::nullopt);
358 openInternal(posGlobal);
361void QMenuProxy::openRelative()
363 QQuickItem *parentItem =
nullptr;
365 if (m_visualParent) {
380 auto boundaryCorrection = [
this, &posLocal, &posGlobal, parentItem](
int hDelta,
int vDelta) {
381 if (
auto window = parentItem->window();
383 QRect
geo = screen->geometry();
385 QPoint pos = mapToGlobalUsingRenderWindowOfItem(parentItem, posLocal);
387 if (pos.
x() <
geo.x()) {
388 pos.
setX(pos.
x() + hDelta);
390 if (pos.
y() <
geo.y()) {
391 pos.
setY(pos.
y() + vDelta);
394 if (
geo.x() +
geo.width() < pos.
x() + this->m_menu->width()) {
395 pos.
setX(pos.
x() + hDelta);
397 if (
geo.y() +
geo.height() < pos.
y() + this->m_menu->height()) {
398 pos.
setY(pos.
y() + vDelta);
402 posGlobal = posLocal.
toPoint();
406 const QQmlProperty enabledProp(parentItem, QStringLiteral(
"LayoutMirroring.enabled"), qmlContext(parentItem));
407 const bool mirrored(enabledProp.read().toBool());
410 using namespace Plasma;
413 case TopPosedLeftAlignedPopup: {
414 posLocal = parentItem->mapToScene(QPointF(0, -m_menu->height()));
415 boundaryCorrection(-m_menu->width() + parentItem->width(), m_menu->height() + parentItem->height());
418 case LeftPosedTopAlignedPopup: {
419 posLocal = parentItem->mapToScene(QPointF(-m_menu->width(), 0));
420 boundaryCorrection(m_menu->width() + parentItem->width(), -m_menu->height() + parentItem->height());
423 case TopPosedRightAlignedPopup:
424 posLocal = parentItem->mapToScene(QPointF(parentItem->width() - m_menu->width(), -m_menu->height()));
425 boundaryCorrection(m_menu->width() - parentItem->width(), m_menu->height() + parentItem->height());
427 case RightPosedTopAlignedPopup: {
428 posLocal = parentItem->mapToScene(QPointF(parentItem->width(), 0));
429 boundaryCorrection(-m_menu->width() - parentItem->width(), -m_menu->height() + parentItem->height());
432 case LeftPosedBottomAlignedPopup:
433 posLocal = parentItem->mapToScene(QPointF(-m_menu->width(), -m_menu->height() + parentItem->height()));
434 boundaryCorrection(m_menu->width() + parentItem->width(), m_menu->height() - parentItem->height());
436 case BottomPosedLeftAlignedPopup: {
437 posLocal = parentItem->mapToScene(QPointF(0, parentItem->height()));
438 boundaryCorrection(-m_menu->width() + parentItem->width(), -m_menu->height() - parentItem->height());
441 case BottomPosedRightAlignedPopup: {
442 posLocal = parentItem->mapToScene(QPointF(parentItem->width() - m_menu->width(), parentItem->height()));
443 boundaryCorrection(m_menu->width() - parentItem->width(), -m_menu->height() - parentItem->height());
446 case RightPosedBottomAlignedPopup: {
447 posLocal = parentItem->mapToScene(QPointF(parentItem->width(), -m_menu->height() + parentItem->height()));
448 boundaryCorrection(-m_menu->width() - parentItem->width(), m_menu->height() - parentItem->height());
456 setupSeamlessEdges(std::optional(placement));
458 openInternal(posGlobal);
461void QMenuProxy::openInternal(
QPoint pos)
463 QQuickItem *parentItem = this->parentItem();
465 if (parentItem && parentItem->window()) {
468 m_menu->windowHandle()->setTransientParent(parentItem->window());
476 QQuickItem *parentItem = this->parentItem();
477 if (parentItem && parentItem->window() && parentItem->window()->mouseGrabberItem()) {
478 parentItem->window()->mouseGrabberItem()->ungrabMouse();
492 if (m_visualParent) {
499void QMenuProxy::close()
504QMenuProxy::PopupPlacement QMenuProxy::visualPopupPlacement(PopupPlacement placement,
Qt::LayoutDirection layoutDirection)
513 case TopPosedLeftAlignedPopup:
514 return TopPosedRightAlignedPopup;
515 case TopPosedRightAlignedPopup:
516 return TopPosedLeftAlignedPopup;
517 case LeftPosedTopAlignedPopup:
518 return RightPosedTopAlignedPopup;
519 case LeftPosedBottomAlignedPopup:
520 return RightPosedBottomAlignedPopup;
521 case BottomPosedLeftAlignedPopup:
522 return BottomPosedRightAlignedPopup;
523 case BottomPosedRightAlignedPopup:
524 return BottomPosedLeftAlignedPopup;
525 case RightPosedTopAlignedPopup:
526 return LeftPosedTopAlignedPopup;
527 case RightPosedBottomAlignedPopup:
528 return LeftPosedBottomAlignedPopup;
535Qt::Edges QMenuProxy::seamlessEdgesForPlacement(std::optional<PopupPlacement> placement)
537 if (m_preferSeamlessEdges && placement.has_value()) {
538 switch (placement.value()) {
539 case TopPosedLeftAlignedPopup:
540 case TopPosedRightAlignedPopup:
543 case LeftPosedTopAlignedPopup:
544 case LeftPosedBottomAlignedPopup:
547 case BottomPosedLeftAlignedPopup:
548 case BottomPosedRightAlignedPopup:
551 case RightPosedTopAlignedPopup:
552 case RightPosedBottomAlignedPopup:
564void QMenuProxy::setupSeamlessEdges(std::optional<PopupPlacement> placement)
567 auto edges = seamlessEdgesForPlacement(placement);
571#include "moc_qmenu.cpp"
static void manage(QWidget *widget, bool programmers_mode=false)
GeoCoordinates geo(const QVariant &location)
QObject * child() const const
QCoreApplication * instance()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
virtual bool event(QEvent *e)
QObject * parent() const const
T qobject_cast(QObject *object)
QPoint toPoint() const const
QQuickWindow * window() const const
QWindow * renderWindowFor(QQuickWindow *win, QPoint *offset)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)