Qt4 Mac SearchBox Wrapper

August 17, 2008 Matteo Bertozzi | 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.

No Comments yet »

RSS feed for comments on this post. TrackBack URI

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>