diff --git a/src/configuredialog/configureappearancepage.cpp b/src/configuredialog/configureappearancepage.cpp index b58e58e53..015a4c8b2 100644 --- a/src/configuredialog/configureappearancepage.cpp +++ b/src/configuredialog/configureappearancepage.cpp @@ -1,1391 +1,1392 @@ /* Copyright (c) 2013-2017 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "configureappearancepage.h" #include "PimCommon/ConfigureImmutableWidgetUtils" using namespace PimCommon::ConfigureImmutableWidgetUtils; #include "configuredialog/colorlistbox.h" #include "messagelist/aggregationcombobox.h" #include "messagelist/aggregationconfigbutton.h" #include "messagelist/themecombobox.h" #include "messagelist/themeconfigbutton.h" #include "messagelistsettings.h" #include "MailCommon/TagWidget" #include "MailCommon/Tag" #include "kmkernel.h" +#include "helper_p.h" #include "util.h" #include "MailCommon/FolderTreeWidget" #include "kmmainwidget.h" #include "mailcommonsettings_base.h" #include "MessageViewer/ConfigureWidget" #include "messageviewer/messageviewersettings.h" #include "messagelist/messagelistutil.h" #include #include "MessageCore/MessageCoreUtil" #include "settings/kmailsettings.h" #include "MailCommon/MailUtil" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include using KMime::DateFormatter; #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; QString AppearancePage::helpAnchor() const { return QStringLiteral("configure-appearance"); } AppearancePage::AppearancePage(QWidget *parent) : ConfigModuleWithTabs(parent) { // // "Fonts" tab: // FontsTab *fontsTab = new FontsTab(); addTab(fontsTab, i18n("Fonts")); // // "Colors" tab: // ColorsTab *colorsTab = new ColorsTab(); addTab(colorsTab, i18n("Colors")); // // "Layout" tab: // LayoutTab *layoutTab = new LayoutTab(); addTab(layoutTab, i18n("Layout")); // // "Headers" tab: // HeadersTab *headersTab = new HeadersTab(); addTab(headersTab, i18n("Message List")); // // "Reader window" tab: // ReaderTab *readerTab = new ReaderTab(); addTab(readerTab, i18n("Message Window")); addConfig(MessageViewer::MessageViewerSettings::self(), readerTab); // // "System Tray" tab: // SystemTrayTab *systemTrayTab = new SystemTrayTab(); addTab(systemTrayTab, i18n("System Tray")); // // "Message Tag" tab: // MessageTagTab *messageTagTab = new MessageTagTab(); addTab(messageTagTab, i18n("Message Tags")); } QString AppearancePage::FontsTab::helpAnchor() const { return QStringLiteral("configure-appearance-fonts"); } static const struct { const char *configName; const char *displayName; bool enableFamilyAndSize; bool onlyFixed; } fontNames[] = { { "body-font", I18N_NOOP("Message Body"), true, false }, { "MessageListFont", I18N_NOOP("Message List"), true, false }, { "UnreadMessageFont", I18N_NOOP("Message List - Unread Messages"), false, false }, { "ImportantMessageFont", I18N_NOOP("Message List - Important Messages"), false, false }, { "TodoMessageFont", I18N_NOOP("Message List - Action Item Messages"), false, false }, { "fixed-font", I18N_NOOP("Fixed Width Font"), true, true }, { "composer-font", I18N_NOOP("Composer"), true, false }, { "print-font", I18N_NOOP("Printing Output"), true, false }, }; static const int numFontNames = sizeof fontNames / sizeof * fontNames; AppearancePageFontsTab::AppearancePageFontsTab(QWidget *parent) : ConfigModuleTab(parent), mActiveFontIndex(-1) { assert(numFontNames == sizeof mFont / sizeof * mFont); // "Use custom fonts" checkbox, followed by
QVBoxLayout *vlay = new QVBoxLayout(this); mCustomFontCheck = new QCheckBox(i18n("&Use custom fonts"), this); vlay->addWidget(mCustomFontCheck); vlay->addWidget(new KSeparator(Qt::Horizontal, this)); connect(mCustomFontCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // "font location" combo box and label: QHBoxLayout *hlay = new QHBoxLayout(); // inherites spacing vlay->addLayout(hlay); mFontLocationCombo = new KComboBox(this); mFontLocationCombo->setEditable(false); mFontLocationCombo->setEnabled(false); // !mCustomFontCheck->isChecked() QStringList fontDescriptions; fontDescriptions.reserve(numFontNames); for (int i = 0; i < numFontNames; ++i) { fontDescriptions << i18n(fontNames[i].displayName); } mFontLocationCombo->addItems(fontDescriptions); QLabel *label = new QLabel(i18n("Apply &to:"), this); label->setBuddy(mFontLocationCombo); label->setEnabled(false); // since !mCustomFontCheck->isChecked() hlay->addWidget(label); hlay->addWidget(mFontLocationCombo); hlay->addStretch(10); mFontChooser = new KFontChooser(this, KFontChooser::DisplayFrame, QStringList(), 4); mFontChooser->setEnabled(false); // since !mCustomFontCheck->isChecked() vlay->addWidget(mFontChooser); connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // {en,dis}able widgets depending on the state of mCustomFontCheck: connect(mCustomFontCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mCustomFontCheck, &QAbstractButton::toggled, mFontLocationCombo, &QWidget::setEnabled); connect(mCustomFontCheck, &QAbstractButton::toggled, mFontChooser, &QWidget::setEnabled); // load the right font settings into mFontChooser: - connect(mFontLocationCombo, SIGNAL(activated(int)), - this, SLOT(slotFontSelectorChanged(int))); + connect(mFontLocationCombo, QOverload::of(&KComboBox::activated), + this, &AppearancePage::FontsTab::slotFontSelectorChanged); } void AppearancePage::FontsTab::slotFontSelectorChanged(int index) { qCDebug(KMAIL_LOG) << "slotFontSelectorChanged() called"; if (index < 0 || index >= mFontLocationCombo->count()) { return; // Should never happen, but it is better to check. } // Save current fontselector setting before we install the new: if (mActiveFontIndex == 0) { mFont[0] = mFontChooser->font(); // hardcode the family and size of "message body" dependant fonts: for (int i = 0; i < numFontNames; ++i) if (!fontNames[i].enableFamilyAndSize) { // ### shall we copy the font and set the save and re-set // {regular,italic,bold,bold italic} property or should we // copy only family and pointSize? mFont[i].setFamily(mFont[0].family()); mFont[i].setPointSize/*Float?*/(mFont[0].pointSize/*Float?*/()); } } else if (mActiveFontIndex > 0) { mFont[ mActiveFontIndex ] = mFontChooser->font(); } mActiveFontIndex = index; // Disonnect so the "Apply" button is not activated by the change disconnect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // Display the new setting: mFontChooser->setFont(mFont[index], fontNames[index].onlyFixed); connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // Disable Family and Size list if we have selected a quote font: mFontChooser->enableColumn(KFontChooser::FamilyList | KFontChooser::SizeList, fontNames[ index ].enableFamilyAndSize); } void AppearancePage::FontsTab::doLoadOther() { if (KMKernel::self()) { KConfigGroup fonts(KMKernel::self()->config(), "Fonts"); mFont[0] = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); for (int i = 0; i < numFontNames; ++i) { const QString configName = QLatin1String(fontNames[i].configName); if (configName == QLatin1String("MessageListFont")) { mFont[i] = MessageList::MessageListSettings::self()->messageListFont(); } else if (configName == QLatin1String("UnreadMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->unreadMessageFont(); } else if (configName == QLatin1String("ImportantMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->importantMessageFont(); } else if (configName == QLatin1String("TodoMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->todoMessageFont(); } else { mFont[i] = fonts.readEntry(configName, (fontNames[i].onlyFixed) ? fixedFont : mFont[0]); } } mCustomFontCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultFonts()); mFontLocationCombo->setCurrentIndex(0); slotFontSelectorChanged(0); } else { setEnabled(false); } } void AppearancePage::FontsTab::save() { if (KMKernel::self()) { KConfigGroup fonts(KMKernel::self()->config(), "Fonts"); // read the current font (might have been modified) if (mActiveFontIndex >= 0) { mFont[ mActiveFontIndex ] = mFontChooser->font(); } const bool customFonts = mCustomFontCheck->isChecked(); MessageCore::MessageCoreSettings::self()->setUseDefaultFonts(!customFonts); for (int i = 0; i < numFontNames; ++i) { const QString configName = QLatin1String(fontNames[i].configName); if (customFonts && configName == QLatin1String("MessageListFont")) { MessageList::MessageListSettings::self()->setMessageListFont(mFont[i]); } else if (customFonts && configName == QLatin1String("UnreadMessageFont")) { MessageList::MessageListSettings::self()->setUnreadMessageFont(mFont[i]); } else if (customFonts && configName == QLatin1String("ImportantMessageFont")) { MessageList::MessageListSettings::self()->setImportantMessageFont(mFont[i]); } else if (customFonts && configName == QLatin1String("TodoMessageFont")) { MessageList::MessageListSettings::self()->setTodoMessageFont(mFont[i]); } else { if (customFonts || fonts.hasKey(configName)) // Don't write font info when we use default fonts, but write // if it's already there: { fonts.writeEntry(configName, mFont[i]); } } } } } void AppearancePage::FontsTab::doResetToDefaultsOther() { mCustomFontCheck->setChecked(false); } QString AppearancePage::ColorsTab::helpAnchor() const { return QStringLiteral("configure-appearance-colors"); } static const struct { const char *configName; const char *displayName; } colorNames[] = { // adjust doLoadOther if you change this: { "QuotedText1", I18N_NOOP("Quoted Text - First Level") }, { "QuotedText2", I18N_NOOP("Quoted Text - Second Level") }, { "QuotedText3", I18N_NOOP("Quoted Text - Third Level") }, { "LinkColor", I18N_NOOP("Link") }, { "UnreadMessageColor", I18N_NOOP("Unread Message") }, { "ImportantMessageColor", I18N_NOOP("Important Message") }, { "TodoMessageColor", I18N_NOOP("Action Item Message") }, { "CloseToQuotaColor", I18N_NOOP("Folder Name and Size When Close to Quota") }, { "ColorbarBackgroundPlain", I18N_NOOP("HTML Status Bar Background - No HTML Message") }, { "ColorbarForegroundPlain", I18N_NOOP("HTML Status Bar Foreground - No HTML Message") }, { "ColorbarBackgroundHTML", I18N_NOOP("HTML Status Bar Background - HTML Message") }, { "ColorbarForegroundHTML", I18N_NOOP("HTML Status Bar Foreground - HTML Message") } }; static const int numColorNames = sizeof colorNames / sizeof * colorNames; AppearancePageColorsTab::AppearancePageColorsTab(QWidget *parent) : ConfigModuleTab(parent) { // "use custom colors" check box QVBoxLayout *vlay = new QVBoxLayout(this); mCustomColorCheck = new QCheckBox(i18n("&Use custom colors"), this); vlay->addWidget(mCustomColorCheck); connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // color list box: mColorList = new ColorListBox(this); mColorList->setEnabled(false); // since !mCustomColorCheck->isChecked() for (int i = 0; i < numColorNames; ++i) { mColorList->addColor(i18n(colorNames[i].displayName)); } vlay->addWidget(mColorList, 1); // "recycle colors" check box: mRecycleColorCheck = new QCheckBox(i18n("Recycle colors on deep "ing"), this); mRecycleColorCheck->setEnabled(false); vlay->addWidget(mRecycleColorCheck); connect(mRecycleColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // close to quota threshold QHBoxLayout *hbox = new QHBoxLayout(); vlay->addLayout(hbox); QLabel *l = new QLabel(i18n("Close to quota threshold:"), this); hbox->addWidget(l); mCloseToQuotaThreshold = new QSpinBox(this); mCloseToQuotaThreshold->setRange(0, 100); mCloseToQuotaThreshold->setSingleStep(1); connect(mCloseToQuotaThreshold, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); mCloseToQuotaThreshold->setSuffix(i18n("%")); hbox->addWidget(mCloseToQuotaThreshold); hbox->addWidget(new QWidget(this), 2); // {en,dir}able widgets depending on the state of mCustomColorCheck: connect(mCustomColorCheck, &QAbstractButton::toggled, mColorList, &QWidget::setEnabled); connect(mCustomColorCheck, &QAbstractButton::toggled, mRecycleColorCheck, &QWidget::setEnabled); connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mColorList, &ColorListBox::changed, this, &ConfigModuleTab::slotEmitChanged); } void AppearancePage::ColorsTab::doLoadOther() { mCustomColorCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultColors()); mRecycleColorCheck->setChecked(MessageViewer::MessageViewerSettings::self()->recycleQuoteColors()); mCloseToQuotaThreshold->setValue(KMailSettings::self()->closeToQuotaThreshold()); loadColor(true); } void AppearancePage::ColorsTab::loadColor(bool loadFromConfig) { if (KMKernel::self()) { KConfigGroup reader(KMKernel::self()->config(), "Reader"); KConfigGroup collectionFolderView(KMKernel::self()->config(), "CollectionFolderView"); static const QColor defaultColor[ numColorNames ] = { MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor(), MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor(), MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor(), MessageCore::ColorUtil::self()->linkColor(), // link MessageList::Util::unreadDefaultMessageColor(), // unread mgs MessageList::Util::importantDefaultMessageColor(), // important msg MessageList::Util::todoDefaultMessageColor(), // action item mgs MailCommon::Util::defaultQuotaColor(), // close to quota Qt::lightGray, // colorbar plain bg Qt::black, // colorbar plain fg Qt::black, // colorbar html bg Qt::white // colorbar html fg }; for (int i = 0; i < numColorNames; ++i) { if (loadFromConfig) { const QString configName = QLatin1String(colorNames[i].configName); if (configName == QLatin1String("UnreadMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->unreadMessageColor()); } else if (configName == QLatin1String("ImportantMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->importantMessageColor()); } else if (configName == QLatin1String("TodoMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->todoMessageColor()); } else { mColorList->setColorSilently(i, reader.readEntry(configName, defaultColor[i])); } } else { mColorList->setColorSilently(i, defaultColor[i]); } } } else { setEnabled(false); } } void AppearancePage::ColorsTab::doResetToDefaultsOther() { mCustomColorCheck->setChecked(false); mRecycleColorCheck->setChecked(false); mCloseToQuotaThreshold->setValue(80); loadColor(false); } void AppearancePage::ColorsTab::save() { if (!KMKernel::self()) { return; } KConfigGroup reader(KMKernel::self()->config(), "Reader"); KConfigGroup collectionFolderView(KMKernel::self()->config(), "CollectionFolderView"); bool customColors = mCustomColorCheck->isChecked(); MessageCore::MessageCoreSettings::self()->setUseDefaultColors(!customColors); KColorScheme scheme(QPalette::Active, KColorScheme::View); for (int i = 0; i < numColorNames; ++i) { const QString configName = QLatin1String(colorNames[i].configName); if (customColors && configName == QLatin1String("UnreadMessageColor")) { MessageList::MessageListSettings::self()->setUnreadMessageColor(mColorList->color(i)); } else if (customColors && configName == QLatin1String("ImportantMessageColor")) { MessageList::MessageListSettings::self()->setImportantMessageColor(mColorList->color(i)); } else if (customColors && configName == QLatin1String("TodoMessageColor")) { MessageList::MessageListSettings::self()->setTodoMessageColor(mColorList->color(i)); } else { if (customColors || reader.hasKey(configName)) { reader.writeEntry(configName, mColorList->color(i)); } } } MessageViewer::MessageViewerSettings::self()->setRecycleQuoteColors(mRecycleColorCheck->isChecked()); KMailSettings::self()->setCloseToQuotaThreshold(mCloseToQuotaThreshold->value()); } QString AppearancePage::LayoutTab::helpAnchor() const { return QStringLiteral("configure-appearance-layout"); } AppearancePageLayoutTab::AppearancePageLayoutTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "folder list" radio buttons: populateButtonGroup(mFolderListGroupBox = new QGroupBox(this), mFolderListGroup = new QButtonGroup(this), Qt::Vertical, KMailSettings::self()->folderListItem()); vlay->addWidget(mFolderListGroupBox); connect(mFolderListGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotEmitChanged())); QHBoxLayout *folderCBHLayout = new QHBoxLayout; mFolderQuickSearchCB = new QCheckBox(i18n("Show folder quick search field"), this); connect(mFolderQuickSearchCB, &QAbstractButton::toggled, this, &ConfigModuleTab::slotEmitChanged); folderCBHLayout->addWidget(mFolderQuickSearchCB); vlay->addLayout(folderCBHLayout); // "favorite folders view mode" radio buttons: mFavoriteFoldersViewGroupBox = new QGroupBox(this); mFavoriteFoldersViewGroupBox->setTitle(i18n("Show Favorite Folders View")); mFavoriteFoldersViewGroupBox->setLayout(new QVBoxLayout()); mFavoriteFoldersViewGroup = new QButtonGroup(this); connect(mFavoriteFoldersViewGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotEmitChanged())); QRadioButton *favoriteFoldersViewHiddenRadio = new QRadioButton(i18n("Never"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewHiddenRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewHiddenRadio); QRadioButton *favoriteFoldersViewIconsRadio = new QRadioButton(i18n("As icons"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewIconsRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewIconsRadio); QRadioButton *favoriteFoldersViewListRadio = new QRadioButton(i18n("As list"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewListRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewListRadio); vlay->addWidget(mFavoriteFoldersViewGroupBox); // "folder tooltips" radio buttons: mFolderToolTipsGroupBox = new QGroupBox(this); mFolderToolTipsGroupBox->setTitle(i18n("Folder Tooltips")); mFolderToolTipsGroupBox->setLayout(new QVBoxLayout()); mFolderToolTipsGroup = new QButtonGroup(this); connect(mFolderToolTipsGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotEmitChanged())); QRadioButton *folderToolTipsAlwaysRadio = new QRadioButton(i18n("Always"), mFolderToolTipsGroupBox); mFolderToolTipsGroup->addButton(folderToolTipsAlwaysRadio, static_cast< int >(FolderTreeWidget::DisplayAlways)); mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsAlwaysRadio); QRadioButton *folderToolTipsNeverRadio = new QRadioButton(i18n("Never"), mFolderToolTipsGroupBox); mFolderToolTipsGroup->addButton(folderToolTipsNeverRadio, static_cast< int >(FolderTreeWidget::DisplayNever)); mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsNeverRadio); vlay->addWidget(mFolderToolTipsGroupBox); // "show reader window" radio buttons: populateButtonGroup(mReaderWindowModeGroupBox = new QGroupBox(this), mReaderWindowModeGroup = new QButtonGroup(this), Qt::Vertical, KMailSettings::self()->readerWindowModeItem()); vlay->addWidget(mReaderWindowModeGroupBox); connect(mReaderWindowModeGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotEmitChanged())); vlay->addStretch(10); // spacer } void AppearancePage::LayoutTab::doLoadOther() { loadWidget(mFolderListGroupBox, mFolderListGroup, KMailSettings::self()->folderListItem()); loadWidget(mReaderWindowModeGroupBox, mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem()); loadWidget(mFavoriteFoldersViewGroupBox, mFavoriteFoldersViewGroup, MailCommon::MailCommonSettings::self()->favoriteCollectionViewModeItem()); loadWidget(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem()); const int checkedFolderToolTipsPolicy = KMailSettings::self()->toolTipDisplayPolicy(); if (checkedFolderToolTipsPolicy >= 0) { mFolderToolTipsGroup->button(checkedFolderToolTipsPolicy)->setChecked(true); } } void AppearancePage::LayoutTab::save() { saveButtonGroup(mFolderListGroup, KMailSettings::self()->folderListItem()); saveButtonGroup(mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem()); saveButtonGroup(mFavoriteFoldersViewGroup, MailCommon::MailCommonSettings::self()->favoriteCollectionViewModeItem()); saveCheckBox(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem()); KMailSettings::self()->setToolTipDisplayPolicy(mFolderToolTipsGroup->checkedId()); } // // Appearance Message List // QString AppearancePage::HeadersTab::helpAnchor() const { return QStringLiteral("configure-appearance-headers"); } static const struct { const char *displayName; DateFormatter::FormatType dateDisplay; } dateDisplayConfig[] = { { I18N_NOOP("Sta&ndard format (%1)"), KMime::DateFormatter::CTime }, { I18N_NOOP("Locali&zed format (%1)"), KMime::DateFormatter::Localized }, { I18N_NOOP("Smart for&mat (%1)"), KMime::DateFormatter::Fancy }, { I18N_NOOP("C&ustom format:"), KMime::DateFormatter::Custom } }; static const int numDateDisplayConfig = sizeof dateDisplayConfig / sizeof * dateDisplayConfig; AppearancePageHeadersTab::AppearancePageHeadersTab(QWidget *parent) : ConfigModuleTab(parent), mCustomDateFormatEdit(nullptr) { QVBoxLayout *vlay = new QVBoxLayout(this); // "General Options" group: QGroupBox *group = new QGroupBox(i18nc("General options for the message list.", "General"), this); QVBoxLayout *gvlay = new QVBoxLayout(group); mDisplayMessageToolTips = new QCheckBox( MessageList::MessageListSettings::self()->messageToolTipEnabledItem()->label(), group); gvlay->addWidget(mDisplayMessageToolTips); connect(mDisplayMessageToolTips, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // "Aggregation" using MessageList::Utils::AggregationComboBox; mAggregationComboBox = new AggregationComboBox(group); QLabel *aggregationLabel = new QLabel(i18n("Default aggregation:"), group); aggregationLabel->setBuddy(mAggregationComboBox); using MessageList::Utils::AggregationConfigButton; AggregationConfigButton *aggregationConfigButton = new AggregationConfigButton(group, mAggregationComboBox); QHBoxLayout *aggregationLayout = new QHBoxLayout(); aggregationLayout->addWidget(aggregationLabel, 1); aggregationLayout->addWidget(mAggregationComboBox, 1); aggregationLayout->addWidget(aggregationConfigButton, 0); gvlay->addLayout(aggregationLayout); connect(aggregationConfigButton, &MessageList::Utils::AggregationConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultAggregation); connect(mAggregationComboBox, SIGNAL(activated(int)), this, SLOT(slotEmitChanged())); // "Theme" using MessageList::Utils::ThemeComboBox; mThemeComboBox = new ThemeComboBox(group); QLabel *themeLabel = new QLabel(i18n("Default theme:"), group); themeLabel->setBuddy(mThemeComboBox); using MessageList::Utils::ThemeConfigButton; ThemeConfigButton *themeConfigButton = new ThemeConfigButton(group, mThemeComboBox); QHBoxLayout *themeLayout = new QHBoxLayout(); themeLayout->addWidget(themeLabel, 1); themeLayout->addWidget(mThemeComboBox, 1); themeLayout->addWidget(themeConfigButton, 0); gvlay->addLayout(themeLayout); connect(themeConfigButton, &MessageList::Utils::ThemeConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultTheme); connect(mThemeComboBox, SIGNAL(activated(int)), this, SLOT(slotEmitChanged())); vlay->addWidget(group); // "Date Display" group: mDateDisplayBox = new QGroupBox(this); mDateDisplayBox->setTitle(i18n("Date Display")); mDateDisplay = new QButtonGroup(this); mDateDisplay->setExclusive(true); gvlay = new QVBoxLayout(mDateDisplayBox); for (int i = 0; i < numDateDisplayConfig; ++i) { const char *label = dateDisplayConfig[i].displayName; QString buttonLabel; if (QString::fromLatin1(label).contains(QStringLiteral("%1"))) { buttonLabel = i18n(label, DateFormatter::formatCurrentDate(dateDisplayConfig[i].dateDisplay)); } else { buttonLabel = i18n(label); } if (dateDisplayConfig[i].dateDisplay == DateFormatter::Custom) { QWidget *hbox = new QWidget(mDateDisplayBox); QHBoxLayout *hboxHBoxLayout = new QHBoxLayout(hbox); hboxHBoxLayout->setMargin(0); QRadioButton *radio = new QRadioButton(buttonLabel, hbox); hboxHBoxLayout->addWidget(radio); mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay); mCustomDateFormatEdit = new KLineEdit(hbox); hboxHBoxLayout->addWidget(mCustomDateFormatEdit); mCustomDateFormatEdit->setEnabled(false); hboxHBoxLayout->setStretchFactor(mCustomDateFormatEdit, 1); connect(radio, &QAbstractButton::toggled, mCustomDateFormatEdit, &QWidget::setEnabled); connect(mCustomDateFormatEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged); QLabel *formatHelp = new QLabel( i18n("Custom format information..."), hbox); formatHelp->setContextMenuPolicy(Qt::NoContextMenu); connect(formatHelp, &QLabel::linkActivated, this, &AppearancePageHeadersTab::slotLinkClicked); hboxHBoxLayout->addWidget(formatHelp); mCustomDateWhatsThis = i18n("

