Good day to all!
I recently wrote about
customizing the window title in Mac OS X and received requests to write in more detail about the interaction of Qt and Cocoa. I think the topic can be expanded a bit and write about the integration of applications written with Qt on Mac OS X. Iāll make a reservation that Qt for Cocoa is used in this case, if you take Qt for Carbon, you will only have to work with carbon. But it is morally obsolete, and it is worth using it only in extreme cases.

A regular Qt program has a number of inconsistencies with the Apple HIG. More precisely, it can have, since not all programs need additional functionality. For example, not any program needs to have a badge over the icon in the dock, expand the dock menu or take out / duplicate certain functions in the Mac menu.
')
But what to do if you need such functionality? If you need to display in the dock the number of notifications (a la skype), handle click on the dock icon, add your menu items to the dock, and even have a normal menu, in general, make the program look like it is in Mac OS? Some of this can be done using Qt's standard or semi-documented functions, and something can only be done using Cocoa and, accordingly, Objective-C ... What to do?
Objective-C ++ will help us!
What kind of animal is it and what is it eaten with? In fact, it is an opportunity to combine Objective-C and C ++ classes in one source file. Moreover, the heading extension remains standard (.h), but for the source you need to specify the .mm extension, so that the compiler can eat it and not choke.
Now imagine that we have a certain (maybe even large) project written with Qt. Initially, it was written under Windows or Linux, but now it needs to be transferred to a makos, so much so that it is beautiful and comfortable, so that makovods do not wrinkle their nose at the sight of this monster.
It is clear that first we need to tweak the interface of the program under the Apple HIG, without it, nowhere, but this will remain beyond the scope of this article, I will mention only the useful Q_WS_ * defines, which allow you to compile different code for different OSes. We will talk about how you can tune your application for a new environment (or how to create a Mac application on Qt from scratch - it depends on your goals).
So let's go in order.
General integration
First we give the name and icon of the program. No, not the name that the bundle has, but the name that will be displayed in the Application Menu. To do this, we need our own Info.plist file, not the one that qmake generates. This is done in a single line in .pro:
macx: QMAKE_INFO_PLIST = MyInfo.plist
In our .plist write something like this:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>LSHasLocalizedDisplayName</key> <true/> <key>CFBundleIconFile</key> <string>myicon.icns</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleGetInfoString</key> <string>Created by Qt/QMake</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleExecutable</key> <string>MyAppName</string> <key>CFBundleIdentifier</key> <string>com.mycompany.myapp</string> <key>NOTE</key> <string>This file was generated by Qt/QMake.</string> </dict> </plist>
Of course, instead of āMyAppNameā and ācom.mycompany.myappā, we write the English name of the program and our own bundle ID. Why English? Yes, because we localize it in another file. This is indicated by the very first parameter in the list: LSHasLocalizedDisplayName. Create a directory "ru.lproj", and in her file InfoPlist.strings. We write something to this file:
CFBundleName = " "; CFBundleDisplayName = "";
Here you need to specify the localized name of the bundle and the name displayed in the Application Menu. For this to work, you need to copy this directory into AppName.app/Contents/Resources when installing the program, do it better by specifying INSTALLS in the .pro file, please refer to the qmake documentation for details. The screenshot shows that the Application Menu has a Russian name, despite the fact that the bundle itself has a name in Latin.

