Use search field for search.

Search options are presented in a context menu on the search field, as well as
links in search header.
This commit is contained in:
Francois Ferrand 2013-04-29 18:04:26 +02:00
parent d5c8787451
commit f4ff8b17f7
33 changed files with 1653 additions and 154 deletions

View File

@ -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}

View File

@ -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);

View File

@ -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<Entry*> 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<Entry*> searchResult = searchGroup->search(m_searchUi->searchEdit->text(), sensitivity);
if (searchGroup != m_db->rootGroup())
message += tr(" in \"%1\". <a href='searchAll'>Search all groups...</a>").arg(searchGroup->name());
else if (m_lastGroup != m_db->rootGroup())
message += tr(". <a href='searchCurrent'>Search in \"%1\"...</a>").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)

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -55,6 +55,34 @@
</widget>
<widget class="QWidget" name="pageWelcome">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QWidget" name="searchPanel" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSearchField" name="searchField" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="WelcomeWidget" name="welcomeWidget" native="true"/>
</item>
@ -173,7 +201,6 @@
<addaction name="actionEntryNew"/>
<addaction name="actionEntryEdit"/>
<addaction name="actionEntryDelete"/>
<addaction name="actionSearch"/>
</widget>
<action name="actionQuit">
<property name="text">
@ -299,17 +326,6 @@
<string>Clone entry</string>
</property>
</action>
<action name="actionSearch">
<property name="checkable">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Find</string>
</property>
</action>
<action name="actionEntryCopyUsername">
<property name="enabled">
<bool>false</bool>
@ -379,6 +395,30 @@
<string>2&amp;8x28</string>
</property>
</action>
<action name="actionFindCaseSensitive">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Case Sensitive</string>
</property>
</action>
<action name="actionFindCurrentGroup">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Current Group</string>
</property>
</action>
<action name="actionFindRootGroup">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Root Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -399,6 +439,12 @@
<header>gui/WelcomeWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QSearchField</class>
<extends>QWidget</extends>
<header>gui/qocoa/qsearchfield.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -7,17 +7,14 @@
<x>0</x>
<y>0</y>
<width>630</width>
<height>87</height>
<height>34</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="LineEdit" name="searchEdit"/>
</item>
<item row="0" column="0">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="closeSearchButton">
@ -27,72 +24,12 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Find:</string>
</property>
</widget>
<widget class="QLabel" name="searchResults"/>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QWidget" name="optionsWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="caseSensitiveCheckBox">
<property name="text">
<string>Case sensitive</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="searchCurrentRadioButton">
<property name="text">
<string>Current group</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="searchRootRadioButton">
<property name="text">
<string>Root group</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>255</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>gui/LineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -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()

19
src/gui/qocoa/LICENSE.txt Normal file
View File

@ -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.

17
src/gui/qocoa/Qocoa.pro Normal file
View File

@ -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
}

36
src/gui/qocoa/README.md Normal file
View File

@ -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)

13
src/gui/qocoa/TODO.md Normal file
View File

@ -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

75
src/gui/qocoa/gallery.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "gallery.h"
#include <QVBoxLayout>
#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);
}

14
src/gui/qocoa/gallery.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef GALLERY_H
#define GALLERY_H
#include <QWidget>
class Gallery : public QWidget
{
Q_OBJECT
public:
explicit Gallery(QWidget *parent = 0);
};
#endif // WIDGET_H

BIN
src/gui/qocoa/gallery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

12
src/gui/qocoa/main.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <QtGui/QApplication>
#include "gallery.h"
int main(int argc, char *argv[])
{
QApplication application(argc, argv);
Gallery gallery;
gallery.show();
return application.exec();
}

49
src/gui/qocoa/qbutton.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef QBUTTON_H
#define QBUTTON_H
#include <QtGui/QWidget>
#include <QtCore/QPointer>
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<QButtonPrivate> pimpl;
};
#endif // QBUTTON_H

View File

@ -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<QButtonPrivate> 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];
}

View File

@ -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 <QtGui/QToolBar>
#include <QtGui/QToolButton>
#include <QtGui/QPushButton>
#include <QtGui/QVBoxLayout>
class QButtonPrivate : public QObject
{
public:
QButtonPrivate(QButton *button, QAbstractButton *abstractButton)
: QObject(button), abstractButton(abstractButton) {}
QPointer<QAbstractButton> abstractButton;
};
QButton::QButton(QWidget *parent, BezelStyle) : QWidget(parent)
{
QAbstractButton *button = 0;
if (qobject_cast<QToolBar*>(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();
}

54
src/gui/qocoa/qocoa_mac.h Normal file
View File

@ -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 <Foundation/NSString.h>
#include <QString>
#include <QVBoxLayout>
#include <QMacCocoaViewContainer>
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));
}

View File

@ -0,0 +1,29 @@
#ifndef QPROGRESSINDICATORSPINNING_H
#define QPROGRESSINDICATORSPINNING_H
#include <QtGui/QWidget>
#include <QtCore/QPointer>
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<QProgressIndicatorSpinningPrivate> pimpl;
};
#endif // QPROGRESSINDICATORSPINNING_H

View File

@ -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];
}

View File

@ -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 <QtGui/QVBoxLayout>
#include <QtGui/QMovie>
#include <QtGui/QLabel>
class QProgressIndicatorSpinningPrivate : public QObject
{
public:
QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning,
QMovie *movie)
: QObject(qProgressIndicatorSpinning), movie(movie) {}
QPointer<QMovie> 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();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/Qocoa">
<file>qprogressindicatorspinning_nonmac.gif</file>
</qresource>
</RCC>

View File

@ -0,0 +1,48 @@
#ifndef QSEARCHFIELD_H
#define QSEARCHFIELD_H
#include <QtGui/QWidget>
#include <QtCore/QPointer>
#include <QtGui/QMenu>
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 <QSearchFieldPrivate> pimpl;
};
#endif // QSEARCHFIELD_H

View File

@ -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 <QApplication>
#include <QClipboard>
#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> qSearchField;
NSSearchField *nsSearchField;
};
@interface QSearchFieldDelegate : NSObject<NSTextFieldDelegate>
{
@public
QPointer<QSearchFieldPrivate> 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);
}

View File

@ -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 <QtGui/QLineEdit>
#include <QtGui/QVBoxLayout>
#include <QtGui/QToolButton>
#include <QtGui/QStyle>
#include <QtGui/QApplication>
#include <QtGui/QDesktopWidget>
#include <QtCore/QDir>
#include <QtCore/QDebug>
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<QLineEdit> lineEdit;
QPointer<QToolButton> clearButton;
QPointer<QToolButton> searchButton;
QPointer<QMenu> 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);
}

View File

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/Qocoa">
<file>qsearchfield_nonmac_clear.png</file>
<file>qsearchfield_nonmac_magnifier_menu.png</file>
<file>qsearchfield_nonmac_magnifier.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B