These expressions may be used for the date:" "

" "
    " "
  • d - the day as a number without a leading zero (1-31)
  • " "
  • dd - the day as a number with a leading zero (01-31)
  • " "
  • ddd - the abbreviated day name (Mon - Sun)
  • " "
  • dddd - the long day name (Monday - Sunday)
  • " "
  • M - the month as a number without a leading zero (1-12)
  • " "
  • MM - the month as a number with a leading zero (01-12)
  • " "
  • MMM - the abbreviated month name (Jan - Dec)
  • " "
  • MMMM - the long month name (January - December)
  • " "
  • yy - the year as a two digit number (00-99)
  • " "
  • yyyy - the year as a four digit number (0000-9999)
  • " "
" "

These expressions may be used for the time:" "

" "
    " "
  • h - the hour without a leading zero (0-23 or 1-12 if AM/PM display)
  • " "
  • hh - the hour with a leading zero (00-23 or 01-12 if AM/PM display)
  • " "
  • m - the minutes without a leading zero (0-59)
  • " "
  • mm - the minutes with a leading zero (00-59)
  • " "
  • s - the seconds without a leading zero (0-59)
  • " "
  • ss - the seconds with a leading zero (00-59)
  • " "
  • z - the milliseconds without leading zeroes (0-999)
  • " "
  • zzz - the milliseconds with leading zeroes (000-999)
  • " "
  • AP - switch to AM/PM display. AP will be replaced by either \"AM\" or \"PM\".
  • " "
  • ap - switch to AM/PM display. ap will be replaced by either \"am\" or \"pm\".
  • " "
  • Z - time zone in numeric form (-0500)
  • " "
" "

All other input characters will be ignored." "

