From f4ff8b17f75e6cd57ca16c665027e7b3f59951b9 Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Mon, 29 Apr 2013 18:04:26 +0200 Subject: [PATCH] Use search field for search. Search options are presented in a context menu on the search field, as well as links in search header. --- src/CMakeLists.txt | 2 + src/gui/DatabaseTabWidget.cpp | 8 +- src/gui/DatabaseWidget.cpp | 150 ++++++---- src/gui/DatabaseWidget.h | 19 +- src/gui/MainWindow.cpp | 59 +++- src/gui/MainWindow.h | 1 + src/gui/MainWindow.ui | 70 ++++- src/gui/SearchWidget.ui | 69 +---- src/gui/qocoa/CMakeLists.txt | 50 ++++ src/gui/qocoa/LICENSE.txt | 19 ++ src/gui/qocoa/Qocoa.pro | 17 ++ src/gui/qocoa/README.md | 36 +++ src/gui/qocoa/TODO.md | 13 + src/gui/qocoa/gallery.cpp | 75 +++++ src/gui/qocoa/gallery.h | 14 + src/gui/qocoa/gallery.png | Bin 0 -> 42195 bytes src/gui/qocoa/main.cpp | 12 + src/gui/qocoa/qbutton.h | 49 ++++ src/gui/qocoa/qbutton_mac.mm | 229 +++++++++++++++ src/gui/qocoa/qbutton_nonmac.cpp | 89 ++++++ src/gui/qocoa/qocoa_mac.h | 54 ++++ src/gui/qocoa/qprogressindicatorspinning.h | 29 ++ .../qocoa/qprogressindicatorspinning_mac.mm | 70 +++++ .../qprogressindicatorspinning_nonmac.cpp | 86 ++++++ .../qprogressindicatorspinning_nonmac.gif | Bin 0 -> 3208 bytes .../qprogressindicatorspinning_nonmac.qrc | 5 + src/gui/qocoa/qsearchfield.h | 48 ++++ src/gui/qocoa/qsearchfield_mac.mm | 257 +++++++++++++++++ src/gui/qocoa/qsearchfield_nonmac.cpp | 270 ++++++++++++++++++ src/gui/qocoa/qsearchfield_nonmac.qrc | 7 + src/gui/qocoa/qsearchfield_nonmac_clear.png | Bin 0 -> 736 bytes .../qocoa/qsearchfield_nonmac_magnifier.png | Bin 0 -> 300 bytes .../qsearchfield_nonmac_magnifier_menu.png | Bin 0 -> 439 bytes 33 files changed, 1653 insertions(+), 154 deletions(-) create mode 100644 src/gui/qocoa/CMakeLists.txt create mode 100644 src/gui/qocoa/LICENSE.txt create mode 100644 src/gui/qocoa/Qocoa.pro create mode 100644 src/gui/qocoa/README.md create mode 100644 src/gui/qocoa/TODO.md create mode 100644 src/gui/qocoa/gallery.cpp create mode 100644 src/gui/qocoa/gallery.h create mode 100644 src/gui/qocoa/gallery.png create mode 100644 src/gui/qocoa/main.cpp create mode 100644 src/gui/qocoa/qbutton.h create mode 100644 src/gui/qocoa/qbutton_mac.mm create mode 100644 src/gui/qocoa/qbutton_nonmac.cpp create mode 100644 src/gui/qocoa/qocoa_mac.h create mode 100644 src/gui/qocoa/qprogressindicatorspinning.h create mode 100644 src/gui/qocoa/qprogressindicatorspinning_mac.mm create mode 100644 src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp create mode 100644 src/gui/qocoa/qprogressindicatorspinning_nonmac.gif create mode 100644 src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc create mode 100644 src/gui/qocoa/qsearchfield.h create mode 100644 src/gui/qocoa/qsearchfield_mac.mm create mode 100644 src/gui/qocoa/qsearchfield_nonmac.cpp create mode 100644 src/gui/qocoa/qsearchfield_nonmac.qrc create mode 100644 src/gui/qocoa/qsearchfield_nonmac_clear.png create mode 100644 src/gui/qocoa/qsearchfield_nonmac_magnifier.png create mode 100644 src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b57ee84d2..6b7ac25b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -219,12 +219,14 @@ qt4_wrap_cpp(keepassx_SOURCES ${keepassx_MOC}) add_library(keepassx_core STATIC ${keepassx_SOURCES}) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) +add_subdirectory(gui/qocoa) add_subdirectory(http/qhttpserver) add_subdirectory(http/qjson) add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE}) target_link_libraries(${PROGNAME} keepassx_core + Qocoa qjson qhttpserver ${QT_QTCORE_LIBRARY} diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 34bf41209..a520fd46d 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -246,12 +246,14 @@ void DatabaseTabWidget::checkReloadDatabases() //Save current group/entry Uuid currentGroup; - if (Group* group = dbStruct.dbWidget->groupView()->currentGroup()) + if (Group* group = dbStruct.dbWidget->currentGroup()) currentGroup = group->uuid(); Uuid currentEntry; if (Entry* entry = dbStruct.dbWidget->entryView()->currentEntry()) currentEntry = entry->uuid(); QString searchText = dbStruct.dbWidget->searchText(); + bool caseSensitive = dbStruct.dbWidget->caseSensitiveSearch(); + bool allGroups = dbStruct.dbWidget->isAllGroupsSearch(); //Reload updated db CompositeKey key = db->key(); @@ -262,11 +264,11 @@ void DatabaseTabWidget::checkReloadDatabases() dbStruct = indexDatabaseManagerStruct(count() - 1); if (dbStruct.dbWidget) { Database * db = dbStruct.dbWidget->database(); - if (!searchText.isEmpty()) - dbStruct.dbWidget->showSearch(searchText); if (!currentGroup.isNull()) if (Group* group = db->resolveGroup(currentGroup)) dbStruct.dbWidget->groupView()->setCurrentGroup(group); + if (!searchText.isEmpty()) + dbStruct.dbWidget->search(searchText, caseSensitive, allGroups); if (!currentEntry.isNull()) if (Entry* entry = db->resolveEntry(currentEntry)) dbStruct.dbWidget->entryView()->setCurrentEntry(entry); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 68b89761f..f368bf945 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -50,6 +50,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) , m_newGroup(Q_NULLPTR) , m_newEntry(Q_NULLPTR) , m_newParent(Q_NULLPTR) + , m_searchAllGroups(false) + , m_searchSensitivity(Qt::CaseInsensitive) { m_searchUi->setupUi(m_searchWidget); @@ -89,8 +91,9 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) closeAction->setIcon(closeIcon); m_searchUi->closeSearchButton->setDefaultAction(closeAction); m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape); + int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize); + m_searchUi->closeSearchButton->setIconSize(QSize(iconsize,iconsize)); m_searchWidget->hide(); - m_searchUi->caseSensitiveCheckBox->setVisible(false); QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget); vLayout->setMargin(0); @@ -149,10 +152,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool))); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); - connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer())); - connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch())); - connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); - connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); + connect(m_searchUi->searchResults, SIGNAL(linkActivated(QString)), this, SLOT(onLinkActivated(QString))); connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search())); connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch())); @@ -547,84 +547,117 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName) setCurrentWidget(m_keepass1OpenWidget); } -void DatabaseWidget::toggleSearch() +void DatabaseWidget::search(const QString& searchString, bool caseSensitive, bool allGroups) { - if (m_entryView->inEntryListMode()) { - closeSearch(); + m_searchSensitivity = caseSensitive ? Qt::CaseSensitive + : Qt::CaseInsensitive; + m_searchAllGroups = allGroups; + search(searchString); +} + +void DatabaseWidget::search(const QString& text) +{ + if (text.isEmpty()) { + if (m_entryView->inEntryListMode()) + closeSearch(); + } + else if (m_entryView->inEntryListMode()) { + m_searchText = text; + startSearchTimer(); } else { - showSearch(); + showSearch(text); + } +} + +bool DatabaseWidget::caseSensitiveSearch() const +{ + return m_searchSensitivity == Qt::CaseSensitive; +} + +void DatabaseWidget::setCaseSensitiveSearch(bool caseSensitive) +{ + if (caseSensitive != caseSensitiveSearch()) { + m_searchSensitivity = caseSensitive ? Qt::CaseSensitive + : Qt::CaseInsensitive; + if (m_entryView->inEntryListMode()) + startSearchTimer(); + } +} + +bool DatabaseWidget::isAllGroupsSearch() const +{ + return m_searchAllGroups; +} + +bool DatabaseWidget::canChooseSearchScope() const +{ + return currentGroup() != m_db->rootGroup(); +} + +Group*DatabaseWidget::currentGroup() const +{ + return m_entryView->inEntryListMode() ? m_lastGroup + : m_groupView->currentGroup(); +} + +void DatabaseWidget::setAllGroupsSearch(bool allGroups) +{ + if (allGroups != isAllGroupsSearch()) { + m_searchAllGroups = allGroups; + if (m_entryView->inEntryListMode()) + startSearchTimer(); } } void DatabaseWidget::closeSearch() { Q_ASSERT(m_lastGroup); + m_searchTimer->stop(); m_groupView->setCurrentGroup(m_lastGroup); } void DatabaseWidget::showSearch(const QString & searchString) { - m_searchUi->searchEdit->blockSignals(true); - m_searchUi->searchEdit->setText(searchString); - m_searchUi->searchEdit->blockSignals(false); - - m_searchUi->searchCurrentRadioButton->blockSignals(true); - m_searchUi->searchRootRadioButton->blockSignals(true); - m_searchUi->searchRootRadioButton->setChecked(true); - m_searchUi->searchCurrentRadioButton->blockSignals(false); - m_searchUi->searchRootRadioButton->blockSignals(false); - + m_searchText = searchString; m_lastGroup = m_groupView->currentGroup(); Q_ASSERT(m_lastGroup); - - if (m_lastGroup == m_db->rootGroup()) { - m_searchUi->optionsWidget->hide(); - m_searchUi->searchCurrentRadioButton->hide(); - m_searchUi->searchRootRadioButton->hide(); - } - else { - m_searchUi->optionsWidget->show(); - m_searchUi->searchCurrentRadioButton->show(); - m_searchUi->searchRootRadioButton->show(); - m_searchUi->searchCurrentRadioButton->setText(tr("Current group") - .append(" (") - .append(m_lastGroup->name()) - .append(")")); - } m_groupView->setCurrentIndex(QModelIndex()); m_searchWidget->show(); search(); - m_searchUi->searchEdit->setFocus(); +} + +void DatabaseWidget::onLinkActivated(const QString& link) +{ + if (link == "searchAll") + setAllGroupsSearch(true); + else if (link == "searchCurrent") + setAllGroupsSearch(false); } void DatabaseWidget::search() { Q_ASSERT(m_lastGroup); - Group* searchGroup; - if (m_searchUi->searchCurrentRadioButton->isChecked()) { - searchGroup = m_lastGroup; - } - else if (m_searchUi->searchRootRadioButton->isChecked()) { - searchGroup = m_db->rootGroup(); - } - else { - Q_ASSERT(false); - return; - } + Group* searchGroup = m_searchAllGroups ? m_db->rootGroup() + : m_lastGroup; + QList searchResult = searchGroup->search(m_searchText, m_searchSensitivity); - Qt::CaseSensitivity sensitivity; - if (m_searchUi->caseSensitiveCheckBox->isChecked()) { - sensitivity = Qt::CaseSensitive; + QString message; + switch(searchResult.count()) { + case 0: message = tr("No result found"); break; + case 1: message = tr("1 result found"); break; + default: message = tr("%1 results found").arg(searchResult.count()); break; } - else { - sensitivity = Qt::CaseInsensitive; - } - QList searchResult = searchGroup->search(m_searchUi->searchEdit->text(), sensitivity); - + if (searchGroup != m_db->rootGroup()) + message += tr(" in \"%1\". Search all groups...").arg(searchGroup->name()); + else if (m_lastGroup != m_db->rootGroup()) + message += tr(". Search in \"%1\"...").arg(m_lastGroup->name()); + else + message += tr("."); + m_searchUi->searchResults->setText(message); m_entryView->setEntryList(searchResult); } @@ -639,6 +672,9 @@ void DatabaseWidget::startSearchTimer() void DatabaseWidget::startSearch() { + if (!isInSearchMode()) + return; + if (!m_searchTimer->isActive()) { m_searchTimer->stop(); } @@ -667,14 +703,14 @@ bool DatabaseWidget::canDeleteCurrentGoup() return !isRootGroup && !isRecycleBin; } -bool DatabaseWidget::isInSearchMode() +bool DatabaseWidget::isInSearchMode() const { return m_entryView->inEntryListMode(); } -QString DatabaseWidget::searchText() +QString DatabaseWidget::searchText() const { - return m_entryView->inEntryListMode() ? m_searchUi->searchEdit->text() : QString(); + return m_entryView->inEntryListMode() ? m_searchText : QString(); } void DatabaseWidget::clearLastGroup(Group* group) diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 92cb79734..0be097724 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -63,14 +63,19 @@ public: Database* database(); bool dbHasKey(); bool canDeleteCurrentGoup(); - bool isInSearchMode(); - QString searchText(); + bool isInSearchMode() const; + QString searchText() const; + bool caseSensitiveSearch() const; + bool isAllGroupsSearch() const; + bool canChooseSearchScope() const; + Group* currentGroup() const; int addWidget(QWidget* w); void setCurrentIndex(int index); void setCurrentWidget(QWidget* widget); DatabaseWidget::Mode currentMode(); void lock(); void updateFilename(const QString& filename); + void search(const QString & searchString, bool caseSensitive, bool allGroups); Q_SIGNALS: void closeRequest(); @@ -102,12 +107,15 @@ public Q_SLOTS: void switchToOpenDatabase(const QString &fileName, const CompositeKey &masterKey); void switchToImportKeepass1(const QString& fileName); void switchToView(bool accepted); - void toggleSearch(); - void showSearch(const QString & searchString = QString()); + void search(const QString & searchString); + void setCaseSensitiveSearch(bool caseSensitive); + void setAllGroupsSearch(bool allGroups); void emitGroupContextMenuRequested(const QPoint& pos); void emitEntryContextMenuRequested(const QPoint& pos); private Q_SLOTS: + void onLinkActivated(const QString& link); + void showSearch(const QString & searchString = QString()); void switchBackToEntryEdit(); void switchToHistoryView(Entry* entry); void switchToEntryEdit(Entry* entry); @@ -145,6 +153,9 @@ private: QTimer* m_searchTimer; QWidget* widgetBeforeLock; QString m_filename; + QString m_searchText; + bool m_searchAllGroups; + Qt::CaseSensitivity m_searchSensitivity; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 657ecce4b..9dab8e048 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -37,6 +37,7 @@ #include "http/HttpSettings.h" #include "http/OptionDialog.h" #include "gui/SettingsWidget.h" +#include "gui/qocoa/qsearchfield.h" class HttpPlugin: public ISettingsPage { public: @@ -114,7 +115,11 @@ MainWindow::MainWindow() setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - setShortcut(m_ui->actionSearch, QKeySequence::Find, Qt::CTRL + Qt::Key_F); + //TODO: do not register shortcut on Q_OS_MAC, if this is done automatically?? + const QKeySequence seq = !QKeySequence::keyBindings(QKeySequence::Find).isEmpty() + ? QKeySequence::Find + : QKeySequence(Qt::CTRL + Qt::Key_F); + connect(new QShortcut(seq, this), SIGNAL(activated()), m_ui->searchField, SLOT(setFocus())); m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); @@ -152,8 +157,6 @@ MainWindow::MainWindow() m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about")); - m_ui->actionSearch->setIcon(filePath()->icon("actions", "system-search")); - m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode))); m_actionMultiplexer.connect(SIGNAL(groupChanged()), @@ -225,8 +228,24 @@ MainWindow::MainWindow() connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); - m_actionMultiplexer.connect(m_ui->actionSearch, SIGNAL(triggered()), - SLOT(toggleSearch())); + m_ui->searchField->setPlaceholderText(tr("Type to search")); + m_ui->searchField->setEnabled(false); + m_ui->toolBar->addWidget(m_ui->searchPanel); + m_actionMultiplexer.connect(m_ui->searchField, SIGNAL(textChanged(QString)), + SLOT(search(QString))); + QMenu* searchMenu = new QMenu(this); + searchMenu->addAction(m_ui->actionFindCaseSensitive); + searchMenu->addSeparator(); + searchMenu->addAction(m_ui->actionFindCurrentGroup); + searchMenu->addAction(m_ui->actionFindRootGroup); + m_ui->searchField->setMenu(searchMenu); + QActionGroup* group = new QActionGroup(this); + group->addAction(m_ui->actionFindCurrentGroup); + group->addAction(m_ui->actionFindRootGroup); + m_actionMultiplexer.connect(m_ui->actionFindCaseSensitive, SIGNAL(toggled(bool)), + SLOT(setCaseSensitiveSearch(bool))); + m_actionMultiplexer.connect(m_ui->actionFindRootGroup, SIGNAL(toggled(bool)), + SLOT(setAllGroupsSearch(bool))); m_ui->tabWidget->reopenLastDatabases(); } @@ -298,6 +317,26 @@ void MainWindow::openDatabase(const QString& fileName, const QString& pw, const m_ui->tabWidget->openDatabase(fileName, pw, keyFile); } +void MainWindow::updateSearchField(DatabaseWidget* dbWidget) +{ + bool enabled = dbWidget != NULL; + + m_ui->actionFindCaseSensitive->setChecked(enabled && dbWidget->caseSensitiveSearch()); + + m_ui->actionFindCurrentGroup->setEnabled(enabled && dbWidget->canChooseSearchScope()); + m_ui->actionFindRootGroup->setEnabled(enabled && dbWidget->canChooseSearchScope()); + if (enabled && dbWidget->isAllGroupsSearch()) + m_ui->actionFindRootGroup->setChecked(true); + else + m_ui->actionFindCurrentGroup->setChecked(true); + + m_ui->searchField->setEnabled(enabled); + if (enabled && dbWidget->isInSearchMode()) + m_ui->searchField->setText(dbWidget->searchText()); + else + m_ui->searchField->clear(); +} + void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) { bool inDatabaseTabWidget = (m_ui->stackedWidget->currentIndex() == 0); @@ -329,9 +368,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGoup()); - m_ui->actionSearch->setEnabled(true); - // TODO: get checked state from db widget - m_ui->actionSearch->setChecked(inSearch); + updateSearchField(dbWidget); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); @@ -349,8 +386,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) } m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->actionSearch->setEnabled(false); - m_ui->actionSearch->setChecked(false); + updateSearchField(); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); @@ -371,8 +407,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) } m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->actionSearch->setEnabled(false); - m_ui->actionSearch->setChecked(false); + updateSearchField(); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 52472a429..04e600992 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -64,6 +64,7 @@ private Q_SLOTS: void setToolbarIconSize28(); private: + void updateSearchField(DatabaseWidget* dbWidget = NULL); static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); static const QString BaseWindowTitle; diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index ec05f82ee..8f04f118a 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -55,6 +55,34 @@ + + + + + 0 + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + @@ -173,7 +201,6 @@ - @@ -299,17 +326,6 @@ Clone entry - - - true - - - false - - - Find - - false @@ -379,6 +395,30 @@ 2&8x28 + + + true + + + Case Sensitive + + + + + true + + + Current Group + + + + + true + + + Root Group + + @@ -399,6 +439,12 @@
gui/WelcomeWidget.h
1
+ + QSearchField + QWidget +
gui/qocoa/qsearchfield.h
+ 1 +
diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index d6205768a..b8be91f32 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -7,17 +7,14 @@ 0 0 630 - 87 + 34 0 - - - - + @@ -27,72 +24,12 @@ - - - Find: - - + - - - - - 0 - - - - - Case sensitive - - - - - - - Current group - - - false - - - - - - - Root group - - - true - - - - - - - Qt::Horizontal - - - - 255 - 1 - - - - - - - - - - LineEdit - QLineEdit -
gui/LineEdit.h
-
-
diff --git a/src/gui/qocoa/CMakeLists.txt b/src/gui/qocoa/CMakeLists.txt new file mode 100644 index 000000000..7ea00cc65 --- /dev/null +++ b/src/gui/qocoa/CMakeLists.txt @@ -0,0 +1,50 @@ +project(Qocoa) +cmake_minimum_required(VERSION 2.8) + +#find_package(Qt4 COMPONENTS QtMain QtCore QtGui REQUIRED) +#include(UseQt4) + +set(SOURCES + #main.cpp + #gallery.cpp +) + +set(HEADERS + #gallery.h + qsearchfield.h + qbutton.h + qprogressindicatorspinning.h +) + +qt4_wrap_cpp(MOC_SOURCES ${HEADERS}) + +if(APPLE) + list(APPEND SOURCES + qsearchfield_mac.mm + qbutton_mac.mm + qprogressindicatorspinning_mac.mm + ) +else() + list(APPEND SOURCES + qsearchfield_nonmac.cpp + qbutton_nonmac.cpp + qprogressindicatorspinning_nonmac.cpp + ) + set(RESOURCES + qsearchfield_nonmac.qrc + qprogressindicatorspinning_nonmac.qrc + ) + qt4_add_resources(RESOURCES_SOURCES ${RESOURCES}) +endif() + +#add_executable(Qocoa +# WIN32 MACOSX_BUNDLE +# ${SOURCES} ${MOC_SOURCES} ${RESOURCES_SOURCES} +#) +#target_link_libraries(Qocoa ${QT_LIBRARIES}) + +add_library (Qocoa STATIC ${SOURCES} ${MOC_SOURCES} ${HEADERS} ${RESOURCES_SOURCES}) + +if(APPLE) + set_target_properties(Qocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit") +endif() diff --git a/src/gui/qocoa/LICENSE.txt b/src/gui/qocoa/LICENSE.txt new file mode 100644 index 000000000..910eb6d20 --- /dev/null +++ b/src/gui/qocoa/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/gui/qocoa/Qocoa.pro b/src/gui/qocoa/Qocoa.pro new file mode 100644 index 000000000..8b325d192 --- /dev/null +++ b/src/gui/qocoa/Qocoa.pro @@ -0,0 +1,17 @@ +SOURCES += main.cpp\ + gallery.cpp \ + +HEADERS += gallery.h \ + qocoa_mac.h \ + qsearchfield.h \ + qbutton.h \ + qprogressindicatorspinning.h \ + +mac { + OBJECTIVE_SOURCES += qsearchfield_mac.mm qbutton_mac.mm qprogressindicatorspinning_mac.mm + LIBS += -framework Foundation -framework Appkit + QMAKE_CFLAGS += -mmacosx-version-min=10.6 +} else { + SOURCES += qsearchfield_nonmac.cpp qbutton_nonmac.cpp qprogressindicatorspinning_nonmac.cpp + RESOURCES += qsearchfield_nonmac.qrc qprogressindicatorspinning_nonmac.qrc +} diff --git a/src/gui/qocoa/README.md b/src/gui/qocoa/README.md new file mode 100644 index 000000000..5f981893e --- /dev/null +++ b/src/gui/qocoa/README.md @@ -0,0 +1,36 @@ +# Qocoa +Qocoa is a collection of Qt wrappers for OSX's Cocoa widgets. + +## Features +- basic fallback to sensible Qt types on non-OSX platforms +- shared class headers which expose no implementation details +- typical Qt signal/slot-based API +- trivial to import into projects (class header/implementation, [single shared global header](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h)) + +## Building +``` +git clone git://github.com/mikemcquaid/Qocoa.git +cd Qocoa +qmake # or cmake . +make +``` + +## Status +Qocoa classes are currently provided for NSButton, a spinning NSProgressIndicator and NSSearchField. There is a [TODO list](https://github.com/mikemcquaid/Qocoa/blob/master/TODO.md) for classes I hope to implement. + +## Usage +For each class you want to use copy the [`qocoa_mac.h`](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h), `$CLASS.h`, `$CLASS_mac.*` and `$CLASS_nonmac.*` files into your source tree and add them to your buildsystem. Examples are provided for [CMake](https://github.com/mikemcquaid/Qocoa/blob/master/CMakeLists.txt) and [QMake](https://github.com/mikemcquaid/Qocoa/blob/master/Qocoa.pro). + +## Contact +[Mike McQuaid](mailto:mike@mikemcquaid.com) + +## License +Qocoa is licensed under the [MIT License](http://en.wikipedia.org/wiki/MIT_License). +The full license text is available in [LICENSE.txt](https://github.com/mikemcquaid/Qocoa/blob/master/LICENSE.txt). + +Magnifier and EditClear icons taken from [QtCreator](http://qt-project.org/), licensed under [LGPL](http://www.gnu.org/copyleft/lesser.html). + +Other icons are taken from the [Oxygen Project](http://www.oxygen-icons.org/) and are licensed under the [Creative Commons Attribution-ShareAlike 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/). + +## Gallery +![Qocoa Gallery](https://github.com/mikemcquaid/Qocoa/raw/master/gallery.png) diff --git a/src/gui/qocoa/TODO.md b/src/gui/qocoa/TODO.md new file mode 100644 index 000000000..45972bafa --- /dev/null +++ b/src/gui/qocoa/TODO.md @@ -0,0 +1,13 @@ +Widgets I hope to implement (or at least investigate): + +- NSTokenField +- NSSegmentedControl +- NSLevelIndicator +- NSPathControl +- NSSlider (Circular) +- NSSplitView +- NSTextFinder +- NSOutlineView in an NSScrollView (Source List) +- NSDrawer +- PDFView +- WebView diff --git a/src/gui/qocoa/gallery.cpp b/src/gui/qocoa/gallery.cpp new file mode 100644 index 000000000..210821ebd --- /dev/null +++ b/src/gui/qocoa/gallery.cpp @@ -0,0 +1,75 @@ +#include "gallery.h" + +#include + +#include "qsearchfield.h" +#include "qbutton.h" +#include "qprogressindicatorspinning.h" + +Gallery::Gallery(QWidget *parent) : QWidget(parent) +{ + setWindowTitle("Qocoa Gallery"); + QVBoxLayout *layout = new QVBoxLayout(this); + + QSearchField *searchField = new QSearchField(this); + layout->addWidget(searchField); + + QSearchField *searchFieldPlaceholder = new QSearchField(this); + searchFieldPlaceholder->setPlaceholderText("Placeholder text"); + layout->addWidget(searchFieldPlaceholder); + + QButton *roundedButton = new QButton(this, QButton::Rounded); + roundedButton->setText("Button"); + layout->addWidget(roundedButton); + + QButton *regularSquareButton = new QButton(this, QButton::RegularSquare); + regularSquareButton->setText("Button"); + layout->addWidget(regularSquareButton); + + QButton *disclosureButton = new QButton(this, QButton::Disclosure); + layout->addWidget(disclosureButton); + + QButton *shadowlessSquareButton = new QButton(this, QButton::ShadowlessSquare); + shadowlessSquareButton->setText("Button"); + layout->addWidget(shadowlessSquareButton); + + QButton *circularButton = new QButton(this, QButton::Circular); + layout->addWidget(circularButton); + + QButton *textureSquareButton = new QButton(this, QButton::TexturedSquare); + textureSquareButton->setText("Textured Button"); + layout->addWidget(textureSquareButton); + + QButton *helpButton = new QButton(this, QButton::HelpButton); + layout->addWidget(helpButton); + + QButton *smallSquareButton = new QButton(this, QButton::SmallSquare); + smallSquareButton->setText("Gradient Button"); + layout->addWidget(smallSquareButton); + + QButton *texturedRoundedButton = new QButton(this, QButton::TexturedRounded); + texturedRoundedButton->setText("Round Textured"); + layout->addWidget(texturedRoundedButton); + + QButton *roundedRectangleButton = new QButton(this, QButton::RoundRect); + roundedRectangleButton->setText("Rounded Rect Button"); + layout->addWidget(roundedRectangleButton); + + QButton *recessedButton = new QButton(this, QButton::Recessed); + recessedButton->setText("Recessed Button"); + layout->addWidget(recessedButton); + + QButton *roundedDisclosureButton = new QButton(this, QButton::RoundedDisclosure); + layout->addWidget(roundedDisclosureButton); + +#ifdef __MAC_10_7 + QButton *inlineButton = new QButton(this, QButton::Inline); + inlineButton->setText("Inline Button"); + layout->addWidget(inlineButton); +#endif + + + QProgressIndicatorSpinning *progressIndicatorSpinning = new QProgressIndicatorSpinning(this); + progressIndicatorSpinning->animate(); + layout->addWidget(progressIndicatorSpinning); +} diff --git a/src/gui/qocoa/gallery.h b/src/gui/qocoa/gallery.h new file mode 100644 index 000000000..1e83bad94 --- /dev/null +++ b/src/gui/qocoa/gallery.h @@ -0,0 +1,14 @@ +#ifndef GALLERY_H +#define GALLERY_H + +#include + +class Gallery : public QWidget +{ + Q_OBJECT + +public: + explicit Gallery(QWidget *parent = 0); +}; + +#endif // WIDGET_H diff --git a/src/gui/qocoa/gallery.png b/src/gui/qocoa/gallery.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2736ff572491af1fefbbface2742caceabd6e3 GIT binary patch literal 42195 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%0DbY5)JT{pQruVuz)6KWO5uY(p-#)v2|KD7@ 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 +#include "gallery.h" + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + + Gallery gallery; + gallery.show(); + + return application.exec(); +} diff --git a/src/gui/qocoa/qbutton.h b/src/gui/qocoa/qbutton.h new file mode 100644 index 000000000..8b8b7a74f --- /dev/null +++ b/src/gui/qocoa/qbutton.h @@ -0,0 +1,49 @@ +#ifndef QBUTTON_H +#define QBUTTON_H + +#include +#include + +class QButtonPrivate; +class QButton : public QWidget +{ + Q_OBJECT +public: + // Matches NSBezelStyle + enum BezelStyle { + Rounded = 1, + RegularSquare = 2, + Disclosure = 5, + ShadowlessSquare = 6, + Circular = 7, + TexturedSquare = 8, + HelpButton = 9, + SmallSquare = 10, + TexturedRounded = 11, + RoundRect = 12, + Recessed = 13, + RoundedDisclosure = 14, +#ifdef __MAC_10_7 + Inline = 15 +#endif + }; + + explicit QButton(QWidget *parent, BezelStyle bezelStyle = Rounded); + +public Q_SLOTS: + void setText(const QString &text); + void setImage(const QPixmap &image); + void setChecked(bool checked); + +public: + void setCheckable(bool checkable); + bool isChecked(); + +Q_SIGNALS: + void clicked(bool checked = false); + +private: + friend class QButtonPrivate; + QPointer pimpl; +}; +#endif // QBUTTON_H diff --git a/src/gui/qocoa/qbutton_mac.mm b/src/gui/qocoa/qbutton_mac.mm new file mode 100644 index 000000000..15490e453 --- /dev/null +++ b/src/gui/qocoa/qbutton_mac.mm @@ -0,0 +1,229 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qbutton.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "AppKit/NSButton.h" +#import "AppKit/NSFont.h" + +class QButtonPrivate : public QObject +{ +public: + QButtonPrivate(QButton *qButton, NSButton *nsButton, QButton::BezelStyle bezelStyle) + : QObject(qButton), qButton(qButton), nsButton(nsButton) + { + switch(bezelStyle) { + case QButton::Disclosure: + case QButton::Circular: +#ifdef __MAC_10_7 + case QButton::Inline: +#endif + case QButton::RoundedDisclosure: + case QButton::HelpButton: + [nsButton setTitle:@""]; + default: + break; + } + + NSFont* font = 0; + switch(bezelStyle) { + case QButton::RoundRect: + font = [NSFont fontWithName:@"Lucida Grande" size:12]; + break; + + case QButton::Recessed: + font = [NSFont fontWithName:@"Lucida Grande Bold" size:12]; + break; + +#ifdef __MAC_10_7 + case QButton::Inline: + font = [NSFont boldSystemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]; + break; +#endif + + default: + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + break; + } + [nsButton setFont:font]; + + switch(bezelStyle) { + case QButton::Rounded: + qButton->setMinimumWidth(40); + qButton->setFixedHeight(24); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::RegularSquare: + case QButton::TexturedSquare: + qButton->setMinimumSize(14, 23); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::ShadowlessSquare: + qButton->setMinimumSize(5, 25); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::SmallSquare: + qButton->setMinimumSize(4, 21); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::TexturedRounded: + qButton->setMinimumSize(10, 22); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::RoundRect: + case QButton::Recessed: + qButton->setMinimumWidth(16); + qButton->setFixedHeight(18); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::Disclosure: + qButton->setMinimumWidth(13); + qButton->setFixedHeight(13); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + case QButton::Circular: + qButton->setMinimumSize(16, 16); + qButton->setMaximumHeight(40); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + break; + case QButton::HelpButton: + case QButton::RoundedDisclosure: + qButton->setMinimumWidth(22); + qButton->setFixedHeight(22); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; +#ifdef __MAC_10_7 + case QButton::Inline: + qButton->setMinimumWidth(10); + qButton->setFixedHeight(16); + qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; +#endif + } + + switch(bezelStyle) { + case QButton::Recessed: + [nsButton setButtonType:NSPushOnPushOffButton]; + case QButton::Disclosure: + [nsButton setButtonType:NSOnOffButton]; + default: + [nsButton setButtonType:NSMomentaryPushInButton]; + } + + [nsButton setBezelStyle:bezelStyle]; + } + + void clicked() + { + emit qButton->clicked(qButton->isChecked()); + } + + ~QButtonPrivate() { + [[nsButton target] release]; + [nsButton setTarget:nil]; + } + + QButton *qButton; + NSButton *nsButton; +}; + +@interface QButtonTarget : NSObject +{ +@public + QPointer pimpl; +} +-(void)clicked; +@end + +@implementation QButtonTarget +-(void)clicked { + Q_ASSERT(pimpl); + if (pimpl) + pimpl->clicked(); +} +@end + +QButton::QButton(QWidget *parent, BezelStyle bezelStyle) : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSButton *button = [[NSButton alloc] init]; + pimpl = new QButtonPrivate(this, button, bezelStyle); + + QButtonTarget *target = [[QButtonTarget alloc] init]; + target->pimpl = pimpl; + [button setTarget:target]; + + [button setAction:@selector(clicked)]; + + setupLayout(button, this); + + [button release]; + + [pool drain]; +} + +void QButton::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pimpl->nsButton setTitle:fromQString(text)]; + [pool drain]; +} + +void QButton::setImage(const QPixmap &image) +{ + Q_ASSERT(pimpl); + if (pimpl) + [pimpl->nsButton setImage:fromQPixmap(image)]; +} + +void QButton::setChecked(bool checked) +{ + Q_ASSERT(pimpl); + if (pimpl) + [pimpl->nsButton setState:checked]; +} + +void QButton::setCheckable(bool checkable) +{ + const NSInteger cellMask = checkable ? NSChangeBackgroundCellMask : NSNoCellMask; + + Q_ASSERT(pimpl); + if (pimpl) + [[pimpl->nsButton cell] setShowsStateBy:cellMask]; +} + +bool QButton::isChecked() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return false; + + return [pimpl->nsButton state]; +} diff --git a/src/gui/qocoa/qbutton_nonmac.cpp b/src/gui/qocoa/qbutton_nonmac.cpp new file mode 100644 index 000000000..c7fafe6e4 --- /dev/null +++ b/src/gui/qocoa/qbutton_nonmac.cpp @@ -0,0 +1,89 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qbutton.h" + +#include +#include +#include +#include + +class QButtonPrivate : public QObject +{ +public: + QButtonPrivate(QButton *button, QAbstractButton *abstractButton) + : QObject(button), abstractButton(abstractButton) {} + QPointer abstractButton; +}; + +QButton::QButton(QWidget *parent, BezelStyle) : QWidget(parent) +{ + QAbstractButton *button = 0; + if (qobject_cast(parent)) + button = new QToolButton(this); + else + button = new QPushButton(this); + connect(button, SIGNAL(clicked()), + this, SIGNAL(clicked())); + pimpl = new QButtonPrivate(this, button); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(button); +} + +void QButton::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setText(text); +} + +void QButton::setImage(const QPixmap &image) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setIcon(image); +} + +void QButton::setChecked(bool checked) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setChecked(checked); +} + +void QButton::setCheckable(bool checkable) +{ + Q_ASSERT(pimpl); + if (pimpl) + pimpl->abstractButton->setCheckable(checkable); +} + +bool QButton::isChecked() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return false; + + return pimpl->abstractButton->isChecked(); +} diff --git a/src/gui/qocoa/qocoa_mac.h b/src/gui/qocoa/qocoa_mac.h new file mode 100644 index 000000000..ced431173 --- /dev/null +++ b/src/gui/qocoa/qocoa_mac.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include +#include +#include +#include + +static inline NSString* fromQString(const QString &string) +{ + const QByteArray utf8 = string.toUtf8(); + const char* cString = utf8.constData(); + return [[NSString alloc] initWithUTF8String:cString]; +} + +static inline QString toQString(NSString *string) +{ + if (!string) + return QString(); + return QString::fromUtf8([string UTF8String]); +} + +static inline NSImage* fromQPixmap(const QPixmap &pixmap) +{ + CGImageRef cgImage = pixmap.toMacCGImageRef(); + return [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize]; +} + +static inline void setupLayout(void *cocoaView, QWidget *parent) +{ + parent->setAttribute(Qt::WA_NativeWindow); + QVBoxLayout *layout = new QVBoxLayout(parent); + layout->setMargin(0); + layout->addWidget(new QMacCocoaViewContainer(cocoaView, parent)); +} diff --git a/src/gui/qocoa/qprogressindicatorspinning.h b/src/gui/qocoa/qprogressindicatorspinning.h new file mode 100644 index 000000000..d78a4868d --- /dev/null +++ b/src/gui/qocoa/qprogressindicatorspinning.h @@ -0,0 +1,29 @@ +#ifndef QPROGRESSINDICATORSPINNING_H +#define QPROGRESSINDICATORSPINNING_H + +#include +#include + +class QProgressIndicatorSpinningPrivate; +class QProgressIndicatorSpinning : public QWidget +{ + Q_OBJECT +public: + // Matches NSProgressIndicatorThickness + enum Thickness { + Default = 14, + Small = 10, + Large = 18, + Aqua = 12 + }; + + explicit QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness = Default); +public Q_SLOTS: + void animate(bool animate = true); +private: + friend class QProgressIndicatorSpinningPrivate; + QPointer pimpl; +}; + +#endif // QPROGRESSINDICATORSPINNING_H diff --git a/src/gui/qocoa/qprogressindicatorspinning_mac.mm b/src/gui/qocoa/qprogressindicatorspinning_mac.mm new file mode 100644 index 000000000..c67c7c567 --- /dev/null +++ b/src/gui/qocoa/qprogressindicatorspinning_mac.mm @@ -0,0 +1,70 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qprogressindicatorspinning.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "AppKit/NSProgressIndicator.h" + +class QProgressIndicatorSpinningPrivate : public QObject +{ +public: + QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning, + NSProgressIndicator *nsProgressIndicator) + : QObject(qProgressIndicatorSpinning), nsProgressIndicator(nsProgressIndicator) {} + + NSProgressIndicator *nsProgressIndicator; +}; + +QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness) + : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSProgressIndicator *progress = [[NSProgressIndicator alloc] init]; + [progress setStyle:NSProgressIndicatorSpinningStyle]; + + pimpl = new QProgressIndicatorSpinningPrivate(this, progress); + + setupLayout(progress, this); + + setFixedSize(thickness, thickness); + + [progress release]; + + [pool drain]; +} + +void QProgressIndicatorSpinning::animate(bool animate) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + if (animate) + [pimpl->nsProgressIndicator startAnimation:nil]; + else + [pimpl->nsProgressIndicator stopAnimation:nil]; +} diff --git a/src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp b/src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp new file mode 100644 index 000000000..fae777830 --- /dev/null +++ b/src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp @@ -0,0 +1,86 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qprogressindicatorspinning.h" + +#include +#include +#include + +class QProgressIndicatorSpinningPrivate : public QObject +{ +public: + QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning, + QMovie *movie) + : QObject(qProgressIndicatorSpinning), movie(movie) {} + + QPointer movie; +}; + +struct QProgressIndicatorSpinningResources +{ +#ifndef Q_OS_MAC + QProgressIndicatorSpinningResources() { + Q_INIT_RESOURCE(qprogressindicatorspinning_nonmac); + } + ~QProgressIndicatorSpinningResources() { + Q_CLEANUP_RESOURCE(qprogressindicatorspinning_nonmac); + } +#endif +}; + +QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent, + Thickness thickness) + : QWidget(parent) +{ + static QProgressIndicatorSpinningResources resources; + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + + QSize size(thickness, thickness); + QMovie *movie = new QMovie(this); + movie->setFileName(":/Qocoa/qprogressindicatorspinning_nonmac.gif"); + movie->setScaledSize(size); + // Roughly match OSX speed. + movie->setSpeed(200); + pimpl = new QProgressIndicatorSpinningPrivate(this, movie); + + QLabel *label = new QLabel(this); + label->setMovie(movie); + + layout->addWidget(label); + setFixedSize(size); +} + + +void QProgressIndicatorSpinning::animate(bool animate) +{ + Q_ASSERT(pimpl && pimpl->movie); + if (!(pimpl && pimpl->movie)) + return; + + if (animate) + pimpl->movie->start(); + else + pimpl->movie->stop(); +} diff --git a/src/gui/qocoa/qprogressindicatorspinning_nonmac.gif b/src/gui/qocoa/qprogressindicatorspinning_nonmac.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 zcmZ?wbhEHbRA5kG_{_lY|Nnmm28LtDjYN)~s1(W@cewVK;8vc=F`Qo;`cy z0|SHNKPl&;)Wnk16ovB4k_?5!ti+0({KOQ! z%)GRG2F0H&oLmeH3_2haK$bGFJQq0OnVGos)!FAs-mNNg-FQS72QfKL3E8B>zSJoA zk(Pm}zW&MFMFomglMkLTkYp0DRyk_m%s5An$x}=5kB75{qk~{RBTKWQv1N$qu9%A! znk#tQ7bF~fw`Eg`2zz;jyKN*3SGovGbGb)Sn`{}IqwORE&ajv{F&);NOsvdHmd=@P zy>!)Vj}={uRU&s*3V1s`JDPlR_KP7Q2({IGzN1Q z25f$R1@e33l2>P!N4lAc%ypY^Wl<2@;VB_W67IXaT6Z6Kv&LuD+?0n>AJkeSb1S4@ z^i4U;Bg&!>!y_W%Fhf$P;nhLWSqdC18(0h_{-^~tZ=59TA;e|EXc)NVc1;+Yq$*Pt zOPL5uy>ga8J6E4bPLF$fwLu+or%j^`XG`>===O|>6-yVouVmq>T)AlF^7ZYDw@r(l zXE1%2^d9#u6Qx%y3uvpFBXm6D)ouB^_N~)uw7sTo69be7nU1$rFl9*u4Kv;Do29;gVNp zpBuWHro^^tOkEgseAAQ=qn;^QJ}uRUFWV>xM_({~;48B40hhrANzGPCE(M<*VoN-k z+CJpAo^@SuJV9g&3u}SU(oin$MwYlV5mk<+xQ(nQp2#w>mGW2%GgVbF)f%)!NON*^ z1{*M^mdjSMWZSUMQRSTPzj$%yWX_4ps+O#(p0sw&s+yInmTX(RNOh;otlc7e-PULJ z7#^Bp(9zk@zU=g#$St#lXC1aUQqi@4QV(a<{F4ryLY>!b?yhEd#4zjl!F!KiFs$Dv z{l?+YqBlMeC{)W1?<(W|rRlCGfwbEhd> zT@cisI3>hM;N}uO;fMPQ?9{hSU6RW2Ri$I5QU|k0OuMqHp)+G%!;wCb=96{TuQGT^ z>WJOZaZH@bu}W%gXnC9oqo-=X-FqB-rQCsH#>Pz5290c)h9Rx)tm)-4m9?tO9jfX< zbLI;3ELh0R%GuZ2W4mw_&$8unSMw}hy4-l)Huq_&JEs)v+H0_kYo_pKub})^Q{fYB z2A)Tjkqo1MmdfcN&mELpvinj{Ane);vGk%3?-_>_{zie*` zJ-T(O@PUu63Y$;1aUfYOtDg2`@5=WQq$x) zN#&DyRm~cd+?Qyis6;EZPx|4tQSN|S#@Y#N%!xwMfh+;N2?eS$huZX=+yY{fLwO(Z z+J{^yVG7qTU}JYz^Dl8>O^lZ*W~r3TaZ&f3F;kd(&fG%Iu9D;#bC+^2Tr_hTchZ7I zMzc1$Fi)P~%gn=LB|CAe)XtsU{V5AORGHWg@UYb{GEptoIC$^i;=6ZLqsOcN$8r8}qX>q$a zRi=FXqB7YWOLd<~lZDx)O=q{3Z)>-lKA&y&oXHE=X0|S#vdWUVLA6(gm7ANnJJWLW zX12LPD-@hL*mrWXE44YvGV?gGuxBuQ6glb*L;a2Jx^T{nFEaD| zmc+Gi$RQ`~G_zD^H>*-kTecy4(q#B!^{*Y=|j z`{^ap#lk-x?bvJKlE~Q1y3k7`!NJF!1zSn@o*w>puad~DGVAEKsIBUp)H|iFYqn}c zi#HQ9v!!ga)uI)R2DM96IayaQvx0oi!sf)WaY>FuG7qy9GaDPb&!VKjMm8Rvz6z&JJ_$RVR-0}@639IUHDaqz4yIi zFK-B^+?IYX@+tPmK_~Nt2N;?bm~lv4SnYx}BfOv}5peJvYY+t&1Of&e#$v3iHc!d8 zxqDo3q!gXE$TuWZugF3WfOf8=IM#%>r!n zLpg3TM|!wTdT41c!JJUGcTPVW`AZVTOob(qn%#tFEf#q9~9d)`NWyRI*7>u zp*)aMkn^y_6t4qWKm3e}z0LZ$HqPcr>Gl+8I3cRZEy*R|bHigp_(FFcGft*c;@%Mw z%Y>TWEZ8_n&umf3h85CvOl(z?Gj1LdVJ|IZFVZ(th%jYq_Oy;~lPTe5^;dS~toENh zyTdY!oo&(LIrA(RuP~WAPl9FP$~hZnPqz-6=B&@f%-pckOhTGt_u(0JT6=}m53_bj z`R2>-5ZbkywIT1^@g(_(L(En}b+OuET4!@bpR!1L$3O7DSme#{akHO;9>c5I7gK&R i)SWB5u7y|&#=&jWN literal 0 HcmV?d00001 diff --git a/src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc b/src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc new file mode 100644 index 000000000..108c78ec1 --- /dev/null +++ b/src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc @@ -0,0 +1,5 @@ + + + qprogressindicatorspinning_nonmac.gif + + diff --git a/src/gui/qocoa/qsearchfield.h b/src/gui/qocoa/qsearchfield.h new file mode 100644 index 000000000..1583b4627 --- /dev/null +++ b/src/gui/qocoa/qsearchfield.h @@ -0,0 +1,48 @@ +#ifndef QSEARCHFIELD_H +#define QSEARCHFIELD_H + +#include +#include +#include + +class QSearchFieldPrivate; +class QSearchField : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged USER true); + Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText); + +public: + explicit QSearchField(QWidget *parent); + + QString text() const; + QString placeholderText() const; + void setFocus(Qt::FocusReason); + void setMenu(QMenu *menu); + +public Q_SLOTS: + void setText(const QString &text); + void setPlaceholderText(const QString &text); + void clear(); + void selectAll(); + void setFocus(); + +Q_SIGNALS: + void textChanged(const QString &text); + void editingFinished(); + void returnPressed(); + +private Q_SLOTS: + void popupMenu(); + +protected: + void changeEvent(QEvent*); + void resizeEvent(QResizeEvent*); + +private: + friend class QSearchFieldPrivate; + QPointer pimpl; +}; + +#endif // QSEARCHFIELD_H diff --git a/src/gui/qocoa/qsearchfield_mac.mm b/src/gui/qocoa/qsearchfield_mac.mm new file mode 100644 index 000000000..7b43ecc75 --- /dev/null +++ b/src/gui/qocoa/qsearchfield_mac.mm @@ -0,0 +1,257 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qsearchfield.h" + +#include "qocoa_mac.h" + +#import "Foundation/NSAutoreleasePool.h" +#import "Foundation/NSNotification.h" +#import "AppKit/NSSearchField.h" + +#include +#include + +#define KEYCODE_A 0 +#define KEYCODE_X 7 +#define KEYCODE_C 8 +#define KEYCODE_V 9 + +class QSearchFieldPrivate : public QObject +{ +public: + QSearchFieldPrivate(QSearchField *qSearchField, NSSearchField *nsSearchField) + : QObject(qSearchField), qSearchField(qSearchField), nsSearchField(nsSearchField) {} + + void textDidChange(const QString &text) + { + if (qSearchField) + emit qSearchField->textChanged(text); + } + + void textDidEndEditing() + { + if (qSearchField) + emit qSearchField->editingFinished(); + } + + void returnPressed() + { + if (qSearchField) + emit qSearchField->returnPressed(); + } + + QPointer qSearchField; + NSSearchField *nsSearchField; +}; + +@interface QSearchFieldDelegate : NSObject +{ +@public + QPointer pimpl; +} +-(void)controlTextDidChange:(NSNotification*)notification; +-(void)controlTextDidEndEditing:(NSNotification*)notification; +@end + +@implementation QSearchFieldDelegate +-(void)controlTextDidChange:(NSNotification*)notification { + Q_ASSERT(pimpl); + if (pimpl) + pimpl->textDidChange(toQString([[notification object] stringValue])); +} + +-(void)controlTextDidEndEditing:(NSNotification*)notification { + Q_UNUSED(notification); + // No Q_ASSERT here as it is called on destruction. + if (pimpl) + pimpl->textDidEndEditing(); + + if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement) + pimpl->returnPressed(); +} +@end + +@interface QocoaSearchField : NSSearchField +-(BOOL)performKeyEquivalent:(NSEvent*)event; +@end + +@implementation QocoaSearchField +-(BOOL)performKeyEquivalent:(NSEvent*)event { + if ([event type] == NSKeyDown && [event modifierFlags] & NSCommandKeyMask) + { + const unsigned short keyCode = [event keyCode]; + if (keyCode == KEYCODE_A) + { + [self performSelector:@selector(selectText:)]; + return YES; + } + else if (keyCode == KEYCODE_C) + { + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(toQString([self stringValue])); + return YES; + } + else if (keyCode == KEYCODE_V) + { + QClipboard* clipboard = QApplication::clipboard(); + [self setStringValue:fromQString(clipboard->text())]; + return YES; + } + else if (keyCode == KEYCODE_X) + { + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(toQString([self stringValue])); + [self setStringValue:@""]; + return YES; + } + } + + return NO; +} +@end + +QSearchField::QSearchField(QWidget *parent) : QWidget(parent) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSSearchField *search = [[QocoaSearchField alloc] init]; + + QSearchFieldDelegate *delegate = [[QSearchFieldDelegate alloc] init]; + pimpl = delegate->pimpl = new QSearchFieldPrivate(this, search); + [search setDelegate:delegate]; + + setupLayout(search, this); + + setFixedHeight(24); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + [search release]; + + [pool drain]; +} + +void QSearchField::setMenu(QMenu *menu) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSMenu *nsMenu = menu->macMenu(); + [[pimpl->nsSearchField cell] setSearchMenuTemplate:nsMenu]; +} + +void QSearchField::popupMenu() +{ +} + +void QSearchField::setText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pimpl->nsSearchField setStringValue:fromQString(text)]; + [pool drain]; +} + +void QSearchField::setPlaceholderText(const QString &text) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[pimpl->nsSearchField cell] setPlaceholderString:fromQString(text)]; + [pool drain]; +} + +void QSearchField::clear() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + [pimpl->nsSearchField setStringValue:@""]; + emit textChanged(QString()); +} + +void QSearchField::selectAll() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + [pimpl->nsSearchField performSelector:@selector(selectText:)]; +} + +QString QSearchField::text() const +{ + Q_ASSERT(pimpl); + if (!pimpl) + return QString(); + + return toQString([pimpl->nsSearchField stringValue]); +} + +QString QSearchField::placeholderText() const +{ + Q_ASSERT(pimpl); + if (!pimpl) + return QString(); + + return toQString([[pimpl->nsSearchField cell] placeholderString]); +} + +void QSearchField::setFocus(Qt::FocusReason) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + if ([pimpl->nsSearchField acceptsFirstResponder]) + [[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField]; +} + +void QSearchField::setFocus() +{ + setFocus(Qt::OtherFocusReason); +} + +void QSearchField::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::EnabledChange) { + Q_ASSERT(pimpl); + if (!pimpl) + return; + + const bool enabled = isEnabled(); + [pimpl->nsSearchField setEnabled: enabled] + } + QWidget::changeEvent(event); +} + +void QSearchField::resizeEvent(QResizeEvent *resizeEvent) +{ + QWidget::resizeEvent(resizeEvent); +} diff --git a/src/gui/qocoa/qsearchfield_nonmac.cpp b/src/gui/qocoa/qsearchfield_nonmac.cpp new file mode 100644 index 000000000..5244bd605 --- /dev/null +++ b/src/gui/qocoa/qsearchfield_nonmac.cpp @@ -0,0 +1,270 @@ +/* +Copyright (C) 2011 by Mike McQuaid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "qsearchfield.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +class QSearchFieldPrivate : public QObject +{ +public: + QSearchFieldPrivate(QSearchField *searchField, QLineEdit *lineEdit, QToolButton *clearButton, QToolButton *searchButton) + : QObject(searchField), lineEdit(lineEdit), clearButton(clearButton), searchButton(searchButton) {} + + int lineEditFrameWidth() const { + return lineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + } + + int clearButtonPaddedWidth() const { + return clearButton->width() + lineEditFrameWidth() * 2; + } + + int clearButtonPaddedHeight() const { + return clearButton->height() + lineEditFrameWidth() * 2; + } + + int searchButtonPaddedWidth() const { + return searchButton->width() + lineEditFrameWidth() * 2; + } + + int searchButtonPaddedHeight() const { + return searchButton->height() + lineEditFrameWidth() * 2; + } + + QPointer lineEdit; + QPointer clearButton; + QPointer searchButton; + QPointer searchMenu; +}; + +struct QSearchFieldResources +{ +#ifndef Q_OS_MAC + QSearchFieldResources() { + Q_INIT_RESOURCE(qsearchfield_nonmac); + } + ~QSearchFieldResources() { + Q_CLEANUP_RESOURCE(qsearchfield_nonmac); + } +#endif +}; + +QSearchField::QSearchField(QWidget *parent) : QWidget(parent) +{ + static QSearchFieldResources resources; + + QLineEdit *lineEdit = new QLineEdit(this); + connect(lineEdit, SIGNAL(textChanged(QString)), + this, SIGNAL(textChanged(QString))); + connect(lineEdit, SIGNAL(editingFinished()), + this, SIGNAL(editingFinished())); + connect(lineEdit, SIGNAL(returnPressed()), + this, SIGNAL(returnPressed())); + connect(lineEdit, SIGNAL(textChanged(QString)), + this, SLOT(setText(QString))); + + int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize); + QToolButton *clearButton = new QToolButton(this); + QIcon clearIcon = QIcon::fromTheme(QLatin1String("edit-clear"), + QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_clear.png"))); + clearButton->setIcon(clearIcon); + clearButton->setIconSize(QSize(iconsize, iconsize)); + clearButton->setFixedSize(QSize(iconsize, iconsize)); + clearButton->setStyleSheet("border: none;"); + clearButton->hide(); + connect(clearButton, SIGNAL(clicked()), this, SLOT(clear())); + + QToolButton *searchButton = new QToolButton(this); + QIcon searchIcon = QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier.png")); + searchButton->setIcon(searchIcon); + searchButton->setIconSize(QSize(iconsize, iconsize)); + searchButton->setFixedSize(QSize(iconsize, iconsize)); + searchButton->setStyleSheet("border: none;"); + searchButton->setPopupMode(QToolButton::InstantPopup); + searchButton->setEnabled(false); + connect(searchButton, SIGNAL(clicked()), this, SLOT(popupMenu())); + + pimpl = new QSearchFieldPrivate(this, lineEdit, clearButton, searchButton); + + lineEdit->setStyleSheet(QString("QLineEdit { padding-left: %1px; padding-right: %2px; } ") + .arg(pimpl->searchButtonPaddedWidth()) + .arg(pimpl->clearButtonPaddedWidth())); + const int width = qMax(lineEdit->minimumSizeHint().width(), pimpl->clearButtonPaddedWidth() + pimpl->searchButtonPaddedWidth()); + const int height = qMax(lineEdit->minimumSizeHint().height(), + qMax(pimpl->clearButtonPaddedHeight(), + pimpl->searchButtonPaddedHeight())); + lineEdit->setMinimumSize(width, height); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(lineEdit); +} + +void QSearchField::setMenu(QMenu *menu) +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + pimpl->searchMenu = menu; + + QIcon searchIcon = menu ? QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier_menu.png")) + : QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier.png")); + pimpl->searchButton->setIcon(searchIcon); + pimpl->searchButton->setEnabled(isEnabled() && menu); +} + +void QSearchField::popupMenu() +{ + Q_ASSERT(pimpl); + if (!pimpl) + return; + + if (pimpl->searchMenu) { + const QRect screenRect = qApp->desktop()->availableGeometry(pimpl->searchButton); + const QSize sizeHint = pimpl->searchMenu->sizeHint(); + const QRect rect = pimpl->searchButton->rect(); + const int x = pimpl->searchButton->isRightToLeft() + ? rect.right() - sizeHint.width() + : rect.left(); + const int y = pimpl->searchButton->mapToGlobal(QPoint(0, rect.bottom())).y() + sizeHint.height() <= screenRect.height() + ? rect.bottom() + : rect.top() - sizeHint.height(); + QPoint point = pimpl->searchButton->mapToGlobal(QPoint(x, y)); + point.rx() = qMax(screenRect.left(), qMin(point.x(), screenRect.right() - sizeHint.width())); + point.ry() += 1; + + pimpl->searchMenu->popup(point); + } +} + +void QSearchField::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::EnabledChange) { + Q_ASSERT(pimpl); + if (!pimpl) + return; + + const bool enabled = isEnabled(); + pimpl->searchButton->setEnabled(enabled && pimpl->searchMenu); + pimpl->lineEdit->setEnabled(enabled); + pimpl->clearButton->setEnabled(enabled); + } + QWidget::changeEvent(event); +} + +void QSearchField::setText(const QString &text) +{ + Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit); + if (!(pimpl && pimpl->clearButton && pimpl->lineEdit)) + return; + + pimpl->clearButton->setVisible(!text.isEmpty()); + + if (text != this->text()) + pimpl->lineEdit->setText(text); +} + +void QSearchField::setPlaceholderText(const QString &text) +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + +#if QT_VERSION >= 0x040700 + pimpl->lineEdit->setPlaceholderText(text); +#endif +} + +void QSearchField::clear() +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + + pimpl->lineEdit->clear(); +} + +void QSearchField::selectAll() +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return; + + pimpl->lineEdit->selectAll(); +} + +QString QSearchField::text() const +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return QString(); + + return pimpl->lineEdit->text(); +} + +QString QSearchField::placeholderText() const { + Q_ASSERT(pimpl && pimpl->lineEdit); + if (!(pimpl && pimpl->lineEdit)) + return QString(); + +#if QT_VERSION >= 0x040700 + return pimpl->lineEdit->placeholderText(); +#else + return QString(); +#endif +} + +void QSearchField::setFocus(Qt::FocusReason reason) +{ + Q_ASSERT(pimpl && pimpl->lineEdit); + if (pimpl && pimpl->lineEdit) + pimpl->lineEdit->setFocus(reason); +} + +void QSearchField::setFocus() +{ + setFocus(Qt::OtherFocusReason); +} + +void QSearchField::resizeEvent(QResizeEvent *resizeEvent) +{ + Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit); + if (!(pimpl && pimpl->clearButton && pimpl->lineEdit)) + return; + + QWidget::resizeEvent(resizeEvent); + const int x = width() - pimpl->clearButtonPaddedWidth(); + const int y = (height() - pimpl->clearButton->height())/2; + pimpl->clearButton->move(x, y); + + pimpl->searchButton->move(pimpl->lineEditFrameWidth() * 2, + (height() - pimpl->searchButton->height())/2); +} diff --git a/src/gui/qocoa/qsearchfield_nonmac.qrc b/src/gui/qocoa/qsearchfield_nonmac.qrc new file mode 100644 index 000000000..68b570d5b --- /dev/null +++ b/src/gui/qocoa/qsearchfield_nonmac.qrc @@ -0,0 +1,7 @@ + + + qsearchfield_nonmac_clear.png + qsearchfield_nonmac_magnifier_menu.png + qsearchfield_nonmac_magnifier.png + + diff --git a/src/gui/qocoa/qsearchfield_nonmac_clear.png b/src/gui/qocoa/qsearchfield_nonmac_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..ec52c41bc2fbba0bcc2b30a96267d73be240dc13 GIT binary patch literal 736 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s77>k44ofy`glX=O&z`&N| z?e4a&{t=l;KZ@#lZz|Hym$rElN9 z{W|EB?)lGFP90(np`pUh@BB^L_@k=0c=BKM1=nBi4fXn~xzuNB5Qky~V?s~j#t0j! zWV`tI`1;DLKkU1DCMyHZr=3BX{p{0>#s-i zAN~-#_+mxy`s+9E-%mH2t+>=B`|PV%TGrOqK5D`(0+%eOZ;4vFf3wbSHO3}|8(fDC zdKY!<-D}&|*Qe3dbop{HLtlS?WOVdl%i~wCie9^QC2BWo%KC=BYf?)^7GLbRdpGvh zTQhZabp{nrr+LqN+?1yTWvrcf{<$mzqr%c0&7~89f~Kh+emLP}iPt0*70*K@R;d;; zLDN)&yyDWL8H5geuWb?dvCm)Tc*ffLPt~cZtoOecp3_aAE^YB#?VRPBtw##aBp58X znKS9RW#zofIcAbPY)1-vwu(KMIk)qqP(bL_y<5eSdG6*-H}RG1KmO%+?XSOfk3Y^Z z@qJmgTOxVGwV=?ee=i;D?d?t1d@4WhxoKX0zGhd`=FP@7=bbtF{pYXK=xREB+Pmid zaf#0sv(MVu+1S)Xt9|_N;ltW%zxEdU+1uMMo}$#H#BgfU5eetDVTV8OS-yPva{lMf zpU3~3cx3nP-8sd@o4?fBb2csbm^>xE|Jj`CIp?3(@9nDp>BG*>{@PI>hf~q2BJOmx q<)*c7_RgHWUR+#Ua{uOi%qtsz=xT&@Ug{G}nwTs%f>}*LAwdqV)+$%27ay??kF)!x> z(H%=?WSM7g+F)&P)}ZFke~CYfOnkRz&SEH#X@C7Si$UW3(a%5kusS+Ov@vYS^}hdo zN@}LWdzahWcE;#gJ4{Q}IeqA1x<#L|mu8EAwf0hpHpY*2ZK;tQigrc`{@-l)*P1TN ze79!nvG0FvE9a%>ZI^z%tH^3DL&mJOb+0wL9(_#^alQSw&fTl?^)4GZeulo|%AS+< zEbD$;A$?p>Q?+tl{h6Ojonx>6*Q#5+YSr8GLLXR~LISJoHt_^AFfcH9y85}Sb4q9e E0Ic(R)c^nh literal 0 HcmV?d00001 diff --git a/src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png b/src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..0e652c945e4287939e4787cd02c891c50238c64a GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0y~yU=U|H*Y zfq{Xuz$3Dlfq`2Xgc%uT&5>YWU<~(kaSYKoKetb@+bB__b$K?>Kx2T7m|ux4E$y!2)N8Kx}&f8Yprw{|C|E3ceXN5E9KVj ziqX5VO?UZa&V~{z-@S3+A1mg}Us7|wp6&j3$^PR(T2ohqXfdq0o?W(E_HJId>g1E> zRiA(UDXBKO^GK3m)Y@yeFP2z6E!%zZWyzK0lk^W8RNN?y)j9oWs$^SZYUG>mzw`eb zJru%qAaA`uOd$4rf!t>#wKE zcB}F@uez$maQf+_vuTG5EJC8PrFz?fyiV5GEf3JR@c#Q$uEP@)I3#%3iY#Odd?)9a zo&H$CqA>mR#k}pIN)vz7&z*8QRkYXbVcq`AufHw{TkW~|rpWov0u$qZW^aw^e7$SN svXdDmK`TSL;=NrqOmixzErGKgAQT^mY3=9kmp00i_>zopr03+PLga7~l literal 0 HcmV?d00001