Commit c993655c authored by David Jarvie's avatar David Jarvie
Browse files

Move Akonadi dependent functions to a plugin

parent 8ef1d814
......@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(PIM_VERSION "5.20.40")
set(PIM_VERSION ${PIM_VERSION})
set(RELEASE_SERVICE_VERSION "22.07.40")
set(KALARM_VERSION "3.4.1")
set(KALARM_VERSION "3.5.0")
project(kalarm VERSION ${KALARM_VERSION})
......
KAlarm Change Log
=== Version 3.5.0 (KDE Gear 22.08) --- 22 April 2022 ===
* Provide option to build KAlarm without any Akonadi dependency.
=== Version 3.4.1 (KDE Gear 22.04.1) --- 20 April 2022 ===
* Fix checkboxes being disabled in Preferences dialogue.
* Fix time spin boxes being displayed in the wrong position.
......
......@@ -4,10 +4,51 @@ include_directories(
)
add_subdirectory(kalarmcalendar)
add_subdirectory(akonadiplugin)
add_subdirectory(appicons)
add_subdirectory(pixmaps)
add_subdirectory(autostart)
########### next target ###############
set(kalarmplugin_common_SRCS)
ecm_qt_declare_logging_category(kalarmplugin_common_SRCS
HEADER kalarmplugin_debug.h
IDENTIFIER KALARMPLUGIN_LOG
CATEGORY_NAME org.kde.pim.kalarm.plugin
DESCRIPTION "kalarm (plugin)"
EXPORT KALARMPLUGIN
)
add_library(kalarmplugin)
target_sources(kalarmplugin PRIVATE
${kalarmplugin_common_SRCS}
pluginbase.cpp
pluginmanager.cpp
)
generate_export_header(kalarmplugin BASE_NAME kalarmplugin)
target_link_libraries(kalarmplugin PRIVATE
kalarmcalendar
KF5::CoreAddons
KF5::CalendarCore
KF5::Mime
)
target_include_directories(kalarmplugin PUBLIC "$<BUILD_INTERFACE:${kalarm_SOURCE_DIR}/src/kalarmcalendar;${kalarm_BINARY_DIR}/src/kalarmcalendar>")
set_target_properties(kalarmplugin
PROPERTIES VERSION ${KDEPIM_LIB_VERSION}
SOVERSION ${KDEPIM_LIB_SOVERSION}
)
install(TARGETS kalarmplugin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP)
########### next target ###############
set(libkalarm_common_SRCS)
ecm_qt_declare_logging_category(libkalarm_common_SRCS
......@@ -20,7 +61,6 @@ ecm_qt_declare_logging_category(libkalarm_common_SRCS
EXPORT KALARM
)
########### next target ###############
set(libkalarm_SRCS
lib/buttongroup.cpp
lib/checkbox.cpp
......@@ -130,7 +170,6 @@ set(kalarm_bin_SRCS ${libkalarm_SRCS} ${resources_SRCS}
${libkalarm_common_SRCS}
main.cpp
birthdaydlg.cpp
birthdaymodel.cpp
editdlg.cpp
editdlgtypes.cpp
soundpicker.cpp
......@@ -179,9 +218,7 @@ set(kalarm_bin_SRCS ${libkalarm_SRCS} ${resources_SRCS}
templatedlg.cpp
templatemenuaction.cpp
migratekde4files.cpp
akonadicollectionsearch.cpp
birthdaydlg.h
birthdaymodel.h
editdlg.h
editdlgtypes.h
soundpicker.h
......@@ -230,7 +267,6 @@ set(kalarm_bin_SRCS ${libkalarm_SRCS} ${resources_SRCS}
templatedlg.h
templatemenuaction.h
migratekde4files.h
akonadicollectionsearch.h
)
if (ENABLE_WAKE_FROM_SUSPEND)
set(kalarm_bin_SRCS ${kalarm_bin_SRCS}
......@@ -269,7 +305,8 @@ target_compile_definitions(kalarm_bin PRIVATE -DVERSION="${KALARM_VERSION}")
target_link_libraries(kalarm_bin
kalarmprivate
kalarmcalendar
kalarmplugin
KF5::Codecs
KF5::ConfigCore
KF5::Completion
......@@ -289,8 +326,6 @@ target_link_libraries(kalarm_bin
Phonon::phonon4qt${QT_MAJOR_VERSION}
KF5::AkonadiCore
KF5::AkonadiMime
KF5::AkonadiContact
KF5::AkonadiWidgets
KF5::CalendarCore
KF5::CalendarUtils
KF5::Contacts
......
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: none
# This builds KAlarm's Akonadi plugin, which provides all functions dependent on
# Akonadi.
kcoreaddons_add_plugin(akonadiplugin
SOURCES
akonadiplugin.cpp
akonadicollectionsearch.cpp
birthdaymodel.cpp
akonadiplugin.h
akonadicollectionsearch.h
birthdaymodel.h
INSTALL_NAMESPACE "kalarm"
)
set_target_properties(akonadiplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/plugins/kalarm")
ecm_qt_declare_logging_category(akonadiplugin
HEADER akonadiplugin_debug.h
IDENTIFIER AKONADIPLUGIN_LOG
CATEGORY_NAME org.kde.pim.kalarm.akonadiplugin
DEFAULT_SEVERITY Warning
DESCRIPTION "kalarm (akonadi plugin)"
EXPORT AKONADIPLUGIN
)
generate_export_header(akonadiplugin BASE_NAME akonadiplugin)
target_link_libraries(akonadiplugin
kalarmplugin
# KF5::Completion
KF5::CalendarCore
KF5::I18n
KF5::Mime
# KF5::XmlGui
KF5::AkonadiCore
KF5::AkonadiMime
KF5::AkonadiContact
KF5::AkonadiWidgets
KF5::MailTransportAkonadi
KF5::MailTransport
# KF5::PimCommon
)
/*
* akonadicollectionsearch.cpp - Search Akonadi Collections
* Program: kalarm
* SPDX-FileCopyrightText: 2014-2020 David Jarvie <djarvie@kde.org>
* SPDX-FileCopyrightText: 2014-2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "akonadicollectionsearch.h"
#include "kalarm_debug.h"
#include "akonadiplugin_debug.h"
#include <Akonadi/AgentInstance>
#include <Akonadi/AgentManager>
......@@ -67,7 +67,7 @@ void AkonadiCollectionSearch::collectionFetchResult(KJob* j)
{
auto job = qobject_cast<CollectionFetchJob*>(j);
if (j->error())
qCCritical(KALARM_LOG) << "AkonadiCollectionSearch::collectionFetchResult: CollectionFetchJob" << job->fetchScope().resource()<< "error: " << j->errorString();
qCCritical(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::collectionFetchResult: CollectionFetchJob" << job->fetchScope().resource()<< "error: " << j->errorString();
else
{
const Collection::List collections = job->collections();
......@@ -119,9 +119,9 @@ void AkonadiCollectionSearch::itemFetchResult(KJob* j)
if (j->error())
{
if (!mUid.isEmpty())
qCDebug(KALARM_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "UID" << mUid << "error: " << j->errorString();
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "UID" << mUid << "error: " << j->errorString();
else
qCDebug(KALARM_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "GID" << mGid << "error: " << j->errorString();
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "GID" << mGid << "error: " << j->errorString();
}
else
{
......@@ -164,9 +164,9 @@ void AkonadiCollectionSearch::itemDeleteResult(KJob* j)
if (j->error())
{
if (!mUid.isEmpty())
qCDebug(KALARM_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "UID" << mUid << "error: " << j->errorString();
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "UID" << mUid << "error: " << j->errorString();
else
qCDebug(KALARM_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "GID" << mGid << "error: " << j->errorString();
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "GID" << mGid << "error: " << j->errorString();
}
else
++mDeleteCount;
......
/*
* akonadicollectionsearch.h - Search Akonadi Collections
* Program: kalarm
* SPDX-FileCopyrightText: 2014, 2019 David Jarvie <djarvie@kde.org>
* SPDX-FileCopyrightText: 2014-2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -37,7 +37,7 @@ class AkonadiCollectionSearch : public QObject
{
Q_OBJECT
public:
explicit AkonadiCollectionSearch(const QString& mimeType, const QString& gid = QString(), const QString& uid = QString(), bool remove = false);
explicit AkonadiCollectionSearch(const QString& mimeType, const QString& gid = {}, const QString& uid = {}, bool remove = false);
Q_SIGNALS:
// Signal emitted if action is to fetch all collections for the mime type
......
/*
* akonadiplugin.cpp - plugin to provide features requiring Akonadi
* Program: kalarm
* SPDX-FileCopyrightText: 2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "akonadiplugin.h"
#include "akonadicollectionsearch.h"
#include "akonadiresourcemigrator.h"
#include "birthdaymodel.h"
#include "lib/autoqpointer.h"
#include "akonadiplugin_debug.h"
#include <Akonadi/ControlGui>
#include <Akonadi/EmailAddressSelectionDialog>
#include <Akonadi/EntityMimeTypeFilterModel>
#include <Akonadi/Item>
#include <Akonadi/ItemFetchJob>
#include <Akonadi/ItemFetchScope>
#include <KCalendarCore/Person>
#include <KPluginFactory>
#include <KLocalizedString>
#include <KDescendantsProxyModel>
#include <QUrlQuery>
K_PLUGIN_CLASS_WITH_JSON(AkonadiPlugin, "akonadiplugin.json")
AkonadiPlugin::AkonadiPlugin(QObject* parent, const QList<QVariant>& args)
: PluginBase(parent, args)
{
setName(QStringLiteral("Akonadi"));
}
/******************************************************************************
* Start Akonadi and create an instance of both birthday models.
*/
QSortFilterProxyModel* AkonadiPlugin::createBirthdayModels(QWidget* messageParent, QObject* parent)
{
// Start Akonadi server as we need it for the birthday model to access contacts information
Akonadi::ControlGui::widgetNeedsAkonadi(messageParent);
BirthdayModel* model = BirthdayModel::instance();
connect(model, &BirthdayModel::dataChanged, this, &AkonadiPlugin::birthdayModelDataChanged);
auto descendantsModel = new KDescendantsProxyModel(parent);
descendantsModel->setSourceModel(model);
auto mimeTypeFilter = new Akonadi::EntityMimeTypeFilterModel(parent);
mimeTypeFilter->setSourceModel(descendantsModel);
mimeTypeFilter->addMimeTypeExclusionFilter(Akonadi::Collection::mimeType());
mimeTypeFilter->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders);
BirthdaySortModel* sortModel = new BirthdaySortModel(parent);
sortModel->setSourceModel(mimeTypeFilter);
sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
return sortModel;
}
void AkonadiPlugin::setPrefixSuffix(QSortFilterProxyModel* model, const QString& prefix, const QString& suffix, const QStringList& alarmMessageList)
{
BirthdaySortModel* bmodel = qobject_cast<BirthdaySortModel*>(model);
if (bmodel)
bmodel->setPrefixSuffix(prefix, suffix, alarmMessageList);
}
int AkonadiPlugin::birthdayModelEnum(BirthdayModelValue value) const
{
switch (value)
{
case BirthdayModelValue::NameColumn: return BirthdayModel::NameColumn;
case BirthdayModelValue::DateColumn: return BirthdayModel::DateColumn;
case BirthdayModelValue::DateRole: return BirthdayModel::DateRole;
default: return -1;
}
}
/******************************************************************************
* Extract dragged and dropped Akonadi RFC822 message data.
*/
KMime::Message::Ptr AkonadiPlugin::fetchAkonadiEmail(const QUrl& url, qint64& emailId)
{
static_assert(sizeof(Akonadi::Item::Id) == sizeof(emailId), "AkonadiPlugin::fetchAkonadiEmail: parameter is wrong type");
emailId = -1;
Akonadi::Item item = Akonadi::Item::fromUrl(url);
if (!item.isValid())
return {};
// It's an Akonadi item
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiPlugin::fetchAkonadiEmail: Akonadi item" << item.id();
if (QUrlQuery(url).queryItemValue(QStringLiteral("type")) != QLatin1String("message/rfc822"))
return {}; // it's not an email
// It's an email held in Akonadi
qCDebug(AKONADIPLUGIN_LOG) << "AkonadiPlugin::fetchAkonadiEmail: Akonadi email";
auto job = new Akonadi::ItemFetchJob(item);
job->fetchScope().fetchFullPayload();
Akonadi::Item::List items;
if (job->exec())
items = job->items();
if (items.isEmpty())
qCWarning(AKONADIPLUGIN_LOG) << "AkonadiPlugin::fetchAkonadiEmail: Akonadi item" << item.id() << "not found";
else
{
const Akonadi::Item& it = items.at(0);
if (!it.isValid() || !it.hasPayload<KMime::Message::Ptr>())
qCWarning(AKONADIPLUGIN_LOG) << "AkonadiPlugin::fetchAkonadiEmail: invalid email";
else
{
emailId = it.id();
return it.payload<KMime::Message::Ptr>();
}
}
return {};
}
bool AkonadiPlugin::getAddressBookSelection(KCalendarCore::Person& person, QWidget* parent)
{
person = KCalendarCore::Person();
// Use AutoQPointer to guard against crash on application exit while
// the dialogue is still open. It prevents double deletion (both on
// deletion of MainWindow, and on return from this function).
AutoQPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(parent);
if (dlg->exec() != QDialog::Accepted)
return false;
Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
if (selections.isEmpty())
return false;
person = KCalendarCore::Person(selections.first().name(), selections.first().email());
return true;
}
qint64 AkonadiPlugin::getCollectionId(qint64 emailId)
{
static_assert(sizeof(Akonadi::Item::Id) == sizeof(emailId), "AkonadiPlugin::getCollectionId: parameter is wrong type");
static_assert(sizeof(Akonadi::Collection::Id) == sizeof(qint64), "AkonadiPlugin::getCollectionId: wrong return type");
Akonadi::ItemFetchJob* job = new Akonadi::ItemFetchJob(Akonadi::Item(emailId));
job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
Akonadi::Item::List items;
if (job->exec())
items = job->items();
if (items.isEmpty() || !items.at(0).isValid())
return -1;
const Akonadi::Item& it = items.at(0);
return it.parentCollection().id();
}
void AkonadiPlugin::deleteEvent(const QString& mimeType, const QString& gid, const QString& uid)
{
new AkonadiCollectionSearch(mimeType, gid, uid, true); // this auto-deletes when complete
}
#include "akonadiplugin.moc"
// vim: et sw=4:
/*
* akonadiplugin.h - plugin to provide features requiring Akonadi
* Program: kalarm
* SPDX-FileCopyrightText: 2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include "pluginbase.h"
class AkonadiPlugin : public PluginBase
{
Q_OBJECT
public:
explicit AkonadiPlugin(QObject* parent = nullptr, const QList<QVariant>& = {});
/** Start Akonadi and create birthday model instances.
* It returns the BirthdaySortModel as a QSortFilterProxyModel, since
* BirthdaySortModel is private to this plugin but is inherited from
* QSortFilterProxyModel.
*/
QSortFilterProxyModel* createBirthdayModels(QWidget* messageParent, QObject* parent = nullptr) override;
/** Set a new prefix and suffix, and the corresponding selection list. */
void setPrefixSuffix(QSortFilterProxyModel* birthdaySortModel, const QString& prefix, const QString& suffix, const QStringList& alarmMessageList) override;
/** Return BirthdayModel enum values. */
int birthdayModelEnum(BirthdayModelValue) const override;
/** Extract dragged and dropped Akonadi RFC822 message data.
* @param url the dropped URL.
* @param emailId updated with the Akonadi email ID.
* @return the email message if an Akonadi email has been extracted, else null.
*/
KMime::Message::Ptr fetchAkonadiEmail(const QUrl& url, qint64& emailId) override;
/** Get a single selection from the address book. */
bool getAddressBookSelection(KCalendarCore::Person& person, QWidget* parent = nullptr) override;
/** Get the Akonadi Collection ID which contains a given email ID. */
qint64 getCollectionId(qint64 emailId) override;
/** Delete a KOrganizer event. */
void deleteEvent(const QString& mimeType, const QString& gid = {}, const QString& uid = {}) override;
};
// vim: et sw=4:
{
"KPlugin": {
"Id": "akonadi",
"Description": "Functions which require Akonadi",
"Name": "Akonadi Functions",
"Version": "1.0"
}
}
......@@ -9,9 +9,6 @@
#include "birthdaymodel.h"
#include "resourcescalendar.h"
#include "kalarmcalendar/kaevent.h"
#include <Akonadi/ChangeRecorder>
#include <Akonadi/EntityDisplayAttribute>
#include <Akonadi/ItemFetchScope>
......@@ -20,8 +17,6 @@
#include <QLocale>
using namespace KAlarmCal;
BirthdayModel* BirthdayModel::mInstance = nullptr;
......@@ -78,20 +73,15 @@ BirthdaySortModel::BirthdaySortModel(QObject* parent)
{
}
void BirthdaySortModel::setPrefixSuffix(const QString& prefix, const QString& suffix)
/******************************************************************************
* Set a new prefix and suffix for the alarm message, and set the selection list
* based on them.
*/
void BirthdaySortModel::setPrefixSuffix(const QString& prefix, const QString& suffix, const QStringList& alarmMessageList)
{
mContactsWithAlarm.clear();
mPrefix = prefix;
mSuffix = suffix;
const QVector<KAEvent> events = ResourcesCalendar::events(CalEvent::ACTIVE);
for (const KAEvent& event : events)
{
if (event.actionSubType() == KAEvent::MESSAGE
&& event.recurType() == KARecurrence::ANNUAL_DATE
&& (prefix.isEmpty() || event.message().startsWith(prefix)))
mContactsWithAlarm.append(event.message());
}
mContactsWithAlarm = alarmMessageList;
invalidateFilter();
}
......
......@@ -2,6 +2,7 @@
* birthdaymodel.h - model class for birthdays from address book
* Program: kalarm
* SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
* SPDX-FileCopyrightText: 2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -27,19 +28,14 @@ class BirthdayModel : public Akonadi::ContactsTreeModel
{
Q_OBJECT
public:
enum { // data columns
enum // data columns
{
NameColumn, DateColumn,
ColumnCount
};
/**
* Destroys the global contact model.
*/
~BirthdayModel() override;
/**
* Returns the global contact model instance.
*/
static BirthdayModel* instance();
QVariant entityData(const Akonadi::Item&, int column, int role = Qt::DisplayRole) const override;
......@@ -59,7 +55,7 @@ class BirthdaySortModel : public QSortFilterProxyModel
public:
explicit BirthdaySortModel(QObject* parent = nullptr);
void setPrefixSuffix(const QString& prefix, const QString& suffix);
void setPrefixSuffix(const QString& prefix, const QString& suffix, const QStringList& alarmMessageList);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
......
/*
* birthdaydlg.cpp - dialog to pick birthdays from address book
* Program: kalarm
* SPDX-FileCopyrightText: 2002-2021 David Jarvie <djarvie@kde.org>
* SPDX-FileCopyrightText: 2002-2022 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "birthdaydlg.h"
#include "birthdaymodel.h"
#include "akonadiplugin/akonadiplugin.h"
#include "editdlgtypes.h"
#include "fontcolourbutton.h"
#include "kalarmapp.h"
......@@ -20,17 +20,14 @@
#include "soundpicker.h"
#include "specialactions.h"
#include "lib/checkbox.h"
#include "pluginmanager.h"
#include "lib/shellprocess.h"
#include "kalarm_debug.h"
#include <Akonadi/ControlGui>
#include <Akonadi/EntityMimeTypeFilterModel>
#include <KLocalizedString>
#include <KConfigGroup>
#include <KStandardAction>
#include <KActionCollection>
#include <KDescendantsProxyModel>
#include <KSharedConfig>
#include <QAction>
......@@ -41,6 +38,7 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QSortFilterProxyModel>
using namespace KCal;
......@@ -51,16 +49,21 @@ BirthdayDlg::BirthdayDlg(QWidget* parent)
setObjectName(QStringLiteral("BirthdayDlg")); // used by LikeBack
setWindowTitle(i18nc("@title:window", "Import Birthdays From KAddressBook"));
auto topLayout = new QVBoxLayout(this);
auto mainLayout = new QVBoxLayout(this);
QWidget* mainWidget = new QWidget;
mainLayout->addWidget(mainWidget);
auto topLayout = new QVBoxLayout(mainWidget);
topLayout->setContentsMargins(0, 0, 0, 0);
if (Preferences::useAlarmName())
{
auto hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0, 0, 0, 0);
topLayout->addLayout(hlayout);
QLabel* label = new QLabel(i18nc("@label:textbox", "Alarm name:"), this);
QLabel* label = new QLabel(i18nc("@label:textbox", "Alarm name:"), mainWidget);
hlayout->addWidget(label);
mName = new KLineEdit(this);
mName = new KLineEdit(mainWidget);
mName->setMinimumSize(mName->sizeHint());
label->setBuddy(mName);
mName->setWhatsThis(i18nc("@info:whatsthis", "Enter a name to help you identify this alarm. This is optional and need not be unique."));
......@@ -73,7 +76,7 @@ BirthdayDlg::BirthdayDlg(QWidget* parent)
mPrefixText = config.readEntry("BirthdayPrefix", i18nc("@info", "Birthday: "));
mSuffixText = config.readEntry("BirthdaySuffix");
QGroupBox* textGroup = new QGroupBox(i18nc("@title:group", "Alarm Text"), this);
QGroupBox* textGroup = new QGroupBox(i18nc("@title:group", "Alarm Text"), mainWidget);
topLayout->addWidget(textGroup);
auto grid = new QGridLayout(textGroup);
QLabel* label = new QLabel(i18nc("@label:textbox", "Prefix:"), textGroup);
......@@ -98,41 +101,34 @@ BirthdayDlg::BirthdayDlg(QWidget* parent)
"including any necessary leading spaces."));