Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Native context menu on macOS

Native context menu on macOS

Scheduled Pinned Locked Moved Unsolved General and Desktop
macosc++objective-cmenu
1 Posts 1 Posters 404 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C Offline
    C Offline
    ctlcltd
    wrote on 7 May 2024, 12:57 last edited by ctlcltd 5 Jul 2024, 13:01
    #1

    Hi, I am new to this forum and I want to bring you this thing regarding the context menus.

    I wanted to use native menus on macOS. Just I prefer context menus with native look and feel.

    Currently up to 6.7 the only widget that has implemented this capability is QComboBox, using a proxy style and QStyle::SH_ComboBox_UseNativePopup, it works perfectly!

    I went to look at the Qt source to figure it. One problem is documented in the source, native menus (NSMenu) alter Qt signals and events. I was able to implement native menus using Objective-C in cpp, with few calls to the native API.

    So I managed to find a compromise. The goal was to use native menus without disrupt my code, cross-platform. Unfortunately the matter becomes more complicated for example if you want to use the native context menu also on QLineEdit selection, or in widgets with the persistent editor.

    My intent was to use native menus in any widget where menu appears.

    I have a QTreeWidget with Drag and Drop and the native context menu interferes. Until version 6.6 it was sufficient to send a native mouse release event to make it just work. Since version 6.7 there is no way to stop them interfering.

    I tried several things, the Qt::WA_TransparentForMouseEvents attribute, different types of signals sent to QCoreApplication, tried using QObject::blockSignals, also disabling updates for QTreeWidget using QWidget::setUpdatesEnabled set to false (in the latter case QTreeWidget disappears completely).

    The only solution that seems to work consist to disable the widget and re-enable it after the native context menu popup. But this is not a really usable solution, the widget undergoes all updates, is visually disabled, with many repaints.

    Currently I disable Drag and Drop when QWidget is QAbstractItemView, from which QTreeWidget and QListWidget descend. A smelly solution.

    Do you have any idea to get around the problem?

    This is a code sample:

    #import <AppKit/AppKit.h>
    
    #include <QApplication>
    #include <QWidget>
    #include <QWindow>
    #include <QMenu>
    #include <QTimer>
    #include <QGridLayout>
    #include <QTreeWidget>
    #include <QList>
    
    int main (int argc, char *argv[])
    {
    	QApplication *app = new QApplication(argc, argv);
    
    	QWidget *mwid = new QWidget;
    	QGridLayout *frm = new QGridLayout(mwid);
    
    
    	QTreeWidget *tree = new QTreeWidget();
    	tree->setUniformRowHeights(true);
    	tree->setSelectionBehavior(QTreeWidget::SelectRows);
    	tree->setSelectionMode(QTreeWidget::ExtendedSelection);
    	tree->setItemsExpandable(false);
    	tree->setExpandsOnDoubleClick(false);
    	tree->setDragDropMode(QTreeWidget::InternalMove);
    	tree->setDefaultDropAction(Qt::MoveAction);
    	tree->setDropIndicatorShown(true);
    	tree->setEditTriggers(QTreeWidget::NoEditTriggers);
    	tree->setRootIsDecorated(false);
    	tree->setContextMenuPolicy(Qt::CustomContextMenu);
    	tree->setHeaderLabels({"Col 1", "Col 2"});
    
    	QList<QTreeWidgetItem*> items;
    
    	for (int i = 0; i < 26; i++)
    	{
    		QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({QString("item %1").arg(QChar(i + 65)), "1901-01-01"}));
    		item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren);
    		items.append(item);
    	}
    
    	tree->addTopLevelItems(items);
    
    	frm->addWidget(tree);
    
    
    	QWidget *widget = tree;
    
    	QMenu* menu = new QMenu();
    	menu->addAction("&Edit");
    	menu->addSeparator();
    	menu->addAction("Cu&t", QKeySequence::Cut);
    	menu->addAction("&Copy", QKeySequence::Copy);
    	menu->addAction("&Paste", QKeySequence::Paste);
    
    
    	// reusable function
    	QApplication::connect(tree, &QTreeWidget::customContextMenuRequested, [=](QPoint pos) {
    
    		QWidget *top = widget->window();
    		QWindow *tlw = top->windowHandle();
    
    		// get the main native NSView (qnsview)
    		NSView *view = (NSView*)top->winId();
    		// get a native menu NSMenu
    		NSMenu *nsMenu = menu->toNSMenu();
    
    
    		// need to convert position
    		QPoint globalPos = widget->mapTo(top, pos);
    		NSPoint nsPos = NSMakePoint(globalPos.x(), globalPos.y());
    		nsPos = [view convertPoint:nsPos toView:nil];
    
    
    		// context menu interfering with Drag and Drop
    		// in Qt >= 6.7
    		// temp workaround to disallow DND
    #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
    		struct dnd
    		{
    			bool dragEnabled = false;
    			QAbstractItemView::DragDropMode dragDropMode = QAbstractItemView::NoDragDrop;
    			bool showDropIndicator = false;
    			bool acceptDrops = false;
    		} 
    		dndState;
    		if (QAbstractItemView *wid = qobject_cast<QAbstractItemView*>(widget))
    		{
    			dndState.dragEnabled = wid->dragEnabled();
    			dndState.dragDropMode = wid->dragDropMode();
    			dndState.showDropIndicator = wid->showDropIndicator();
    			dndState.acceptDrops = wid->acceptDrops();
    			wid->setDragEnabled(false);
    			wid->setDragDropMode(QAbstractItemView::NoDragDrop);
    			wid->setDropIndicatorShown(false);
    			wid->setAcceptDrops(false);
    		}
    #endif
    
    
    		// signal emitter
    		menu->aboutToShow();
    
    		NSEvent *nsEventMenuShow = [NSEvent
    			mouseEventWithType:NSEventTypeRightMouseDown
    			location:nsPos
    			modifierFlags:0
    			timestamp:0
    			windowNumber:view ? view.window.windowNumber : 0
    			context:nil
    			eventNumber:0
    			clickCount:1
    			pressure:1.0
    		];
    
    		// inner items disabled when modal
    		// a workaround to set enabled state on each item
    		if (tlw != nullptr && tlw->type() != Qt::Window)
    		{
    			[nsMenu setAutoenablesItems:FALSE];
    
    			int i = 0;
    			for (auto & item : menu->actions())
    			{
    				NSMenuItem *nsMenuItem = [nsMenu itemAtIndex:i++];
    				[nsMenuItem setEnabled:(item->isEnabled())];
    			}
    		}
    
    		// native menu is blocking
    		[NSMenu popUpContextMenu:nsMenu withEvent:nsEventMenuShow forView:view];
    
    		// native menu alters Qt QApplication mouse events
    		// send mouse release with native events
    		NSEvent *nsEventMouseRelease = [NSEvent
    			mouseEventWithType:NSEventTypeRightMouseUp
    			location:nsPos
    			modifierFlags:0
    			timestamp:0
    			windowNumber:view ? view.window.windowNumber : 0
    			context:nil
    			eventNumber:0
    			clickCount:1
    			pressure:1.0
    		];
    		[view mouseUp:nsEventMouseRelease];
    
    
    
    		// signal emitter
    		menu->aboutToHide();
    
    
    		// context menu interfering with Drag and Drop
    		// in Qt >= 6.7
    		// temp workaround to re-allow DND
    #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
    		if (QAbstractItemView *wid = qobject_cast<QAbstractItemView*>(widget))
    		{
    			// delay to zero needed
    			QTimer::singleShot(0, [=]() {
    				wid->setDragEnabled(dndState.dragEnabled);
    				wid->setDragDropMode(dndState.dragDropMode);
    				wid->setDropIndicatorShown(dndState.showDropIndicator);
    				wid->setAcceptDrops(dndState.acceptDrops);
    			});
    		}
    #endif
    
    	});
    
    
    	mwid->show();
    
    	return app->exec();
    }
    
    1 Reply Last reply
    0

    1/1

    7 May 2024, 12:57

    • Login

    • Login or register to search.
    1 out of 1
    • First post
      1/1
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Get Qt Extensions
    • Unsolved