Qt4 Mac SearchBox Wrapper

August 17, 2008 | Filed Under Mac OS X, Qt4 | No Comments

Do you want to use Mac OS X SearchBox in your Qt Application? Ok, pick up your XCode or your preferred code editor and we’ll start to write a Qt Wrapper for Carbon HISearchField.

The Header File will be something like this, with two signals textChanged(const QString&) that will be used to “Filter on the Fly” it will be raised when the user type something, findNext() will be raised when user press Return Button.

#if defined(Q_WS_MAC) && !defined(_QF_MAC_SEARCHBOX_H_)
#define _QF_MAC_SEARCHBOX_H_

// Qt4 Headers
#include <QWidget>

// Mac OS X - Carbon Headers
#include <Carbon/Carbon.h>

class QfMacSearchBox : public QWidget {
    Q_OBJECT

    public:
        QfMacSearchBox (QWidget *parent = 0);
        ~QfMacSearchBox();

        // Methods
        void raiseTextChanged (void);
        void raiseFindNext (void);

        QSize sizeHint (void) const;

        // GET Properties
        QString text (void) const;

    signals:
        void textChanged (const QString& text);
        void findNext (void);

    public slots:
        void setText (const QString& text);
        void clear (void);

    private:
        CFStringRef searchFieldText;
        HIViewRef searchField;
};

#endif // Q_WS_MAC && !_QF_MAC_SEARCHBOX_H_

And now the Class Implementation. I’ve internally managed the clear button, so you haven’t a clear signals but a textChanged(const QString&) signal with an empty string.

// Qt4 Headers
#include <QVarLengthArray>
#include <QMenu>

// SearchBox Headers
#include "searchbox_mac.h"

// =======================================
//  MacSearchBox: PRIVATE FILE Methods
// =======================================
static QString toQString(CFStringRef str) {
    if(!str)
        return QString();

    CFIndex length = CFStringGetLength(str);
    const UniChar *chars = CFStringGetCharactersPtr(str);
    if (chars)
        return QString(reinterpret_cast<const QChar *>(chars), length);

    QVarLengthArray<UniChar> buffer(length);
    CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data());
    return QString(reinterpret_cast<const QChar *>(buffer.constData()), length);
}

static OSStatus SearchFieldEventHandler(EventHandlerCallRef handlerCallRef,
                                        EventRef event, void *userData)
{
    QfMacSearchBox *searchBox = (QfMacSearchBox *) userData;
    OSType eventClass = GetEventClass(event);
    UInt32 eventKind = GetEventKind(event);

    if (eventClass == kEventClassSearchField) {
        switch (eventKind) {
            case kEventSearchFieldCancelClicked:
                searchBox->clear();
                break;
            case kEventSearchFieldSearchClicked:
                searchBox->raiseFindNext();
                break;
            default:
                break;
        }
    } else if (eventClass == kEventClassTextField) {
        switch (eventKind) {
            case kEventTextDidChange:
                searchBox->raiseTextChanged();
                break;
            case kEventTextAccepted:
                searchBox->raiseFindNext();
                break;
            default:
                break;
        }
    }

    return(eventNotHandledErr);
}

// =======================================
//  MacSearchBox: PUBLIC Constructors/Destructors
// =======================================
QfMacSearchBox::QfMacSearchBox (QWidget *parent)
    : QWidget(parent)
{
    // Set Widget Properties
    //setFocusPolicy(Qt::StrongFocus);
    setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));

    // Create a native search field and pass its window id to QWidget::create.
    searchFieldText = CFStringCreateWithCString(0,
                                     (const char *) tr("Search").toAscii(), 0);
    HISearchFieldCreate(NULL, kHISearchFieldAttributesSearchIcon |
                              kHISearchFieldAttributesCancel,
                        NULL, searchFieldText, &searchField);
    create(reinterpret_cast<WId>(searchField));

    // Subscribe Events
    EventTypeSpec mySFieldEvents[] = {
                     { kEventClassSearchField, kEventSearchFieldCancelClicked },
                     { kEventClassTextField, kEventTextDidChange },
                     { kEventClassTextField, kEventTextAccepted }
                 };

    HIViewInstallEventHandler(searchField, SearchFieldEventHandler,
                              GetEventTypeCount(mySFieldEvents), mySFieldEvents,
                              (void *) this, NULL);

    // Use a Qt menu for the search field menu.
    QMenu *searchMenu = new QMenu(this);

    QAction *indexAction = searchMenu->addAction(tr("Index Search"));
    indexAction->setCheckable(true);
    indexAction->setChecked(true);

    QAction *fulltextAction = searchMenu->addAction(tr("Full Text Search"));
    fulltextAction->setCheckable(true);

    QActionGroup *searchActionGroup = new QActionGroup(this);
    searchActionGroup->addAction(indexAction);
    searchActionGroup->addAction(fulltextAction);
    searchActionGroup->setExclusive(true);

    MenuRef macSearchMenu = searchMenu->macMenu(0);
    HISearchFieldSetSearchMenu(searchField, macSearchMenu);
}

