File indexing completed on 2024-05-12 05:21:22
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2004 Tobias Koenig <tokoe@kde.org> 0005 SPDX-FileCopyrightText: 2004 Cornelius Schumacher <schumacher@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kcmdesignerfields.h" 0011 0012 #include "korganizer_debug.h" 0013 #include <KAboutData> 0014 #include <KDialogJobUiDelegate> 0015 #include <KDirWatch> 0016 #include <KIO/CommandLauncherJob> 0017 #include <KIO/FileCopyJob> 0018 #include <KJobWidgets> 0019 #include <KLocalizedString> 0020 #include <KMessageBox> 0021 #include <KShell> 0022 #include <QFileDialog> 0023 0024 #include <QDir> 0025 #include <QGroupBox> 0026 #include <QHBoxLayout> 0027 #include <QHeaderView> 0028 #include <QLabel> 0029 #include <QPushButton> 0030 #include <QStandardPaths> 0031 #include <QTreeWidget> 0032 #include <QUiLoader> 0033 #include <QWhatsThis> 0034 0035 class PageItem : public QTreeWidgetItem 0036 { 0037 public: 0038 PageItem(QTreeWidget *parent, const QString &path) 0039 : QTreeWidgetItem(parent) 0040 , mPath(path) 0041 { 0042 setFlags(flags() | Qt::ItemIsUserCheckable); 0043 setCheckState(0, Qt::Unchecked); 0044 mName = path.mid(path.lastIndexOf(QLatin1Char('/')) + 1); 0045 0046 QFile f(mPath); 0047 if (!f.open(QFile::ReadOnly)) { 0048 return; 0049 } 0050 QUiLoader builder; 0051 QWidget *wdg = builder.load(&f, nullptr); 0052 f.close(); 0053 if (wdg) { 0054 setText(0, wdg->windowTitle()); 0055 0056 QPixmap pm = wdg->grab(); 0057 const QImage img = pm.toImage().scaled(300, 300, Qt::KeepAspectRatio, Qt::SmoothTransformation); 0058 mPreview = QPixmap::fromImage(img); 0059 0060 QMap<QString, QString> allowedTypes; 0061 allowedTypes.insert(QStringLiteral("QLineEdit"), i18n("Text")); 0062 allowedTypes.insert(QStringLiteral("QTextEdit"), i18n("Text")); 0063 allowedTypes.insert(QStringLiteral("QSpinBox"), i18n("Numeric Value")); 0064 allowedTypes.insert(QStringLiteral("QCheckBox"), i18n("Boolean")); 0065 allowedTypes.insert(QStringLiteral("QComboBox"), i18n("Selection")); 0066 allowedTypes.insert(QStringLiteral("QDateTimeEdit"), i18n("Date & Time")); 0067 allowedTypes.insert(QStringLiteral("KLineEdit"), i18n("Text")); 0068 allowedTypes.insert(QStringLiteral("KTextEdit"), i18n("Text")); 0069 allowedTypes.insert(QStringLiteral("KDateTimeWidget"), i18n("Date & Time")); 0070 allowedTypes.insert(QStringLiteral("KDatePicker"), i18n("Date")); 0071 0072 const QList<QWidget *> list = wdg->findChildren<QWidget *>(); 0073 for (QWidget *it : list) { 0074 if (allowedTypes.contains(QLatin1StringView(it->metaObject()->className()))) { 0075 const QString objectName = it->objectName(); 0076 if (objectName.startsWith(QLatin1StringView("X_"))) { 0077 new QTreeWidgetItem(this, 0078 QStringList() << objectName << allowedTypes[QLatin1StringView(it->metaObject()->className())] 0079 << QLatin1StringView(it->metaObject()->className()) << it->whatsThis()); 0080 } 0081 } 0082 } 0083 } 0084 } 0085 0086 [[nodiscard]] QString name() const 0087 { 0088 return mName; 0089 } 0090 0091 [[nodiscard]] QString path() const 0092 { 0093 return mPath; 0094 } 0095 0096 [[nodiscard]] QPixmap preview() const 0097 { 0098 return mPreview; 0099 } 0100 0101 void setIsActive(bool isActive) 0102 { 0103 mIsActive = isActive; 0104 } 0105 0106 [[nodiscard]] bool isActive() const 0107 { 0108 return mIsActive; 0109 } 0110 0111 [[nodiscard]] bool isOn() const 0112 { 0113 return checkState(0) == Qt::Checked; 0114 } 0115 0116 private: 0117 QString mName; 0118 const QString mPath; 0119 QPixmap mPreview; 0120 bool mIsActive = false; 0121 }; 0122 0123 KCMDesignerFields::KCMDesignerFields(QObject *parent, const KPluginMetaData &data) 0124 : KCModule(parent, data) 0125 { 0126 } 0127 0128 void KCMDesignerFields::delayedInit() 0129 { 0130 qCDebug(KORGANIZER_LOG) << "KCMDesignerFields::delayedInit()"; 0131 0132 initGUI(); 0133 0134 connect(mPageView, &QTreeWidget::itemSelectionChanged, this, &KCMDesignerFields::updatePreview); 0135 connect(mPageView, &QTreeWidget::itemClicked, this, &KCMDesignerFields::itemClicked); 0136 0137 connect(mDeleteButton, &QPushButton::clicked, this, &KCMDesignerFields::deleteFile); 0138 connect(mImportButton, &QPushButton::clicked, this, &KCMDesignerFields::importFile); 0139 connect(mDesignerButton, &QPushButton::clicked, this, &KCMDesignerFields::startDesigner); 0140 0141 load(); 0142 0143 // Install a dirwatcher that will detect newly created or removed designer files 0144 auto dw = new KDirWatch(this); 0145 QDir().mkpath(localUiDir()); 0146 dw->addDir(localUiDir(), KDirWatch::WatchFiles); 0147 connect(dw, &KDirWatch::created, this, &KCMDesignerFields::rebuildList); 0148 connect(dw, &KDirWatch::deleted, this, &KCMDesignerFields::rebuildList); 0149 connect(dw, &KDirWatch::dirty, this, &KCMDesignerFields::rebuildList); 0150 } 0151 0152 void KCMDesignerFields::deleteFile() 0153 { 0154 const auto selectedItems = mPageView->selectedItems(); 0155 for (QTreeWidgetItem *item : selectedItems) { 0156 auto pageItem = static_cast<PageItem *>(item->parent() ? item->parent() : item); 0157 if (KMessageBox::warningContinueCancel(widget(), 0158 i18n("<qt>Do you really want to delete '<b>%1</b>'?</qt>", pageItem->text(0)), 0159 QString(), 0160 KStandardGuiItem::del()) 0161 == KMessageBox::Continue) { 0162 QFile::remove(pageItem->path()); 0163 } 0164 } 0165 // The actual view refresh will be done automagically by the slots connected to kdirwatch 0166 } 0167 0168 void KCMDesignerFields::importFile() 0169 { 0170 const QUrl src = QFileDialog::getOpenFileUrl(widget(), 0171 i18n("Import Page"), 0172 QUrl::fromLocalFile(QDir::homePath()), 0173 QStringLiteral("%1 (*.ui)").arg(i18n("Designer Files"))); 0174 0175 QUrl dest = QUrl::fromLocalFile(localUiDir()); 0176 QDir().mkpath(localUiDir()); 0177 dest = dest.adjusted(QUrl::RemoveFilename); 0178 dest.setPath(src.fileName()); 0179 KIO::Job *job = KIO::file_copy(src, dest, -1, KIO::Overwrite); 0180 KJobWidgets::setWindow(job, widget()); 0181 job->exec(); 0182 // The actual view refresh will be done automagically by the slots connected to kdirwatch 0183 } 0184 0185 void KCMDesignerFields::loadUiFiles() 0186 { 0187 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, uiPath(), QStandardPaths::LocateDirectory); 0188 for (const QString &dir : dirs) { 0189 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.ui")); 0190 for (const QString &file : fileNames) { 0191 new PageItem(mPageView, dir + QLatin1Char('/') + file); 0192 } 0193 } 0194 } 0195 0196 void KCMDesignerFields::rebuildList() 0197 { 0198 // If nothing is initialized there is no need to do something 0199 if (mPageView) { 0200 const QStringList ai = saveActivePages(); 0201 updatePreview(); 0202 mPageView->clear(); 0203 loadUiFiles(); 0204 loadActivePages(ai); 0205 } 0206 } 0207 0208 void KCMDesignerFields::loadActivePages(const QStringList &ai) 0209 { 0210 QTreeWidgetItemIterator it(mPageView); 0211 while (*it) { 0212 if ((*it)->parent() == nullptr) { 0213 auto item = static_cast<PageItem *>(*it); 0214 if (ai.contains(item->name())) { 0215 item->setCheckState(0, Qt::Checked); 0216 item->setIsActive(true); 0217 } 0218 } 0219 0220 ++it; 0221 } 0222 } 0223 0224 void KCMDesignerFields::load() 0225 { 0226 // see KCModule::showEvent() 0227 if (!mPageView) { 0228 delayedInit(); 0229 } 0230 loadActivePages(readActivePages()); 0231 } 0232 0233 QStringList KCMDesignerFields::saveActivePages() 0234 { 0235 QTreeWidgetItemIterator it(mPageView, QTreeWidgetItemIterator::Checked | QTreeWidgetItemIterator::Selectable); 0236 0237 QStringList activePages; 0238 while (*it) { 0239 if ((*it)->parent() == nullptr) { 0240 auto item = static_cast<PageItem *>(*it); 0241 activePages.append(item->name()); 0242 } 0243 0244 ++it; 0245 } 0246 0247 return activePages; 0248 } 0249 0250 void KCMDesignerFields::save() 0251 { 0252 writeActivePages(saveActivePages()); 0253 } 0254 0255 void KCMDesignerFields::defaults() 0256 { 0257 } 0258 0259 void KCMDesignerFields::initGUI() 0260 { 0261 auto layout = new QVBoxLayout(widget()); 0262 0263 const bool noDesigner = QStandardPaths::findExecutable(QStringLiteral("designer")).isEmpty(); 0264 0265 if (noDesigner) { 0266 const QString txt = i18n( 0267 "<qt><b>Warning:</b> Qt Designer could not be found. It is probably not " 0268 "installed. You will only be able to import existing designer files.</qt>"); 0269 auto lbl = new QLabel(txt, widget()); 0270 layout->addWidget(lbl); 0271 } 0272 0273 auto hbox = new QHBoxLayout(); 0274 layout->addLayout(hbox); 0275 0276 mPageView = new QTreeWidget(widget()); 0277 mPageView->setHeaderLabel(i18n("Available Pages")); 0278 mPageView->setRootIsDecorated(true); 0279 mPageView->setAllColumnsShowFocus(true); 0280 mPageView->header()->setSectionResizeMode(QHeaderView::Stretch); 0281 hbox->addWidget(mPageView); 0282 0283 auto box = new QGroupBox(i18n("Preview of Selected Page"), widget()); 0284 auto boxLayout = new QVBoxLayout(box); 0285 0286 mPagePreview = new QLabel(box); 0287 mPagePreview->setMinimumWidth(300); 0288 boxLayout->addWidget(mPagePreview); 0289 0290 mPageDetails = new QLabel(box); 0291 boxLayout->addWidget(mPageDetails); 0292 boxLayout->addStretch(1); 0293 0294 hbox->addWidget(box); 0295 0296 loadUiFiles(); 0297 0298 hbox = new QHBoxLayout(); 0299 layout->addLayout(hbox); 0300 0301 const QString cwHowto = i18n( 0302 "<qt><p>This section allows you to add your own GUI" 0303 " Elements ('<i>Widgets</i>') to store your own values" 0304 " into %1. Proceed as described below:</p>" 0305 "<ol>" 0306 "<li>Click on '<i>Edit with Qt Designer</i>'</li>" 0307 "<li>In the dialog, select '<i>Widget</i>', then click <i>OK</i></li>" 0308 "<li>Add your widgets to the form</li>" 0309 "<li>Save the file in the directory proposed by Qt Designer</li>" 0310 "<li>Close Qt Designer</li>" 0311 "</ol>" 0312 "<p>In case you already have a designer file (*.ui) located" 0313 " somewhere on your hard disk, simply choose '<i>Import Page</i>'</p>" 0314 "<p><b>Important:</b> The name of each input widget you place within" 0315 " the form must start with '<i>X_</i>'; so if you want the widget to" 0316 " correspond to your custom entry '<i>X-Foo</i>', set the widget's" 0317 " <i>name</i> property to '<i>X_Foo</i>'.</p>" 0318 "<p><b>Important:</b> The widget will edit custom fields with an" 0319 " application name of %2. To change the application name" 0320 " to be edited, set the widget name in Qt Designer.</p></qt>", 0321 applicationName(), 0322 applicationName()); 0323 0324 auto activeLabel = new QLabel(i18n("<a href=\"whatsthis:%1\">How does this work?</a>", cwHowto), widget()); 0325 activeLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); 0326 connect(activeLabel, &QLabel::linkActivated, this, &KCMDesignerFields::showWhatsThis); 0327 activeLabel->setContextMenuPolicy(Qt::NoContextMenu); 0328 hbox->addWidget(activeLabel); 0329 0330 // ### why is this needed? Looks like a KActiveLabel bug... 0331 activeLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); 0332 0333 hbox->addStretch(1); 0334 0335 mDeleteButton = new QPushButton(i18n("Delete Page"), widget()); 0336 mDeleteButton->setEnabled(false); 0337 hbox->addWidget(mDeleteButton); 0338 mImportButton = new QPushButton(i18n("Import Page..."), widget()); 0339 hbox->addWidget(mImportButton); 0340 mDesignerButton = new QPushButton(i18n("Edit with Qt Designer..."), widget()); 0341 hbox->addWidget(mDesignerButton); 0342 0343 if (noDesigner) { 0344 mDesignerButton->setEnabled(false); 0345 } 0346 } 0347 0348 void KCMDesignerFields::updatePreview() 0349 { 0350 QTreeWidgetItem *item = nullptr; 0351 if (mPageView->selectedItems().size() == 1) { 0352 item = mPageView->selectedItems().first(); 0353 } 0354 bool widgetItemSelected = false; 0355 0356 if (item) { 0357 if (item->parent()) { 0358 const QString details = QStringLiteral( 0359 "<qt><table>" 0360 "<tr><td align=\"right\"><b>%1</b></td><td>%2</td></tr>" 0361 "<tr><td align=\"right\"><b>%3</b></td><td>%4</td></tr>" 0362 "<tr><td align=\"right\"><b>%5</b></td><td>%6</td></tr>" 0363 "<tr><td align=\"right\"><b>%7</b></td><td>%8</td></tr>" 0364 "</table></qt>") 0365 .arg(i18n("Key:"), 0366 item->text(0).replace(QLatin1StringView("X_"), QStringLiteral("X-")), 0367 i18n("Type:"), 0368 item->text(1), 0369 i18n("Classname:"), 0370 item->text(2), 0371 i18n("Description:"), 0372 item->text(3)); 0373 0374 mPageDetails->setText(details); 0375 0376 auto pageItem = static_cast<PageItem *>(item->parent()); 0377 mPagePreview->setWindowIcon(pageItem->preview()); 0378 } else { 0379 mPageDetails->setText(QString()); 0380 0381 auto pageItem = static_cast<PageItem *>(item); 0382 mPagePreview->setWindowIcon(pageItem->preview()); 0383 0384 widgetItemSelected = true; 0385 } 0386 0387 mPagePreview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); 0388 } else { 0389 mPagePreview->setWindowIcon(QPixmap()); 0390 mPagePreview->setFrameStyle(0); 0391 mPageDetails->setText(QString()); 0392 } 0393 0394 mDeleteButton->setEnabled(widgetItemSelected); 0395 } 0396 0397 void KCMDesignerFields::itemClicked(QTreeWidgetItem *item) 0398 { 0399 if (!item || item->parent() != nullptr) { 0400 return; 0401 } 0402 0403 auto pageItem = static_cast<PageItem *>(item); 0404 0405 if (pageItem->isOn() != pageItem->isActive()) { 0406 markAsChanged(); 0407 pageItem->setIsActive(pageItem->isOn()); 0408 } 0409 } 0410 0411 void KCMDesignerFields::startDesigner() 0412 { 0413 // check if path exists and create one if not. 0414 QString cepPath = localUiDir(); 0415 if (!QDir(cepPath).exists()) { 0416 QDir().mkdir(cepPath); 0417 } 0418 0419 // finally jump there 0420 QDir::setCurrent(QLatin1StringView(cepPath.toLocal8Bit())); 0421 0422 QStringList args; 0423 QTreeWidgetItem *item = nullptr; 0424 if (mPageView->selectedItems().size() == 1) { 0425 item = mPageView->selectedItems().constFirst(); 0426 } 0427 if (item) { 0428 auto pageItem = static_cast<PageItem *>(item->parent() ? item->parent() : item); 0429 args.append(pageItem->path()); 0430 } 0431 0432 auto job = new KIO::CommandLauncherJob(QStringLiteral("designer"), args, this); 0433 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, widget())); 0434 job->start(); 0435 } 0436 0437 void KCMDesignerFields::showWhatsThis(const QString &href) 0438 { 0439 if (href.startsWith(QLatin1StringView("whatsthis:"))) { 0440 const QPoint pos = QCursor::pos(); 0441 QWhatsThis::showText(pos, href.mid(10), widget()); 0442 } 0443 } 0444 0445 #include "moc_kcmdesignerfields.cpp"