To set the program icon (see myicon.icns in the file MyInfo.plist), we need the .icns file, which can be created by yourself in a graphical editor, or converted from .ico or a handful of .png using
programs or
online services . To make the icon look good, itās better to make several sizes in it: 512x512, 256x256, 128x128, 64x64, 48x48, 32x32, 16x16. The system will choose which size to display in which situation. The file with the icon must also be installed in Resources. The easiest way to get it installed is to write the following in the .pro file:
macx: ICON = myicon.icns
Code separation
Objective-C ++ has many limitations. It is impossible, for example, to include a header with the declaration of the Objective-C class interface in the source file .cpp, because the compiler will choke. For new projects that are designed only for Mac OS X, this will not be a limitation, because you can keep all C ++ code in .mm files and enjoy life. But the meaning of Qt in cross-platform, so we assume that our source is still in the .cpp files.
The way out is simple. We need to create a "wrapper" for Objective-C calls and classes in C ++. I chose this structure: there is a Qt / C ++ class that provides integration with Mac OS X. It does some work on its own, and delegates some work to a ādelegateā, a private class actively using Cocoa / Objective-C. In this case, we obtain the following files:
* myclass.h - header of the main class
* myclass.cpp - implementation
* myclass_p.h - header of the private class (without Objective-C-interfaces)
* myclass_p.mm - sources of a private class, may include interfaces and implementation of Objective-C classes, include any headers, etc.
Thus, we clearly distinguish between C ++ and Objective-C ++.
By the way, in order for Objective-C ++ to work, in the .pro file, all headers / sources that use it must be placed in the OBJECTIVE_HEADERS and OBJECTIVE_SORCES sections. And, of course, do it inside the macx block: {}. And also, if we want to use Cocoa, then we need to add this framework to the project. We write in .pro:
macx: QMAKE_LFLAGS += -framework Cocoa
And now goes interesting.
Work with Dock
Consider the five main functions of working with the dock: adding a badge, adding an overlay, handling a click on the icon in the dock, āflippingā the program icon in the dock and adding your own menu. With Qt, you can solve only the last problem, and even then the weakly documented function qt_mac_set_dock_menu (QMenu *). And you need to declare it yourself, as external:
extern void qt_mac_set_dock_menu(QMenu *);
The menu, based on personal experience, imposes some restrictions in comparison with the ānativeā Makovsky menu:
* inactive (disabled) menu items become for some reason active
* QMenu doesnāt emittit aboutToHide and aboutToShow signals
* you cannot indent any elements
* if the first QAction in the menu is a separator, then it will be visible (unlike all other manifestations of QMenu)
So under this will have to adapt. You can, of course, do everything on Objective-C / Cocoa, but then you have to make your own QAction mapping mechanism and native menu items. But it makes sense to do it only if there is a really big need to eliminate these limitations.

Consider now click on the dock. If we wrote to Cocoa, then there would be no problems, it would be enough to implement the applicationShouldHandleReopen: hasVisibleWindows: method in AppDelegate. But we do not have direct access to the program delegate created in the depths of Qt. Therefore, we will use
runtime magic to add an implementation of this method. First, let's declare a function that will implement what we need. To do this, turn our private class into a singleton (we still do not need more than one object of this class) and write the following function:
void dockClickHandler(id self, SEL _cmd) { Q_UNUSED(self) Q_UNUSED(_cmd) MyPrivate::instance()->emitClick(); }
The function is dead without embedding it in the delegate. Let's not hesitate with this!
MyPrivate::MyPrivate() : QObject(NULL) { Class cls = [[[NSApplication sharedApplication] delegate] class]; if (!class_addMethod(cls, @selector(applicationShouldHandleReopen:hasVisibleWindows:), (IMP) dockClickHandler, "v@:")) NSLog(@"MyPrivate::MyPrivate() : class_addMethod failed!"); } void MyPrivate::emitClick() { emit dockClicked(); }
Here we take the delegate class of our application and add a method to it on the fly. And at the same time we are implementing the emitClick () method, emitting a Qt-signal about a click. Actually, that's all, now by clicking on the dock we can show, for example, the main window of the program.
Then you can try to throw the program icon in the dock. The very first thought: āthis is how QApplication :: alert (QWidget *) can do the same!ā The thought is correct, but it is prematurely optimistic. The thing is that in Mac OS X 10.6. * This function works as it should, but in 10.7. * For some reason it does not want (maybe this is due to the fact that Qt 4.7.x does not officially support Lion, but at 4.8 it will be fixed). I did not begin to understand why this is happening, and I just wrote a fling on Cocoa, since this is done in one line:
void MyPrivate::requestAttention() { [NSApp requestUserAttention: NSInformationalRequest]; }
And let this piece of code duplicate Qt-shny alert (), but it will work in all versions of Mac OS X. And for other systems, you can still use alert ().
Now let's deal with the badge. If someone does not know, then this is a red circle with text displayed over the program icon in the dock. For example, it is used to display the number of unread emails in Mail.app. Apple's documentation says it should be done like this:
[[NSApp dockTile] setBadgeLabel: badgeString];
Here badgeString is of type NSString *. Yeah, that's the first inconvenience! In Qt, QString is usually manipulated, which means you need to write a certain "converter" of strings:
As can be seen from the code (and from the commentary to it), the returned string is not released, so you have to do it in the calling code (Qt does not use ARC, so we will monitor the memory ourselves).
Now we can write a function of our private class that will display the line we need in the dock badge:
void MyPrivate::setDockBadge(const QString & badgeText) { NSString * badgeString = nsStringFromQString(badgeText); [[NSApp dockTile] setBadgeLabel: badgeString]; [badgeString release]; }