QfMacSearchBox::~QfMacSearchBox() {
    CFRelease(searchField);
    CFRelease(searchFieldText);
}

// =======================================
//  MacSearchBox: PUBLIC Methods
// =======================================
void QfMacSearchBox::raiseTextChanged (void) {
    emit textChanged(text());
}

void QfMacSearchBox::raiseFindNext (void) {
    emit findNext();
}

QSize QfMacSearchBox::sizeHint (void) const {
    HIRect optimalBounds;
    EventRef event;

    CreateEvent(0, kEventClassControl, kEventControlGetOptimalBounds,
                GetCurrentEventTime(), kEventAttributeUserEvent, &event);

    SendEventToEventTargetWithOptions(event,
                                HIObjectGetEventTarget(HIObjectRef(winId())),
                                kEventTargetDontPropagate);

    GetEventParameter(event, kEventParamControlOptimalBounds, typeHIRect,
                      0, sizeof(HIRect), 0, &optimalBounds);

    ReleaseEvent(event);
    return QSize(optimalBounds.size.width + 200, optimalBounds.size.height);
}

// =======================================
//  MacSearchBox: PUBLIC GET Properties
// =======================================
QString QfMacSearchBox::text (void) const {
    CFStringRef cfString = HIViewCopyText(searchField);
    QString text = toQString(cfString);
    CFRelease(cfString);
    return(text);
}

// =======================================
//  MacSearchBox: PUBLIC SET Properties
// =======================================
void QfMacSearchBox::setText (const QString& text) {
    CFRelease(searchFieldText);

    searchFieldText = CFStringCreateWithCString(0,
                          (const char *) text.toAscii(), 0);
    HIViewSetText(searchField, searchFieldText);

    emit textChanged(text);
}

void QfMacSearchBox::clear (void) {
    setText(QString());
}

That’s all folks.

Qt4 Mac Dock Icon Click

August 11, 2008 | Filed Under Mac OS X, Qt4 | 6 Comments

When you click the “close button” of an application’s window, on Mac OS X the window will be hidden but the application continue running. When you click on the Dock Icon, the application’s window will be showed. This is the default behaviour for Mac Apps but Qt Apps doesn’t the same. So, how we could implement this behaviour?

#ifndef _APPLICATION_H_
#define _APPLICATION_H_

// Qt4 Headers
include <QApplication>
include <QWidget>

// Carbon Headers
#ifdef Q_WS_MAC
    #include <Carbon/Carbon.h>
#endif

class Application : public QApplication {
    Q_OBJECT

    public:
        Application (int& argc, char **argv);

         QWidget *mainWindow (void) const;

    private:
         QWidget *m_mainWindow;

#ifdef Q_WS_MAC
    AEEventHandlerUPP m_appleEventProcessorUPP;
#endif
};

#endif // !_APPLICATION_H_

Here, we’ve implemented an Application class that Inherits from QApplication. This class contains a reference to the App’s Main Window.

#ifdef Q_WS_MAC
static OSStatus appleEventProcessor(const AppleEvent *ae,
                                    AppleEvent *event,
                                    long handlerRefCon)
{
    Application *app = (Application *) handlerRefCon;

     OSType aeID = typeWildCard;
     OSType aeClass = typeWildCard;

     AEGetAttributePtr(ae, keyEventClassAttr, typeType, 0,
                       &aeClass, sizeof(aeClass), 0);
     AEGetAttributePtr(ae, keyEventIDAttr, typeType, 0,
                       &aeID, sizeof(aeID), 0);

     if (aeClass == kCoreEventClass) {
          if (aeID == kAEReopenApplication) {
               app->mainWindow()->show();
          }
          return noErr;
    }

    return eventNotHandledErr;
}
#endif

Application::Application (int& argc, char **argv)
    : QApplication(argc, argv)
{
    // Don't Quit the App on Window Close
    setQuitOnLastWindowClosed(false);

    // Initialize Main Window
    m_mainWindow = new QLabel("Test Main Window");

#ifdef Q_WS_MAC
    // Install Reopen Application Event (Dock Clicked)
    m_appleEventProcessorUPP = AEEventHandlerUPP(appleEventProcessor);
    AEInstallEventHandler(kCoreEventClass, kAEReopenApplication,
                          m_appleEventProcessorUPP, (long) this, true);
#endif
}

QWidget *Application::mainWindow (void) const {
    return(m_mainWindow);
}

Thats all folks! Run the app, Click on close button and then click on the dock icon to see the result.