| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de> |
| 3 | SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | |
| 8 | #include "kpluginwidget.h" |
| 9 | #include "kcmoduleloader.h" |
| 10 | #include "kpluginproxymodel.h" |
| 11 | #include "kpluginwidget_p.h" |
| 12 | |
| 13 | #include <kcmutils_debug.h> |
| 14 | |
| 15 | #include <QApplication> |
| 16 | #include <QCheckBox> |
| 17 | #include <QDialog> |
| 18 | #include <QDialogButtonBox> |
| 19 | #include <QDir> |
| 20 | #include <QLineEdit> |
| 21 | #include <QPainter> |
| 22 | #include <QPushButton> |
| 23 | #include <QSortFilterProxyModel> |
| 24 | #include <QStandardPaths> |
| 25 | #include <QStyle> |
| 26 | #include <QStyleOptionViewItem> |
| 27 | #include <QVBoxLayout> |
| 28 | |
| 29 | #include <KAboutPluginDialog> |
| 30 | #include <KCategorizedSortFilterProxyModel> |
| 31 | #include <KCategorizedView> |
| 32 | #include <KCategoryDrawer> |
| 33 | #include <KLocalizedString> |
| 34 | #include <KPluginMetaData> |
| 35 | #include <KStandardGuiItem> |
| 36 | #include <utility> |
| 37 | |
| 38 | static constexpr int s_margin = 5; |
| 39 | |
| 40 | int KPluginWidgetPrivate::dependantLayoutValue(int value, int width, int totalWidth) const |
| 41 | { |
| 42 | if (listView->layoutDirection() == Qt::LeftToRight) { |
| 43 | return value; |
| 44 | } |
| 45 | |
| 46 | return totalWidth - width - value; |
| 47 | } |
| 48 | |
| 49 | KPluginWidget::KPluginWidget(QWidget *parent) |
| 50 | : QWidget(parent) |
| 51 | , d(new KPluginWidgetPrivate) |
| 52 | { |
| 53 | auto layout = new QVBoxLayout(this); |
| 54 | layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
| 55 | layout->setSpacing(0); |
| 56 | |
| 57 | // Adding content margins on a QLineEdit breaks inline actions |
| 58 | auto lineEditWrapper = new QWidget(this); |
| 59 | auto lineEditWrapperLayout = new QVBoxLayout(lineEditWrapper); |
| 60 | lineEditWrapperLayout->setContentsMargins(left: style()->pixelMetric(metric: QStyle::PM_LayoutLeftMargin), |
| 61 | top: style()->pixelMetric(metric: QStyle::PM_LayoutTopMargin), |
| 62 | right: style()->pixelMetric(metric: QStyle::PM_LayoutRightMargin), |
| 63 | bottom: style()->pixelMetric(metric: QStyle::PM_LayoutBottomMargin)); |
| 64 | |
| 65 | d->lineEdit = new QLineEdit(lineEditWrapper); |
| 66 | d->lineEdit->setClearButtonEnabled(true); |
| 67 | d->lineEdit->setPlaceholderText(i18n("Search…" )); |
| 68 | lineEditWrapperLayout->addWidget(d->lineEdit); |
| 69 | d->listView = new KCategorizedView(this); |
| 70 | d->listView->setProperty(name: "_breeze_borders_sides" , value: QVariant::fromValue(value: QFlags{Qt::TopEdge})); |
| 71 | d->categoryDrawer = new KCategoryDrawer(d->listView); |
| 72 | d->listView->setVerticalScrollMode(QListView::ScrollPerPixel); |
| 73 | d->listView->setAlternatingRowColors(true); |
| 74 | d->listView->setCategoryDrawer(d->categoryDrawer); |
| 75 | |
| 76 | d->pluginModel = new KPluginModel(this); |
| 77 | |
| 78 | connect(sender: d->pluginModel, signal: &KPluginModel::defaulted, context: this, slot: &KPluginWidget::defaulted); |
| 79 | connect(sender: d->pluginModel, |
| 80 | signal: &QAbstractItemModel::dataChanged, |
| 81 | context: this, |
| 82 | slot: [this](const QModelIndex &topLeft, const QModelIndex & /*bottomRight*/, const QList<int> &roles) { |
| 83 | if (roles.contains(t: KPluginModel::EnabledRole)) { |
| 84 | Q_EMIT pluginEnabledChanged(pluginId: topLeft.data(arole: KPluginModel::IdRole).toString(), enabled: topLeft.data(arole: KPluginModel::EnabledRole).toBool()); |
| 85 | Q_EMIT changed(enabled: d->pluginModel->isSaveNeeded()); |
| 86 | } |
| 87 | }); |
| 88 | |
| 89 | d->proxyModel = new KPluginProxyModel(this); |
| 90 | d->proxyModel->setModel(d->pluginModel); |
| 91 | d->listView->setModel(d->proxyModel); |
| 92 | d->listView->setAlternatingRowColors(true); |
| 93 | |
| 94 | auto pluginDelegate = new PluginDelegate(d.get(), this); |
| 95 | d->listView->setItemDelegate(pluginDelegate); |
| 96 | |
| 97 | d->listView->setMouseTracking(true); |
| 98 | d->listView->viewport()->setAttribute(Qt::WA_Hover); |
| 99 | |
| 100 | connect(sender: d->lineEdit, signal: &QLineEdit::textChanged, context: d->proxyModel, slot: [this](const QString &query) { |
| 101 | d->proxyModel->setProperty(name: "query" , value: query); |
| 102 | d->proxyModel->invalidate(); |
| 103 | }); |
| 104 | connect(sender: pluginDelegate, signal: &PluginDelegate::configCommitted, context: this, slot: &KPluginWidget::pluginConfigSaved); |
| 105 | connect(sender: pluginDelegate, signal: &PluginDelegate::changed, context: this, slot: &KPluginWidget::pluginEnabledChanged); |
| 106 | |
| 107 | layout->addWidget(lineEditWrapper); |
| 108 | layout->addWidget(d->listView); |
| 109 | |
| 110 | // When a KPluginWidget instance gets focus, |
| 111 | // it should pass over the focus to its child searchbar. |
| 112 | setFocusProxy(d->lineEdit); |
| 113 | } |
| 114 | |
| 115 | KPluginWidget::~KPluginWidget() |
| 116 | { |
| 117 | delete d->listView->itemDelegate(); |
| 118 | delete d->listView; // depends on some other things in d, make sure this dies first. |
| 119 | } |
| 120 | |
| 121 | void KPluginWidget::addPlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel) |
| 122 | { |
| 123 | d->pluginModel->addPlugins(plugins, categoryLabel); |
| 124 | d->proxyModel->sort(column: 0); |
| 125 | } |
| 126 | |
| 127 | void KPluginWidget::setConfig(const KConfigGroup &config) |
| 128 | { |
| 129 | d->pluginModel->setConfig(config); |
| 130 | } |
| 131 | |
| 132 | void KPluginWidget::clear() |
| 133 | { |
| 134 | d->pluginModel->clear(); |
| 135 | } |
| 136 | |
| 137 | void KPluginWidget::save() |
| 138 | { |
| 139 | d->pluginModel->save(); |
| 140 | } |
| 141 | |
| 142 | void KPluginWidget::load() |
| 143 | { |
| 144 | d->pluginModel->load(); |
| 145 | } |
| 146 | |
| 147 | void KPluginWidget::defaults() |
| 148 | { |
| 149 | d->pluginModel->defaults(); |
| 150 | } |
| 151 | |
| 152 | bool KPluginWidget::isDefault() const |
| 153 | { |
| 154 | for (int i = 0, count = d->pluginModel->rowCount(); i < count; ++i) { |
| 155 | const QModelIndex index = d->pluginModel->index(row: i, column: 0); |
| 156 | if (d->pluginModel->data(index, role: Qt::CheckStateRole).toBool() != d->pluginModel->data(index, role: KPluginModel::EnabledByDefaultRole).toBool()) { |
| 157 | return false; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | return true; |
| 162 | } |
| 163 | |
| 164 | bool KPluginWidget::isSaveNeeded() const |
| 165 | { |
| 166 | return d->pluginModel->isSaveNeeded(); |
| 167 | } |
| 168 | |
| 169 | void KPluginWidget::setConfigurationArguments(const QVariantList &arguments) |
| 170 | { |
| 171 | d->kcmArguments = arguments; |
| 172 | } |
| 173 | |
| 174 | QVariantList KPluginWidget::configurationArguments() const |
| 175 | { |
| 176 | return d->kcmArguments; |
| 177 | } |
| 178 | |
| 179 | void KPluginWidget::showConfiguration(const QString &pluginId) |
| 180 | { |
| 181 | QModelIndex idx; |
| 182 | for (int i = 0, c = d->proxyModel->rowCount(); i < c; ++i) { |
| 183 | const auto currentIndex = d->proxyModel->index(row: i, column: 0); |
| 184 | const QString id = currentIndex.data(arole: KPluginModel::IdRole).toString(); |
| 185 | if (id == pluginId) { |
| 186 | idx = currentIndex; |
| 187 | break; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | if (idx.isValid()) { |
| 192 | auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate()); |
| 193 | delegate->configure(idx); |
| 194 | } else { |
| 195 | qCWarning(KCMUTILS_LOG) << "Could not find plugin" << pluginId; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | void KPluginWidget::setDefaultsIndicatorsVisible(bool isVisible) |
| 200 | { |
| 201 | auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate()); |
| 202 | delegate->resetModel(); |
| 203 | |
| 204 | d->showDefaultIndicator = isVisible; |
| 205 | } |
| 206 | |
| 207 | void KPluginWidget::setAdditionalButtonHandler(const std::function<QPushButton *(const KPluginMetaData &)> &handler) |
| 208 | { |
| 209 | auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate()); |
| 210 | delegate->handler = handler; |
| 211 | } |
| 212 | |
| 213 | PluginDelegate::PluginDelegate(KPluginWidgetPrivate *pluginSelector_d_ptr, QObject *parent) |
| 214 | : KWidgetItemDelegate(pluginSelector_d_ptr->listView, parent) |
| 215 | , checkBox(new QCheckBox) |
| 216 | , pushButton(new QPushButton) |
| 217 | , pluginSelector_d(pluginSelector_d_ptr) |
| 218 | { |
| 219 | // set the icon to make sure the size can be properly calculated |
| 220 | pushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic" ))); |
| 221 | } |
| 222 | |
| 223 | PluginDelegate::~PluginDelegate() |
| 224 | { |
| 225 | delete checkBox; |
| 226 | delete pushButton; |
| 227 | } |
| 228 | |
| 229 | void PluginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const |
| 230 | { |
| 231 | if (!index.isValid()) { |
| 232 | return; |
| 233 | } |
| 234 | |
| 235 | const int xOffset = checkBox->sizeHint().width(); |
| 236 | const bool disabled = !index.model()->data(index, role: KPluginModel::IsChangeableRole).toBool(); |
| 237 | |
| 238 | painter->save(); |
| 239 | |
| 240 | QApplication::style()->drawPrimitive(pe: QStyle::PE_PanelItemViewItem, opt: &option, p: painter, w: nullptr); |
| 241 | |
| 242 | const int iconSize = option.rect.height() - (s_margin * 2); |
| 243 | QIcon icon = QIcon::fromTheme(name: index.model()->data(index, role: Qt::DecorationRole).toString()); |
| 244 | icon.paint(painter, |
| 245 | rect: QRect(pluginSelector_d->dependantLayoutValue(value: s_margin + option.rect.left() + xOffset, width: iconSize, totalWidth: option.rect.width()), |
| 246 | s_margin + option.rect.top(), |
| 247 | iconSize, |
| 248 | iconSize)); |
| 249 | |
| 250 | QRect contentsRect(pluginSelector_d->dependantLayoutValue(value: s_margin * 2 + iconSize + option.rect.left() + xOffset, |
| 251 | width: option.rect.width() - (s_margin * 3) - iconSize - xOffset, |
| 252 | totalWidth: option.rect.width()), |
| 253 | s_margin + option.rect.top(), |
| 254 | option.rect.width() - (s_margin * 3) - iconSize - xOffset, |
| 255 | option.rect.height() - (s_margin * 2)); |
| 256 | |
| 257 | int lessHorizontalSpace = s_margin * 2 + pushButton->sizeHint().width(); |
| 258 | if (index.model()->data(index, role: KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) { |
| 259 | lessHorizontalSpace += s_margin + pushButton->sizeHint().width(); |
| 260 | } |
| 261 | // Reserve space for extra button |
| 262 | if (handler) { |
| 263 | lessHorizontalSpace += s_margin + pushButton->sizeHint().width(); |
| 264 | } |
| 265 | |
| 266 | contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); |
| 267 | |
| 268 | if (option.state & QStyle::State_Selected) { |
| 269 | painter->setPen(option.palette.highlightedText().color()); |
| 270 | } |
| 271 | |
| 272 | if (pluginSelector_d->listView->layoutDirection() == Qt::RightToLeft) { |
| 273 | contentsRect.translate(dx: lessHorizontalSpace, dy: 0); |
| 274 | } |
| 275 | |
| 276 | painter->save(); |
| 277 | if (disabled) { |
| 278 | QPalette pal(option.palette); |
| 279 | pal.setCurrentColorGroup(QPalette::Disabled); |
| 280 | painter->setPen(pal.text().color()); |
| 281 | } |
| 282 | |
| 283 | painter->save(); |
| 284 | QFont font = titleFont(baseFont: option.font); |
| 285 | QFontMetrics fmTitle(font); |
| 286 | painter->setFont(font); |
| 287 | painter->drawText(r: contentsRect, |
| 288 | flags: Qt::AlignLeft | Qt::AlignTop, |
| 289 | text: fmTitle.elidedText(text: index.model()->data(index, role: Qt::DisplayRole).toString(), mode: Qt::ElideRight, width: contentsRect.width())); |
| 290 | painter->restore(); |
| 291 | |
| 292 | painter->drawText( |
| 293 | r: contentsRect, |
| 294 | flags: Qt::AlignLeft | Qt::AlignBottom, |
| 295 | text: option.fontMetrics.elidedText(text: index.model()->data(index, role: KPluginModel::DescriptionRole).toString(), mode: Qt::ElideRight, width: contentsRect.width())); |
| 296 | |
| 297 | painter->restore(); |
| 298 | painter->restore(); |
| 299 | } |
| 300 | |
| 301 | QSize PluginDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const |
| 302 | { |
| 303 | int i = 5; |
| 304 | int j = 1; |
| 305 | if (index.model()->data(index, role: KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) { |
| 306 | i = 6; |
| 307 | j = 2; |
| 308 | } |
| 309 | // Reserve space for extra button |
| 310 | if (handler) { |
| 311 | ++j; |
| 312 | } |
| 313 | |
| 314 | const QFont font = titleFont(baseFont: option.font); |
| 315 | const QFontMetrics fmTitle(font); |
| 316 | const QString text = index.model()->data(index, role: Qt::DisplayRole).toString(); |
| 317 | const QString = index.model()->data(index, role: KPluginModel::DescriptionRole).toString(); |
| 318 | const int maxTextWidth = qMax(a: fmTitle.boundingRect(text).width(), b: option.fontMetrics.boundingRect(text: comment).width()); |
| 319 | |
| 320 | const auto iconSize = pluginSelector_d->listView->style()->pixelMetric(metric: QStyle::PM_IconViewIconSize); |
| 321 | return QSize(maxTextWidth + iconSize + s_margin * i + pushButton->sizeHint().width() * j, |
| 322 | qMax(a: iconSize + s_margin * 2, b: fmTitle.height() + option.fontMetrics.height() + s_margin * 2)); |
| 323 | } |
| 324 | |
| 325 | QList<QWidget *> PluginDelegate::createItemWidgets(const QModelIndex &index) const |
| 326 | { |
| 327 | Q_UNUSED(index); |
| 328 | QList<QWidget *> widgetList; |
| 329 | |
| 330 | auto enabledCheckBox = new QCheckBox; |
| 331 | connect(sender: enabledCheckBox, signal: &QAbstractButton::clicked, context: this, slot: &PluginDelegate::slotStateChanged); |
| 332 | |
| 333 | auto aboutPushButton = new QPushButton; |
| 334 | aboutPushButton->setIcon(QIcon::fromTheme(QStringLiteral("help-about-symbolic" ))); |
| 335 | aboutPushButton->setToolTip(i18n("About" )); |
| 336 | connect(sender: aboutPushButton, signal: &QAbstractButton::clicked, context: this, slot: &PluginDelegate::slotAboutClicked); |
| 337 | |
| 338 | auto configurePushButton = new QPushButton; |
| 339 | configurePushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic" ))); |
| 340 | configurePushButton->setToolTip(i18n("Configure" )); |
| 341 | connect(sender: configurePushButton, signal: &QAbstractButton::clicked, context: this, slot: &PluginDelegate::slotConfigureClicked); |
| 342 | |
| 343 | const static QList<QEvent::Type> blockedEvents{ |
| 344 | QEvent::MouseButtonPress, |
| 345 | QEvent::MouseButtonRelease, |
| 346 | QEvent::MouseButtonDblClick, |
| 347 | QEvent::KeyPress, |
| 348 | QEvent::KeyRelease, |
| 349 | }; |
| 350 | setBlockedEventTypes(widget: enabledCheckBox, types: blockedEvents); |
| 351 | |
| 352 | setBlockedEventTypes(widget: aboutPushButton, types: blockedEvents); |
| 353 | |
| 354 | setBlockedEventTypes(widget: configurePushButton, types: blockedEvents); |
| 355 | |
| 356 | widgetList << enabledCheckBox << aboutPushButton << configurePushButton; |
| 357 | if (handler) { |
| 358 | QPushButton *btn = handler(pluginSelector_d->pluginModel->data(index, role: KPluginModel::MetaDataRole).value<KPluginMetaData>()); |
| 359 | if (btn) { |
| 360 | widgetList << btn; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | return widgetList; |
| 365 | } |
| 366 | |
| 367 | void PluginDelegate::updateItemWidgets(const QList<QWidget *> &widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const |
| 368 | { |
| 369 | int = 0; |
| 370 | QPushButton * = nullptr; |
| 371 | if (widgets.count() == 4) { |
| 372 | extraButton = static_cast<QPushButton *>(widgets[3]); |
| 373 | extraButtonWidth = extraButton->sizeHint().width() + s_margin; |
| 374 | } |
| 375 | auto checkBox = static_cast<QCheckBox *>(widgets[0]); |
| 376 | checkBox->resize(checkBox->sizeHint()); |
| 377 | checkBox->move(ax: pluginSelector_d->dependantLayoutValue(value: s_margin, width: checkBox->sizeHint().width(), totalWidth: option.rect.width()), |
| 378 | ay: option.rect.height() / 2 - checkBox->sizeHint().height() / 2); |
| 379 | |
| 380 | auto aboutPushButton = static_cast<QPushButton *>(widgets[1]); |
| 381 | const QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); |
| 382 | aboutPushButton->resize(aboutPushButtonSizeHint); |
| 383 | aboutPushButton->move(ax: pluginSelector_d->dependantLayoutValue(value: option.rect.width() - s_margin - aboutPushButtonSizeHint.width() - extraButtonWidth, |
| 384 | width: aboutPushButtonSizeHint.width(), |
| 385 | totalWidth: option.rect.width()), |
| 386 | ay: option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); |
| 387 | |
| 388 | auto configurePushButton = static_cast<QPushButton *>(widgets[2]); |
| 389 | const QSize configurePushButtonSizeHint = configurePushButton->sizeHint(); |
| 390 | configurePushButton->resize(configurePushButtonSizeHint); |
| 391 | configurePushButton->move(ax: pluginSelector_d->dependantLayoutValue(value: option.rect.width() - s_margin * 2 - configurePushButtonSizeHint.width() |
| 392 | - aboutPushButtonSizeHint.width() - extraButtonWidth, |
| 393 | width: configurePushButtonSizeHint.width(), |
| 394 | totalWidth: option.rect.width()), |
| 395 | ay: option.rect.height() / 2 - configurePushButtonSizeHint.height() / 2); |
| 396 | |
| 397 | if (extraButton) { |
| 398 | const QSize = extraButton->sizeHint(); |
| 399 | extraButton->resize(extraPushButtonSizeHint); |
| 400 | extraButton->move(ax: pluginSelector_d->dependantLayoutValue(value: option.rect.width() - extraButtonWidth, width: extraPushButtonSizeHint.width(), totalWidth: option.rect.width()), |
| 401 | ay: option.rect.height() / 2 - extraPushButtonSizeHint.height() / 2); |
| 402 | } |
| 403 | |
| 404 | if (!index.isValid() || !index.internalPointer()) { |
| 405 | checkBox->setVisible(false); |
| 406 | aboutPushButton->setVisible(false); |
| 407 | configurePushButton->setVisible(false); |
| 408 | if (extraButton) { |
| 409 | extraButton->setVisible(false); |
| 410 | } |
| 411 | } else { |
| 412 | const bool enabledByDefault = index.model()->data(index, role: KPluginModel::EnabledByDefaultRole).toBool(); |
| 413 | const bool enabled = index.model()->data(index, role: KPluginModel::EnabledRole).toBool(); |
| 414 | checkBox->setProperty(name: "_kde_highlight_neutral" , value: pluginSelector_d->showDefaultIndicator && enabledByDefault != enabled); |
| 415 | checkBox->setChecked(index.model()->data(index, role: Qt::CheckStateRole).toBool()); |
| 416 | checkBox->setEnabled(index.model()->data(index, role: KPluginModel::IsChangeableRole).toBool()); |
| 417 | configurePushButton->setVisible(index.model()->data(index, role: KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()); |
| 418 | configurePushButton->setEnabled(index.model()->data(index, role: Qt::CheckStateRole).toBool()); |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | void PluginDelegate::slotStateChanged(bool state) |
| 423 | { |
| 424 | if (!focusedIndex().isValid()) { |
| 425 | return; |
| 426 | } |
| 427 | |
| 428 | QModelIndex index = focusedIndex(); |
| 429 | |
| 430 | const_cast<QAbstractItemModel *>(index.model())->setData(index, value: state, role: Qt::CheckStateRole); |
| 431 | } |
| 432 | |
| 433 | void PluginDelegate::slotAboutClicked() |
| 434 | { |
| 435 | const QModelIndex index = focusedIndex(); |
| 436 | |
| 437 | auto pluginMetaData = index.data(arole: KPluginModel::MetaDataRole).value<KPluginMetaData>(); |
| 438 | |
| 439 | auto *aboutPlugin = new KAboutPluginDialog(pluginMetaData, itemView()); |
| 440 | aboutPlugin->setAttribute(Qt::WA_DeleteOnClose); |
| 441 | aboutPlugin->show(); |
| 442 | } |
| 443 | |
| 444 | void PluginDelegate::slotConfigureClicked() |
| 445 | { |
| 446 | configure(idx: focusedIndex()); |
| 447 | } |
| 448 | |
| 449 | void PluginDelegate::configure(const QModelIndex &index) |
| 450 | { |
| 451 | const QAbstractItemModel *model = index.model(); |
| 452 | const auto kcm = model->data(index, role: KPluginModel::ConfigRole).value<KPluginMetaData>(); |
| 453 | |
| 454 | auto configDialog = new QDialog(itemView()); |
| 455 | configDialog->setAttribute(Qt::WA_DeleteOnClose); |
| 456 | configDialog->setModal(true); |
| 457 | configDialog->setWindowTitle(model->data(index, role: KPluginModel::NameRole).toString()); |
| 458 | |
| 459 | QWidget *kcmWrapper = new QWidget; |
| 460 | auto kcmInstance = KCModuleLoader::loadModule(metaData: kcm, parent: kcmWrapper, args: pluginSelector_d->kcmArguments); |
| 461 | |
| 462 | auto layout = new QVBoxLayout(configDialog); |
| 463 | layout->addWidget(kcmWrapper); |
| 464 | |
| 465 | auto buttonBox = new QDialogButtonBox(configDialog); |
| 466 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); |
| 467 | KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::Ok), item: KStandardGuiItem::ok()); |
| 468 | KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::Cancel), item: KStandardGuiItem::cancel()); |
| 469 | KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), item: KStandardGuiItem::defaults()); |
| 470 | connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: configDialog, slot: &QDialog::accept); |
| 471 | connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: configDialog, slot: &QDialog::reject); |
| 472 | connect(sender: configDialog, signal: &QDialog::accepted, context: this, slot: [kcmInstance, this, model, index]() { |
| 473 | Q_EMIT configCommitted(pluginId: model->data(index, role: KPluginModel::IdRole).toString()); |
| 474 | kcmInstance->save(); |
| 475 | }); |
| 476 | connect(sender: configDialog, signal: &QDialog::rejected, context: this, slot: [kcmInstance]() { |
| 477 | kcmInstance->load(); |
| 478 | }); |
| 479 | |
| 480 | connect(sender: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), signal: &QAbstractButton::clicked, context: this, slot: [kcmInstance] { |
| 481 | kcmInstance->defaults(); |
| 482 | }); |
| 483 | layout->addWidget(buttonBox); |
| 484 | |
| 485 | // Load KCM right before showing it |
| 486 | kcmInstance->load(); |
| 487 | configDialog->show(); |
| 488 | } |
| 489 | |
| 490 | QFont PluginDelegate::titleFont(const QFont &baseFont) const |
| 491 | { |
| 492 | QFont retFont(baseFont); |
| 493 | retFont.setBold(true); |
| 494 | |
| 495 | return retFont; |
| 496 | } |
| 497 | |
| 498 | #include "moc_kpluginwidget.cpp" |
| 499 | #include "moc_kpluginwidget_p.cpp" |
| 500 | |