"); mCustomDateFormatEdit->setWhatsThis(mCustomDateWhatsThis); radio->setWhatsThis(mCustomDateWhatsThis); gvlay->addWidget(hbox); } else { QRadioButton *radio = new QRadioButton(buttonLabel, mDateDisplayBox); gvlay->addWidget(radio); mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay); } } // end for loop populating mDateDisplay vlay->addWidget(mDateDisplayBox); connect(mDateDisplay, SIGNAL(buttonClicked(int)), this, SLOT(slotEmitChanged())); vlay->addStretch(10); // spacer } void AppearancePageHeadersTab::slotLinkClicked(const QString &link) { if (link == QLatin1String("whatsthis1")) { QWhatsThis::showText(QCursor::pos(), mCustomDateWhatsThis); } } void AppearancePage::HeadersTab::slotSelectDefaultAggregation() { // Select current default aggregation. mAggregationComboBox->selectDefault(); } void AppearancePage::HeadersTab::slotSelectDefaultTheme() { // Select current default theme. mThemeComboBox->selectDefault(); } void AppearancePage::HeadersTab::doLoadOther() { // "General Options": loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); // "Aggregation": slotSelectDefaultAggregation(); // "Theme": slotSelectDefaultTheme(); // "Date Display": setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat()); } void AppearancePage::HeadersTab::doLoadFromGlobalSettings() { loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); // "Aggregation": slotSelectDefaultAggregation(); // "Theme": slotSelectDefaultTheme(); setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat()); } void AppearancePage::HeadersTab::setDateDisplay(int num, const QString &format) { DateFormatter::FormatType dateDisplay = static_cast(num); // special case: needs text for the line edit: if (dateDisplay == DateFormatter::Custom) { mCustomDateFormatEdit->setText(format); } for (int i = 0; i < numDateDisplayConfig; ++i) if (dateDisplay == dateDisplayConfig[i].dateDisplay) { mDateDisplay->button(dateDisplay)->setChecked(true); return; } // fell through since none found: mDateDisplay->button(numDateDisplayConfig - 2)->setChecked(true); // default } void AppearancePage::HeadersTab::save() { saveCheckBox(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); KMKernel::self()->savePaneSelection(); // "Aggregation" mAggregationComboBox->writeDefaultConfig(); // "Theme" mThemeComboBox->writeDefaultConfig(); const int dateDisplayID = mDateDisplay->checkedId(); MessageCore::MessageCoreSettings::self()->setDateFormat(dateDisplayID); MessageCore::MessageCoreSettings::self()->setCustomDateFormat(mCustomDateFormatEdit->text()); } // // Message Window // QString AppearancePage::ReaderTab::helpAnchor() const { return QStringLiteral("configure-appearance-reader"); } AppearancePageReaderTab::AppearancePageReaderTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *topLayout = new QVBoxLayout(this); // "Close message window after replying or forwarding" check box: populateCheckBox(mCloseAfterReplyOrForwardCheck = new QCheckBox(this), KMailSettings::self()->closeAfterReplyOrForwardItem()); mCloseAfterReplyOrForwardCheck->setToolTip( i18n("Close the standalone message window after replying or forwarding the message")); topLayout->addWidget(mCloseAfterReplyOrForwardCheck); connect(mCloseAfterReplyOrForwardCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); mViewerSettings = new MessageViewer::ConfigureWidget; connect(mViewerSettings, &MessageViewer::ConfigureWidget::settingsChanged, this, &ConfigModuleTab::slotEmitChanged); topLayout->addWidget(mViewerSettings); mGravatarConfigWidget = new Gravatar::GravatarConfigWidget; connect(mGravatarConfigWidget, &Gravatar::GravatarConfigWidget::configChanged, this, &ConfigModuleTab::slotEmitChanged); topLayout->addWidget(mGravatarConfigWidget); topLayout->addStretch(100); // spacer } void AppearancePage::ReaderTab::doResetToDefaultsOther() { mGravatarConfigWidget->doResetToDefaultsOther(); } void AppearancePage::ReaderTab::doLoadOther() { loadWidget(mCloseAfterReplyOrForwardCheck, KMailSettings::self()->closeAfterReplyOrForwardItem()); mViewerSettings->readConfig(); mGravatarConfigWidget->doLoadFromGlobalSettings(); } void AppearancePage::ReaderTab::save() { saveCheckBox(mCloseAfterReplyOrForwardCheck, KMailSettings::self()->closeAfterReplyOrForwardItem()); mViewerSettings->writeConfig(); mGravatarConfigWidget->save(); } QString AppearancePage::SystemTrayTab::helpAnchor() const { return QStringLiteral("configure-appearance-systemtray"); } AppearancePageSystemTrayTab::AppearancePageSystemTrayTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "Enable system tray applet" check box mSystemTrayCheck = new QCheckBox(i18n("Enable system tray icon"), this); vlay->addWidget(mSystemTrayCheck); connect(mSystemTrayCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addStretch(10); } void AppearancePage::SystemTrayTab::doLoadFromGlobalSettings() { loadWidget(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem()); } void AppearancePage::SystemTrayTab::save() { saveCheckBox(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem()); KMailSettings::self()->save(); } QString AppearancePage::MessageTagTab::helpAnchor() const { return QStringLiteral("configure-appearance-messagetag"); } TagListWidgetItem::TagListWidgetItem(QListWidget *parent) : QListWidgetItem(parent), mTag(nullptr) { } TagListWidgetItem::TagListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent) : QListWidgetItem(icon, text, parent), mTag(nullptr) { } TagListWidgetItem::~TagListWidgetItem() { } void TagListWidgetItem::setKMailTag(const MailCommon::Tag::Ptr &tag) { mTag = tag; } MailCommon::Tag::Ptr TagListWidgetItem::kmailTag() const { return mTag; } AppearancePageMessageTagTab::AppearancePageMessageTagTab(QWidget *parent) : ConfigModuleTab(parent) { mPreviousTag = -1; QHBoxLayout *maingrid = new QHBoxLayout(this); //Lefthand side Listbox and friends //Groupbox frame mTagsGroupBox = new QGroupBox(i18n("A&vailable Tags"), this); maingrid->addWidget(mTagsGroupBox); QVBoxLayout *tageditgrid = new QVBoxLayout(mTagsGroupBox); //Listbox, add, remove row QHBoxLayout *addremovegrid = new QHBoxLayout(); tageditgrid->addLayout(addremovegrid); mTagAddLineEdit = new KLineEdit(mTagsGroupBox); mTagAddLineEdit->setTrapReturnKey(true); addremovegrid->addWidget(mTagAddLineEdit); mTagAddButton = new QPushButton(mTagsGroupBox); mTagAddButton->setToolTip(i18n("Add new tag")); mTagAddButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addremovegrid->addWidget(mTagAddButton); mTagRemoveButton = new QPushButton(mTagsGroupBox); mTagRemoveButton->setToolTip(i18n("Remove selected tag")); mTagRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); addremovegrid->addWidget(mTagRemoveButton); //Up and down buttons QHBoxLayout *updowngrid = new QHBoxLayout(); tageditgrid->addLayout(updowngrid); mTagUpButton = new QPushButton(mTagsGroupBox); mTagUpButton->setToolTip(i18n("Increase tag priority")); mTagUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); mTagUpButton->setAutoRepeat(true); updowngrid->addWidget(mTagUpButton); mTagDownButton = new QPushButton(mTagsGroupBox); mTagDownButton->setToolTip(i18n("Decrease tag priority")); mTagDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); mTagDownButton->setAutoRepeat(true); updowngrid->addWidget(mTagDownButton); //Listbox for tag names QHBoxLayout *listboxgrid = new QHBoxLayout(); tageditgrid->addLayout(listboxgrid); mTagListBox = new QListWidget(mTagsGroupBox); mTagListBox->setDragDropMode(QAbstractItemView::InternalMove); connect(mTagListBox->model(), &QAbstractItemModel::rowsMoved, this, &AppearancePageMessageTagTab::slotRowsMoved); mTagListBox->setMinimumWidth(150); listboxgrid->addWidget(mTagListBox); //RHS for individual tag settings //Extra VBoxLayout for stretchers around settings QVBoxLayout *tagsettinggrid = new QVBoxLayout(); maingrid->addLayout(tagsettinggrid); //tagsettinggrid->addStretch( 10 ); //Groupbox frame mTagSettingGroupBox = new QGroupBox(i18n("Ta&g Settings"), this); tagsettinggrid->addWidget(mTagSettingGroupBox); QList actionCollections; if (kmkernel->getKMMainWidget()) { actionCollections = kmkernel->getKMMainWidget()->actionCollections(); } QHBoxLayout *lay = new QHBoxLayout(mTagSettingGroupBox); mTagWidget = new MailCommon::TagWidget(actionCollections, this); lay->addWidget(mTagWidget); connect(mTagWidget, &TagWidget::changed, this, &AppearancePageMessageTagTab::slotEmitChangeCheck); //For enabling the add button in case box is non-empty connect(mTagAddLineEdit, &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotAddLineTextChanged); //For on-the-fly updating of tag name in editbox connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); connect(mTagWidget, &TagWidget::iconNameChanged, this, &AppearancePageMessageTagTab::slotIconNameChanged); connect(mTagAddLineEdit, &KLineEdit::returnPressed, this, &AppearancePageMessageTagTab::slotAddNewTag); connect(mTagAddButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotAddNewTag); connect(mTagRemoveButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotRemoveTag); connect(mTagUpButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagUp); connect(mTagDownButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagDown); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); //Adjust widths for columns maingrid->setStretchFactor(mTagsGroupBox, 1); maingrid->setStretchFactor(lay, 1); tagsettinggrid->addStretch(10); } AppearancePageMessageTagTab::~AppearancePageMessageTagTab() { } void AppearancePage::MessageTagTab::slotEmitChangeCheck() { slotEmitChanged(); } void AppearancePage::MessageTagTab::slotRowsMoved(const QModelIndex &, int sourcestart, int sourceEnd, const QModelIndex &, int destinationRow) { Q_UNUSED(sourceEnd); Q_UNUSED(sourcestart); Q_UNUSED(destinationRow); updateButtons(); slotEmitChangeCheck(); } void AppearancePage::MessageTagTab::updateButtons() { const int currentIndex = mTagListBox->currentRow(); const bool theFirst = (currentIndex == 0); const bool theLast = (currentIndex >= (int)mTagListBox->count() - 1); const bool aFilterIsSelected = (currentIndex >= 0); mTagUpButton->setEnabled(aFilterIsSelected && !theFirst); mTagDownButton->setEnabled(aFilterIsSelected && !theLast); } void AppearancePage::MessageTagTab::slotMoveTagUp() { const int tmp_index = mTagListBox->currentRow(); if (tmp_index <= 0) { return; } swapTagsInListBox(tmp_index, tmp_index - 1); updateButtons(); } void AppearancePage::MessageTagTab::slotMoveTagDown() { const int tmp_index = mTagListBox->currentRow(); if ((tmp_index < 0) || (tmp_index >= int(mTagListBox->count()) - 1)) { return; } swapTagsInListBox(tmp_index, tmp_index + 1); updateButtons(); } void AppearancePage::MessageTagTab::swapTagsInListBox(const int first, const int second) { disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); QListWidgetItem *item = mTagListBox->takeItem(first); // now selected item is at idx(idx-1), so // insert the other item at idx, ie. above(below). mPreviousTag = second; mTagListBox->insertItem(second, item); mTagListBox->setCurrentRow(second); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotEmitChangeCheck(); } void AppearancePage::MessageTagTab::slotRecordTagSettings(int aIndex) { if ((aIndex < 0) || (aIndex >= int(mTagListBox->count()))) { return; } QListWidgetItem *item = mTagListBox->item(aIndex); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); tmp_desc->tagName = tagItem->text(); mTagWidget->recordTagSettings(tmp_desc); } void AppearancePage::MessageTagTab::slotUpdateTagSettingWidgets(int aIndex) { //Check if selection is valid if ((aIndex < 0) || (mTagListBox->currentRow() < 0)) { mTagRemoveButton->setEnabled(false); mTagUpButton->setEnabled(false); mTagDownButton->setEnabled(false); mTagWidget->setEnabled(false); return; } mTagWidget->setEnabled(true); mTagRemoveButton->setEnabled(true); mTagUpButton->setEnabled((0 != aIndex)); mTagDownButton->setEnabled(((int(mTagListBox->count()) - 1) != aIndex)); QListWidgetItem *item = mTagListBox->currentItem(); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); disconnect(mTagWidget->tagNameLineEdit(), &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotNameLineTextChanged); mTagWidget->tagNameLineEdit()->setEnabled(!tmp_desc->isImmutable); mTagWidget->tagNameLineEdit()->setText(tmp_desc->tagName); connect(mTagWidget->tagNameLineEdit(), &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotNameLineTextChanged); mTagWidget->setTagTextColor(tmp_desc->textColor); mTagWidget->setTagBackgroundColor(tmp_desc->backgroundColor); mTagWidget->setTagTextFormat(tmp_desc->isBold, tmp_desc->isItalic); mTagWidget->iconButton()->setEnabled(!tmp_desc->isImmutable); mTagWidget->iconButton()->setIcon(tmp_desc->iconName); mTagWidget->keySequenceWidget()->setEnabled(true); mTagWidget->keySequenceWidget()->setKeySequence(tmp_desc->shortcut, KKeySequenceWidget::NoValidate); mTagWidget->inToolBarCheck()->setEnabled(true); mTagWidget->inToolBarCheck()->setChecked(tmp_desc->inToolbar); } void AppearancePage::MessageTagTab::slotSelectionChanged() { mEmitChanges = false; slotRecordTagSettings(mPreviousTag); slotUpdateTagSettingWidgets(mTagListBox->currentRow()); mPreviousTag = mTagListBox->currentRow(); mEmitChanges = true; } void AppearancePage::MessageTagTab::slotRemoveTag() { const int tmp_index = mTagListBox->currentRow(); if (tmp_index >= 0) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, i18n("Do you want to remove tag \'%1\'?", mTagListBox->item(mTagListBox->currentRow())->text()))) { QListWidgetItem *item = mTagListBox->takeItem(mTagListBox->currentRow()); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); if (tmp_desc->tag().isValid()) { new Akonadi::TagDeleteJob(tmp_desc->tag()); } else { qCWarning(KMAIL_LOG) << "Can't remove tag with invalid akonadi tag"; } mPreviousTag = -1; //Before deleting the current item, make sure the selectionChanged signal //is disconnected, so that the widgets will not get updated while the //deletion takes place. disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); delete item; connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotSelectionChanged(); slotEmitChangeCheck(); } } } void AppearancePage::MessageTagTab::slotDeleteTagJob(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << "Failed to delete tag " << job->errorString(); } } void AppearancePage::MessageTagTab::slotNameLineTextChanged(const QString &aText) { //If deleted all, leave the first character for the sake of not having an //empty tag name if (aText.isEmpty() && !mTagListBox->currentItem()) { return; } const int count = mTagListBox->count(); for (int i = 0; i < count; ++i) { if (mTagListBox->item(i)->text() == aText) { KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists.")); disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); mTagWidget->tagNameLineEdit()->setText(mTagListBox->currentItem()->text()); connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); return; } } //Disconnect so the tag information is not saved and reloaded with every //letter disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); mTagListBox->currentItem()->setText(aText); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); } void AppearancePage::MessageTagTab::slotIconNameChanged(const QString &iconName) { mTagListBox->currentItem()->setIcon(QIcon::fromTheme(iconName)); } void AppearancePage::MessageTagTab::slotAddLineTextChanged(const QString &aText) { mTagAddButton->setEnabled(!aText.trimmed().isEmpty()); } void AppearancePage::MessageTagTab::slotAddNewTag() { const QString newTagName = mTagAddLineEdit->text().trimmed(); if (newTagName.isEmpty()) { return; } const int count = mTagListBox->count(); for (int i = 0; i < count; ++i) { if (mTagListBox->item(i)->text() == newTagName) { KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists.")); return; } } const int tmp_priority = mTagListBox->count(); MailCommon::Tag::Ptr tag(Tag::createDefaultTag(newTagName)); tag->priority = tmp_priority; slotEmitChangeCheck(); TagListWidgetItem *newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), newTagName, mTagListBox); newItem->setKMailTag(tag); mTagListBox->addItem(newItem); mTagListBox->setCurrentItem(newItem); mTagAddLineEdit->clear(); } void AppearancePage::MessageTagTab::doLoadFromGlobalSettings() { mTagListBox->clear(); Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); connect(fetchJob, &KJob::result, this, &AppearancePageMessageTagTab::slotTagsFetched); } void AppearancePage::MessageTagTab::slotTagsFetched(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << "Failed to load tags " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); QList msgTagList; const Akonadi::Tag::List tagList = fetchJob->tags(); msgTagList.reserve(tagList.count()); for (const Akonadi::Tag &akonadiTag : tagList) { MailCommon::Tag::Ptr tag = MailCommon::Tag::fromAkonadi(akonadiTag); msgTagList.append(tag); } std::sort(msgTagList.begin(), msgTagList.end(), MailCommon::Tag::compare); for (const MailCommon::Tag::Ptr &tag : qAsConst(msgTagList)) { TagListWidgetItem *newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), tag->tagName, mTagListBox); newItem->setKMailTag(tag); if (tag->priority == -1) { tag->priority = mTagListBox->count() - 1; } } //Disconnect so that insertItem's do not trigger an update procedure disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotUpdateTagSettingWidgets(-1); //Needed since the previous function doesn't affect add button mTagAddButton->setEnabled(false); // Save the original list mOriginalMsgTagList.clear(); for (const MailCommon::TagPtr &tag : qAsConst(msgTagList)) { mOriginalMsgTagList.append(MailCommon::TagPtr(new MailCommon::Tag(*tag))); } } void AppearancePage::MessageTagTab::save() { const int currentRow = mTagListBox->currentRow(); if (currentRow < 0) { return; } const int count = mTagListBox->count(); if (!count) { return; } QListWidgetItem *item = mTagListBox->currentItem(); if (!item) { return; } slotRecordTagSettings(currentRow); const int numberOfMsgTagList = count; for (int i = 0; i < numberOfMsgTagList; ++i) { TagListWidgetItem *tagItem = static_cast(mTagListBox->item(i)); if ((i >= mOriginalMsgTagList.count()) || *(tagItem->kmailTag()) != *(mOriginalMsgTagList[i])) { MailCommon::Tag::Ptr tag = tagItem->kmailTag(); tag->priority = i; Akonadi::Tag akonadiTag = tag->saveToAkonadi(); if ((*tag).id() > 0) { akonadiTag.setId((*tag).id()); } if (akonadiTag.isValid()) { new Akonadi::TagModifyJob(akonadiTag); } else { new Akonadi::TagCreateJob(akonadiTag); } } } } diff --git a/src/configuredialog/configurecomposerpage.cpp b/src/configuredialog/configurecomposerpage.cpp index 0b3f643b6..3b21b912a 100644 --- a/src/configuredialog/configurecomposerpage.cpp +++ b/src/configuredialog/configurecomposerpage.cpp @@ -1,1335 +1,1336 @@ /* Copyright (c) 2013-2017 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "configurecomposerpage.h" #include "PimCommon/ConfigureImmutableWidgetUtils" using namespace PimCommon::ConfigureImmutableWidgetUtils; #include "kmkernel.h" +#include "helper_p.h" #include "kmmainwidget.h" #include "PimCommon/AutoCorrectionWidget" #include "MessageComposer/ImageScalingWidget" #include "MessageComposer/MessageComposerSettings" #include #include "settings/kmailsettings.h" #include "configuredialog/configuredialoglistview.h" #include "PimCommon/SimpleStringlistEditor" #include "templatesconfiguration_kfg.h" #include "TemplateParser/TemplatesConfiguration" #include "templateparser/customtemplates.h" #include "globalsettings_templateparser.h" #include "libkdepim/recentaddresses.h" #include #include "libkdepim/completionordereditor.h" using KPIM::RecentAddresses; #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include QString ComposerPage::helpAnchor() const { return QStringLiteral("configure-composer"); } ComposerPage::ComposerPage(QWidget *parent) : ConfigModuleWithTabs(parent) { // // "General" tab: // GeneralTab *generalTab = new GeneralTab(); addTab(generalTab, i18nc("General settings for the composer.", "General")); addConfig(KMailSettings::self(), generalTab); // // "Templates" tab: // TemplatesTab *templatesTab = new TemplatesTab(); addTab(templatesTab, i18n("Standard Templates")); // // "Custom Templates" tab: // CustomTemplatesTab *customTemplatesTab = new CustomTemplatesTab(); addTab(customTemplatesTab, i18n("Custom Templates")); // // "Subject" tab: // SubjectTab *subjectTab = new SubjectTab(); addTab(subjectTab, i18nc("Settings regarding the subject when composing a message.", "Subject")); addConfig(KMailSettings::self(), subjectTab); // // "Charset" tab: // CharsetTab *charsetTab = new CharsetTab(); addTab(charsetTab, i18n("Charset")); // // "Headers" tab: // HeadersTab *headersTab = new HeadersTab(); addTab(headersTab, i18n("Headers")); // // "Attachments" tab: // AttachmentsTab *attachmentsTab = new AttachmentsTab(); addTab(attachmentsTab, i18nc("Config->Composer->Attachments", "Attachments")); // // "autocorrection" tab: // AutoCorrectionTab *autoCorrectionTab = new AutoCorrectionTab(); addTab(autoCorrectionTab, i18n("Autocorrection")); // // "autoresize" tab: // AutoImageResizeTab *autoImageResizeTab = new AutoImageResizeTab(); addTab(autoImageResizeTab, i18n("Auto Resize Image")); } QString ComposerPage::GeneralTab::helpAnchor() const { return QStringLiteral("configure-composer-general"); } ComposerPageGeneralTab::ComposerPageGeneralTab(QWidget *parent) : ConfigModuleTab(parent) { // Main layout QHBoxLayout *hb1 = new QHBoxLayout(); // box with 2 columns QVBoxLayout *vb1 = new QVBoxLayout(); // first with 2 groupboxes QVBoxLayout *vb2 = new QVBoxLayout(); // second with 1 groupbox // "Signature" group QGroupBox *groupBox = new QGroupBox(i18nc("@title:group", "Signature")); QVBoxLayout *groupVBoxLayout = new QVBoxLayout(); // "Automatically insert signature" checkbox mAutoAppSignFileCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->autoTextSignatureItem()->label(), this); QString helpText = i18n("Automatically insert the configured signature\n" "when starting to compose a message"); mAutoAppSignFileCheck->setToolTip(helpText); mAutoAppSignFileCheck->setWhatsThis(helpText); connect(mAutoAppSignFileCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupVBoxLayout->addWidget(mAutoAppSignFileCheck); // "Insert signature above quoted text" checkbox mTopQuoteCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->prependSignatureItem()->label(), this); mTopQuoteCheck->setEnabled(false); helpText = i18n("Insert the signature above any quoted text"); mTopQuoteCheck->setToolTip(helpText); mTopQuoteCheck->setWhatsThis(helpText); connect(mTopQuoteCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mAutoAppSignFileCheck, &QAbstractButton::toggled, mTopQuoteCheck, &QWidget::setEnabled); groupVBoxLayout->addWidget(mTopQuoteCheck); // "Prepend separator to signature" checkbox mDashDashCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()->label(), this); mDashDashCheck->setEnabled(false); helpText = i18n("Insert the RFC-compliant signature separator\n" "(two dashes and a space on a line) before the signature"); mDashDashCheck->setToolTip(helpText); mDashDashCheck->setWhatsThis(helpText); connect(mDashDashCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mAutoAppSignFileCheck, &QAbstractButton::toggled, mDashDashCheck, &QWidget::setEnabled); groupVBoxLayout->addWidget(mDashDashCheck); // "Remove signature when replying" checkbox mStripSignatureCheck = new QCheckBox(TemplateParser::TemplateParserSettings::self()->stripSignatureItem()->label(), this); helpText = i18n("When replying, do not quote any existing signature"); mStripSignatureCheck->setToolTip(helpText); mStripSignatureCheck->setWhatsThis(helpText); connect(mStripSignatureCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupVBoxLayout->addWidget(mStripSignatureCheck); groupBox->setLayout(groupVBoxLayout); vb1->addWidget(groupBox); // "Format" group groupBox = new QGroupBox(i18nc("@title:group", "Format")); QGridLayout *groupGridLayout = new QGridLayout(); int row = 0; // "Only quote selected text when replying" checkbox mQuoteSelectionOnlyCheck = new QCheckBox(MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()->label(), this); #if QT_VERSION < QT_VERSION_CHECK(5, 6, 1) mQuoteSelectionOnlyCheck->setEnabled(false); #endif helpText = i18n("When replying, only quote the selected text\n" "(instead of the complete message), if\n" "there is text selected in the message window."); mQuoteSelectionOnlyCheck->setToolTip(helpText); mQuoteSelectionOnlyCheck->setWhatsThis(helpText); connect(mQuoteSelectionOnlyCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mQuoteSelectionOnlyCheck, row, 0, 1, -1); ++row; // "Use smart quoting" checkbox mSmartQuoteCheck = new QCheckBox( TemplateParser::TemplateParserSettings::self()->smartQuoteItem()->label(), this); helpText = i18n("When replying, add quote signs in front of all lines of the quoted text,\n" "even when the line was created by adding an additional line break while\n" "word-wrapping the text."); mSmartQuoteCheck->setToolTip(helpText); mSmartQuoteCheck->setWhatsThis(helpText); connect(mSmartQuoteCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mSmartQuoteCheck, row, 0, 1, -1); ++row; // "Word wrap at column" checkbox/spinbox mWordWrapCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->wordWrapItem()->label(), this); helpText = i18n("Enable automatic word wrapping at the specified width"); mWordWrapCheck->setToolTip(helpText); mWordWrapCheck->setWhatsThis(helpText); mWrapColumnSpin = new QSpinBox(this); mWrapColumnSpin->setMaximum(78/*max*/); mWrapColumnSpin->setMinimum(30/*min*/); mWrapColumnSpin->setSingleStep(1/*step*/); mWrapColumnSpin->setValue(78/*init*/); mWrapColumnSpin->setEnabled(false); // since !mWordWrapCheck->isChecked() helpText = i18n("Set the text width for automatic word wrapping"); mWrapColumnSpin->setToolTip(helpText); mWrapColumnSpin->setWhatsThis(helpText); connect(mWordWrapCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); - connect(mWrapColumnSpin, SIGNAL(valueChanged(int)), - this, SLOT(slotEmitChanged())); + connect(mWrapColumnSpin, QOverload::of(&QSpinBox::valueChanged), + this, &ComposerPageGeneralTab::slotEmitChanged); // only enable the spinbox if the checkbox is checked connect(mWordWrapCheck, &QAbstractButton::toggled, mWrapColumnSpin, &QWidget::setEnabled); groupGridLayout->addWidget(mWordWrapCheck, row, 0); groupGridLayout->addWidget(mWrapColumnSpin, row, 1); ++row; // Spacing ++row; // "Reply/Forward using HTML if present" checkbox mReplyUsingHtml = new QCheckBox(TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()->label(), this); helpText = i18n("When replying or forwarding, quote the message\n" "in the original format it was received.\n" "If unchecked, the reply will be as plain text by default."); mReplyUsingHtml->setToolTip(helpText); mReplyUsingHtml->setWhatsThis(helpText); connect(mReplyUsingHtml, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mReplyUsingHtml, row, 0, 1, -1); ++row; // "Improve plain text of HTML" checkbox mImprovePlainTextOfHtmlMessage = new QCheckBox(MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessageItem()->label(), this); // For what is supported see http://www.grantlee.org/apidox/classGrantlee_1_1PlainTextMarkupBuilder.html helpText = i18n("Format the plain text part of a message from the HTML markup.\n" "Bold, italic and underlined text, lists, and external references\n" "are supported."); mImprovePlainTextOfHtmlMessage->setToolTip(helpText); mImprovePlainTextOfHtmlMessage->setWhatsThis(helpText); connect(mImprovePlainTextOfHtmlMessage, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mImprovePlainTextOfHtmlMessage, row, 0, 1, -1); ++row; // Spacing ++row; // "Autosave interval" spinbox mAutoSave = new KPluralHandlingSpinBox(this); mAutoSave->setMaximum(60); mAutoSave->setMinimum(0); mAutoSave->setSingleStep(1); mAutoSave->setValue(1); mAutoSave->setObjectName(QStringLiteral("kcfg_AutosaveInterval")); mAutoSave->setSpecialValueText(i18n("No autosave")); mAutoSave->setSuffix(ki18ncp("Interval suffix", " minute", " minutes")); helpText = i18n("Automatically save the message at this specified interval"); mAutoSave->setToolTip(helpText); mAutoSave->setWhatsThis(helpText); QLabel *label = new QLabel(KMailSettings::self()->autosaveIntervalItem()->label(), this); label->setBuddy(mAutoSave); connect(mAutoSave, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); groupGridLayout->addWidget(label, row, 0); groupGridLayout->addWidget(mAutoSave, row, 1); ++row; #ifdef KDEPIM_ENTERPRISE_BUILD // "Default forwarding type" combobox mForwardTypeCombo = new KComboBox(false, this); mForwardTypeCombo->addItems(QStringList() << i18nc("@item:inlistbox Inline mail forwarding", "Inline") << i18n("As Attachment")); helpText = i18n("Set the default forwarded message format"); mForwardTypeCombo->setToolTip(helpText); mForwardTypeCombo->setWhatsThis(helpText); label = new QLabel(i18n("Default forwarding type:"), this); label->setBuddy(mForwardTypeCombo); connect(mForwardTypeCombo, SIGNAL(activated(int)), this, SLOT(slotEmitChanged())); groupGridLayout->addWidget(label, row, 0); groupGridLayout->addWidget(mForwardTypeCombo, row, 1); ++row; #endif groupBox->setLayout(groupGridLayout); vb1->addWidget(groupBox); // "Recipients" group groupBox = new QGroupBox(i18nc("@title:group", "Recipients")); groupGridLayout = new QGridLayout(); row = 0; // "Automatically request MDNs" checkbox mAutoRequestMDNCheck = new QCheckBox(KMailSettings::self()->requestMDNItem()->label(), this); helpText = i18n("By default, request an MDN when starting to compose a message.\n" "You can select this on a per-message basis using \"Options - Request Disposition Notification\""); mAutoRequestMDNCheck->setToolTip(helpText); mAutoRequestMDNCheck->setWhatsThis(helpText); connect(mAutoRequestMDNCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mAutoRequestMDNCheck, row, 0, 1, -1); ++row; // Spacing ++row; // "Use Baloo seach in composer" checkbox mShowBalooSearchAddressesInComposer = new QCheckBox( MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposerItem()->label(), this); connect(mShowBalooSearchAddressesInComposer, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mShowBalooSearchAddressesInComposer, row, 0, 1, -1); ++row; #ifdef KDEPIM_ENTERPRISE_BUILD // "Warn if too many recipients" checkbox/spinbox mRecipientCheck = new QCheckBox( GlobalSettings::self()->tooManyRecipientsItem()->label(), this); mRecipientCheck->setObjectName(QStringLiteral("kcfg_TooManyRecipients")); helpText = i18n(KMailSettings::self()->tooManyRecipientsItem()->whatsThis().toUtf8()); mRecipientCheck->setWhatsThis(helpText); mRecipientCheck->setToolTip(i18n("Warn if too many recipients are specified")); mRecipientSpin = new QSpinBox(this); mRecipientSpin->setMaximum(100/*max*/); mRecipientSpin->setMinimum(1/*min*/); mRecipientSpin->setSingleStep(1/*step*/); mRecipientSpin->setValue(5/*init*/); mRecipientSpin->setObjectName(QStringLiteral("kcfg_RecipientThreshold")); mRecipientSpin->setEnabled(false); helpText = i18n(KMailSettings::self()->recipientThresholdItem()->whatsThis().toUtf8()); mRecipientSpin->setWhatsThis(helpText); mRecipientSpin->setToolTip(i18n("Set the maximum number of recipients for the warning")); connect(mRecipientCheck, SIGNAL(stateChanged(int)), this, SLOT(slotEmitChanged())); connect(mRecipientSpin, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); // only enable the spinbox if the checkbox is checked connect(mRecipientCheck, SIGNAL(toggled(bool)), mRecipientSpin, SLOT(setEnabled(bool))); groupGridLayout->addWidget(mRecipientCheck, row, 0, 1, 2); groupGridLayout->addWidget(mRecipientSpin, row, 2); ++row; #endif // "Maximum Reply-to-All recipients" spinbox mMaximumRecipients = new QSpinBox(this); mMaximumRecipients->setMaximum(9999); mMaximumRecipients->setMinimum(0); mMaximumRecipients->setSingleStep(1); mMaximumRecipients->setValue(1); helpText = i18n("Only allow this many recipients to be specified for the message.\n" "This applies to doing a \"Reply to All\", entering recipients manually\n" "or using the \"Select...\" picker. Setting this limit helps you to\n" "avoid accidentally sending a message to too many people. Note,\n" "however, that it does not take account of distribution lists or\n" "mailing lists."); mMaximumRecipients->setToolTip(helpText); mMaximumRecipients->setWhatsThis(helpText); label = new QLabel(MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()->label(), this); label->setBuddy(mMaximumRecipients); connect(mMaximumRecipients, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); groupGridLayout->addWidget(label, row, 0, 1, 2); groupGridLayout->addWidget(mMaximumRecipients, row, 2); ++row; // Spacing ++row; // "Use recent addresses for autocompletion" checkbox mShowRecentAddressesInComposer = new QCheckBox( MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposerItem()->label(), this); helpText = i18n("Remember recent addresses entered,\n" "and offer them for recipient completion"); mShowRecentAddressesInComposer->setToolTip(helpText); mShowRecentAddressesInComposer->setWhatsThis(helpText); connect(mShowRecentAddressesInComposer, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mShowRecentAddressesInComposer, row, 0, 1, -1); ++row; // "Maximum recent addresses retained" spinbox mMaximumRecentAddress = new QSpinBox(this); mMaximumRecentAddress->setMinimum(0); mMaximumRecentAddress->setMaximum(999); mMaximumRecentAddress->setSpecialValueText(i18nc("No addresses are retained", "No save")); mMaximumRecentAddress->setEnabled(false); label = new QLabel(i18n("Maximum recent addresses retained:")); label->setBuddy(mMaximumRecentAddress); label->setEnabled(false); helpText = i18n("The maximum number of recently entered addresses that will\n" "be remembered for completion"); mMaximumRecentAddress->setToolTip(helpText); mMaximumRecentAddress->setWhatsThis(helpText); connect(mMaximumRecentAddress, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); connect(mShowRecentAddressesInComposer, &QAbstractButton::toggled, mMaximumRecentAddress, &QWidget::setEnabled); connect(mShowRecentAddressesInComposer, &QAbstractButton::toggled, label, &QWidget::setEnabled); groupGridLayout->addWidget(label, row, 0, 1, 2); groupGridLayout->addWidget(mMaximumRecentAddress, row, 2); ++row; // Configure All Address settings QPushButton *configureCompletionButton = new QPushButton(i18n("Configure Completion..."), this); connect(configureCompletionButton, &QAbstractButton::clicked, this, &ComposerPageGeneralTab::slotConfigureAddressCompletion); groupGridLayout->addWidget(configureCompletionButton, row, 1, 1, 2); ++row; groupBox->setLayout(groupGridLayout); vb2->addWidget(groupBox); // Finish up main layout vb1->addStretch(1); vb2->addStretch(1); hb1->addLayout(vb1); hb1->addLayout(vb2); setLayout(hb1); } void ComposerPage::GeneralTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); const bool autoAppSignFile = MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto"); const bool topQuoteCheck = MessageComposer::MessageComposerSettings::self()->prependSignature(); const bool dashDashSignature = MessageComposer::MessageComposerSettings::self()->dashDashSignature(); const bool smartQuoteCheck = MessageComposer::MessageComposerSettings::self()->quoteSelectionOnly(); const bool wordWrap = MessageComposer::MessageComposerSettings::self()->wordWrap(); const int wrapColumn = MessageComposer::MessageComposerSettings::self()->lineWrapWidth(); const bool showRecentAddress = MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposer(); const int maximumRecipient = MessageComposer::MessageComposerSettings::self()->maximumRecipients(); const bool improvePlainText = MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage(); const bool showBalooSearchInComposer = MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposer(); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); mAutoAppSignFileCheck->setChecked(autoAppSignFile); mTopQuoteCheck->setChecked(topQuoteCheck); mDashDashCheck->setChecked(dashDashSignature); mQuoteSelectionOnlyCheck->setChecked(smartQuoteCheck); mWordWrapCheck->setChecked(wordWrap); mWrapColumnSpin->setValue(wrapColumn); mMaximumRecipients->setValue(maximumRecipient); mShowRecentAddressesInComposer->setChecked(showRecentAddress); mShowBalooSearchAddressesInComposer->setChecked(showBalooSearchInComposer); mImprovePlainTextOfHtmlMessage->setChecked(improvePlainText); mMaximumRecentAddress->setValue(40); } void ComposerPage::GeneralTab::doLoadFromGlobalSettings() { // various check boxes: mAutoAppSignFileCheck->setChecked( MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")); loadWidget(mTopQuoteCheck, MessageComposer::MessageComposerSettings::self()->prependSignatureItem()); loadWidget(mDashDashCheck, MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()); loadWidget(mSmartQuoteCheck, TemplateParser::TemplateParserSettings::self()->smartQuoteItem()); loadWidget(mQuoteSelectionOnlyCheck, MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()); loadWidget(mReplyUsingHtml, TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()); loadWidget(mStripSignatureCheck, TemplateParser::TemplateParserSettings::self()->stripSignatureItem()); loadWidget(mAutoRequestMDNCheck, KMailSettings::self()->requestMDNItem()); loadWidget(mWordWrapCheck, MessageComposer::MessageComposerSettings::self()->wordWrapItem()); loadWidget(mWrapColumnSpin, MessageComposer::MessageComposerSettings::self()->lineWrapWidthItem()); loadWidget(mMaximumRecipients, MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()); mAutoSave->setValue(KMailSettings::self()->autosaveInterval()); loadWidget(mShowRecentAddressesInComposer, MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposerItem()); loadWidget(mShowBalooSearchAddressesInComposer, MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposerItem()); mImprovePlainTextOfHtmlMessage->setChecked(MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage()); #ifdef KDEPIM_ENTERPRISE_BUILD mRecipientCheck->setChecked(KMailSettings::self()->tooManyRecipients()); mRecipientSpin->setValue(KMailSettings::self()->recipientThreshold()); if (KMailSettings::self()->forwardingInlineByDefault()) { mForwardTypeCombo->setCurrentIndex(0); } else { mForwardTypeCombo->setCurrentIndex(1); } #endif mMaximumRecentAddress->setValue(RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->maxCount()); } void ComposerPage::GeneralTab::save() { saveCheckBox(mTopQuoteCheck, MessageComposer::MessageComposerSettings::self()->prependSignatureItem()); saveCheckBox(mDashDashCheck, MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()); saveCheckBox(mSmartQuoteCheck, TemplateParser::TemplateParserSettings::self()->smartQuoteItem()); saveCheckBox(mQuoteSelectionOnlyCheck, MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()); saveCheckBox(mReplyUsingHtml, TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()); saveCheckBox(mStripSignatureCheck, TemplateParser::TemplateParserSettings::self()->stripSignatureItem()); saveCheckBox(mAutoRequestMDNCheck, KMailSettings::self()->requestMDNItem()); saveCheckBox(mWordWrapCheck, MessageComposer::MessageComposerSettings::self()->wordWrapItem()); MessageComposer::MessageComposerSettings::self()->setAutoTextSignature( mAutoAppSignFileCheck->isChecked() ? QStringLiteral("auto") : QStringLiteral("manual")); saveSpinBox(mWrapColumnSpin, MessageComposer::MessageComposerSettings::self()->lineWrapWidthItem()); saveSpinBox(mMaximumRecipients, MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()); KMailSettings::self()->setAutosaveInterval(mAutoSave->value()); MessageComposer::MessageComposerSettings::self()->setShowRecentAddressesInComposer(mShowRecentAddressesInComposer->isChecked()); MessageComposer::MessageComposerSettings::self()->setShowBalooSearchInComposer(mShowBalooSearchAddressesInComposer->isChecked()); MessageComposer::MessageComposerSettings::self()->setImprovePlainTextOfHtmlMessage(mImprovePlainTextOfHtmlMessage->isChecked()); #ifdef KDEPIM_ENTERPRISE_BUILD KMailSettings::self()->setTooManyRecipients(mRecipientCheck->isChecked()); KMailSettings::self()->setRecipientThreshold(mRecipientSpin->value()); KMailSettings::self()->setForwardingInlineByDefault(mForwardTypeCombo->currentIndex() == 0); #endif RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->setMaxCount(mMaximumRecentAddress->value()); MessageComposer::MessageComposerSettings::self()->requestSync(); } void ComposerPage::GeneralTab::slotConfigureAddressCompletion() { QScopedPointer dlg(new KPIM::CompletionConfigureDialog(this)); dlg->setRecentAddresses(KPIM::RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->addresses()); KLDAP::LdapClientSearch search; dlg->setLdapClientSearch(&search); KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist")); KConfigGroup group(config, "AddressLineEdit"); const QStringList balooBlackList = group.readEntry("BalooBackList", QStringList()); dlg->setEmailBlackList(balooBlackList); dlg->load(); if (dlg->exec() && dlg) { if (dlg->recentAddressWasChanged()) { KPIM::RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->clear(); dlg->storeAddresses(MessageComposer::MessageComposerSettings::self()->config()); } } } QString ComposerPage::TemplatesTab::helpAnchor() const { return QStringLiteral("configure-composer-templates"); } ComposerPageTemplatesTab::ComposerPageTemplatesTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); mWidget = new TemplateParser::TemplatesConfiguration(this); vlay->addWidget(mWidget); connect(mWidget, &TemplateParser::TemplatesConfiguration::changed, this, &ConfigModuleTab::slotEmitChanged); } void ComposerPage::TemplatesTab::doLoadFromGlobalSettings() { mWidget->loadFromGlobal(); } void ComposerPage::TemplatesTab::save() { mWidget->saveToGlobal(); } void ComposerPage::TemplatesTab::doResetToDefaultsOther() { mWidget->resetToDefault(); } QString ComposerPage::CustomTemplatesTab::helpAnchor() const { return QStringLiteral("configure-composer-custom-templates"); } ComposerPageCustomTemplatesTab::ComposerPageCustomTemplatesTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); mWidget = new TemplateParser::CustomTemplates(kmkernel->getKMMainWidget() ? kmkernel->getKMMainWidget()->actionCollections() : QList(), this); vlay->addWidget(mWidget); connect(mWidget, &TemplateParser::CustomTemplates::changed, this, &ConfigModuleTab::slotEmitChanged); if (KMKernel::self()) { connect(mWidget, &TemplateParser::CustomTemplates::templatesUpdated, KMKernel::self(), &KMKernel::updatedTemplates); } } void ComposerPage::CustomTemplatesTab::doLoadFromGlobalSettings() { mWidget->load(); } void ComposerPage::CustomTemplatesTab::save() { mWidget->save(); } QString ComposerPage::SubjectTab::helpAnchor() const { return QStringLiteral("configure-composer-subject"); } ComposerPageSubjectTab::ComposerPageSubjectTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); QGroupBox *group = new QGroupBox(i18n("Repl&y Subject Prefixes"), this); QLayout *layout = new QVBoxLayout(group); // row 0: help text: QLabel *label = new QLabel(i18n("Recognize any sequence of the following prefixes\n" "(entries are case-insensitive regular expressions):"), group); label->setWordWrap(true); label->setAlignment(Qt::AlignLeft); // row 1, string list editor: PimCommon::SimpleStringListEditor::ButtonCode buttonCode = static_cast(PimCommon::SimpleStringListEditor::Add | PimCommon::SimpleStringListEditor::Remove | PimCommon::SimpleStringListEditor::Modify); mReplyListEditor = new PimCommon::SimpleStringListEditor(group, buttonCode, i18n("A&dd..."), i18n("Re&move"), i18n("Mod&ify..."), i18n("Enter new reply prefix:")); connect(mReplyListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); // row 2: "replace [...]" check box: mReplaceReplyPrefixCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->replaceReplyPrefixItem()->label(), group); connect(mReplaceReplyPrefixCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); layout->addWidget(label); layout->addWidget(mReplyListEditor); layout->addWidget(mReplaceReplyPrefixCheck); vlay->addWidget(group); group = new QGroupBox(i18n("For&ward Subject Prefixes"), this); layout = new QVBoxLayout(group); // row 0: help text: label = new QLabel(i18n("Recognize any sequence of the following prefixes\n" "(entries are case-insensitive regular expressions):"), group); label->setAlignment(Qt::AlignLeft); label->setWordWrap(true); // row 1: string list editor mForwardListEditor = new PimCommon::SimpleStringListEditor(group, buttonCode, i18n("Add..."), i18n("Remo&ve"), i18n("Modify..."), i18n("Enter new forward prefix:")); connect(mForwardListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); // row 3: "replace [...]" check box: mReplaceForwardPrefixCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->replaceForwardPrefixItem()->label(), group); connect(mReplaceForwardPrefixCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); layout->addWidget(label); layout->addWidget(mForwardListEditor); layout->addWidget(mReplaceForwardPrefixCheck); vlay->addWidget(group); } void ComposerPage::SubjectTab::doLoadFromGlobalSettings() { loadWidget(mReplyListEditor, MessageComposer::MessageComposerSettings::self()->replyPrefixesItem()); loadWidget(mForwardListEditor, MessageComposer::MessageComposerSettings::self()->forwardPrefixesItem()); loadWidget(mReplaceForwardPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceForwardPrefixItem()); loadWidget(mReplaceReplyPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceReplyPrefixItem()); } void ComposerPage::SubjectTab::save() { saveSimpleStringListEditor(mReplyListEditor, MessageComposer::MessageComposerSettings::self()->replyPrefixesItem()); saveSimpleStringListEditor(mForwardListEditor, MessageComposer::MessageComposerSettings::self()->forwardPrefixesItem()); saveCheckBox(mReplaceForwardPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceForwardPrefixItem()); saveCheckBox(mReplaceReplyPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceReplyPrefixItem()); } void ComposerPage::SubjectTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); loadWidget(mReplyListEditor, MessageComposer::MessageComposerSettings::self()->replyPrefixesItem()); loadWidget(mForwardListEditor, MessageComposer::MessageComposerSettings::self()->forwardPrefixesItem()); loadWidget(mReplaceForwardPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceForwardPrefixItem()); loadWidget(mReplaceReplyPrefixCheck, MessageComposer::MessageComposerSettings::self()->replaceReplyPrefixItem()); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); } QString ComposerPage::CharsetTab::helpAnchor() const { return QStringLiteral("configure-composer-charset"); } ComposerPageCharsetTab::ComposerPageCharsetTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); QLabel *label = new QLabel(i18n("This list is checked for every outgoing message " "from the top to the bottom for a charset that " "contains all required characters."), this); label->setWordWrap(true); vlay->addWidget(label); mCharsetListEditor = new PimCommon::SimpleStringListEditor(this, PimCommon::SimpleStringListEditor::All, i18n("A&dd..."), i18n("Remo&ve"), i18n("&Modify..."), i18n("Enter charset:")); mCharsetListEditor->setUpDownAutoRepeat(true); connect(mCharsetListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mCharsetListEditor, 1); mKeepReplyCharsetCheck = new QCheckBox(i18n("&Keep original charset when " "replying or forwarding (if " "possible)"), this); connect(mKeepReplyCharsetCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mKeepReplyCharsetCheck); connect(mCharsetListEditor, &PimCommon::SimpleStringListEditor::aboutToAdd, this, &ComposerPageCharsetTab::slotVerifyCharset); setEnabled(kmkernel); } void ComposerPage::CharsetTab::slotVerifyCharset(QString &charset) { if (charset.isEmpty()) { return; } // KCharsets::codecForName("us-ascii") returns "iso-8859-1" (cf. Bug #49812) // therefore we have to treat this case specially if (charset.toLower() == QLatin1String("us-ascii")) { charset = QStringLiteral("us-ascii"); return; } if (charset.toLower() == QLatin1String("locale")) { charset = QStringLiteral("%1 (locale)") .arg(QString::fromLatin1(kmkernel->networkCodec()->name()).toLower()); return; } bool ok = false; QTextCodec *codec = KCharsets::charsets()->codecForName(charset, ok); if (ok && codec) { charset = QString::fromLatin1(codec->name()).toLower(); return; } KMessageBox::sorry(this, i18n("This charset is not supported.")); charset.clear(); } void ComposerPage::CharsetTab::doLoadOther() { if (!kmkernel) { return; } QStringList charsets = MessageComposer::MessageComposerSettings::preferredCharsets(); QStringList::Iterator end(charsets.end()); for (QStringList::Iterator it = charsets.begin(); it != end; ++it) if ((*it) == QLatin1String("locale")) { QByteArray cset = kmkernel->networkCodec()->name(); cset = cset.toLower(); (*it) = QStringLiteral("%1 (locale)").arg(QString::fromLatin1(cset)); } mCharsetListEditor->setStringList(charsets); loadWidget(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); } void ComposerPage::CharsetTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); mCharsetListEditor->setStringList(MessageComposer::MessageComposerSettings::preferredCharsets()); mKeepReplyCharsetCheck->setChecked(MessageComposer::MessageComposerSettings::forceReplyCharset()); saveCheckBox(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); slotEmitChanged(); } void ComposerPage::CharsetTab::save() { if (!kmkernel) { return; } QStringList charsetList = mCharsetListEditor->stringList(); QStringList::Iterator it = charsetList.begin(); QStringList::Iterator end = charsetList.end(); for (; it != end; ++it) if ((*it).endsWith(QLatin1String("(locale)"))) { (*it) = QStringLiteral("locale"); } MessageComposer::MessageComposerSettings::setPreferredCharsets(charsetList); saveCheckBox(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); } QString ComposerPage::HeadersTab::helpAnchor() const { return QStringLiteral("configure-composer-headers"); } ComposerPageHeadersTab::ComposerPageHeadersTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "Use custom Message-Id suffix" checkbox: mCreateOwnMessageIdCheck = new QCheckBox(i18n("&Use custom message-id suffix"), this); connect(mCreateOwnMessageIdCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mCreateOwnMessageIdCheck); // "Message-Id suffix" line edit and label: QHBoxLayout *hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mMessageIdSuffixEdit = new QLineEdit(this); mMessageIdSuffixEdit->setClearButtonEnabled(true); // only ASCII letters, digits, plus, minus and dots are allowed QRegularExpressionValidator *messageIdSuffixValidator = new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[a-zA-Z0-9+-]+(?:\\.[a-zA-Z0-9+-]+)*")), this); mMessageIdSuffixEdit->setValidator(messageIdSuffixValidator); QLabel *label = new QLabel(i18n("Custom message-&id suffix:"), this); label->setBuddy(mMessageIdSuffixEdit); label->setEnabled(false); // since !mCreateOwnMessageIdCheck->isChecked() mMessageIdSuffixEdit->setEnabled(false); hlay->addWidget(label); hlay->addWidget(mMessageIdSuffixEdit, 1); connect(mCreateOwnMessageIdCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mCreateOwnMessageIdCheck, &QAbstractButton::toggled, mMessageIdSuffixEdit, &QWidget::setEnabled); connect(mMessageIdSuffixEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged); // horizontal rule and "custom header fields" label: vlay->addWidget(new KSeparator(Qt::Horizontal, this)); vlay->addWidget(new QLabel(i18n("Define custom mime header fields:"), this)); // "custom header fields" listbox: QGridLayout *glay = new QGridLayout(); // inherits spacing vlay->addLayout(glay); glay->setRowStretch(2, 1); glay->setColumnStretch(1, 1); mHeaderList = new ListView(this); mHeaderList->setHeaderLabels(QStringList() << i18nc("@title:column Name of the mime header.", "Name") << i18nc("@title:column Value of the mimeheader.", "Value")); mHeaderList->setSortingEnabled(false); connect(mHeaderList, &QTreeWidget::currentItemChanged, this, &ComposerPageHeadersTab::slotMimeHeaderSelectionChanged); connect(mHeaderList, &ListView::addHeader, this, &ComposerPageHeadersTab::slotNewMimeHeader); connect(mHeaderList, &ListView::removeHeader, this, &ComposerPageHeadersTab::slotRemoveMimeHeader); glay->addWidget(mHeaderList, 0, 0, 3, 2); // "new" and "remove" buttons: QPushButton *button = new QPushButton(i18nc("@action:button Add new mime header field.", "Ne&w"), this); connect(button, &QAbstractButton::clicked, this, &ComposerPageHeadersTab::slotNewMimeHeader); button->setAutoDefault(false); glay->addWidget(button, 0, 2); mRemoveHeaderButton = new QPushButton(i18n("Re&move"), this); connect(mRemoveHeaderButton, &QAbstractButton::clicked, this, &ComposerPageHeadersTab::slotRemoveMimeHeader); button->setAutoDefault(false); glay->addWidget(mRemoveHeaderButton, 1, 2); // "name" and "value" line edits and labels: mTagNameEdit = new QLineEdit(this); mTagNameEdit->setClearButtonEnabled(true); mTagNameEdit->setEnabled(false); mTagNameLabel = new QLabel(i18nc("@label:textbox Name of the mime header.", "&Name:"), this); mTagNameLabel->setBuddy(mTagNameEdit); mTagNameLabel->setEnabled(false); glay->addWidget(mTagNameLabel, 3, 0); glay->addWidget(mTagNameEdit, 3, 1); connect(mTagNameEdit, &QLineEdit::textChanged, this, &ComposerPageHeadersTab::slotMimeHeaderNameChanged); mTagValueEdit = new QLineEdit(this); mTagValueEdit->setClearButtonEnabled(true); mTagValueEdit->setEnabled(false); mTagValueLabel = new QLabel(i18n("&Value:"), this); mTagValueLabel->setBuddy(mTagValueEdit); mTagValueLabel->setEnabled(false); glay->addWidget(mTagValueLabel, 4, 0); glay->addWidget(mTagValueEdit, 4, 1); connect(mTagValueEdit, &QLineEdit::textChanged, this, &ComposerPageHeadersTab::slotMimeHeaderValueChanged); } void ComposerPage::HeadersTab::slotMimeHeaderSelectionChanged() { mEmitChanges = false; QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { mTagNameEdit->setText(item->text(0)); mTagValueEdit->setText(item->text(1)); } else { mTagNameEdit->clear(); mTagValueEdit->clear(); } mRemoveHeaderButton->setEnabled(item); mTagNameEdit->setEnabled(item); mTagValueEdit->setEnabled(item); mTagNameLabel->setEnabled(item); mTagValueLabel->setEnabled(item); mEmitChanges = true; } void ComposerPage::HeadersTab::slotMimeHeaderNameChanged(const QString &text) { // is called on ::setup(), when clearing the line edits. So be // prepared to not find a selection: QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { item->setText(0, text); } slotEmitChanged(); } void ComposerPage::HeadersTab::slotMimeHeaderValueChanged(const QString &text) { // is called on ::setup(), when clearing the line edits. So be // prepared to not find a selection: QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { item->setText(1, text); } slotEmitChanged(); } void ComposerPage::HeadersTab::slotNewMimeHeader() { QTreeWidgetItem *listItem = new QTreeWidgetItem(mHeaderList); mHeaderList->setCurrentItem(listItem); slotEmitChanged(); } void ComposerPage::HeadersTab::slotRemoveMimeHeader() { // calling this w/o selection is a programming error: QTreeWidgetItem *item = mHeaderList->currentItem(); if (!item) { qCDebug(KMAIL_LOG) << "==================================================" << "Error: Remove button was pressed although no custom header was selected\n" << "==================================================\n"; return; } QTreeWidgetItem *below = mHeaderList->itemBelow(item); if (below) { qCDebug(KMAIL_LOG) << "below"; mHeaderList->setCurrentItem(below); delete item; item = nullptr; } else if (mHeaderList->topLevelItemCount() > 0) { delete item; item = nullptr; mHeaderList->setCurrentItem( mHeaderList->topLevelItem(mHeaderList->topLevelItemCount() - 1) ); } slotEmitChanged(); } void ComposerPage::HeadersTab::doLoadOther() { mMessageIdSuffixEdit->setText(MessageComposer::MessageComposerSettings::customMsgIDSuffix()); const bool state = (!MessageComposer::MessageComposerSettings::customMsgIDSuffix().isEmpty() && MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()); mCreateOwnMessageIdCheck->setChecked(state); mHeaderList->clear(); mTagNameEdit->clear(); mTagValueEdit->clear(); QTreeWidgetItem *item = nullptr; const int count = KMailSettings::self()->customMessageHeadersCount(); for (int i = 0; i < count; ++i) { KConfigGroup config(KMKernel::self()->config(), QLatin1String("Mime #") + QString::number(i)); const QString name = config.readEntry("name"); const QString value = config.readEntry("value"); if (!name.isEmpty()) { item = new QTreeWidgetItem(mHeaderList, item); item->setText(0, name); item->setText(1, value); } } if (mHeaderList->topLevelItemCount() > 0) { mHeaderList->setCurrentItem(mHeaderList->topLevelItem(0)); } else { // disable the "Remove" button mRemoveHeaderButton->setEnabled(false); } } void ComposerPage::HeadersTab::save() { MessageComposer::MessageComposerSettings::self()->setCustomMsgIDSuffix(mMessageIdSuffixEdit->text()); MessageComposer::MessageComposerSettings::self()->setUseCustomMessageIdSuffix(mCreateOwnMessageIdCheck->isChecked()); //Clean config const int oldHeadersCount = KMailSettings::self()->customMessageHeadersCount(); for (int i = 0; i < oldHeadersCount; ++i) { const QString groupMimeName = QStringLiteral("Mime #%1").arg(i); if (KMKernel::self()->config()->hasGroup(groupMimeName)) { KConfigGroup config(KMKernel::self()->config(), groupMimeName); config.deleteGroup(); } } int numValidEntries = 0; QTreeWidgetItem *item = nullptr; const int numberOfEntry = mHeaderList->topLevelItemCount(); for (int i = 0; i < numberOfEntry; ++i) { item = mHeaderList->topLevelItem(i); if (!item->text(0).isEmpty()) { KConfigGroup config(KMKernel::self()->config(), QStringLiteral("Mime #%1").arg(numValidEntries)); config.writeEntry("name", item->text(0)); config.writeEntry("value", item->text(1)); numValidEntries++; } } KMailSettings::self()->setCustomMessageHeadersCount(numValidEntries); } void ComposerPage::HeadersTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); const QString messageIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix(); const bool useCustomMessageIdSuffix = MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix(); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); mMessageIdSuffixEdit->setText(messageIdSuffix); const bool state = (!messageIdSuffix.isEmpty() && useCustomMessageIdSuffix); mCreateOwnMessageIdCheck->setChecked(state); mHeaderList->clear(); mTagNameEdit->clear(); mTagValueEdit->clear(); // disable the "Remove" button mRemoveHeaderButton->setEnabled(false); } QString ComposerPage::AttachmentsTab::helpAnchor() const { return QStringLiteral("configure-composer-attachments"); } ComposerPageAttachmentsTab::ComposerPageAttachmentsTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "Outlook compatible attachment naming" check box mOutlookCompatibleCheck = new QCheckBox(i18n("Outlook-compatible attachment naming"), this); mOutlookCompatibleCheck->setChecked(false); mOutlookCompatibleCheck->setToolTip(i18n( "Turn this option on to make Outlook(tm) understand attachment names " "containing non-English characters")); connect(mOutlookCompatibleCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mOutlookCompatibleCheck, &QAbstractButton::clicked, this, &ComposerPageAttachmentsTab::slotOutlookCompatibleClicked); vlay->addWidget(mOutlookCompatibleCheck); vlay->addSpacing(5); // "Enable detection of missing attachments" check box mMissingAttachmentDetectionCheck = new QCheckBox(i18n("E&nable detection of missing attachments"), this); mMissingAttachmentDetectionCheck->setChecked(true); connect(mMissingAttachmentDetectionCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mMissingAttachmentDetectionCheck); // "Attachment key words" label and string list editor QLabel *label = new QLabel(i18n("Recognize any of the following key words as " "intention to attach a file:"), this); label->setAlignment(Qt::AlignLeft); label->setWordWrap(true); vlay->addWidget(label); PimCommon::SimpleStringListEditor::ButtonCode buttonCode = static_cast(PimCommon::SimpleStringListEditor::Add | PimCommon::SimpleStringListEditor::Remove | PimCommon::SimpleStringListEditor::Modify); mAttachWordsListEditor = new PimCommon::SimpleStringListEditor(this, buttonCode, i18n("A&dd..."), i18n("Re&move"), i18n("Mod&ify..."), i18n("Enter new key word:")); connect(mAttachWordsListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mAttachWordsListEditor); connect(mMissingAttachmentDetectionCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mMissingAttachmentDetectionCheck, &QAbstractButton::toggled, mAttachWordsListEditor, &QWidget::setEnabled); QHBoxLayout *layAttachment = new QHBoxLayout; label = new QLabel(i18n("Offer to share for files larger than:"), this); label->setAlignment(Qt::AlignLeft); layAttachment->addWidget(label); label->hide(); mMaximumAttachmentSize = new QSpinBox(this); mMaximumAttachmentSize->setRange(-1, 99999); mMaximumAttachmentSize->setSingleStep(100); mMaximumAttachmentSize->setSuffix(i18nc("spinbox suffix: unit for kilobyte", " kB")); mMaximumAttachmentSize->hide(); connect(mMaximumAttachmentSize, SIGNAL(valueChanged(int)), this, SLOT(slotEmitChanged())); mMaximumAttachmentSize->setSpecialValueText(i18n("No limit")); layAttachment->addWidget(mMaximumAttachmentSize); vlay->addLayout(layAttachment); } void ComposerPage::AttachmentsTab::doLoadFromGlobalSettings() { loadWidget(mOutlookCompatibleCheck, MessageComposer::MessageComposerSettings::self()->outlookCompatibleAttachmentsItem()); loadWidget(mMissingAttachmentDetectionCheck, KMailSettings::self()->showForgottenAttachmentWarningItem()); loadWidget(mAttachWordsListEditor, KMailSettings::self()->attachmentKeywordsItem()); const int maximumAttachmentSize(MessageCore::MessageCoreSettings::self()->maximumAttachmentSize()); mMaximumAttachmentSize->setValue(maximumAttachmentSize == -1 ? -1 : MessageCore::MessageCoreSettings::self()->maximumAttachmentSize() / 1024); } void ComposerPage::AttachmentsTab::save() { saveCheckBox(mOutlookCompatibleCheck, MessageComposer::MessageComposerSettings::self()->outlookCompatibleAttachmentsItem()); saveCheckBox(mMissingAttachmentDetectionCheck, KMailSettings::self()->showForgottenAttachmentWarningItem()); saveSimpleStringListEditor(mAttachWordsListEditor, KMailSettings::self()->attachmentKeywordsItem()); KMime::setUseOutlookAttachmentEncoding(mOutlookCompatibleCheck->isChecked()); const int maximumAttachmentSize(mMaximumAttachmentSize->value()); MessageCore::MessageCoreSettings::self()->setMaximumAttachmentSize(maximumAttachmentSize == -1 ? -1 : maximumAttachmentSize * 1024); } void ComposerPageAttachmentsTab::slotOutlookCompatibleClicked() { if (mOutlookCompatibleCheck->isChecked()) { KMessageBox::information(nullptr, i18n("You have chosen to " "encode attachment names containing non-English characters in a way that " "is understood by Outlook(tm) and other mail clients that do not " "support standard-compliant encoded attachment names.\n" "Note that KMail may create non-standard compliant messages, " "and consequently it is possible that your messages will not be " "understood by standard-compliant mail clients; so, unless you have no " "other choice, you should not enable this option.")); } } ComposerPageAutoCorrectionTab::ComposerPageAutoCorrectionTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setSpacing(0); vlay->setMargin(0); autocorrectionWidget = new PimCommon::AutoCorrectionWidget(this); if (KMKernel::self()) { autocorrectionWidget->setAutoCorrection(KMKernel::self()->composerAutoCorrection()); } vlay->addWidget(autocorrectionWidget); setLayout(vlay); connect(autocorrectionWidget, &PimCommon::AutoCorrectionWidget::changed, this, &ConfigModuleTab::slotEmitChanged); } QString ComposerPageAutoCorrectionTab::helpAnchor() const { return QStringLiteral("configure-autocorrection"); } void ComposerPageAutoCorrectionTab::save() { autocorrectionWidget->writeConfig(); } void ComposerPageAutoCorrectionTab::doLoadFromGlobalSettings() { autocorrectionWidget->loadConfig(); } void ComposerPageAutoCorrectionTab::doResetToDefaultsOther() { autocorrectionWidget->resetToDefault(); } ComposerPageAutoImageResizeTab::ComposerPageAutoImageResizeTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setSpacing(0); vlay->setMargin(0); autoResizeWidget = new MessageComposer::ImageScalingWidget(this); vlay->addWidget(autoResizeWidget); setLayout(vlay); connect(autoResizeWidget, &MessageComposer::ImageScalingWidget::changed, this, &ConfigModuleTab::slotEmitChanged); } QString ComposerPageAutoImageResizeTab::helpAnchor() const { return QStringLiteral("configure-image-resize"); } void ComposerPageAutoImageResizeTab::save() { autoResizeWidget->writeConfig(); } void ComposerPageAutoImageResizeTab::doLoadFromGlobalSettings() { autoResizeWidget->loadConfig(); } void ComposerPageAutoImageResizeTab::doResetToDefaultsOther() { autoResizeWidget->resetToDefault(); } diff --git a/src/helper_p.h b/src/helper_p.h index 3048b9ab0..fb4b67575 100644 --- a/src/helper_p.h +++ b/src/helper_p.h @@ -1,40 +1,81 @@ /* Copyright (c) 2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _HELPER_H #define _HELPER_H #include #if QT_VERSION < QT_VERSION_CHECK(5,7,0) +template +struct QNonConstOverload +{ + template + Q_DECL_CONSTEXPR auto operator()(R (T::*ptr)(Args...)) const Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } + + template + static Q_DECL_CONSTEXPR auto of(R (T::*ptr)(Args...)) Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } +}; + +template +struct QConstOverload +{ + template + Q_DECL_CONSTEXPR auto operator()(R (T::*ptr)(Args...) const) const Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } + + template + static Q_DECL_CONSTEXPR auto of(R (T::*ptr)(Args...) const) Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } +}; + +template +struct QOverload : QConstOverload, QNonConstOverload +{ + using QConstOverload::of; + using QConstOverload::operator(); + using QNonConstOverload::of; + using QNonConstOverload::operator(); + + template + Q_DECL_CONSTEXPR auto operator()(R (*ptr)(Args...)) const Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } + + template + static Q_DECL_CONSTEXPR auto of(R (*ptr)(Args...)) Q_DECL_NOTHROW -> decltype(ptr) + { return ptr; } +}; + namespace QtPrivate { template struct QAddConst { typedef const T Type; }; } // this adds const to non-const objects (like std::as_const) template Q_DECL_CONSTEXPR typename QtPrivate::QAddConst::Type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } // prevent rvalue arguments: template void qAsConst(const T &&) Q_DECL_EQ_DELETE; #endif #endif diff --git a/src/searchdialog/searchwindow.cpp b/src/searchdialog/searchwindow.cpp index 7e39c45bf..eb1e40cc0 100644 --- a/src/searchdialog/searchwindow.cpp +++ b/src/searchdialog/searchwindow.cpp @@ -1,975 +1,975 @@ /* * kmail: KDE mail client * Copyright (c) 1996-1998 Stefan Taferner * Copyright (c) 2001 Aaron J. Seigo * Copyright (c) 2010 Till Adam * Copyright (C) 2011-2017 Laurent Montel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "incompleteindexdialog.h" #include "searchwindow.h" #include "helper_p.h" #include "MailCommon/FolderRequester" #include "kmcommands.h" #include "kmmainwidget.h" #include "MailCommon/MailKernel" #include "MailCommon/SearchPatternEdit" #include "searchdescriptionattribute.h" #include "MailCommon/FolderTreeView" #include "kmsearchmessagemodel.h" #include "kmsearchfilterproxymodel.h" #include "searchpatternwarning.h" #include "PimCommon/SelectMultiCollectionDialog" #include #include #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIM; using namespace MailCommon; using namespace KMail; SearchWindow::SearchWindow(KMMainWidget *widget, const Akonadi::Collection &collection) : QDialog(nullptr), mCloseRequested(false), mSortColumn(0), mSortOrder(Qt::AscendingOrder), mSearchJob(nullptr), mResultModel(nullptr), mKMMainWidget(widget), mAkonadiStandardAction(nullptr) { setWindowTitle(i18n("Find Messages")); KWindowSystem::setIcons(winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small), IconSize(KIconLoader::Small))); QVBoxLayout *mainLayout = new QVBoxLayout(this); QWidget *topWidget = new QWidget; QVBoxLayout *lay = new QVBoxLayout; lay->setMargin(0); topWidget->setLayout(lay); mSearchPatternWidget = new SearchPatternWarning; lay->addWidget(mSearchPatternWidget); mainLayout->addWidget(topWidget); QWidget *searchWidget = new QWidget(this); mUi.setupUi(searchWidget); lay->addWidget(searchWidget); mStartSearchGuiItem = KGuiItem(i18nc("@action:button Search for messages", "&Search"), QStringLiteral("edit-find")); mStopSearchGuiItem = KStandardGuiItem::stop(); mSearchButton = new QPushButton; KGuiItem::assign(mSearchButton, mStartSearchGuiItem); mUi.mButtonBox->addButton(mSearchButton, QDialogButtonBox::ActionRole); connect(mUi.mButtonBox, &QDialogButtonBox::rejected, this, &SearchWindow::slotClose); searchWidget->layout()->setMargin(0); mUi.mCbxFolders->setMustBeReadWrite(false); mUi.mCbxFolders->setNotAllowToCreateNewFolder(true); activateFolder(collection); connect(mUi.mPatternEdit, &KMail::KMailSearchPatternEdit::returnPressed, this, &SearchWindow::slotSearch); // enable/disable widgets depending on radio buttons: connect(mUi.mChkbxAllFolders, &QRadioButton::toggled, this, &SearchWindow::setEnabledSearchButton); mUi.mLbxMatches->setXmlGuiClient(mKMMainWidget->guiClient()); /* Default is to sort by date. TODO: Unfortunately this sorts *while* inserting, which looks rather strange - the user cannot read the results so far as they are constantly re-sorted --dnaber Sorting is now disabled when a search is started and reenabled when it stops. Items are appended to the list. This not only solves the above problem, but speeds searches with many hits up considerably. - till TODO: subclass QTreeWidgetItem and do proper (and performant) compare functions */ mUi.mLbxMatches->setSortingEnabled(true); connect(mUi.mLbxMatches, &Akonadi::EntityTreeView::customContextMenuRequested, this, &SearchWindow::slotContextMenuRequested); connect(mUi.mLbxMatches, SIGNAL(doubleClicked(Akonadi::Item)), this, SLOT(slotViewMsg(Akonadi::Item))); connect(mUi.mLbxMatches, SIGNAL(currentChanged(Akonadi::Item)), this, SLOT(slotCurrentChanged(Akonadi::Item))); connect(mUi.selectMultipleFolders, &QPushButton::clicked, this, &SearchWindow::slotSelectMultipleFolders); connect(KMKernel::self()->folderCollectionMonitor(), &Akonadi::Monitor::collectionStatisticsChanged, this, &SearchWindow::updateCollectionStatistic); connect(mUi.mSearchFolderEdt, &KLineEdit::textChanged, this, &SearchWindow::scheduleRename); connect(&mRenameTimer, &QTimer::timeout, this, &SearchWindow::renameSearchFolder); connect(mUi.mSearchFolderOpenBtn, &QPushButton::clicked, this, &SearchWindow::openSearchFolder); connect(mUi.mSearchResultOpenBtn, &QPushButton::clicked, this, &SearchWindow::slotViewSelectedMsg); const int mainWidth = KMailSettings::self()->searchWidgetWidth(); const int mainHeight = KMailSettings::self()->searchWidgetHeight(); if (mainWidth || mainHeight) { resize(mainWidth, mainHeight); } connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); connect(this, &SearchWindow::finished, this, &SearchWindow::deleteLater); connect(mUi.mButtonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &SearchWindow::slotClose); // give focus to the value field of the first search rule KLineEdit *r = mUi.mPatternEdit->findChild(QStringLiteral("regExpLineEdit")); if (r) { r->setFocus(); } else { qCDebug(KMAIL_LOG) << "SearchWindow: regExpLineEdit not found"; } //set up actions KActionCollection *ac = actionCollection(); mReplyAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("&Reply..."), this); actionCollection()->addAction(QStringLiteral("search_reply"), mReplyAction); connect(mReplyAction, &QAction::triggered, this, &SearchWindow::slotReplyToMsg); mReplyAllAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n("Reply to &All..."), this); actionCollection()->addAction(QStringLiteral("search_reply_all"), mReplyAllAction); connect(mReplyAllAction, &QAction::triggered, this, &SearchWindow::slotReplyAllToMsg); mReplyListAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-list")), i18n("Reply to Mailing-&List..."), this); actionCollection()->addAction(QStringLiteral("search_reply_list"), mReplyListAction); connect(mReplyListAction, &QAction::triggered, this, &SearchWindow::slotReplyListToMsg); mForwardActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->", "&Forward"), this); actionCollection()->addAction(QStringLiteral("search_message_forward"), mForwardActionMenu); connect(mForwardActionMenu, &KActionMenu::triggered, this, &SearchWindow::slotForwardMsg); mForwardInlineAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Forward message inline.", "&Inline..."), this); actionCollection()->addAction(QStringLiteral("search_message_forward_inline"), mForwardInlineAction); connect(mForwardInlineAction, &QAction::triggered, this, &SearchWindow::slotForwardMsg); mForwardAttachedAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->Forward->", "As &Attachment..."), this); actionCollection()->addAction(QStringLiteral("search_message_forward_as_attachment"), mForwardAttachedAction); connect(mForwardAttachedAction, &QAction::triggered, this, &SearchWindow::slotForwardAttachedMsg); if (KMailSettings::self()->forwardingInlineByDefault()) { mForwardActionMenu->addAction(mForwardInlineAction); mForwardActionMenu->addAction(mForwardAttachedAction); } else { mForwardActionMenu->addAction(mForwardAttachedAction); mForwardActionMenu->addAction(mForwardInlineAction); } mSaveAsAction = actionCollection()->addAction(KStandardAction::SaveAs, QStringLiteral("search_file_save_as"), this, SLOT(slotSaveMsg())); mSaveAtchAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save Attachments..."), this); actionCollection()->addAction(QStringLiteral("search_save_attachments"), mSaveAtchAction); connect(mSaveAtchAction, &QAction::triggered, this, &SearchWindow::slotSaveAttachments); mPrintAction = actionCollection()->addAction(KStandardAction::Print, QStringLiteral("search_print"), this, SLOT(slotPrintMsg())); mClearAction = new QAction(i18n("Clear Selection"), this); actionCollection()->addAction(QStringLiteral("search_clear_selection"), mClearAction); connect(mClearAction, &QAction::triggered, this, &SearchWindow::slotClearSelection); mJumpToFolderAction = new QAction(i18n("Jump to original folder"), this); actionCollection()->addAction(QStringLiteral("search_jump_folder"), mJumpToFolderAction); connect(mJumpToFolderAction, &QAction::triggered, this, &SearchWindow::slotJumpToFolder); connect(mUi.mCbxFolders, &MailCommon::FolderRequester::folderChanged, this, &SearchWindow::slotFolderActivated); ac->addAssociatedWidget(this); const QList actList = ac->actions(); for (QAction *action : actList) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } } SearchWindow::~SearchWindow() { if (mResultModel) { if (mUi.mLbxMatches->columnWidth(0) > 0) { KMailSettings::self()->setCollectionWidth(mUi.mLbxMatches->columnWidth(0)); } if (mUi.mLbxMatches->columnWidth(1) > 0) { KMailSettings::self()->setSubjectWidth(mUi.mLbxMatches->columnWidth(1)); } if (mUi.mLbxMatches->columnWidth(2) > 0) { KMailSettings::self()->setSenderWidth(mUi.mLbxMatches->columnWidth(2)); } if (mUi.mLbxMatches->columnWidth(3) > 0) { KMailSettings::self()->setReceiverWidth(mUi.mLbxMatches->columnWidth(3)); } if (mUi.mLbxMatches->columnWidth(4) > 0) { KMailSettings::self()->setDateWidth(mUi.mLbxMatches->columnWidth(4)); } if (mUi.mLbxMatches->columnWidth(5) > 0) { KMailSettings::self()->setFolderWidth(mUi.mLbxMatches->columnWidth(5)); } KMailSettings::self()->setSearchWidgetWidth(width()); KMailSettings::self()->setSearchWidgetHeight(height()); KMailSettings::self()->requestSync(); mResultModel->deleteLater(); } } void SearchWindow::createSearchModel() { if (mResultModel) { mResultModel->deleteLater(); } mResultModel = new KMSearchMessageModel(this); mResultModel->setCollection(mFolder); KMSearchFilterProxyModel *sortproxy = new KMSearchFilterProxyModel(mResultModel); sortproxy->setSourceModel(mResultModel); mUi.mLbxMatches->setModel(sortproxy); mUi.mLbxMatches->setColumnWidth(0, KMailSettings::self()->collectionWidth()); mUi.mLbxMatches->setColumnWidth(1, KMailSettings::self()->subjectWidth()); mUi.mLbxMatches->setColumnWidth(2, KMailSettings::self()->senderWidth()); mUi.mLbxMatches->setColumnWidth(3, KMailSettings::self()->receiverWidth()); mUi.mLbxMatches->setColumnWidth(4, KMailSettings::self()->dateWidth()); mUi.mLbxMatches->setColumnWidth(5, KMailSettings::self()->folderWidth()); mUi.mLbxMatches->setColumnHidden(6, true); mUi.mLbxMatches->setColumnHidden(7, true); mUi.mLbxMatches->header()->setSortIndicator(2, Qt::DescendingOrder); mUi.mLbxMatches->header()->setStretchLastSection(false); mUi.mLbxMatches->header()->restoreState(mHeaderState); //mUi.mLbxMatches->header()->setResizeMode( 3, QHeaderView::Stretch ); if (!mAkonadiStandardAction) { mAkonadiStandardAction = new Akonadi::StandardMailActionManager(actionCollection(), this); } mAkonadiStandardAction->setItemSelectionModel(mUi.mLbxMatches->selectionModel()); mAkonadiStandardAction->setCollectionSelectionModel(mKMMainWidget->folderTreeView()->selectionModel()); } void SearchWindow::setEnabledSearchButton(bool) { //Make sure that button is enable //Before when we selected a folder == "Local Folder" as that it was not a folder //search button was disable, and when we select "Search in all local folder" //Search button was never enabled :( mSearchButton->setEnabled(true); } void SearchWindow::updateCollectionStatistic(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistic) { QString genMsg; if (id == mFolder.id()) { genMsg = i18np("%1 match", "%1 matches", statistic.count()); } mUi.mStatusLbl->setText(genMsg); } void SearchWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape && mSearchJob) { slotStop(); return; } QDialog::keyPressEvent(event); } void SearchWindow::slotFolderActivated() { mUi.mChkbxSpecificFolders->setChecked(true); } void SearchWindow::activateFolder(const Akonadi::Collection &collection) { mUi.mChkbxSpecificFolders->setChecked(true); mSearchPattern.clear(); bool currentFolderIsSearchFolder = false; if (!collection.hasAttribute()) { // it's not a search folder, make a new search mSearchPattern.append(SearchRule::createInstance("Subject")); mUi.mCbxFolders->setCollection(collection); } else { // it's a search folder if (collection.hasAttribute()) { currentFolderIsSearchFolder = true; // FIXME is there a better way to tell? const Akonadi::SearchDescriptionAttribute *searchDescription = collection.attribute(); mSearchPattern.deserialize(searchDescription->description()); const QList lst = searchDescription->listCollection(); if (!lst.isEmpty()) { mUi.mChkMultiFolders->setChecked(true); mCollectionId.clear(); for (Akonadi::Collection::Id col : lst) { mCollectionId.append(Akonadi::Collection(col)); } } else { const Akonadi::Collection col = searchDescription->baseCollection(); if (col.isValid()) { mUi.mChkbxSpecificFolders->setChecked(true); mUi.mCbxFolders->setCollection(col); mUi.mChkSubFolders->setChecked(searchDescription->recursive()); } else { mUi.mChkbxAllFolders->setChecked(true); mUi.mChkSubFolders->setChecked(searchDescription->recursive()); } } } else { // it's a search folder, but not one of ours, warn the user that we can't edit it // FIXME show results, but disable edit GUI qCWarning(KMAIL_LOG) << "This search was not created with KMail. It cannot be edited within it."; mSearchPattern.clear(); } } mUi.mPatternEdit->setSearchPattern(&mSearchPattern); if (currentFolderIsSearchFolder) { mFolder = collection; mUi.mSearchFolderEdt->setText(collection.name()); createSearchModel(); } else if (mUi.mSearchFolderEdt->text().isEmpty()) { mUi.mSearchFolderEdt->setText(i18n("Last Search")); // find last search and reuse it if possible mFolder = CommonKernel->collectionFromId(KMailSettings::lastSearchCollectionId()); // when the last folder got renamed, create a new one if (mFolder.isValid() && mFolder.name() != mUi.mSearchFolderEdt->text()) { mFolder = Akonadi::Collection(); } } } void SearchWindow::slotSearch() { if (mFolder.isValid()) { doSearch(); return; } //We're going to try to create a new search folder, let's ensure first the name is not yet used. //Fetch all search collections Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection(1), Akonadi::CollectionFetchJob::FirstLevel); connect(fetchJob, &Akonadi::CollectionFetchJob::result, this, &SearchWindow::slotSearchCollectionsFetched); } void SearchWindow::slotSearchCollectionsFetched(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << job->errorString(); } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); const Akonadi::Collection::List lstCol = fetchJob->collections(); for (const Akonadi::Collection &col : lstCol) { if (col.name() == mUi.mSearchFolderEdt->text()) { mFolder = col; } } doSearch(); } void SearchWindow::doSearch() { mSearchPatternWidget->hideWarningPattern(); if (mUi.mSearchFolderEdt->text().isEmpty()) { mUi.mSearchFolderEdt->setText(i18n("Last Search")); } if (mResultModel) { mHeaderState = mUi.mLbxMatches->header()->saveState(); } mUi.mLbxMatches->setModel(nullptr); mSortColumn = mUi.mLbxMatches->header()->sortIndicatorSection(); mSortOrder = mUi.mLbxMatches->header()->sortIndicatorOrder(); mUi.mLbxMatches->setSortingEnabled(false); if (mSearchJob) { mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; } mUi.mSearchFolderEdt->setEnabled(false); QVector searchCollections; bool recursive = false; if (mUi.mChkbxSpecificFolders->isChecked()) { const Akonadi::Collection col = mUi.mCbxFolders->collection(); if (!col.isValid()) { mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You did not selected a valid folder.")); mUi.mSearchFolderEdt->setEnabled(true); return; } searchCollections << col; if (mUi.mChkSubFolders->isChecked()) { recursive = true; } } else if (mUi.mChkMultiFolders->isChecked()) { if (!mSelectMultiCollectionDialog) { if (mCollectionId.isEmpty()) { mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to select collections.")); return; } } else { mCollectionId = mSelectMultiCollectionDialog->selectedCollection(); } searchCollections.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { searchCollections << col; } if (searchCollections.isEmpty()) { mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to select collections.")); mQuery = Akonadi::SearchQuery(); return; } } mUi.mPatternEdit->updateSearchPattern(); SearchPattern searchPattern(mSearchPattern); searchPattern.purify(); MailCommon::SearchPattern::SparqlQueryError queryError = searchPattern.asAkonadiQuery(mQuery); switch (queryError) { case MailCommon::SearchPattern::NoError: break; case MailCommon::SearchPattern::MissingCheck: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to define condition.")); mQuery = Akonadi::SearchQuery(); return; case MailCommon::SearchPattern::FolderEmptyOrNotIndexed: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("All folders selected are empty or were not indexed.")); mQuery = Akonadi::SearchQuery(); return; case MailCommon::SearchPattern::EmptyResult: mUi.mSearchFolderEdt->setEnabled(true); mQuery = Akonadi::SearchQuery(); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to add conditions.")); return; case MailCommon::SearchPattern::NotEnoughCharacters: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("Contains condition cannot be used with a number of characters inferior to 4.")); mQuery = Akonadi::SearchQuery(); return; } mSearchPatternWidget->hideWarningPattern(); qCDebug(KMAIL_LOG) << mQuery.toJSON(); mUi.mSearchFolderOpenBtn->setEnabled(true); const QVector unindexedCollections = checkIncompleteIndex(searchCollections, recursive); if (!unindexedCollections.isEmpty()) { QScopedPointer dlg(new IncompleteIndexDialog(unindexedCollections)); dlg->exec(); } if (!mFolder.isValid()) { qCDebug(KMAIL_LOG) << " create new folder " << mUi.mSearchFolderEdt->text(); Akonadi::SearchCreateJob *searchJob = new Akonadi::SearchCreateJob(mUi.mSearchFolderEdt->text(), mQuery, this); searchJob->setSearchMimeTypes(QStringList() << QStringLiteral("message/rfc822")); searchJob->setSearchCollections(searchCollections); searchJob->setRecursive(recursive); searchJob->setRemoteSearchEnabled(false); mSearchJob = searchJob; } else { qCDebug(KMAIL_LOG) << " use existing folder " << mFolder.id(); Akonadi::PersistentSearchAttribute *attribute = new Akonadi::PersistentSearchAttribute(); mFolder.setContentMimeTypes(QStringList() << QStringLiteral("message/rfc822")); attribute->setQueryString(QString::fromLatin1(mQuery.toJSON())); attribute->setQueryCollections(searchCollections); attribute->setRecursive(recursive); attribute->setRemoteSearchEnabled(false); mFolder.addAttribute(attribute); mSearchJob = new Akonadi::CollectionModifyJob(mFolder, this); } connect(mSearchJob, &Akonadi::CollectionModifyJob::result, this, &SearchWindow::searchDone); mUi.mProgressIndicator->start(); enableGUI(); mUi.mStatusLbl->setText(i18n("Searching...")); } void SearchWindow::searchDone(KJob *job) { Q_ASSERT(job == mSearchJob); QMetaObject::invokeMethod(this, "enableGUI", Qt::QueuedConnection); mUi.mProgressIndicator->stop(); if (job->error()) { qCDebug(KMAIL_LOG) << job->errorString(); KMessageBox::sorry(this, i18n("Cannot get search result. %1", job->errorString())); if (mSearchJob) { mSearchJob = nullptr; } enableGUI(); mUi.mSearchFolderEdt->setEnabled(true); mUi.mStatusLbl->setText(i18n("Search failed.")); } else { if (Akonadi::SearchCreateJob *searchJob = qobject_cast(mSearchJob)) { mFolder = searchJob->createdCollection(); } else if (Akonadi::CollectionModifyJob *modifyJob = qobject_cast(mSearchJob)) { mFolder = modifyJob->collection(); } /// TODO: cope better with cases where this fails Q_ASSERT(mFolder.isValid()); Q_ASSERT(mFolder.hasAttribute()); KMailSettings::setLastSearchCollectionId(mFolder.id()); KMailSettings::self()->save(); KMailSettings::self()->requestSync(); // store the kmail specific serialization of the search in an attribute on // the server, for easy retrieval when editing it again const QByteArray search = mSearchPattern.serialize(); Q_ASSERT(!search.isEmpty()); Akonadi::SearchDescriptionAttribute *searchDescription = mFolder.attribute(Akonadi::Collection::AddIfMissing); searchDescription->setDescription(search); if (mUi.mChkMultiFolders->isChecked()) { searchDescription->setBaseCollection(Akonadi::Collection()); QList lst; lst.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { lst << col.id(); } searchDescription->setListCollection(lst); } else if (mUi.mChkbxSpecificFolders->isChecked()) { const Akonadi::Collection collection = mUi.mCbxFolders->collection(); searchDescription->setBaseCollection(collection); } else { searchDescription->setBaseCollection(Akonadi::Collection()); } searchDescription->setRecursive(mUi.mChkSubFolders->isChecked()); new Akonadi::CollectionModifyJob(mFolder, this); mSearchJob = nullptr; Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(mFolder, Akonadi::CollectionFetchJob::Base, this); fetch->fetchScope().setIncludeStatistics(true); - connect(fetch, SIGNAL(result(KJob*)), this, SLOT(slotCollectionStatisticsRetrieved(KJob*))); + connect(fetch, &KJob::result, this, &SearchWindow::slotCollectionStatisticsRetrieved); mUi.mStatusLbl->setText(i18n("Search complete.")); createSearchModel(); if (mCloseRequested) { close(); } mUi.mLbxMatches->setSortingEnabled(true); mUi.mLbxMatches->header()->setSortIndicator(mSortColumn, mSortOrder); mUi.mSearchFolderEdt->setEnabled(true); } } void SearchWindow::slotCollectionStatisticsRetrieved(KJob *job) { Akonadi::CollectionFetchJob *fetch = qobject_cast(job); if (!fetch || fetch->error()) { return; } const Akonadi::Collection::List cols = fetch->collections(); if (cols.isEmpty()) { mUi.mStatusLbl->clear(); return; } const Akonadi::Collection col = cols.at(0); updateCollectionStatistic(col.id(), col.statistics()); } void SearchWindow::slotStop() { mUi.mProgressIndicator->stop(); if (mSearchJob) { mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; mUi.mStatusLbl->setText(i18n("Search stopped.")); } enableGUI(); } void SearchWindow::slotClose() { accept(); } void SearchWindow::closeEvent(QCloseEvent *event) { if (mSearchJob) { mCloseRequested = true; //Cancel search in progress mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; QTimer::singleShot(0, this, &SearchWindow::slotClose); } else { QDialog::closeEvent(event); } } void SearchWindow::scheduleRename(const QString &text) { if (!text.isEmpty()) { mRenameTimer.setSingleShot(true); mRenameTimer.start(250); mUi.mSearchFolderOpenBtn->setEnabled(false); } else { mRenameTimer.stop(); mUi.mSearchFolderOpenBtn->setEnabled(!text.isEmpty()); } } void SearchWindow::renameSearchFolder() { const QString name = mUi.mSearchFolderEdt->text(); if (mFolder.isValid()) { const QString oldFolderName = mFolder.name(); if (oldFolderName != name) { mFolder.setName(name); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob(mFolder, this); job->setProperty("oldfoldername", oldFolderName); connect(job, &Akonadi::CollectionModifyJob::result, this, &SearchWindow::slotSearchFolderRenameDone); } mUi.mSearchFolderOpenBtn->setEnabled(true); } } void SearchWindow::slotSearchFolderRenameDone(KJob *job) { Q_ASSERT(job); if (job->error()) { qCWarning(KMAIL_LOG) << "Job failed:" << job->errorText(); KMessageBox::information(this, i18n("There was a problem renaming your search folder. " "A common reason for this is that another search folder " "with the same name already exists. Error returned \"%1\".", job->errorText())); mUi.mSearchFolderEdt->blockSignals(true); mUi.mSearchFolderEdt->setText(job->property("oldfoldername").toString()); mUi.mSearchFolderEdt->blockSignals(false); } } void SearchWindow::openSearchFolder() { Q_ASSERT(mFolder.isValid()); renameSearchFolder(); mKMMainWidget->slotSelectCollectionFolder(mFolder); slotClose(); } void SearchWindow::slotViewSelectedMsg() { mKMMainWidget->slotMessageActivated(selectedMessage()); } void SearchWindow::slotViewMsg(const Akonadi::Item &item) { if (item.isValid()) { mKMMainWidget->slotMessageActivated(item); } } void SearchWindow::slotCurrentChanged(const Akonadi::Item &item) { mUi.mSearchResultOpenBtn->setEnabled(item.isValid()); } void SearchWindow::enableGUI() { const bool searching = (mSearchJob != nullptr); KGuiItem::assign(mSearchButton, searching ? mStopSearchGuiItem : mStartSearchGuiItem); if (searching) { disconnect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotStop); } else { disconnect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotStop); connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); } } Akonadi::Item::List SearchWindow::selectedMessages() const { Akonadi::Item::List messages; const QModelIndexList lst = mUi.mLbxMatches->selectionModel()->selectedRows(); for (const QModelIndex &index : lst) { const Akonadi::Item item = index.data(Akonadi::ItemModel::ItemRole).value(); if (item.isValid()) { messages.append(item); } } return messages; } Akonadi::Item SearchWindow::selectedMessage() const { return mUi.mLbxMatches->currentIndex().data(Akonadi::ItemModel::ItemRole).value(); } void SearchWindow::updateContextMenuActions() { const int count = selectedMessages().count(); const bool singleActions = (count == 1); const bool notEmpty = (count > 0); mJumpToFolderAction->setEnabled(singleActions); mReplyAction->setEnabled(singleActions); mReplyAllAction->setEnabled(singleActions); mReplyListAction->setEnabled(singleActions); mPrintAction->setEnabled(singleActions); mSaveAtchAction->setEnabled(notEmpty); mSaveAsAction->setEnabled(notEmpty); mClearAction->setEnabled(notEmpty); } void SearchWindow::slotContextMenuRequested(const QPoint &) { if (!selectedMessage().isValid() || selectedMessages().isEmpty()) { return; } QMenu *menu = new QMenu(this); updateContextMenuActions(); // show most used actions menu->addAction(mReplyAction); menu->addAction(mReplyAllAction); menu->addAction(mReplyListAction); menu->addAction(mForwardActionMenu); menu->addSeparator(); menu->addAction(mJumpToFolderAction); menu->addSeparator(); QAction *act = mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CopyItems); mAkonadiStandardAction->setActionText(Akonadi::StandardActionManager::CopyItems, ki18np("Copy Message", "Copy %1 Messages")); menu->addAction(act); act = mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CutItems); mAkonadiStandardAction->setActionText(Akonadi::StandardActionManager::CutItems, ki18np("Cut Message", "Cut %1 Messages")); menu->addAction(act); menu->addAction(mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CopyItemToMenu)); menu->addAction(mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::MoveItemToMenu)); menu->addSeparator(); menu->addAction(mSaveAsAction); menu->addAction(mSaveAtchAction); menu->addAction(mPrintAction); menu->addSeparator(); menu->addAction(mClearAction); menu->exec(QCursor::pos(), nullptr); delete menu; } void SearchWindow::slotClearSelection() { mUi.mLbxMatches->clearSelection(); } void SearchWindow::slotReplyToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplySmart); command->start(); } void SearchWindow::slotReplyAllToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplyAll); command->start(); } void SearchWindow::slotReplyListToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplyList); command->start(); } void SearchWindow::slotForwardMsg() { KMCommand *command = new KMForwardCommand(this, selectedMessages()); command->start(); } void SearchWindow::slotForwardAttachedMsg() { KMCommand *command = new KMForwardAttachedCommand(this, selectedMessages()); command->start(); } void SearchWindow::slotSaveMsg() { KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand(this, selectedMessages()); saveCommand->start(); } void SearchWindow::slotSaveAttachments() { KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand(this, selectedMessages()); saveCommand->start(); } void SearchWindow::slotPrintMsg() { KMCommand *command = new KMPrintCommand(this, selectedMessage()); command->start(); } void SearchWindow::addRulesToSearchPattern(const SearchPattern &pattern) { SearchPattern p(mSearchPattern); p.purify(); QList::const_iterator it; QList::const_iterator end(pattern.constEnd()); p.reserve(pattern.count()); for (it = pattern.constBegin(); it != end; ++it) { p.append(SearchRule::createInstance(**it)); } mSearchPattern = p; mUi.mPatternEdit->setSearchPattern(&mSearchPattern); } void SearchWindow::slotSelectMultipleFolders() { mUi.mChkMultiFolders->setChecked(true); if (!mSelectMultiCollectionDialog) { QList lst; lst.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { lst << col.id(); } mSelectMultiCollectionDialog = new PimCommon::SelectMultiCollectionDialog(KMime::Message::mimeType(), lst, this); } mSelectMultiCollectionDialog->show(); } void SearchWindow::slotJumpToFolder() { if (selectedMessage().isValid()) { mKMMainWidget->slotSelectCollectionFolder(selectedMessage().parentCollection()); } } QVector SearchWindow::checkIncompleteIndex(const Akonadi::Collection::List &searchCols, bool recursive) { QVector results; Akonadi::Collection::List cols; if (recursive) { cols = searchCollectionsRecursive(searchCols); } else { for (const Akonadi::Collection &col : searchCols) { QAbstractItemModel *etm = KMKernel::self()->collectionModel(); const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(etm, col); const Akonadi::Collection modelCol = etm->data(idx, Akonadi::EntityTreeModel::CollectionRole).value(); // Only index offline IMAP collections if (PimCommon::Util::isImapResource(modelCol.resource()) && !modelCol.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { continue; } else { cols.push_back(modelCol); } } } enableGUI(); mUi.mProgressIndicator->start(); mUi.mStatusLbl->setText(i18n("Checking index status...")); //Fetch collection ? for (const Akonadi::Collection &col : qAsConst(cols)) { const qlonglong num = KMKernel::self()->indexedItems()->indexedItems((qlonglong)col.id()); if (col.statistics().count() != num) { results.push_back(col.id()); } } return results; } Akonadi::Collection::List SearchWindow::searchCollectionsRecursive(const Akonadi::Collection::List &cols) const { QAbstractItemModel *etm = KMKernel::self()->collectionModel(); Akonadi::Collection::List result; for (const Akonadi::Collection &col : cols) { const QModelIndex colIdx = Akonadi::EntityTreeModel::modelIndexForCollection(etm, col); if (col.statistics().count() > -1) { if (col.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { result.push_back(col); } } else { const Akonadi::Collection collection = etm->data(colIdx, Akonadi::EntityTreeModel::CollectionRole).value(); if (!collection.hasAttribute() && collection.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { result.push_back(collection); } } const int childrenCount = etm->rowCount(colIdx); if (childrenCount > 0) { Akonadi::Collection::List subCols; subCols.reserve(childrenCount); for (int i = 0; i < childrenCount; ++i) { const QModelIndex idx = etm->index(i, 0, colIdx); const Akonadi::Collection child = etm->data(idx, Akonadi::EntityTreeModel::CollectionRole).value(); if (child.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { subCols.push_back(child); } } result += searchCollectionsRecursive(subCols); } } return result; } diff --git a/src/tag/tagactionmanager.cpp b/src/tag/tagactionmanager.cpp index e26b88ff5..636d7923e 100644 --- a/src/tag/tagactionmanager.cpp +++ b/src/tag/tagactionmanager.cpp @@ -1,360 +1,360 @@ /* Copyright 2010 Thomas McGuire Copyright 2011-2017 Laurent Montel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "tagactionmanager.h" #include "messageactions.h" #include "helper_p.h" #include "MailCommon/AddTagDialog" #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include using namespace KMail; static int s_numberMaxTag = 10; TagActionManager::TagActionManager(QObject *parent, KActionCollection *actionCollection, MessageActions *messageActions, KXMLGUIClient *guiClient) : QObject(parent), mActionCollection(actionCollection), mMessageActions(messageActions), mMessageTagToggleMapper(nullptr), mGUIClient(guiClient), mSeparatorMoreAction(nullptr), mSeparatorNewTagAction(nullptr), mMoreAction(nullptr), mNewTagAction(nullptr), mNewTagId(-1), mTagFetchInProgress(false), mMonitor(new Akonadi::Monitor(this)) { mMessageActions->messageStatusMenu()->menu()->addSeparator(); mMonitor->setTypeMonitored(Akonadi::Monitor::Tags); mMonitor->tagFetchScope().fetchAttribute(); connect(mMonitor, &Akonadi::Monitor::tagAdded, this, &TagActionManager::onTagAdded); connect(mMonitor, &Akonadi::Monitor::tagRemoved, this, &TagActionManager::onTagRemoved); connect(mMonitor, &Akonadi::Monitor::tagChanged, this, &TagActionManager::onTagChanged); } TagActionManager::~TagActionManager() { } void TagActionManager::clearActions() { //Remove the tag actions from the toolbar if (!mToolbarActions.isEmpty()) { if (mGUIClient->factory()) { mGUIClient->unplugActionList(QStringLiteral("toolbar_messagetag_actions")); } mToolbarActions.clear(); } //Remove the tag actions from the status menu and the action collection, //then delete them. for (KToggleAction *action : qAsConst(mTagActions)) { mMessageActions->messageStatusMenu()->removeAction(action); // This removes and deletes the action at the same time mActionCollection->removeAction(action); } if (mSeparatorMoreAction) { mMessageActions->messageStatusMenu()->removeAction(mSeparatorMoreAction); } if (mSeparatorNewTagAction) { mMessageActions->messageStatusMenu()->removeAction(mSeparatorNewTagAction); } if (mNewTagAction) { mMessageActions->messageStatusMenu()->removeAction(mNewTagAction); } if (mMoreAction) { mMessageActions->messageStatusMenu()->removeAction(mMoreAction); } mTagActions.clear(); delete mMessageTagToggleMapper; mMessageTagToggleMapper = nullptr; } void TagActionManager::createTagAction(const MailCommon::Tag::Ptr &tag, bool addToMenu) { QString cleanName(i18n("Message Tag: %1", tag->tagName)); cleanName.replace(QLatin1Char('&'), QStringLiteral("&&")); KToggleAction *const tagAction = new KToggleAction(QIcon::fromTheme(tag->iconName), cleanName, this); tagAction->setIconText(tag->name()); tagAction->setChecked(tag->id() == mNewTagId); mActionCollection->addAction(tag->name(), tagAction); mActionCollection->setDefaultShortcut(tagAction, QKeySequence(tag->shortcut)); connect(tagAction, SIGNAL(triggered(bool)), mMessageTagToggleMapper, SLOT(map())); // The shortcut configuration is done in the config dialog. // The shortcut set in the shortcut dialog would not be saved back to // the tag descriptions correctly. mActionCollection->setShortcutsConfigurable(tagAction, false); mMessageTagToggleMapper->setMapping(tagAction, QString::number(tag->tag().id())); mTagActions.insert(tag->id(), tagAction); if (addToMenu) { mMessageActions->messageStatusMenu()->menu()->addAction(tagAction); } if (tag->inToolbar) { mToolbarActions.append(tagAction); } } void TagActionManager::createActions() { if (mTagFetchInProgress) { return; } if (mTags.isEmpty()) { mTagFetchInProgress = true; Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); connect(fetchJob, &Akonadi::TagFetchJob::result, this, &TagActionManager::finishedTagListing); } else { mTagFetchInProgress = false; createTagActions(mTags); } } void TagActionManager::finishedTagListing(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << job->errorString(); } Akonadi::TagFetchJob *fetchJob = static_cast(job); const Akonadi::Tag::List lstTags = fetchJob->tags(); for (const Akonadi::Tag &result : lstTags) { mTags.append(MailCommon::Tag::fromAkonadi(result)); } mTagFetchInProgress = false; std::sort(mTags.begin(), mTags.end(), MailCommon::Tag::compare); createTagActions(mTags); } void TagActionManager::onSignalMapped(const QString &tag) { Q_EMIT tagActionTriggered(Akonadi::Tag(tag.toLongLong())); } void TagActionManager::createTagActions(const QList &tags) { clearActions(); //Use a mapper to understand which tag button is triggered mMessageTagToggleMapper = new QSignalMapper(this); - connect(mMessageTagToggleMapper, SIGNAL(mapped(QString)), - this, SLOT(onSignalMapped(QString))); + connect(mMessageTagToggleMapper, QOverload::of(&QSignalMapper::mapped), + this, &TagActionManager::onSignalMapped); // Create a action for each tag and plug it into various places int i = 0; bool needToAddMoreAction = false; const int numberOfTag(tags.size()); //It is assumed the tags are sorted for (const MailCommon::Tag::Ptr &tag : tags) { if (i < s_numberMaxTag) { createTagAction(tag, true); } else { if (tag->inToolbar || !tag->shortcut.isEmpty()) { createTagAction(tag, false); } if (i == s_numberMaxTag && i < numberOfTag) { needToAddMoreAction = true; } } ++i; } if (!mSeparatorNewTagAction) { mSeparatorNewTagAction = new QAction(this); mSeparatorNewTagAction->setSeparator(true); } mMessageActions->messageStatusMenu()->menu()->addAction(mSeparatorNewTagAction); if (!mNewTagAction) { mNewTagAction = new QAction(i18n("Add new tag..."), this); connect(mNewTagAction, &QAction::triggered, this, &TagActionManager::newTagActionClicked); } mMessageActions->messageStatusMenu()->menu()->addAction(mNewTagAction); if (needToAddMoreAction) { if (!mSeparatorMoreAction) { mSeparatorMoreAction = new QAction(this); mSeparatorMoreAction->setSeparator(true); } mMessageActions->messageStatusMenu()->menu()->addAction(mSeparatorMoreAction); if (!mMoreAction) { mMoreAction = new QAction(i18n("More..."), this); connect(mMoreAction, &QAction::triggered, this, &TagActionManager::tagMoreActionClicked); } mMessageActions->messageStatusMenu()->menu()->addAction(mMoreAction); } if (!mToolbarActions.isEmpty() && mGUIClient->factory()) { mGUIClient->plugActionList(QStringLiteral("toolbar_messagetag_actions"), mToolbarActions); } } void TagActionManager::updateActionStates(int numberOfSelectedMessages, const Akonadi::Item &selectedItem) { mNewTagId = -1; QMap::const_iterator it = mTagActions.constBegin(); QMap::const_iterator end = mTagActions.constEnd(); if (numberOfSelectedMessages >= 1) { Q_ASSERT(selectedItem.isValid()); for (; it != end; ++it) { //FIXME Not very performant tag label retrieval QString label(QStringLiteral("not found")); for (const MailCommon::Tag::Ptr &tag : qAsConst(mTags)) { if (tag->id() == it.key()) { label = tag->name(); break; } } it.value()->setEnabled(true); if (numberOfSelectedMessages == 1) { const bool hasTag = selectedItem.hasTag(Akonadi::Tag(it.key())); it.value()->setChecked(hasTag); it.value()->setText(i18n("Message Tag: %1", label)); } else { it.value()->setChecked(false); it.value()->setText(i18n("Toggle Message Tag: %1", label)); } } } else { for (; it != end; ++it) { it.value()->setEnabled(false); } } } void TagActionManager::onTagAdded(const Akonadi::Tag &akonadiTag) { const QList checked = checkedTags(); clearActions(); mTags.append(MailCommon::Tag::fromAkonadi(akonadiTag)); std::sort(mTags.begin(), mTags.end(), MailCommon::Tag::compare); createTagActions(mTags); checkTags(checked); } void TagActionManager::onTagRemoved(const Akonadi::Tag &akonadiTag) { for (const MailCommon::Tag::Ptr &tag : qAsConst(mTags)) { if (tag->id() == akonadiTag.id()) { mTags.removeAll(tag); break; } } fillTagList(); } void TagActionManager::onTagChanged(const Akonadi::Tag &akonadiTag) { for (const MailCommon::Tag::Ptr &tag : qAsConst(mTags)) { if (tag->id() == akonadiTag.id()) { mTags.removeAll(tag); break; } } mTags.append(MailCommon::Tag::fromAkonadi(akonadiTag)); fillTagList(); } void TagActionManager::fillTagList() { const QList checked = checkedTags(); clearActions(); std::sort(mTags.begin(), mTags.end(), MailCommon::Tag::compare); createTagActions(mTags); checkTags(checked); } void TagActionManager::newTagActionClicked() { QPointer dialog = new MailCommon::AddTagDialog(QList() << mActionCollection, nullptr); dialog->setTags(mTags); if (dialog->exec() == QDialog::Accepted) { mNewTagId = dialog->tag().id(); // Assign tag to all selected items right away Q_EMIT tagActionTriggered(dialog->tag()); } delete dialog; } void TagActionManager::checkTags(const QList &tags) { for (const qint64 id : tags) { if (mTagActions.contains(id)) { mTagActions[id]->setChecked(true); } } } QList TagActionManager::checkedTags() const { QMap::const_iterator it = mTagActions.constBegin(); QMap::const_iterator end = mTagActions.constEnd(); QList checked; for (; it != end; ++it) { if (it.value()->isChecked()) { checked << it.key(); } } return checked; }