File indexing completed on 2024-05-19 05:21:21
0001 /******************************************************************* 0002 KNotes -- Notes for the KDE project 0003 0004 SPDX-FileCopyrightText: 1997-2013 The KNotes Developers 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 *******************************************************************/ 0008 0009 #include "knote.h" 0010 #include "alarms/notealarmdialog.h" 0011 #include "attributes/notealarmattribute.h" 0012 #include "attributes/notedisplayattribute.h" 0013 #include "attributes/notelockattribute.h" 0014 #include "configdialog/knotesimpleconfigdialog.h" 0015 #include "knotedisplaysettings.h" 0016 #include "knoteedit.h" 0017 #include "knotes_debug.h" 0018 #include "knotesglobalconfig.h" 0019 #include "notes/knotebutton.h" 0020 #include "noteutils.h" 0021 #include "print/knoteprinter.h" 0022 #include "print/knoteprintobject.h" 0023 #include "print/knoteprintselectthemedialog.h" 0024 #include "utils/knoteutils.h" 0025 0026 #include <Akonadi/ItemModifyJob> 0027 0028 #include <Debug/akonadisearchdebugdialog.h> 0029 0030 #include <KMime/KMimeMessage> 0031 0032 #include <KActionCollection> 0033 #include <KComboBox> 0034 #include <KFileCustomDialog> 0035 #include <KIconEffect> 0036 #include <KLocalizedString> 0037 #include <KMessageBox> 0038 #include <KToggleAction> 0039 #include <KToolBar> 0040 #include <KWindowSystem> 0041 #include <KXMLGUIBuilder> 0042 #include <KXMLGUIFactory> 0043 0044 #include <QApplication> 0045 #include <QCheckBox> 0046 #include <QInputDialog> 0047 #include <QLabel> 0048 #include <QMenu> 0049 #include <QMimeData> 0050 #include <QPointer> 0051 #include <QScreen> 0052 #include <QSizeGrip> 0053 #include <QTextEdit> 0054 #include <QVBoxLayout> 0055 #include <QWindow> 0056 0057 #if KDEPIM_HAVE_X11 0058 #include <KWindowInfo> 0059 #include <KX11Extras> 0060 #include <NETWM> 0061 #include <fixx11h.h> 0062 #endif 0063 0064 //#define DEBUG_SAVE_NOTE 1 0065 0066 KNote::KNote(const QDomDocument &buildDoc, const Akonadi::Item &item, bool allowAkonadiSearchDebug, QWidget *parent) 0067 : QFrame(parent, Qt::FramelessWindowHint) 0068 , mItem(item) 0069 , m_kwinConf(KSharedConfig::openConfig(QStringLiteral("kwinrc"))) 0070 , mDisplayAttribute(new KNoteDisplaySettings) 0071 , mAllowDebugAkonadiSearch(allowAkonadiSearchDebug) 0072 { 0073 if (mItem.hasAttribute<NoteShared::NoteDisplayAttribute>()) { 0074 mDisplayAttribute->setDisplayAttribute(mItem.attribute<NoteShared::NoteDisplayAttribute>()); 0075 } else { 0076 setDisplayDefaultValue(); 0077 // save default display value 0078 } 0079 setAcceptDrops(true); 0080 setAttribute(Qt::WA_DeleteOnClose); 0081 setDOMDocument(buildDoc); 0082 setXMLFile(componentName() + QLatin1StringView("ui.rc"), false, false); 0083 0084 // create the main layout 0085 m_noteLayout = new QVBoxLayout(this); 0086 m_noteLayout->setContentsMargins(0, 0, 0, 0); 0087 createActions(); 0088 0089 buildGui(); 0090 prepare(); 0091 } 0092 0093 KNote::~KNote() 0094 { 0095 delete mDisplayAttribute; 0096 } 0097 0098 void KNote::setDisplayDefaultValue() 0099 { 0100 KNoteUtils::setDefaultValue(mItem); 0101 auto job = new Akonadi::ItemModifyJob(mItem); 0102 #ifdef DEBUG_SAVE_NOTE 0103 qCDebug(KNOTES_LOG) << "setDisplayDefaultValue slotNoteSaved(KJob*)"; 0104 #endif 0105 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0106 } 0107 0108 void KNote::setChangeItem(const Akonadi::Item &item, const QSet<QByteArray> &set) 0109 { 0110 mItem = item; 0111 if (item.hasAttribute<NoteShared::NoteDisplayAttribute>()) { 0112 mDisplayAttribute->setDisplayAttribute(item.attribute<NoteShared::NoteDisplayAttribute>()); 0113 } 0114 if (set.contains("KJotsLockAttribute")) { 0115 m_editor->setReadOnly(item.hasAttribute<NoteShared::NoteLockAttribute>()); 0116 } 0117 if (set.contains("PLD:RFC822")) { 0118 loadNoteContent(item); 0119 } 0120 if (set.contains("NoteDisplayAttribute")) { 0121 qCDebug(KNOTES_LOG) << " ATR:NoteDisplayAttribute"; 0122 slotApplyConfig(); 0123 } 0124 // TODO update display/content etc. 0125 updateLabelAlignment(); 0126 } 0127 0128 void KNote::slotKill(bool force) 0129 { 0130 if (!force 0131 && KMessageBox::warningContinueCancel(this, 0132 i18n("<qt>Do you really want to delete note <b>%1</b>?</qt>", m_label->text()), 0133 i18n("Confirm Delete"), 0134 KGuiItem(i18n("&Delete"), QStringLiteral("edit-delete")), 0135 KStandardGuiItem::cancel(), 0136 QStringLiteral("ConfirmDeleteNote")) 0137 != KMessageBox::Continue) { 0138 return; 0139 } 0140 0141 Q_EMIT sigKillNote(mItem.id()); 0142 } 0143 0144 // -------------------- public member functions -------------------- // 0145 0146 void KNote::saveNote(bool force, bool sync) 0147 { 0148 if (!force && !m_editor->document()->isModified()) { 0149 return; 0150 } 0151 bool needToSave = false; 0152 auto attribute = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 0153 const QPoint notePosition = pos(); 0154 if (attribute->position() != notePosition) { 0155 needToSave = true; 0156 attribute->setPosition(notePosition); 0157 } 0158 const QSize currentSize(QSize(width(), height())); 0159 if (attribute->size() != currentSize) { 0160 needToSave = true; 0161 attribute->setSize(currentSize); 0162 } 0163 #if KDEPIM_HAVE_X11 0164 KWindowInfo info(winId(), NET::WMDesktop); 0165 const int count = KX11Extras::numberOfDesktops(); 0166 for (int n = 1; n <= count; ++n) { 0167 if (info.isOnDesktop(n)) { 0168 if (attribute->desktop() != n) { 0169 needToSave = true; 0170 attribute->setDesktop(n); 0171 break; 0172 } 0173 } 0174 } 0175 #endif 0176 if (m_editor->document()->isModified()) { 0177 needToSave = true; 0178 saveNoteContent(); 0179 } 0180 if (needToSave) { 0181 #ifdef DEBUG_SAVE_NOTE 0182 qCDebug(KNOTES_LOG) << "save Note slotClose() slotNoteSaved(KJob*) : sync" << sync; 0183 #endif 0184 auto job = new Akonadi::ItemModifyJob(mItem); 0185 if (sync) { 0186 job->exec(); 0187 } else { 0188 #ifdef DEBUG_SAVE_NOTE 0189 qCDebug(KNOTES_LOG) << "save Note slotClose() slotNoteSaved(KJob*)"; 0190 #endif 0191 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0192 } 0193 } 0194 } 0195 0196 void KNote::slotNoteSaved(KJob *job) 0197 { 0198 qCDebug(KNOTES_LOG) << " void KNote::slotNoteSaved(KJob *job)"; 0199 if (job->error()) { 0200 qCDebug(KNOTES_LOG) << " problem during save note:" << job->errorString(); 0201 } else { 0202 m_editor->document()->setModified(false); 0203 } 0204 } 0205 0206 Akonadi::Item::Id KNote::noteId() const 0207 { 0208 return mItem.id(); 0209 } 0210 0211 QString KNote::name() const 0212 { 0213 return m_label->text(); 0214 } 0215 0216 QString KNote::text() const 0217 { 0218 return m_editor->text(); 0219 } 0220 0221 void KNote::setName(const QString &name) 0222 { 0223 m_label->setText(name); 0224 updateLabelAlignment(); 0225 0226 if (m_editor) { // not called from CTOR? 0227 saveNote(); 0228 } 0229 setWindowTitle(name); 0230 0231 Q_EMIT sigNameChanged(name); 0232 } 0233 0234 void KNote::setText(const QString &text) 0235 { 0236 m_editor->setText(text); 0237 0238 saveNote(); 0239 } 0240 0241 bool KNote::isDesktopAssigned() const 0242 { 0243 return mDisplayAttribute->rememberDesktop(); 0244 } 0245 0246 bool KNote::isModified() const 0247 { 0248 return m_editor->document()->isModified(); 0249 } 0250 0251 // ------------------ private slots (menu actions) ------------------ // 0252 0253 void KNote::slotRename() 0254 { 0255 // pop up dialog to get the new name 0256 bool ok; 0257 const QString oldName = m_label->text(); 0258 const QString newName = QInputDialog::getText(this, QString(), i18n("Please enter the new name:"), QLineEdit::Normal, m_label->text(), &ok); 0259 if (!ok || (oldName == newName)) { // handle cancel 0260 return; 0261 } 0262 0263 setName(newName); 0264 } 0265 0266 void KNote::slotUpdateReadOnly() 0267 { 0268 const bool readOnly = m_readOnly->isChecked(); 0269 0270 m_editor->setReadOnly(readOnly); 0271 0272 if (mItem.hasAttribute<NoteShared::NoteLockAttribute>()) { 0273 if (!readOnly) { 0274 mItem.removeAttribute<NoteShared::NoteLockAttribute>(); 0275 } 0276 } else { 0277 if (readOnly) { 0278 mItem.attribute<NoteShared::NoteLockAttribute>(Akonadi::Item::AddIfMissing); 0279 } 0280 } 0281 if (!mBlockSave) { 0282 updateAllAttributes(); 0283 auto job = new Akonadi::ItemModifyJob(mItem); 0284 #ifdef DEBUG_SAVE_NOTE 0285 qCDebug(KNOTES_LOG) << " void KNote::slotUpdateReadOnly() slotNoteSaved(KJob*)"; 0286 #endif 0287 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0288 } 0289 0290 // enable/disable actions accordingly 0291 actionCollection()->action(QStringLiteral("configure_note"))->setEnabled(!readOnly); 0292 actionCollection()->action(QStringLiteral("delete_note"))->setEnabled(!readOnly); 0293 actionCollection()->action(QStringLiteral("format_bold"))->setEnabled(!readOnly); 0294 actionCollection()->action(QStringLiteral("format_italic"))->setEnabled(!readOnly); 0295 actionCollection()->action(QStringLiteral("format_underline"))->setEnabled(!readOnly); 0296 actionCollection()->action(QStringLiteral("format_strikeout"))->setEnabled(!readOnly); 0297 actionCollection()->action(QStringLiteral("format_alignleft"))->setEnabled(!readOnly); 0298 actionCollection()->action(QStringLiteral("format_aligncenter"))->setEnabled(!readOnly); 0299 actionCollection()->action(QStringLiteral("format_alignright"))->setEnabled(!readOnly); 0300 actionCollection()->action(QStringLiteral("format_alignblock"))->setEnabled(!readOnly); 0301 actionCollection()->action(QStringLiteral("format_list"))->setEnabled(!readOnly); 0302 actionCollection()->action(QStringLiteral("format_super"))->setEnabled(!readOnly); 0303 actionCollection()->action(QStringLiteral("format_sub"))->setEnabled(!readOnly); 0304 actionCollection()->action(QStringLiteral("format_increaseindent"))->setEnabled(!readOnly); 0305 actionCollection()->action(QStringLiteral("format_decreaseindent"))->setEnabled(!readOnly); 0306 actionCollection()->action(QStringLiteral("text_background_color"))->setEnabled(!readOnly); 0307 actionCollection()->action(QStringLiteral("format_size"))->setEnabled(!readOnly); 0308 actionCollection()->action(QStringLiteral("format_color"))->setEnabled(!readOnly); 0309 actionCollection()->action(QStringLiteral("rename_note"))->setEnabled(!readOnly); 0310 actionCollection()->action(QStringLiteral("set_alarm"))->setEnabled(!readOnly); 0311 m_keepAbove->setEnabled(!readOnly); 0312 m_keepBelow->setEnabled(!readOnly); 0313 0314 #if KDEPIM_HAVE_X11 0315 m_toDesktop->setEnabled(!readOnly); 0316 #endif 0317 0318 updateFocus(); 0319 } 0320 0321 void KNote::updateAllAttributes() 0322 { 0323 auto attribute = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 0324 #if KDEPIM_HAVE_X11 0325 KWindowInfo info(winId(), NET::WMDesktop); 0326 const int count = KX11Extras::numberOfDesktops(); 0327 for (int n = 1; n <= count; ++n) { 0328 if (info.isOnDesktop(n)) { 0329 attribute->setDesktop(n); 0330 } 0331 } 0332 #endif 0333 saveNoteContent(); 0334 attribute->setIsHidden(true); 0335 attribute->setPosition(pos()); 0336 const QSize currentSize(QSize(width(), height())); 0337 if (attribute->size() != currentSize) { 0338 attribute->setSize(currentSize); 0339 } 0340 } 0341 0342 void KNote::slotClose() 0343 { 0344 updateAllAttributes(); 0345 m_editor->clearFocus(); 0346 auto job = new Akonadi::ItemModifyJob(mItem); 0347 #ifdef DEBUG_SAVE_NOTE 0348 qCDebug(KNOTES_LOG) << "slotClose() slotNoteSaved(KJob*)"; 0349 #endif 0350 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0351 hide(); 0352 } 0353 0354 void KNote::slotSetAlarm() 0355 { 0356 QPointer<NoteShared::NoteAlarmDialog> dlg = new NoteShared::NoteAlarmDialog(name(), this); 0357 if (mItem.hasAttribute<NoteShared::NoteAlarmAttribute>()) { 0358 dlg->setAlarm(mItem.attribute<NoteShared::NoteAlarmAttribute>()->dateTime()); 0359 } 0360 if (dlg->exec()) { 0361 bool needToModify = true; 0362 QDateTime dateTime = dlg->alarm(); 0363 if (dateTime.isValid()) { 0364 auto attribute = mItem.attribute<NoteShared::NoteAlarmAttribute>(Akonadi::Item::AddIfMissing); 0365 attribute->setDateTime(dateTime); 0366 } else { 0367 if (mItem.hasAttribute<NoteShared::NoteAlarmAttribute>()) { 0368 mItem.removeAttribute<NoteShared::NoteAlarmAttribute>(); 0369 } else { 0370 needToModify = false; 0371 } 0372 } 0373 if (needToModify) { 0374 // Verify it! 0375 saveNoteContent(); 0376 auto job = new Akonadi::ItemModifyJob(mItem); 0377 #ifdef DEBUG_SAVE_NOTE 0378 qCDebug(KNOTES_LOG) << "setAlarm() slotNoteSaved(KJob*)"; 0379 #endif 0380 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0381 } 0382 } 0383 delete dlg; 0384 } 0385 0386 void KNote::saveNoteContent() 0387 { 0388 auto message = mItem.payload<KMime::Message::Ptr>(); 0389 const QByteArray encoding("utf-8"); 0390 message->subject(true)->fromUnicodeString(name(), encoding); 0391 message->contentType(true)->setMimeType(m_editor->acceptRichText() ? "text/html" : "text/plain"); 0392 message->contentType()->setCharset(encoding); 0393 message->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEquPr); 0394 message->date(true)->setDateTime(QDateTime::currentDateTime()); 0395 message->mainBodyPart()->fromUnicodeString(text().isEmpty() ? QStringLiteral(" ") : text()); 0396 0397 auto header = new KMime::Headers::Generic("X-Cursor-Position"); 0398 header->fromUnicodeString(QString::number(m_editor->cursorPositionFromStart()), "utf-8"); 0399 message->setHeader(header); 0400 0401 message->assemble(); 0402 0403 mItem.setPayload(message); 0404 } 0405 0406 void KNote::slotPreferences() 0407 { 0408 // create a new preferences dialog... 0409 QPointer<KNoteSimpleConfigDialog> dialog = new KNoteSimpleConfigDialog(name(), this); 0410 auto attribute = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 0411 attribute->setSize(QSize(width(), height())); 0412 0413 dialog->load(mItem, m_editor->acceptRichText()); 0414 connect(this, &KNote::sigNameChanged, dialog.data(), &KNoteSimpleConfigDialog::slotUpdateCaption); 0415 if (dialog->exec()) { 0416 bool isRichText; 0417 dialog->save(mItem, isRichText); 0418 m_editor->setAcceptRichText(isRichText); 0419 saveNoteContent(); 0420 auto job = new Akonadi::ItemModifyJob(mItem); 0421 #ifdef DEBUG_SAVE_NOTE 0422 qCDebug(KNOTES_LOG) << "slotPreference slotNoteSaved(KJob*)"; 0423 #endif 0424 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0425 } 0426 delete dialog; 0427 } 0428 0429 void KNote::slotSend() 0430 { 0431 NoteShared::NoteUtils noteUtils; 0432 noteUtils.sendToNetwork(this, name(), text()); 0433 } 0434 0435 void KNote::slotMail() 0436 { 0437 NoteShared::NoteUtils noteUtils; 0438 noteUtils.sendToMail(this, m_label->text(), m_editor->toPlainText()); 0439 } 0440 0441 void KNote::slotPrint() 0442 { 0443 print(false); 0444 } 0445 0446 void KNote::slotPrintPreview() 0447 { 0448 print(true); 0449 } 0450 0451 void KNote::print(bool preview) 0452 { 0453 if (isModified()) { 0454 saveNote(); 0455 } 0456 0457 KNotesGlobalConfig *globalConfig = KNotesGlobalConfig::self(); 0458 QString printingTheme = globalConfig->theme(); 0459 if (printingTheme.isEmpty()) { 0460 QPointer<KNotePrintSelectThemeDialog> dlg = new KNotePrintSelectThemeDialog(this); 0461 if (dlg->exec()) { 0462 printingTheme = dlg->selectedTheme(); 0463 } 0464 delete dlg; 0465 } 0466 if (!printingTheme.isEmpty()) { 0467 KNotePrinter printer(this); 0468 QList<KNotePrintObject *> lst; 0469 lst.append(new KNotePrintObject(mItem)); 0470 printer.setDefaultFont(mDisplayAttribute->font()); 0471 printer.printNotes(lst, printingTheme, preview); 0472 qDeleteAll(lst); 0473 } 0474 } 0475 0476 void KNote::slotSaveAs() 0477 { 0478 // TODO: where to put pdf file support? In the printer??!??! 0479 QUrl url; 0480 QPointer<KFileCustomDialog> dlg = new KFileCustomDialog(this); 0481 QCheckBox *convert = nullptr; 0482 if (m_editor->acceptRichText()) { 0483 convert = new QCheckBox(dlg.data()); 0484 convert->setText(i18n("Save note as plain text")); 0485 } 0486 if (convert) { 0487 dlg->setCustomWidget(convert); 0488 } 0489 dlg->setUrl(url); 0490 dlg->setOperationMode(KFileWidget::Saving); 0491 dlg->setWindowTitle(i18nc("@title:window", "Save As")); 0492 if (!dlg->exec()) { 0493 delete dlg; 0494 return; 0495 } 0496 0497 const QString fileName = dlg->fileWidget()->selectedFile(); 0498 const bool htmlFormatAndSaveAsHtml = (convert && !convert->isChecked()); 0499 delete dlg; 0500 if (fileName.isEmpty()) { 0501 return; 0502 } 0503 0504 QFile file(fileName); 0505 0506 if (file.exists() 0507 && KMessageBox::warningContinueCancel(this, 0508 i18n("<qt>A file named <b>%1</b> already exists.<br />" 0509 "Are you sure you want to overwrite it?</qt>", 0510 QFileInfo(file).fileName())) 0511 != KMessageBox::Continue) { 0512 return; 0513 } 0514 0515 if (file.open(QIODevice::WriteOnly)) { 0516 QTextStream stream(&file); 0517 if (htmlFormatAndSaveAsHtml) { 0518 QString htmlStr = m_editor->toHtml(); 0519 htmlStr.replace(QStringLiteral("meta name=\"qrichtext\" content=\"1\""), 0520 QStringLiteral("meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"")); 0521 stream << htmlStr; 0522 } else { 0523 stream << m_editor->toPlainText(); 0524 } 0525 } 0526 } 0527 0528 void KNote::slotPopupActionToDesktop(QAction *act) 0529 { 0530 const int id = act->data().toInt(); 0531 toDesktop(id); // compensate for the menu separator, -1 == all desktops 0532 } 0533 0534 // ------------------ private slots (configuration) ------------------ // 0535 0536 void KNote::slotApplyConfig() 0537 { 0538 m_label->setFont(mDisplayAttribute->titleFont()); 0539 m_editor->setTextFont(mDisplayAttribute->font()); 0540 m_editor->setTabStop(mDisplayAttribute->tabSize()); 0541 m_editor->setAutoIndentMode(mDisplayAttribute->autoIndent()); 0542 0543 setColor(mDisplayAttribute->foregroundColor(), mDisplayAttribute->backgroundColor()); 0544 0545 updateLayout(); 0546 #if KDEPIM_HAVE_X11 0547 slotUpdateShowInTaskbar(); 0548 #endif 0549 resize(mDisplayAttribute->size()); 0550 } 0551 0552 void KNote::slotKeepAbove() 0553 { 0554 if (m_keepBelow->isChecked()) { 0555 m_keepBelow->setChecked(false); 0556 } 0557 updateKeepAboveBelow(); 0558 } 0559 0560 void KNote::slotKeepBelow() 0561 { 0562 if (m_keepAbove->isChecked()) { 0563 m_keepAbove->setChecked(false); 0564 } 0565 updateKeepAboveBelow(); 0566 } 0567 0568 void KNote::updateKeepAboveBelow(bool save) 0569 { 0570 auto attribute = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 0571 if (m_keepAbove->isChecked()) { 0572 attribute->setKeepAbove(true); 0573 attribute->setKeepBelow(false); 0574 windowHandle()->setFlag(Qt::WindowStaysOnTopHint, true); 0575 } else if (m_keepBelow->isChecked()) { 0576 attribute->setKeepAbove(false); 0577 attribute->setKeepBelow(true); 0578 windowHandle()->setFlag(Qt::WindowStaysOnBottomHint, true); 0579 } else { 0580 attribute->setKeepAbove(false); 0581 attribute->setKeepBelow(false); 0582 windowHandle()->setFlag(Qt::WindowStaysOnTopHint, false); 0583 windowHandle()->setFlag(Qt::WindowStaysOnBottomHint, false); 0584 } 0585 if (!mBlockSave && save) { 0586 saveNoteContent(); 0587 auto job = new Akonadi::ItemModifyJob(mItem); 0588 #ifdef DEBUG_SAVE_NOTE 0589 qCDebug(KNOTES_LOG) << "slotUpdateKeepAboveBelow slotNoteSaved(KJob*)"; 0590 #endif 0591 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 0592 } 0593 } 0594 0595 void KNote::slotUpdateShowInTaskbar() 0596 { 0597 #if KDEPIM_HAVE_X11 0598 if (KWindowSystem::isPlatformX11()) { 0599 if (!mDisplayAttribute->showInTaskbar()) { 0600 KX11Extras::setState(winId(), KWindowInfo(winId(), NET::WMState).state() | NET::SkipTaskbar); 0601 } else { 0602 KX11Extras::clearState(winId(), NET::SkipTaskbar); 0603 } 0604 } 0605 #endif 0606 } 0607 0608 void KNote::slotUpdateDesktopActions() 0609 { 0610 #if KDEPIM_HAVE_X11 0611 m_toDesktop->clear(); 0612 0613 QAction *act = m_toDesktop->addAction(i18n("&All Desktops")); 0614 KWindowInfo info(winId(), NET::WMDesktop); 0615 0616 if (info.onAllDesktops()) { 0617 act->setChecked(true); 0618 act->setData(NETWinInfo::OnAllDesktops); 0619 } 0620 auto separator = new QAction(m_toDesktop); 0621 separator->setSeparator(true); 0622 m_toDesktop->addAction(separator); 0623 const int count = KX11Extras::numberOfDesktops(); 0624 for (int n = 1; n <= count; ++n) { 0625 QAction *desktopAct = m_toDesktop->addAction(QStringLiteral("&%1 %2").arg(n).arg(KX11Extras::desktopName(n))); 0626 desktopAct->setData(n); 0627 if (info.isOnDesktop(n)) { 0628 desktopAct->setChecked(true); 0629 } 0630 } 0631 #endif 0632 } 0633 0634 // -------------------- private methods -------------------- // 0635 0636 void KNote::buildGui() 0637 { 0638 createNoteHeader(); 0639 createNoteEditor(QString()); 0640 0641 KXMLGUIBuilder builder(this); 0642 KXMLGUIFactory factory(&builder, this); 0643 factory.addClient(this); 0644 0645 m_menu = qobject_cast<QMenu *>(factory.container(QStringLiteral("note_context"), this)); 0646 m_tool = qobject_cast<KToolBar *>(factory.container(QStringLiteral("note_tool"), this)); 0647 0648 createNoteFooter(); 0649 } 0650 0651 void KNote::createActions() 0652 { 0653 // create the menu items for the note - not the editor... 0654 // rename, mail, print, save as, insert date, alarm, close, delete, new note 0655 auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New"), this); 0656 actionCollection()->addAction(QStringLiteral("new_note"), action); 0657 connect(action, &QAction::triggered, this, &KNote::slotRequestNewNote); 0658 0659 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename..."), this); 0660 actionCollection()->addAction(QStringLiteral("rename_note"), action); 0661 connect(action, &QAction::triggered, this, &KNote::slotRename); 0662 0663 m_readOnly = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-locked")), i18n("Lock"), this); 0664 actionCollection()->addAction(QStringLiteral("lock_note"), m_readOnly); 0665 connect(m_readOnly, &KToggleAction::triggered, this, &KNote::slotUpdateReadOnly); 0666 m_readOnly->setCheckedState(KGuiItem(i18n("Unlock"), QStringLiteral("object-unlocked"))); 0667 0668 action = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("Hide"), this); 0669 actionCollection()->addAction(QStringLiteral("hide_note"), action); 0670 connect(action, &QAction::triggered, this, &KNote::slotClose); 0671 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Escape)); 0672 0673 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete"), this); 0674 actionCollection()->addAction(QStringLiteral("delete_note"), action); 0675 connect(action, &QAction::triggered, this, &KNote::slotKill); 0676 0677 action = new QAction(QIcon::fromTheme(QStringLiteral("knotes_alarm")), i18n("Set Alarm..."), this); 0678 actionCollection()->addAction(QStringLiteral("set_alarm"), action); 0679 connect(action, &QAction::triggered, this, &KNote::slotSetAlarm); 0680 0681 action = new QAction(QIcon::fromTheme(QStringLiteral("network-wired")), i18n("Send..."), this); 0682 actionCollection()->addAction(QStringLiteral("send_note"), action); 0683 connect(action, &QAction::triggered, this, &KNote::slotSend); 0684 0685 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("Mail..."), this); 0686 actionCollection()->addAction(QStringLiteral("mail_note"), action); 0687 connect(action, &QAction::triggered, this, &KNote::slotMail); 0688 0689 action = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As..."), this); 0690 actionCollection()->addAction(QStringLiteral("save_note"), action); 0691 connect(action, &QAction::triggered, this, &KNote::slotSaveAs); 0692 action = actionCollection()->addAction(KStandardAction::Print, QStringLiteral("print_note")); 0693 connect(action, &QAction::triggered, this, &KNote::slotPrint); 0694 0695 action = actionCollection()->addAction(KStandardAction::PrintPreview, QStringLiteral("print_preview_note")); 0696 connect(action, &QAction::triggered, this, &KNote::slotPrintPreview); 0697 0698 action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Preferences..."), this); 0699 actionCollection()->addAction(QStringLiteral("configure_note"), action); 0700 connect(action, &QAction::triggered, this, &KNote::slotPreferences); 0701 0702 m_keepAbove = new KToggleAction(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Keep Above Others"), this); 0703 actionCollection()->addAction(QStringLiteral("keep_above"), m_keepAbove); 0704 connect(m_keepAbove, &KToggleAction::triggered, this, &KNote::slotKeepAbove); 0705 0706 m_keepBelow = new KToggleAction(QIcon::fromTheme(QStringLiteral("go-down")), i18n("Keep Below Others"), this); 0707 actionCollection()->addAction(QStringLiteral("keep_below"), m_keepBelow); 0708 connect(m_keepBelow, &KToggleAction::triggered, this, &KNote::slotKeepBelow); 0709 0710 #if KDEPIM_HAVE_X11 0711 m_toDesktop = new KSelectAction(i18n("To Desktop"), this); 0712 actionCollection()->addAction(QStringLiteral("to_desktop"), m_toDesktop); 0713 connect(m_toDesktop, &KSelectAction::actionTriggered, this, &KNote::slotPopupActionToDesktop); 0714 connect(m_toDesktop->menu(), &QMenu::aboutToShow, this, &KNote::slotUpdateDesktopActions); 0715 // initially populate it, otherwise stays disabled 0716 slotUpdateDesktopActions(); 0717 #endif 0718 // invisible action to walk through the notes to make this configurable 0719 action = new QAction(i18n("Walk Through Notes"), this); 0720 actionCollection()->addAction(QStringLiteral("walk_notes"), action); 0721 connect(action, &QAction::triggered, this, &KNote::sigShowNextNote); 0722 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT | Qt::Key_Backtab)); 0723 0724 actionCollection()->addAssociatedWidget(this); 0725 const auto lst = actionCollection()->actions(); 0726 for (QAction *act : lst) { 0727 act->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0728 } 0729 if (mAllowDebugAkonadiSearch) { 0730 // Don't translate it it's just for debugging 0731 action = new QAction(QStringLiteral("Debug Akonadi Search..."), this); 0732 actionCollection()->addAction(QStringLiteral("debug_akonadi_search"), action); 0733 connect(action, &QAction::triggered, this, &KNote::slotDebugAkonadiSearch); 0734 } 0735 } 0736 0737 void KNote::createNoteHeader() 0738 { 0739 // load style configuration 0740 KConfigGroup styleGroup(m_kwinConf, QStringLiteral("Style")); 0741 0742 QBoxLayout::Direction headerLayoutDirection = QBoxLayout::LeftToRight; 0743 0744 if (styleGroup.readEntry("CustomButtonPositions", false)) { 0745 if (styleGroup.readEntry("ButtonsOnLeft").contains(QLatin1Char('X'))) { 0746 headerLayoutDirection = QBoxLayout::RightToLeft; 0747 } 0748 } 0749 0750 auto headerLayout = new QBoxLayout(headerLayoutDirection); 0751 0752 // create header label 0753 m_label = new QLabel(this); 0754 headerLayout->addWidget(m_label); 0755 m_label->setFrameStyle(NoFrame); 0756 m_label->setBackgroundRole(QPalette::Base); 0757 m_label->setLineWidth(0); 0758 m_label->setAutoFillBackground(true); 0759 m_label->installEventFilter(this); // receive events ( for dragging & 0760 // action menu ) 0761 m_button = new KNoteButton(QStringLiteral("knotes_close"), this); 0762 headerLayout->addWidget(m_button); 0763 0764 connect(m_button, &KNoteButton::clicked, this, &KNote::slotClose); 0765 0766 m_noteLayout->addLayout(headerLayout); 0767 } 0768 0769 void KNote::createNoteEditor(const QString &configFile) 0770 { 0771 Q_UNUSED(configFile) 0772 m_editor = new KNoteEdit(actionCollection(), this); 0773 m_noteLayout->addWidget(m_editor); 0774 m_editor->setNote(this); 0775 m_editor->installEventFilter(this); // receive focus events for modified 0776 setFocusProxy(m_editor); 0777 } 0778 0779 void KNote::slotRequestNewNote() 0780 { 0781 // Be sure to save before to request a new note 0782 saveNote(); 0783 Q_EMIT sigRequestNewNote(); 0784 } 0785 0786 void KNote::createNoteFooter() 0787 { 0788 if (m_tool) { 0789 m_tool->setIconSize(QSize(10, 10)); 0790 m_tool->setFixedHeight(24); 0791 m_tool->setToolButtonStyle(Qt::ToolButtonIconOnly); 0792 } 0793 0794 // create size grip 0795 auto gripLayout = new QHBoxLayout; 0796 m_grip = new QSizeGrip(this); 0797 m_grip->setFixedSize(m_grip->sizeHint()); 0798 0799 if (m_tool) { 0800 gripLayout->addWidget(m_tool); 0801 gripLayout->setAlignment(m_tool, Qt::AlignBottom | Qt::AlignLeft); 0802 m_tool->hide(); 0803 } 0804 0805 gripLayout->addWidget(m_grip); 0806 gripLayout->setAlignment(m_grip, Qt::AlignBottom | Qt::AlignRight); 0807 m_noteLayout->addLayout(gripLayout); 0808 0809 // if there was just a way of making KComboBox adhere the toolbar height... 0810 if (m_tool) { 0811 const auto comboboxs = m_tool->findChildren<KComboBox *>(); 0812 for (KComboBox *combo : comboboxs) { 0813 QFont font = combo->font(); 0814 font.setPointSize(7); 0815 combo->setFont(font); 0816 combo->setFixedHeight(14); 0817 } 0818 } 0819 } 0820 0821 void KNote::loadNoteContent(const Akonadi::Item &item) 0822 { 0823 auto noteMessage = item.payload<KMime::Message::Ptr>(); 0824 const KMime::Headers::Subject *const subject = noteMessage ? noteMessage->subject(false) : nullptr; 0825 setName(subject ? subject->asUnicodeString() : QString()); 0826 if (noteMessage->contentType()->isHTMLText()) { 0827 m_editor->setAcceptRichText(true); 0828 m_editor->setAutoFormatting(QTextEdit::AutoAll); 0829 m_editor->setHtml(noteMessage->mainBodyPart()->decodedText()); 0830 } else { 0831 m_editor->setAcceptRichText(false); 0832 m_editor->setAutoFormatting(QTextEdit::AutoNone); 0833 m_editor->setPlainText(noteMessage->mainBodyPart()->decodedText()); 0834 } 0835 if (auto hrd = noteMessage->headerByType("X-Cursor-Position")) { 0836 m_editor->setCursorPositionFromStart(hrd->asUnicodeString().toInt()); 0837 } 0838 } 0839 0840 void KNote::prepare() 0841 { 0842 mBlockSave = true; 0843 loadNoteContent(mItem); 0844 0845 resize(mDisplayAttribute->size()); 0846 const QPoint &position = mDisplayAttribute->position(); 0847 QRect desk = qApp->primaryScreen()->virtualGeometry(); 0848 desk.adjust(10, 10, -10, -10); 0849 if (desk.intersects(QRect(position, mDisplayAttribute->size()))) { 0850 move(position); // do before calling show() to avoid flicker 0851 } 0852 if (mDisplayAttribute->isHidden()) { 0853 hide(); 0854 } else { 0855 show(); 0856 } 0857 // read configuration settings... 0858 slotApplyConfig(); 0859 0860 if (mItem.hasAttribute<NoteShared::NoteLockAttribute>()) { 0861 m_editor->setReadOnly(true); 0862 m_readOnly->setChecked(true); 0863 } else { 0864 m_readOnly->setChecked(false); 0865 } 0866 slotUpdateReadOnly(); 0867 // if this is a new note put on current desktop - we can't use defaults 0868 // in KConfig XT since only _changes_ will be stored in the config file 0869 int desktop = mDisplayAttribute->desktop(); 0870 0871 #if KDEPIM_HAVE_X11 0872 if ((desktop < 0 && desktop != NETWinInfo::OnAllDesktops) || !mDisplayAttribute->rememberDesktop()) { 0873 desktop = KX11Extras::currentDesktop(); 0874 } 0875 #endif 0876 0877 // show the note if desired 0878 if (desktop != 0 && !mDisplayAttribute->isHidden()) { 0879 // to avoid flicker, call this before show() 0880 toDesktop(desktop); 0881 show(); 0882 0883 // because KWin forgets about that for hidden windows 0884 #if KDEPIM_HAVE_X11 0885 if (desktop == NETWinInfo::OnAllDesktops) { 0886 toDesktop(desktop); 0887 } 0888 #endif 0889 } 0890 0891 if (mDisplayAttribute->keepAbove()) { 0892 m_keepAbove->setChecked(true); 0893 } else if (mDisplayAttribute->keepBelow()) { 0894 m_keepBelow->setChecked(true); 0895 } else { 0896 m_keepAbove->setChecked(false); 0897 m_keepBelow->setChecked(false); 0898 } 0899 0900 updateKeepAboveBelow(); 0901 0902 // set up the look&feel of the note 0903 setFrameStyle(Panel | Raised); 0904 setMinimumSize(20, 20); 0905 setBackgroundRole(QPalette::Base); 0906 0907 m_editor->setContentsMargins(0, 0, 0, 0); 0908 m_editor->setBackgroundRole(QPalette::Base); 0909 m_editor->setFrameStyle(NoFrame); 0910 m_editor->document()->setModified(false); 0911 mBlockSave = false; 0912 } 0913 0914 void KNote::toDesktop(int desktop) 0915 { 0916 if (desktop == 0) { 0917 return; 0918 } 0919 0920 #if KDEPIM_HAVE_X11 0921 if (desktop == NETWinInfo::OnAllDesktops) { 0922 KX11Extras::setOnAllDesktops(winId(), true); 0923 } else { 0924 KX11Extras::setOnDesktop(winId(), desktop); 0925 } 0926 #endif 0927 } 0928 0929 void KNote::setColor(const QColor &fg, const QColor &bg) 0930 { 0931 m_editor->setColor(fg, bg); 0932 QPalette p = palette(); 0933 0934 // better: from light(150) to light(100) to light(75) 0935 // QLinearGradient g( width()/2, 0, width()/2, height() ); 0936 // g.setColorAt( 0, bg ); 0937 // g.setColorAt( 1, bg.darker(150) ); 0938 0939 p.setColor(QPalette::Window, bg); 0940 // p.setBrush( QPalette::Window, g ); 0941 p.setColor(QPalette::Base, bg); 0942 // p.setBrush( QPalette::Base, g ); 0943 0944 p.setColor(QPalette::WindowText, fg); 0945 p.setColor(QPalette::Text, fg); 0946 0947 p.setColor(QPalette::Button, bg.darker(116)); 0948 p.setColor(QPalette::ButtonText, fg); 0949 0950 // p.setColor( QPalette::Highlight, bg ); 0951 // p.setColor( QPalette::HighlightedText, fg ); 0952 0953 // order: Light, Midlight, Button, Mid, Dark, Shadow 0954 0955 // the shadow 0956 p.setColor(QPalette::Light, bg.lighter(180)); 0957 p.setColor(QPalette::Midlight, bg.lighter(150)); 0958 p.setColor(QPalette::Mid, bg.lighter(150)); 0959 p.setColor(QPalette::Dark, bg.darker(108)); 0960 p.setColor(QPalette::Shadow, bg.darker(116)); 0961 0962 setPalette(p); 0963 0964 // darker values for the active label 0965 p.setColor(QPalette::Active, QPalette::Base, bg.darker(116)); 0966 0967 m_label->setPalette(p); 0968 0969 // set the text color 0970 m_editor->setTextColor(fg); 0971 0972 // update the color of the title 0973 updateFocus(); 0974 Q_EMIT sigColorChanged(); 0975 } 0976 0977 void KNote::updateLabelAlignment() 0978 { 0979 // if the name is too long to fit, left-align it, otherwise center it (#59028) 0980 const QString labelText = m_label->text(); 0981 if (m_label->fontMetrics().boundingRect(labelText).width() > m_label->width()) { 0982 m_label->setAlignment(Qt::AlignLeft); 0983 } else { 0984 m_label->setAlignment(Qt::AlignHCenter); 0985 } 0986 } 0987 0988 void KNote::updateFocus() 0989 { 0990 if (hasFocus()) { 0991 if (!m_editor->isReadOnly()) { 0992 if (m_tool && m_tool->isHidden() && m_editor->acceptRichText()) { 0993 m_tool->show(); 0994 updateLayout(); 0995 } 0996 m_grip->show(); 0997 } else { 0998 if (m_tool && !m_tool->isHidden()) { 0999 m_tool->hide(); 1000 updateLayout(); // to update the minimum height 1001 } 1002 m_grip->hide(); 1003 } 1004 } else { 1005 m_grip->hide(); 1006 1007 if (m_tool && !m_tool->isHidden()) { 1008 m_tool->hide(); 1009 updateLayout(); // to update the minimum height 1010 } 1011 } 1012 } 1013 1014 void KNote::updateLayout() 1015 { 1016 // TODO: remove later if no longer needed. 1017 updateLabelAlignment(); 1018 } 1019 1020 // -------------------- protected methods -------------------- // 1021 1022 void KNote::contextMenuEvent(QContextMenuEvent *e) 1023 { 1024 if (m_menu) { 1025 m_menu->popup(e->globalPos()); 1026 } 1027 } 1028 1029 void KNote::showEvent(QShowEvent *) 1030 { 1031 if (mDisplayAttribute->isHidden()) { 1032 // KWin does not preserve these properties for hidden windows 1033 updateKeepAboveBelow(false); 1034 #if KDEPIM_HAVE_X11 1035 slotUpdateShowInTaskbar(); 1036 #endif 1037 toDesktop(mDisplayAttribute->desktop()); 1038 move(mDisplayAttribute->position()); 1039 auto attr = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 1040 saveNoteContent(); 1041 attr->setIsHidden(false); 1042 if (!mBlockSave) { 1043 auto job = new Akonadi::ItemModifyJob(mItem); 1044 #ifdef DEBUG_SAVE_NOTE 1045 qCDebug(KNOTES_LOG) << "showEvent slotNoteSaved(KJob*)"; 1046 #endif 1047 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 1048 } 1049 } 1050 } 1051 1052 void KNote::resizeEvent(QResizeEvent *qre) 1053 { 1054 QFrame::resizeEvent(qre); 1055 updateLayout(); 1056 } 1057 1058 void KNote::closeEvent(QCloseEvent *event) 1059 { 1060 if (qApp->isSavingSession()) { 1061 return; 1062 } 1063 event->ignore(); // We don't want to close (and delete the widget). Just hide it 1064 slotClose(); 1065 } 1066 1067 void KNote::dragEnterEvent(QDragEnterEvent *e) 1068 { 1069 if (!m_editor->isReadOnly()) { 1070 e->setAccepted(e->mimeData()->hasColor()); 1071 } 1072 } 1073 1074 void KNote::dropEvent(QDropEvent *e) 1075 { 1076 if (m_editor->isReadOnly()) { 1077 return; 1078 } 1079 1080 const QMimeData *md = e->mimeData(); 1081 if (md->hasColor()) { 1082 const auto bg = qvariant_cast<QColor>(md->colorData()); 1083 1084 auto attr = mItem.attribute<NoteShared::NoteDisplayAttribute>(Akonadi::Item::AddIfMissing); 1085 saveNoteContent(); 1086 attr->setBackgroundColor(bg); 1087 auto job = new Akonadi::ItemModifyJob(mItem); 1088 #ifdef DEBUG_SAVE_NOTE 1089 qCDebug(KNOTES_LOG) << "dropEvent slotNoteSaved(KJob*)"; 1090 #endif 1091 connect(job, &Akonadi::ItemModifyJob::result, this, &KNote::slotNoteSaved); 1092 } 1093 } 1094 1095 bool KNote::event(QEvent *ev) 1096 { 1097 if (ev->type() == QEvent::LayoutRequest) { 1098 updateLayout(); 1099 return true; 1100 } else { 1101 return QFrame::event(ev); 1102 } 1103 } 1104 1105 bool KNote::eventFilter(QObject *o, QEvent *ev) 1106 { 1107 if (ev->type() == QEvent::DragEnter && static_cast<QDragEnterEvent *>(ev)->mimeData()->hasColor()) { 1108 dragEnterEvent(static_cast<QDragEnterEvent *>(ev)); 1109 return true; 1110 } 1111 1112 if (ev->type() == QEvent::Drop && static_cast<QDropEvent *>(ev)->mimeData()->hasColor()) { 1113 dropEvent(static_cast<QDropEvent *>(ev)); 1114 return true; 1115 } 1116 1117 if (o == m_label) { 1118 auto e = (QMouseEvent *)ev; 1119 1120 if (ev->type() == QEvent::MouseButtonDblClick) { 1121 if (!m_editor->isReadOnly()) { 1122 slotRename(); 1123 } 1124 } 1125 1126 if (ev->type() == QEvent::MouseButtonPress 1127 && ((e->buttons() & Qt::LeftButton) == Qt::LeftButton || (e->buttons() & Qt::MiddleButton) == Qt::MiddleButton)) { 1128 mOrigPos = e->pos(); 1129 return false; 1130 } 1131 1132 if (ev->type() == QEvent::MouseMove && ((e->buttons() & Qt::LeftButton) == Qt::LeftButton || (e->buttons() & Qt::MiddleButton) == Qt::MiddleButton)) { 1133 QPoint newPos = e->globalPosition().toPoint() - mOrigPos - QPoint(1, 1); 1134 move(newPos); 1135 return true; 1136 } 1137 1138 if (ev->type() == QEvent::MouseButtonRelease 1139 && ((e->buttons() & Qt::LeftButton) == Qt::LeftButton || (e->buttons() & Qt::MiddleButton) == Qt::MiddleButton)) { 1140 QPoint newPos = e->globalPosition().toPoint() - mOrigPos - QPoint(1, 1); 1141 move(newPos); 1142 return false; 1143 } 1144 return false; 1145 } 1146 1147 if (o == m_editor) { 1148 if (ev->type() == QEvent::FocusOut) { 1149 auto fe = static_cast<QFocusEvent *>(ev); 1150 if (fe->reason() != Qt::PopupFocusReason && fe->reason() != Qt::MouseFocusReason) { 1151 updateFocus(); 1152 if (!mBlockSave) { 1153 saveNote(true); 1154 } 1155 } 1156 } else if (ev->type() == QEvent::FocusIn) { 1157 updateFocus(); 1158 } 1159 1160 return false; 1161 } 1162 1163 return false; 1164 } 1165 1166 Akonadi::Item KNote::item() const 1167 { 1168 return mItem; 1169 } 1170 1171 void KNote::slotDebugAkonadiSearch() 1172 { 1173 QPointer<Akonadi::Search::AkonadiSearchDebugDialog> dlg = new Akonadi::Search::AkonadiSearchDebugDialog; 1174 dlg->setAkonadiId(mItem.id()); 1175 dlg->setAttribute(Qt::WA_DeleteOnClose); 1176 dlg->setSearchType(Akonadi::Search::AkonadiSearchDebugSearchPathComboBox::Notes); 1177 dlg->doSearch(); 1178 dlg->show(); 1179 } 1180 1181 #include "moc_knote.cpp"