Now the last aspect of working with the dock is to add an arbitrary overlay to the dock. This is done using the following code:
[[NSApp dockTile] setContentView: view];
Here, the view is NSView *. But we are working with Qt! So, we need to put QWidget * in the overlay. How to get its NSView from QWidget? We write a simple function:
NSView * nsViewFromWidget(QWidget * w) { return (NSView *)w->winId(); }
Quite simply, the creators of Qt did almost all the work for us.

But, alas, a bummer awaits us: NSView obtained from QWidget is not suitable for installation in the dock. That is, it is installed, NSDockTile eats it, but instead of the contents of the widget, an empty space is formed in the dock. I do not know why. But even in Qt Creator, the progress bar is docked to the dock through its pure NSView, created specifically for this. So, if you need your own overlay, then you are welcome to write your View on Cocoa. Alas.
Menu operation
Let us turn to the Makovsky menu (the one on the top panel). By default, the Qt program does not create it (well, except for the standard Application Menu with system functions). The first thing that comes to mind Qt-developer is QMenuBar. And indeed, the documentation for it says that it can perform the functions of the Makovsky menu. But there are two options: either make a separate QMenuBar for each program window, or make one global. Choose the second option because of its undeniable advantages.
Again, based on the documentation, we need to create a QMenuBar with a zero parent, i.e., QMenuBar * macMenuBar = new QMenuBar (NULL), then it will be global. More precisely, the first menu item created in this way will become global.
And now - a bunch of monotonous hands. Create your own menu "Edit", "File", "Help" and so on. This is quite a tedious job, but without it the program will not look good. In addition, the standard W and āM key combinations will not close and minimize windows, respectively. They will also have to do it yourself (well, at least āQ works right away).

I will note some features of QActions in Mac OS X. If they are given shortcuts, then in the QKeySequence constructor it is necessary to transfer "Ctrl + M" to create the "āM" shortcut. And in general, the "ā" key in Qt for poppy everywhere goes as Ctrl, and the "Ctrl" key as Meta. For easier portability programs, I suppose.
Well, about the limitations of this approach to creating a menu.
* you cannot indent menu items
* QMenu does not emit an aboutToHide signal (only aboutToShow)
* there is the following bug: if the menu āHelpā is called āHelpā, then the system search block will be automatically created in the menu items, but if it is named in Russian, this will not happen even if the current locale is Russian in the system . How to get rid of this glitch, I have not yet found.
Almost final - custom window title in Qt
To apply everything that I wrote in the
last article to the Qt program, you need to do the following. First, we put where it is necessary to retain / release, because not included ARC. Secondly, in Objective-C ++ it is allowed what could not be done in pure Objective-C: taking a class if it is declared only by a forward:
id _class = [NSThemeFrame class];
This saves us from having to have at least one window in the program before calling this function. Otherwise, everything is the same, so I will not copy the code here. You can see this in the example (link below).
Conclusion
So, we looked at Objective-C ++ as applied to the Qt + Cocoa bundle for integrating a program written in Qt on Mac OS X. By and large, there is nothing difficult about this if there is a basic knowledge of Objective-C and Cocoa. You just need to know some of the features that I have tried to focus on in this article.
If anyone is interested in the complete source code of the test project, then welcome to
GitHub !
PS: you could still consider embedding into the program of notifications through Growl, but the reader can do it himself if he has learned the material.