diff --git a/src/backup.cpp b/src/backup.cpp index 685fc8c..ee574c5 100644 --- a/src/backup.cpp +++ b/src/backup.cpp @@ -1,421 +1,421 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "backup.h" #include "formatimporter.h" // To move a folder #include "global.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // usleep() /** * Backups are wrapped in a .tar.gz, inside that folder name. * An archive is not a backup or is corrupted if data are not in that folder! */ const QString backupMagicFolder = "BasKet-Note-Pads_Backup"; /** class BackupDialog: */ BackupDialog::BackupDialog(QWidget *parent, const char *name) : QDialog(parent) { setObjectName(name); setModal(true); setWindowTitle(i18n("Backup & Restore")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QWidget *page = new QWidget(this); QVBoxLayout *pageVBoxLayout = new QVBoxLayout(page); pageVBoxLayout->setMargin(0); mainLayout->addWidget(page); // pageVBoxLayout->setSpacing(spacingHint()); QString savesFolder = Global::savesFolder(); savesFolder = savesFolder.left(savesFolder.length() - 1); // savesFolder ends with "/" QGroupBox *folderGroup = new QGroupBox(i18n("Save Folder"), page); pageVBoxLayout->addWidget(folderGroup); mainLayout->addWidget(folderGroup); QVBoxLayout *folderGroupLayout = new QVBoxLayout; folderGroup->setLayout(folderGroupLayout); folderGroupLayout->addWidget(new QLabel("" + i18n("Your baskets are currently stored in that folder:
%1", savesFolder), folderGroup)); QWidget *folderWidget = new QWidget; folderGroupLayout->addWidget(folderWidget); QHBoxLayout *folderLayout = new QHBoxLayout(folderWidget); folderLayout->setContentsMargins(0, 0, 0, 0); QPushButton *moveFolder = new QPushButton(i18n("&Move to Another Folder..."), folderWidget); QPushButton *useFolder = new QPushButton(i18n("&Use Another Existing Folder..."), folderWidget); HelpLabel *helpLabel = new HelpLabel(i18n("Why to do that?"), i18n("

You can move the folder where %1 store your baskets to:

    " "
  • Store your baskets in a visible place in your home folder, like ~/Notes or ~/Baskets, so you can manually backup them when you want.
  • " "
  • Store your baskets on a server to share them between two computers.
    " "In this case, mount the shared-folder to the local file system and ask %1 to use that mount point.
    " "Warning: you should not run %1 at the same time on both computers, or you risk to loss data while the two applications are desynced.
  • " "

Please remember that you should not change the content of that folder manually (eg. adding a file in a basket folder will not add that file to the basket).

", QGuiApplication::applicationDisplayName()), folderWidget); folderLayout->addWidget(moveFolder); folderLayout->addWidget(useFolder); folderLayout->addWidget(helpLabel); folderLayout->addStretch(); - connect(moveFolder, SIGNAL(clicked()), this, SLOT(moveToAnotherFolder())); - connect(useFolder, SIGNAL(clicked()), this, SLOT(useAnotherExistingFolder())); + connect(moveFolder, &QPushButton::clicked, this, &BackupDialog::moveToAnotherFolder); + connect(useFolder, &QPushButton::clicked, this, &BackupDialog::useAnotherExistingFolder); QGroupBox *backupGroup = new QGroupBox(i18n("Backups"), page); pageVBoxLayout->addWidget(backupGroup); mainLayout->addWidget(backupGroup); QVBoxLayout *backupGroupLayout = new QVBoxLayout; backupGroup->setLayout(backupGroupLayout); QWidget *backupWidget = new QWidget; backupGroupLayout->addWidget(backupWidget); QHBoxLayout *backupLayout = new QHBoxLayout(backupWidget); backupLayout->setContentsMargins(0, 0, 0, 0); QPushButton *backupButton = new QPushButton(i18n("&Backup..."), backupWidget); QPushButton *restoreButton = new QPushButton(i18n("&Restore a Backup..."), backupWidget); m_lastBackup = new QLabel(QString(), backupWidget); backupLayout->addWidget(backupButton); backupLayout->addWidget(restoreButton); backupLayout->addWidget(m_lastBackup); backupLayout->addStretch(); - connect(backupButton, SIGNAL(clicked()), this, SLOT(backup())); - connect(restoreButton, SIGNAL(clicked()), this, SLOT(restore())); + connect(backupButton, &QPushButton::clicked, this, &BackupDialog::backup); + connect(restoreButton, &QPushButton::clicked, this, &BackupDialog::restore); populateLastBackup(); (new QWidget(page))->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::rejected, this, &BackupDialog::reject); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); } BackupDialog::~BackupDialog() { } void BackupDialog::populateLastBackup() { QString lastBackupText = i18n("Last backup: never"); if (Settings::lastBackup().isValid()) lastBackupText = i18n("Last backup: %1", Settings::lastBackup().toString(Qt::LocalDate)); m_lastBackup->setText(lastBackupText); } void BackupDialog::moveToAnotherFolder() { QUrl selectedURL = QFileDialog::getExistingDirectoryUrl(/*parent=*/nullptr, /*caption=*/i18n("Choose a Folder Where to Move Baskets"), /*startDir=*/Global::savesFolder()); if (!selectedURL.isEmpty()) { QString folder = selectedURL.path(); QDir dir(folder); // The folder should not exists, or be empty (because KDirSelectDialog will likely create it anyway): if (dir.exists()) { // Get the content of the folder: QStringList content = dir.entryList(); if (content.count() > 2) { // "." and ".." int result = KMessageBox::questionYesNo(nullptr, "" + i18n("The folder %1 is not empty. Do you want to override it?", folder), i18n("Override Folder?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::No) return; } Tools::deleteRecursively(folder); } FormatImporter copier; copier.moveFolder(Global::savesFolder(), folder); Backup::setFolderAndRestart(folder, i18n("Your baskets have been successfully moved to %1. %2 is going to be restarted to take this change into account.")); } } void BackupDialog::useAnotherExistingFolder() { QUrl selectedURL = QFileDialog::getExistingDirectoryUrl(/*parent=*/nullptr, /*caption=*/i18n("Choose an Existing Folder to Store Baskets"), /*startDir=*/Global::savesFolder()); if (!selectedURL.isEmpty()) { Backup::setFolderAndRestart(selectedURL.path(), i18n("Your basket save folder has been successfully changed to %1. %2 is going to be restarted to take this change into account.")); } } void BackupDialog::backup() { QDir dir; // Compute a default file name & path (eg. "Baskets_2007-01-31.tar.gz"): KConfig *config = KSharedConfig::openConfig().data(); KConfigGroup configGroup(config, "Backups"); QString folder = configGroup.readEntry("lastFolder", QDir::homePath()) + '/'; QString fileName = i18nc("Backup filename (without extension), %1 is the date", "Baskets_%1", QDate::currentDate().toString(Qt::ISODate)); QString url = folder + fileName; // Ask a file name & path to the user: QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files"); QString destination = url; for (bool askAgain = true; askAgain;) { // Ask: destination = QFileDialog::getSaveFileName(nullptr, i18n("Backup Baskets"), destination, filter); // User canceled? if (destination.isEmpty()) return; // File already existing? Ask for overriding: if (dir.exists(destination)) { int result = KMessageBox::questionYesNoCancel( nullptr, "" + i18n("The file %1 already exists. Do you really want to override it?", QUrl::fromLocalFile(destination).fileName()), i18n("Override File?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::Cancel) return; else if (result == KMessageBox::Yes) askAgain = false; } else askAgain = false; } QProgressDialog dialog; dialog.setWindowTitle(i18n("Backup Baskets")); dialog.setLabelText(i18n("Backing up baskets. Please wait...")); dialog.setModal(true); dialog.setCancelButton(nullptr); dialog.setAutoClose(true); dialog.setRange(0, 0 /*Busy/Undefined*/); dialog.setValue(0); dialog.show(); /* If needed, uncomment this and call similar code in other places below QProgressBar* progress = new QProgressBar(dialog); progress->setTextVisible(false); dialog.setBar(progress);*/ BackupThread thread(destination, Global::savesFolder()); thread.start(); while (thread.isRunning()) { dialog.setValue(dialog.value() + 1); // Or else, the animation is not played! qApp->processEvents(); usleep(300); // Not too long because if the backup process is finished, we wait for nothing } Settings::setLastBackup(QDate::currentDate()); Settings::saveConfig(); populateLastBackup(); } void BackupDialog::restore() { // Get last backup folder: KConfig *config = KSharedConfig::openConfig().data(); KConfigGroup configGroup(config, "Backups"); QString folder = configGroup.readEntry("lastFolder", QDir::homePath()) + '/'; // Ask a file name to the user: QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files"); QString path = QFileDialog::getOpenFileName(this, i18n("Open Basket Archive"), folder, filter); if (path.isEmpty()) // User has canceled return; // Before replacing the basket data folder with the backup content, we safely backup the current baskets to the home folder. // So if the backup is corrupted or something goes wrong while restoring (power cut...) the user will be able to restore the old working data: QString safetyPath = Backup::newSafetyFolder(); FormatImporter copier; copier.moveFolder(Global::savesFolder(), safetyPath); // Add the README file for user to cancel a bad restoration: QString readmePath = safetyPath + i18n("README.txt"); QFile file(readmePath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << i18n("This is a safety copy of your baskets like they were before you started to restore the backup %1.", QUrl::fromLocalFile(path).fileName()) + "\n\n" << i18n("If the restoration was a success and you restored what you wanted to restore, you can remove this folder.") + "\n\n" << i18n("If something went wrong during the restoration process, you can re-use this folder to store your baskets and nothing will be lost.") + "\n\n" << i18n("Choose \"Basket\" -> \"Backup & Restore...\" -> \"Use Another Existing Folder...\" and select that folder.") + '\n'; file.close(); } QString message = "

" + i18n("Restoring %1. Please wait...", QUrl::fromLocalFile(path).fileName()) + "

" + i18n("If something goes wrong during the restoration process, read the file %1.", readmePath); QProgressDialog *dialog = new QProgressDialog(); dialog->setWindowTitle(i18n("Restore Baskets")); dialog->setLabelText(message); dialog->setModal(/*modal=*/true); dialog->setCancelButton(nullptr); dialog->setAutoClose(true); dialog->setRange(0, 0 /*Busy/Undefined*/); dialog->setValue(0); dialog->show(); // Uncompress: RestoreThread thread(path, Global::savesFolder()); thread.start(); while (thread.isRunning()) { dialog->setValue(dialog->value() + 1); // Or else, the animation is not played! qApp->processEvents(); usleep(300); // Not too long because if the restore process is finished, we wait for nothing } dialog->hide(); // The restore is finished, do not continue to show it while telling the user the application is going to be restarted delete dialog; // If we only hidden it, it reappeared just after having restored a small backup... Very strange. dialog = nullptr; // This was annoying since it is modal and the "BasKet Note Pads is going to be restarted" message was not reachable. // qApp->processEvents(); // Check for errors: if (!thread.success()) { // Restore the old baskets: QDir dir; dir.remove(readmePath); copier.moveFolder(safetyPath, Global::savesFolder()); // Tell the user: KMessageBox::error(nullptr, i18n("This archive is either not a backup of baskets or is corrupted. It cannot be imported. Your old baskets have been preserved instead."), i18n("Restore Error")); return; } // Note: The safety backup is not removed now because the code can has been wrong, somehow, or the user perhaps restored an older backup by error... // The restore process will not be called very often (it is possible it will only be called once or twice around the world during the next years). // So it is rare enough to force the user to remove the safety folder, but keep him in control and let him safely recover from restoration errors. Backup::setFolderAndRestart(Global::savesFolder() /*No change*/, i18n("Your backup has been successfully restored to %1. %2 is going to be restarted to take this change into account.")); } /** class Backup: */ QString Backup::binaryPath; void Backup::figureOutBinaryPath(const char *argv0, QApplication &app) { /* The application can be launched by two ways: - Globally (app.applicationFilePath() is good) - In KDevelop or with an absolute path (app.applicationFilePath() is wrong) This function is called at the very start of main() so that the current directory has not been changed yet. Command line (argv[0]) QDir(argv[0]).canonicalPath() app.applicationFilePath() ====================== ============================================= ========================= "basket" "" "/opt/kde3/bin/basket" "./src/.libs/basket" "/home/seb/prog/basket/debug/src/.lib/basket" "/opt/kde3/bin/basket" */ binaryPath = QDir(argv0).canonicalPath(); if (binaryPath.isEmpty()) binaryPath = app.applicationFilePath(); } void Backup::setFolderAndRestart(const QString &folder, const QString &message) { // Set the folder: Settings::setDataFolder(folder); Settings::saveConfig(); // Reassure the user that the application main window disappearance is not a crash, but a normal restart. // This is important for users to trust the application in such a critical phase and understands what's happening: KMessageBox::information(nullptr, "" + message.arg((folder.endsWith('/') ? folder.left(folder.length() - 1) : folder), QGuiApplication::applicationDisplayName()), i18n("Restart")); // Restart the application: KRun::runCommand(binaryPath, QCoreApplication::applicationName(), QCoreApplication::applicationName(), nullptr); exit(0); } QString Backup::newSafetyFolder() { QDir dir; QString fullPath; fullPath = QDir::homePath() + '/' + i18nc("Safety folder name before restoring a basket data archive", "Baskets Before Restoration") + '/'; if (!dir.exists(fullPath)) return fullPath; for (int i = 2;; ++i) { fullPath = QDir::homePath() + '/' + i18nc("Safety folder name before restoring a basket data archive", "Baskets Before Restoration (%1)", i) + '/'; if (!dir.exists(fullPath)) return fullPath; } return QString(); } /** class BackupThread: */ BackupThread::BackupThread(const QString &tarFile, const QString &folderToBackup) : m_tarFile(tarFile) , m_folderToBackup(folderToBackup) { } void BackupThread::run() { KTar tar(m_tarFile, "application/x-gzip"); tar.open(QIODevice::WriteOnly); tar.addLocalDirectory(m_folderToBackup, backupMagicFolder); // KArchive does not add hidden files. Basket description files (".basket") are hidden, we add them manually: QDir dir(m_folderToBackup + "baskets/"); QStringList baskets = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (QStringList::Iterator it = baskets.begin(); it != baskets.end(); ++it) { tar.addLocalFile(m_folderToBackup + "baskets/" + *it + "/.basket", backupMagicFolder + "/baskets/" + *it + "/.basket"); } // We finished: tar.close(); } /** class RestoreThread: */ RestoreThread::RestoreThread(const QString &tarFile, const QString &destFolder) : m_tarFile(tarFile) , m_destFolder(destFolder) { } void RestoreThread::run() { m_success = false; KTar tar(m_tarFile, "application/x-gzip"); tar.open(QIODevice::ReadOnly); if (tar.isOpen()) { const KArchiveDirectory *directory = tar.directory(); if (directory->entries().contains(backupMagicFolder)) { const KArchiveEntry *entry = directory->entry(backupMagicFolder); if (entry->isDirectory()) { ((const KArchiveDirectory *)entry)->copyTo(m_destFolder); m_success = true; } } tar.close(); } } diff --git a/src/basketlistview.cpp b/src/basketlistview.cpp index 7095a46..ccfa129 100644 --- a/src/basketlistview.cpp +++ b/src/basketlistview.cpp @@ -1,637 +1,637 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "basketlistview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "decoratedbasket.h" #include "global.h" #include "icon_names.h" #include "notedrag.h" #include "settings.h" #include "tools.h" /** class BasketListViewItem: */ BasketListViewItem::BasketListViewItem(QTreeWidget *parent, BasketScene *basket) : QTreeWidgetItem(parent) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidgetItem *parent, BasketScene *basket) : QTreeWidgetItem(parent) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, BasketScene *basket) : QTreeWidgetItem(parent, after) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::BasketListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, BasketScene *basket) : QTreeWidgetItem(parent, after) , m_basket(basket) , m_isUnderDrag(false) , m_isAbbreviated(false) { } BasketListViewItem::~BasketListViewItem() { } QString BasketListViewItem::escapedName(const QString &string) { // Underlining the Alt+Letter shortcut (and escape all other '&' characters), if any: QString basketName = string; basketName.replace('&', "&&"); // First escape all the amperstamp QString letter; QRegExp letterExp("^Alt\\+(?:Shift\\+)?(.)$"); QString basketShortcut = m_basket->shortcut().toString(); if (letterExp.indexIn(basketShortcut) != -1) { int index; letter = letterExp.cap(1); if ((index = basketName.indexOf(letter)) != -1) basketName.insert(index, '&'); } return basketName; } void BasketListViewItem::setup() { setText(/*column=*/0, escapedName(m_basket->basketName())); QPixmap icon = KIconLoader::global()->loadIcon(m_basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/false); setIcon(/*column=*/0, icon); /* QBrush brush; bool withIcon = m_stateCopy || (m_tagCopy && !m_tagCopy->isMultiState()); State* state = (m_tagCopy ? m_tagCopy->stateCopies[0]->newState : m_stateCopy->newState); brush.setColor(isSelected() ? qApp->palette().color(QPalette::Highlight) : (withIcon && state->backgroundColor().isValid() ? state->backgroundColor() : viewport->palette().color(viewwport->backgroundRole()))); setBackground(brush); */ } BasketListViewItem *BasketListViewItem::lastChild() { int count = childCount(); if (count <= 0) return nullptr; return (BasketListViewItem *)(child(count - 1)); } QStringList BasketListViewItem::childNamesTree(int deep) { QStringList result; // Compute indentation spaces: QString spaces; for (int j = 0; j < deep; ++j) spaces += " "; // Append the names of sub baskets if (deep > 0) result.append(spaces + basket()->basketName()); // Append the children: for (int i = 0; i < childCount(); i++) { QStringList children = ((BasketListViewItem *)child(i))->childNamesTree(deep + 1); result.append(children); } return result; } void BasketListViewItem::moveChildsBaskets() { int insertAfterThis = 0; if (!parent()) insertAfterThis = treeWidget()->indexOfTopLevelItem(this); for (int i = 0; i < childCount(); i++) { // Re-insert the item with the good parent: if (parent()) parent()->insertChild(insertAfterThis, child(i)); else treeWidget()->insertTopLevelItem(insertAfterThis, child(i)); // And move it at the good place: insertAfterThis++; } } void BasketListViewItem::ensureVisible() { BasketListViewItem *item = this; while (item->parent()) { item = (BasketListViewItem *)(item->parent()); item->setExpanded(true); } } bool BasketListViewItem::isShown() { QTreeWidgetItem *item = parent(); while (item) { if (!item->isExpanded()) return false; item = item->parent(); } return true; } bool BasketListViewItem::isCurrentBasket() { return basket() == Global::bnpView->currentBasket(); } bool BasketListViewItem::isUnderDrag() { return m_isUnderDrag; } bool BasketListViewItem::haveChildsLoading() { for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); if (!childItem->basket()->isLoaded() && !childItem->basket()->isLocked()) return true; if (childItem->haveChildsLoading()) return true; } return false; } bool BasketListViewItem::haveHiddenChildsLoading() { if (isExpanded()) return false; return haveChildsLoading(); } bool BasketListViewItem::haveChildsLocked() { for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); if (/*!*/ childItem->basket()->isLocked()) return true; if (childItem->haveChildsLocked()) return true; } return false; } bool BasketListViewItem::haveHiddenChildsLocked() { if (isExpanded()) return false; return haveChildsLocked(); } int BasketListViewItem::countChildsFound() { int count = 0; for (int i = 0; i < childCount(); i++) { BasketListViewItem *childItem = (BasketListViewItem *)child(i); count += childItem->basket()->countFounds(); count += childItem->countChildsFound(); } return count; } int BasketListViewItem::countHiddenChildsFound() { if (isExpanded()) return 0; return countChildsFound(); } void BasketListViewItem::setUnderDrag(bool underDrag) { m_isUnderDrag = underDrag; } bool BasketListViewItem::isAbbreviated() { return m_isAbbreviated; } void BasketListViewItem::setAbbreviated(bool b) { m_isAbbreviated = b; } /** class BasketTreeListView: */ QString BasketTreeListView::TREE_ITEM_MIME_STRING = "application/x-basket-item"; BasketTreeListView::BasketTreeListView(QWidget *parent) : QTreeWidget(parent) , m_autoOpenItem(nullptr) , m_itemUnderDrag(nullptr) { - connect(&m_autoOpenTimer, SIGNAL(timeout()), this, SLOT(autoOpen())); + connect(&m_autoOpenTimer, &QTimer::timeout, this, &BasketTreeListView::autoOpen); setItemDelegate(new FoundCountIcon(this)); } void BasketTreeListView::contextMenuEvent(QContextMenuEvent *e) { emit contextMenuRequested(e->pos()); } QStringList BasketTreeListView::mimeTypes() const { QStringList types; types << TREE_ITEM_MIME_STRING; types << NoteDrag::NOTE_MIME_STRING; return types; } QMimeData *BasketTreeListView::mimeData(const QList items) const { QString mimeType = TREE_ITEM_MIME_STRING; QByteArray data = QByteArray(); QDataStream out(&data, QIODevice::WriteOnly); if (items.isEmpty()) return new QMimeData(); for (int i = 0; i < items.count(); ++i) { BasketListViewItem *basketItem = static_cast(items[i]); out << basketItem->basket()->basketName() << basketItem->basket()->folderName() << basketItem->basket()->icon(); } QMimeData *mimeData = new QMimeData(); mimeData->setData(mimeType, data); return mimeData; } bool BasketTreeListView::event(QEvent *e) { if (e->type() == QEvent::ToolTip) { QHelpEvent *he = static_cast(e); QTreeWidgetItem *item = itemAt(he->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (bitem && bitem->isAbbreviated()) { QRect rect = visualItemRect(bitem); QToolTip::showText(rect.topLeft(), bitem->basket()->basketName(), viewport(), rect); } return true; } return QTreeWidget::event(e); } void BasketTreeListView::mousePressEvent(QMouseEvent *event) { m_dragStartPosition = event->pos(); QTreeWidget::mousePressEvent(event); } void BasketTreeListView::mouseMoveEvent(QMouseEvent *event) { // QTreeWidget::mouseMoveEvent(event); if (!(event->buttons() & Qt::LeftButton)) { event->ignore(); return; } if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->ignore(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = this->mimeData(this->selectedItems()); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction); if (dropAction == Qt::MoveAction || dropAction == Qt::CopyAction) event->accept(); } void BasketTreeListView::dragEnterEvent(QDragEnterEvent *event) { // TODO: accept everything? (forwarding dropped basket-notes and arbitrary data to the basket) // files: MoveAction vs. CopyAction, or acceptProposedAction() QTreeWidget::dragEnterEvent(event); } void BasketTreeListView::removeExpands() { QTreeWidgetItemIterator it(this); while (*it) { QTreeWidgetItem *item = *it; if (item->childCount() <= 0) item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); ++it; } } void BasketTreeListView::dragLeaveEvent(QDragLeaveEvent *event) { qDebug() << "BasketTreeListView::dragLeaveEvent"; m_autoOpenItem = nullptr; m_autoOpenTimer.stop(); setItemUnderDrag(nullptr); removeExpands(); QTreeWidget::dragLeaveEvent(event); } void BasketTreeListView::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat(TREE_ITEM_MIME_STRING)) { event->setDropAction(Qt::MoveAction); QTreeWidget::dropEvent(event); } else { // this handels application/x-basket-note drag events. qDebug() << "Forwarding dropped data to the basket"; event->setDropAction(Qt::MoveAction); QTreeWidgetItem *item = itemAt(event->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (bitem) { bitem->basket()->blindDrop(event->mimeData(), event->dropAction(), event->source()); } else { qDebug() << "Forwarding failed: no bitem found"; } } m_autoOpenItem = nullptr; m_autoOpenTimer.stop(); setItemUnderDrag(nullptr); removeExpands(); Global::bnpView->save(); // TODO: Don't save if it was not a basket drop... } void BasketTreeListView::dragMoveEvent(QDragMoveEvent *event) { // qDebug() << "BasketTreeListView::dragMoveEvent"; if (!event->mimeData()->hasFormat(TREE_ITEM_MIME_STRING)) { QTreeWidgetItem *item = itemAt(event->pos()); BasketListViewItem *bitem = dynamic_cast(item); if (m_autoOpenItem != item) { m_autoOpenItem = item; m_autoOpenTimer.setSingleShot(true); m_autoOpenTimer.start(1700); } if (item) { event->accept(); } setItemUnderDrag(bitem); } QTreeWidget::dragMoveEvent(event); } void BasketTreeListView::setItemUnderDrag(BasketListViewItem *item) { if (m_itemUnderDrag != item) { if (m_itemUnderDrag) { // Remove drag status from the old item m_itemUnderDrag->setUnderDrag(false); } m_itemUnderDrag = item; if (m_itemUnderDrag) { // add drag status to the new item m_itemUnderDrag->setUnderDrag(true); } } } void BasketTreeListView::autoOpen() { BasketListViewItem *item = (BasketListViewItem *)m_autoOpenItem; if (item) Global::bnpView->setCurrentBasket(item->basket()); } void BasketTreeListView::resizeEvent(QResizeEvent *event) { QTreeWidget::resizeEvent(event); } /** We should NEVER get focus (because of QWidget::NoFocus focusPolicy()) * but QTreeView can programatically give us the focus. * So we give it to the basket. */ void BasketTreeListView::focusInEvent(QFocusEvent *) { BasketScene *basket = Global::bnpView->currentBasket(); if (basket) basket->setFocus(); } Qt::DropActions BasketTreeListView::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } BasketListViewItem *BasketTreeListView::getBasketInTree(const QModelIndex &index) const { QTreeWidgetItem *item = itemFromIndex(index); return dynamic_cast(item); } void FoundCountIcon::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); // Get access to basket pointer BasketListViewItem *basketInTree = m_basketTree->getBasketInTree(index); if (basketInTree == nullptr) return; const int BASKET_ICON_SIZE = 16; // [replace with m_basketTree->iconSize()] const int MARGIN = 1; BasketScene *basket = basketInTree->basket(); // If we are filtering all baskets, and are effectively filtering on something: bool showLoadingIcon = false; bool showEncryptedIcon = false; QPixmap countPixmap; bool showCountPixmap = Global::bnpView->isFilteringAllBaskets() && Global::bnpView->currentBasket()->decoration()->filterBar()->filterData().isFiltering; if (showCountPixmap) { showLoadingIcon = (!basket->isLoaded() && !basket->isLocked()) || basketInTree->haveHiddenChildsLoading(); showEncryptedIcon = basket->isLocked() || basketInTree->haveHiddenChildsLocked(); bool childrenAreLoading = basketInTree->haveHiddenChildsLoading() || basketInTree->haveHiddenChildsLocked(); countPixmap = foundCountPixmap(!basket->isLoaded(), basket->countFounds(), childrenAreLoading, basketInTree->countHiddenChildsFound(), m_basketTree->font(), option.rect.height() - 2 * MARGIN); } int effectiveWidth = option.rect.right() - (countPixmap.isNull() ? 0 : countPixmap.width() + MARGIN) - (showLoadingIcon || showEncryptedIcon ? BASKET_ICON_SIZE + MARGIN : 0); bool drawRoundRect = basket->backgroundColorSetting().isValid() || basket->textColorSetting().isValid(); // Draw the rounded rectangle: if (drawRoundRect) { QPixmap roundRectBmp; QColor background = basket->backgroundColor(); int textWidth = m_basketTree->fontMetrics().horizontalAdvance(basketInTree->text(/*column=*/0)); int iconTextMargin = m_basketTree->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); ///< Space between icon and text // Don't forget to update the key computation if parameters // affecting the rendering logic change QString key = QString("BLIRR::%1.%2.%3.%4").arg(option.rect.width()).arg(option.rect.size().height()).arg(textWidth).arg(background.rgb()); if (!QPixmapCache::find(key, &roundRectBmp)) { // Draw first time roundRectBmp = QPixmap(option.rect.size()); roundRectBmp.fill(Qt::transparent); QPainter brushPainter(&roundRectBmp); int cornerR = option.rect.height() / 2 - MARGIN; QRect roundRect(0, MARGIN, BASKET_ICON_SIZE + iconTextMargin + textWidth + 2 * cornerR, option.rect.height() - 2 * MARGIN); brushPainter.setPen(background); brushPainter.setBrush(background); brushPainter.setRenderHint(QPainter::Antialiasing); brushPainter.drawRoundedRect(roundRect, cornerR, cornerR); QPixmapCache::insert(key, roundRectBmp); } basketInTree->setBackground(0, QBrush(roundRectBmp)); basketInTree->setForeground(0, QBrush(basket->textColor())); } // end if drawRoundRect // Render icons on the right int y = option.rect.center().y() - BASKET_ICON_SIZE / 2; if (!countPixmap.isNull()) { painter->drawPixmap(effectiveWidth, y, countPixmap); effectiveWidth += countPixmap.width() + MARGIN; } if (showLoadingIcon) { QPixmap icon = KIconLoader::global()->loadIcon(IconNames::LOADING, KIconLoader::NoGroup, BASKET_ICON_SIZE); painter->drawPixmap(effectiveWidth, y, icon); effectiveWidth += BASKET_ICON_SIZE + MARGIN; } if (showEncryptedIcon && !showLoadingIcon) { QPixmap icon = KIconLoader::global()->loadIcon(IconNames::LOCKED, KIconLoader::NoGroup, BASKET_ICON_SIZE); painter->drawPixmap(effectiveWidth, y, icon); } } QPixmap FoundCountIcon::circledTextPixmap(const QString &text, int height, const QFont &font, const QColor &color) const { QString key = QString("BLI-%1.%2.%3.%4").arg(text).arg(height).arg(font.toString()).arg(color.rgb()); QPixmap cached; if (QPixmapCache::find(key, &cached)) { return cached; } // Compute the sizes of the image components: QRectF textRect = QFontMetrics(font).boundingRect(0, 0, /*width=*/1, height, Qt::AlignLeft | Qt::AlignTop, text); qreal xMargin = height / 6; qreal width = xMargin + textRect.width() + xMargin; // Create the background image: QPixmap background(3 * width, 3 * height); // We double the size to be able to smooth scale down it (== antialiased curves) QPainter backgroundPainter(&background); const QPalette &palette = m_basketTree->palette(); backgroundPainter.fillRect(0, 0, background.width(), background.height(), palette.color(QPalette::Highlight)); backgroundPainter.end(); // Draw the curved rectangle: QBitmap curvedRectangle(3 * width, 3 * height); curvedRectangle.fill(Qt::color0); QPainter curvePainter(&curvedRectangle); curvePainter.setPen(Qt::color1); curvePainter.setBrush(Qt::color1); curvePainter.setClipRect(0, 0, 3 * (height / 5), 3 * (height)); // If the width is small, don't fill the right part of the pixmap curvePainter.drawEllipse(0, 3 * (-height / 4), 3 * (height), 3 * (height * 3 / 2)); // Don't forget we double the sizes curvePainter.setClipRect(3 * (width - height / 5), 0, 3 * (height / 5), 3 * (height)); curvePainter.drawEllipse(3 * (width - height), 3 * (-height / 4), 3 * (height), 3 * (height * 3 / 2)); curvePainter.setClipping(false); curvePainter.fillRect(3 * (height / 6), 0, 3 * (width - 2 * height / 6), 3 * (height), curvePainter.brush()); curvePainter.end(); // Apply the curved rectangle as the mask of the background: background.setMask(curvedRectangle); QImage resultImage = background.toImage(); // resultImage.setAlphaBuffer(true); // resultImage.convertToFormat(QImage::Format_ARGB32); // Scale down the image smoothly to get anti-aliasing: QPixmap pmScaled = QPixmap::fromImage(resultImage.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); // Draw the text, and return the result: QPainter painter(&pmScaled); painter.setPen(color); painter.setFont(font); painter.drawText(0 + 1, 0, width, height, Qt::AlignHCenter | Qt::AlignVCenter, text); painter.end(); QPixmapCache::insert(key, pmScaled); return pmScaled; } QPixmap FoundCountIcon::foundCountPixmap(bool isLoading, int countFound, bool childrenAreLoading, int countChildsFound, const QFont &font, int height) const { if (isLoading) return QPixmap(); QFont boldFont(font); boldFont.setBold(true); QString text; if (childrenAreLoading) { if (countChildsFound > 0) text = i18n("%1+%2+", QString::number(countFound), QString::number(countChildsFound)); else text = i18n("%1+", QString::number(countFound)); } else { if (countChildsFound > 0) text = i18n("%1+%2", QString::number(countFound), QString::number(countChildsFound)); else if (countFound > 0) text = QString::number(countFound); else return QPixmap(); } return circledTextPixmap(text, height, boldFont, m_basketTree->palette().color(QPalette::HighlightedText)); } diff --git a/src/basketproperties.cpp b/src/basketproperties.cpp index d287348..84b825c 100644 --- a/src/basketproperties.cpp +++ b/src/basketproperties.cpp @@ -1,215 +1,215 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "basketproperties.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundmanager.h" #include "basketscene.h" #include "gitwrapper.h" #include "global.h" #include "kcolorcombo2.h" #include "variouswidgets.h" #include "ui_basketproperties.h" BasketPropertiesDialog::BasketPropertiesDialog(BasketScene *basket, QWidget *parent) : QDialog(parent) , Ui::BasketPropertiesUi() , m_basket(basket) { // Set up dialog options setWindowTitle(i18n("Basket Properties")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, this); QWidget *mainWidget = new QWidget(this); setupUi(mainWidget); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &BasketPropertiesDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &BasketPropertiesDialog::reject); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setObjectName("BasketProperties"); setModal(true); Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); // cast to remove name ambiguity propsUi->icon->setIconType(KIconLoader::NoGroup, KIconLoader::Action); propsUi->icon->setIconSize(16); propsUi->icon->setIcon(m_basket->icon()); int size = qMax(propsUi->icon->sizeHint().width(), propsUi->icon->sizeHint().height()); propsUi->icon->setFixedSize(size, size); // Make it square! propsUi->icon->setToolTip(i18n("Icon")); propsUi->name->setText(m_basket->basketName()); propsUi->name->setMinimumWidth(propsUi->name->fontMetrics().maxWidth() * 20); propsUi->name->setToolTip(i18n("Name")); // Appearance: m_backgroundColor = new KColorCombo2(m_basket->backgroundColorSetting(), palette().color(QPalette::Base), appearanceGroup); m_textColor = new KColorCombo2(m_basket->textColorSetting(), palette().color(QPalette::Text), appearanceGroup); bgColorLbl->setBuddy(m_backgroundColor); txtColorLbl->setBuddy(m_textColor); appearanceLayout->addWidget(m_backgroundColor, 1, 2); appearanceLayout->addWidget(m_textColor, 2, 2); setTabOrder(backgroundImage, m_backgroundColor); setTabOrder(m_backgroundColor, m_textColor); setTabOrder(m_textColor, columnForm); backgroundImage->addItem(i18n("(None)")); m_backgroundImagesMap.insert(0, QString()); backgroundImage->setIconSize(QSize(100, 75)); QStringList backgrounds = Global::backgroundManager->imageNames(); int index = 1; for (QStringList::Iterator it = backgrounds.begin(); it != backgrounds.end(); ++it) { QPixmap *preview = Global::backgroundManager->preview(*it); if (preview) { m_backgroundImagesMap.insert(index, *it); backgroundImage->insertItem(index, *it); backgroundImage->setItemData(index, *preview, Qt::DecorationRole); if (m_basket->backgroundImageName() == *it) backgroundImage->setCurrentIndex(index); index++; } } // m_backgroundImage->insertItem(i18n("Other..."), -1); int BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); backgroundImage->setMaxVisibleItems(50 /*75 * 6 / m_backgroundImage->sizeHint().height()*/); backgroundImage->setMinimumHeight(75 + 2 * BUTTON_MARGIN); // Disposition: columnCount->setRange(1, 20); columnCount->setValue(m_basket->columnsCount()); connect(columnCount, SIGNAL(valueChanged(int)), this, SLOT(selectColumnsLayout())); - + int height = qMax(mindMap->sizeHint().height(), columnCount->sizeHint().height()); // Make all radioButtons vertically equally-spaced! mindMap->setMinimumSize(mindMap->sizeHint().width(), height); // Because the m_columnCount can be higher, and make radio1 and radio2 more spaced than radio2 and radio3. if (!m_basket->isFreeLayout()) columnForm->setChecked(true); else if (m_basket->isMindMap()) mindMap->setChecked(true); else freeForm->setChecked(true); mindMap->hide(); // Keyboard Shortcut: QList shortcuts {m_basket->shortcut()}; shortcut->setShortcut(shortcuts); HelpLabel *helpLabel = new HelpLabel(i18n("Learn some tips..."), i18n("

Easily Remember your Shortcuts:
" "With the first option, giving the basket a shortcut of the form Alt+Letter will underline that letter in the basket tree.
" "For instance, if you are assigning the shortcut Alt+T to a basket named Tips, the basket will be displayed as Tips in the tree. " "It helps you visualize the shortcuts to remember them more quickly.

" "

Local vs Global:
" "The first option allows you to show the basket while the main window is active. " "Global shortcuts are valid from anywhere, even if the window is hidden.

" "

Show vs Switch:
" "The last option makes this basket the current one without opening the main window. " "It is useful in addition to the configurable global shortcuts, eg. to paste the clipboard or the selection into the current basket from anywhere.

"), nullptr); shortcutLayout->addWidget(helpLabel); - connect(shortcut, SIGNAL(shortcutChanged(const QList &)), this, SLOT(capturedShortcut(const QList &))); + connect(shortcut, &KShortcutWidget::shortcutChanged, this, &BasketPropertiesDialog::capturedShortcut); setTabOrder(columnCount, shortcut); setTabOrder(shortcut, helpLabel); setTabOrder(helpLabel, showBasket); switch (m_basket->shortcutAction()) { default: case 0: showBasket->setChecked(true); break; case 1: globalButton->setChecked(true); break; case 2: switchButton->setChecked(true); break; } // Connect the Ok and Apply buttons to actually apply the changes - connect(okButton, SIGNAL(clicked()), SLOT(applyChanges())); - connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(applyChanges())); + connect(okButton, &QPushButton::clicked, this, &BasketPropertiesDialog::applyChanges); + connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &BasketPropertiesDialog::applyChanges); } BasketPropertiesDialog::~BasketPropertiesDialog() { } void BasketPropertiesDialog::ensurePolished() { ensurePolished(); Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); propsUi->name->setFocus(); } void BasketPropertiesDialog::applyChanges() { if (columnForm->isChecked()) { m_basket->setDisposition(0, columnCount->value()); } else if (freeForm->isChecked()) { m_basket->setDisposition(1, columnCount->value()); } else { m_basket->setDisposition(2, columnCount->value()); } if (showBasket->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 0); } else if (globalButton->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 1); } else if (switchButton->isChecked()) { m_basket->setShortcut(shortcut->shortcut()[0], 2); } Ui::BasketPropertiesUi *propsUi = dynamic_cast(this); // Should be called LAST, because it will emit the propertiesChanged() signal and the tree will be able to show the newly set Alt+Letter shortcut: m_basket->setAppearance(propsUi->icon->icon(), propsUi->name->text(), m_backgroundImagesMap[backgroundImage->currentIndex()], m_backgroundColor->color(), m_textColor->color()); GitWrapper::commitBasket(m_basket); m_basket->save(); } void BasketPropertiesDialog::capturedShortcut(const QList &sc) { // TODO: Validate it! shortcut->setShortcut(sc); } void BasketPropertiesDialog::selectColumnsLayout() { columnForm->setChecked(true); } diff --git a/src/basketscene.cpp b/src/basketscene.cpp index f307541..c328fc0 100644 --- a/src/basketscene.cpp +++ b/src/basketscene.cpp @@ -1,5219 +1,5219 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "basketscene.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // seed for rand() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for KStatefulBrush #include #include #include #include #include #include #include #include #include #include // rand() function #include "backgroundmanager.h" #include "basketview.h" #include "debugwindow.h" #include "decoratedbasket.h" #include "diskerrordialog.h" #include "focusedwidgets.h" #include "gitwrapper.h" #include "global.h" #include "note.h" #include "notedrag.h" #include "noteedit.h" #include "notefactory.h" #include "noteselection.h" #include "settings.h" #include "tagsedit.h" #include "tools.h" #include "transparentwidget.h" #include "xmlwork.h" #include "config.h" #ifdef HAVE_LIBGPGME #include "kgpgme.h" #endif void debugZone(int zone) { QString s; switch (zone) { case Note::Handle: s = "Handle"; break; case Note::Group: s = "Group"; break; case Note::TagsArrow: s = "TagsArrow"; break; case Note::Custom0: s = "Custom0"; break; case Note::GroupExpander: s = "GroupExpander"; break; case Note::Content: s = "Content"; break; case Note::Link: s = "Link"; break; case Note::TopInsert: s = "TopInsert"; break; case Note::TopGroup: s = "TopGroup"; break; case Note::BottomInsert: s = "BottomInsert"; break; case Note::BottomGroup: s = "BottomGroup"; break; case Note::BottomColumn: s = "BottomColumn"; break; case Note::None: s = "None"; break; default: if (zone == Note::Emblem0) s = "Emblem0"; else s = "Emblem0+" + QString::number(zone - Note::Emblem0); break; } qDebug() << s; } #define FOR_EACH_NOTE(noteVar) for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next()) void BasketScene::prependNoteIn(Note *note, Note *in) { if (!note) // No note to prepend: return; if (in) { // The normal case: preparePlug(note); Note *last = note->lastSibling(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); // note->setPrev(0L); last->setNext(in->firstChild()); if (in->firstChild()) in->firstChild()->setPrev(last); in->setFirstChild(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteBefore(note, firstNote()); } void BasketScene::appendNoteIn(Note *note, Note *in) { if (!note) // No note to append: return; if (in) { // The normal case: preparePlug(note); // Note *last = note->lastSibling(); Note *lastChild = in->lastChild(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); note->setPrev(lastChild); // last->setNext(0L); if (!in->firstChild()) in->setFirstChild(note); if (lastChild) lastChild->setNext(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteAfter(note, lastNote()); } void BasketScene::appendNoteAfter(Note *note, Note *after) { if (!note) // No note to append: return; if (!after) // By default, insert after the last note: after = lastNote(); if (m_loaded && after && !after->isFree() && !after->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(after); // if (!alreadyInBasket) preparePlug(note); Note *last = note->lastSibling(); if (after) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(after->parentNote()); note->setPrev(after); last->setNext(after->next()); after->setNext(note); if (last->next()) last->next()->setPrev(last); } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(nullptr); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } // if (!alreadyInBasket) if (m_loaded) signalCountsChanged(); } void BasketScene::appendNoteBefore(Note *note, Note *before) { if (!note) // No note to append: return; if (!before) // By default, insert before the first note: before = firstNote(); if (m_loaded && before && !before->isFree() && !before->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(before); preparePlug(note); Note *last = note->lastSibling(); if (before) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(before->parentNote()); note->setPrev(before->prev()); last->setNext(before); before->setPrev(last); if (note->prev()) note->prev()->setNext(note); else { if (note->parentNote()) note->parentNote()->setFirstChild(note); else m_firstNote = note; } } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(nullptr); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } if (m_loaded) signalCountsChanged(); } DecoratedBasket *BasketScene::decoration() { return (DecoratedBasket *)parent(); } void BasketScene::preparePlug(Note *note) { // Select only the new notes, compute the new notes count and the new number of found notes: if (m_loaded) unselectAll(); int count = 0; int founds = 0; Note *last = nullptr; for (Note *n = note; n; n = n->next()) { if (m_loaded) n->setSelectedRecursively(true); // Notes should have a parent basket (and they have, so that's OK). count += n->count(); founds += n->newFilter(decoration()->filterData()); last = n; } m_count += count; m_countFounds += founds; // Focus the last inserted note: if (m_loaded && last) { setFocusedNote(last); m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last); } // If some notes don't match (are hidden), tell it to the user: if (m_loaded && founds < count) { if (count == 1) postMessage(i18n("The new note does not match the filter and is hidden.")); else if (founds == count - 1) postMessage(i18n("A new note does not match the filter and is hidden.")); else if (founds > 0) postMessage(i18n("Some new notes do not match the filter and are hidden.")); else postMessage(i18n("The new notes do not match the filter and are hidden.")); } } void BasketScene::unplugNote(Note *note) { // If there is nothing to do... if (!note) return; // if (!willBeReplugged) { note->setSelectedRecursively(false); // To removeSelectedNote() and decrease the selectedsCount. m_count -= note->count(); m_countFounds -= note->newFilter(decoration()->filterData()); signalCountsChanged(); // } // If it was the first note, change the first note: if (m_firstNote == note) m_firstNote = note->next(); // Change previous and next notes: if (note->prev()) note->prev()->setNext(note->next()); if (note->next()) note->next()->setPrev(note->prev()); if (note->parentNote()) { // If it was the first note of a group, change the first note of the group: if (note->parentNote()->firstChild() == note) note->parentNote()->setFirstChild(note->next()); if (!note->parentNote()->isColumn()) { // Delete parent if now 0 notes inside parent group: if (!note->parentNote()->firstChild()) { unplugNote(note->parentNote()); // a group could call this method for one or more of its children, // each children could call this method for its parent's group... // we have to do the deletion later otherwise we may corrupt the current process m_notesToBeDeleted << note; if (m_notesToBeDeleted.count() == 1) { QTimer::singleShot(0, this, SLOT(doCleanUp())); } } // Ungroup if still 1 note inside parent group: else if (!note->parentNote()->firstChild()->next()) { ungroupNote(note->parentNote()); } } } note->setParentNote(nullptr); note->setPrev(nullptr); note->setNext(nullptr); // Reste focus and hover note if necessary if (m_focusedNote == note) m_focusedNote = nullptr; if (m_hoveredNote == note) m_hoveredNote = nullptr; // recomputeBlankRects(); // FIXME: called too much time. It's here because when dragging and moving a note to another basket and then go back to the original basket, the note is deleted but the note rect is not painter anymore. } void BasketScene::ungroupNote(Note *group) { Note *note = group->firstChild(); Note *lastGroupedNote = group; Note *nextNote; // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild): while (note) { nextNote = note->next(); if (lastGroupedNote->next()) lastGroupedNote->next()->setPrev(note); note->setNext(lastGroupedNote->next()); lastGroupedNote->setNext(note); note->setParentNote(group->parentNote()); note->setPrev(lastGroupedNote); note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH); lastGroupedNote = note; note = nextNote; } // Unplug the group: group->setFirstChild(nullptr); unplugNote(group); // a group could call this method for one or more of its children, // each children could call this method for its parent's group... // we have to do the deletion later otherwise we may corrupt the current process m_notesToBeDeleted << group; if (m_notesToBeDeleted.count() == 1) { QTimer::singleShot(0, this, SLOT(doCleanUp())); } } void BasketScene::groupNoteBefore(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(note); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(last); with->setNext(nullptr); for (Note *n = note; n; n = n->next()) n->setParentNote(group); // note->setPrev(0L); last->setNext(with); if (m_loaded) signalCountsChanged(); } void BasketScene::groupNoteAfter(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); // Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(with); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(nullptr); with->setNext(note); for (Note *n = note; n; n = n->next()) n->setParentNote(group); note->setPrev(with); // last->setNext(0L); if (m_loaded) signalCountsChanged(); } void BasketScene::doCleanUp() { QSet::iterator it = m_notesToBeDeleted.begin(); while (it != m_notesToBeDeleted.end()) { delete *it; it = m_notesToBeDeleted.erase(it); } } void BasketScene::loadNotes(const QDomElement ¬es, Note *parent) { Note *note; for (QDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) // Cannot handle that! continue; note = nullptr; // Load a Group: if (e.tagName() == "group") { note = new Note(this); // 1. Create the group... loadNotes(e, note); // 3. ... And populate it with child notes. int noteCount = note->count(); if (noteCount > 0 || (parent == nullptr && !isFreeLayout())) { // But don't remove columns! appendNoteIn(note, parent); // 2. ... Insert it... FIXME: Initially, the if() the insertion was the step 2. Was it on purpose? // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes): m_count -= noteCount; // TODO: Recompute note count every time noteCount() is emitted! m_countFounds -= noteCount; } } // Load a Content-Based Note: if (e.tagName() == "note" || e.tagName() == "item") { // Keep compatible with 0.6.0 Alpha 1 note = new Note(this); // Create the note... NoteFactory::loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content... if (e.attribute("type") == "text") m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! appendNoteIn(note, parent); // ... And insert it. // Load dates: if (e.hasAttribute("added")) note->setAddedDate(QDateTime::fromString(e.attribute("added"), Qt::ISODate)); if (e.hasAttribute("lastModification")) note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate)); } // If we successfully loaded a note: if (note) { // Free Note Properties: if (note->isFree()) { int x = e.attribute("x").toInt(); int y = e.attribute("y").toInt(); note->setX(x < 0 ? 0 : x); note->setY(y < 0 ? 0 : y); } // Resizeable Note Properties: if (note->hasResizer() || note->isColumn()) note->setGroupWidth(e.attribute("width", "200").toInt()); // Group Properties: if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false"))) note->toggleFolded(); // Tags: if (note->content()) { QString tagsString = XMLWork::getElementText(e, QStringLiteral("tags"), QString()); QStringList tagsId = tagsString.split(';'); for (QStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) { State *state = Tag::stateForId(*it); if (state) note->addState(state, /*orReplace=*/true); } } } qApp->processEvents(); } } void BasketScene::saveNotes(QXmlStreamWriter &stream, Note *parent) { Note *note = (parent ? parent->firstChild() : firstNote()); while (note) { // Create Element: stream.writeStartElement(note->isGroup() ? "group" : "note"); // Free Note Properties: if (note->isFree()) { stream.writeAttribute("x", QString::number(note->x())); stream.writeAttribute("y", QString::number(note->y())); } // Resizeable Note Properties: if (note->hasResizer()) stream.writeAttribute("width", QString::number(note->groupWidth())); // Group Properties: if (note->isGroup() && !note->isColumn()) stream.writeAttribute("folded", XMLWork::trueOrFalse(note->isFolded())); // Save Content: if (note->content()) { // Save Dates: stream.writeAttribute("added", note->addedDate().toString(Qt::ISODate)); stream.writeAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate)); // Save Content: stream.writeAttribute("type", note->content()->lowerTypeName()); note->content()->saveToNode(stream); // Save Tags: if (note->states().count() > 0) { QString tags; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) { tags += (tags.isEmpty() ? QString() : QStringLiteral(";")) + (*it)->id(); } stream.writeTextElement("tags", tags); } } else { // Save Child Notes: saveNotes(stream, note); } stream.writeEndElement(); // Go to the Next One: note = note->next(); } } void BasketScene::loadProperties(const QDomElement &properties) { // Compute Default Values for When Loading the Properties: QString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : QString()); QString defaultTextColor = (textColorSetting().isValid() ? textColorSetting().name() : QString()); // Load the Properties: QString icon = XMLWork::getElementText(properties, "icon", this->icon()); QString name = XMLWork::getElementText(properties, "name", basketName()); QDomElement appearance = XMLWork::getElement(properties, "appearance"); // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background" QString backgroundImage = appearance.attribute("backgroundImage", appearance.attribute("backroundImage", backgroundImageName())); QString backgroundColorString = appearance.attribute("backgroundColor", appearance.attribute("backroundColor", defaultBackgroundColor)); QString textColorString = appearance.attribute("textColor", defaultTextColor); QColor backgroundColor = (backgroundColorString.isEmpty() ? QColor() : QColor(backgroundColorString)); QColor textColor = (textColorString.isEmpty() ? QColor() : QColor(textColorString)); QDomElement disposition = XMLWork::getElement(properties, "disposition"); bool free = XMLWork::trueOrFalse(disposition.attribute("free", XMLWork::trueOrFalse(isFreeLayout()))); int columnCount = disposition.attribute("columnCount", QString::number(this->columnsCount())).toInt(); bool mindMap = XMLWork::trueOrFalse(disposition.attribute("mindMap", XMLWork::trueOrFalse(isMindMap()))); QDomElement shortcut = XMLWork::getElement(properties, "shortcut"); QString actionStrings[] = {"show", "globalShow", "globalSwitch"}; QKeySequence combination = QKeySequence(shortcut.attribute("combination", m_action->shortcut().toString())); QString actionString = shortcut.attribute("action"); int action = shortcutAction(); if (actionString == actionStrings[0]) action = 0; if (actionString == actionStrings[1]) action = 1; if (actionString == actionStrings[2]) action = 2; QDomElement protection = XMLWork::getElement(properties, "protection"); m_encryptionType = protection.attribute("type").toInt(); m_encryptionKey = protection.attribute("key"); // Apply the Properties: setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount); setShortcut(combination, action); setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this) } void BasketScene::saveProperties(QXmlStreamWriter &stream) { stream.writeStartElement("properties"); stream.writeTextElement("name", basketName()); stream.writeTextElement("icon", icon()); stream.writeStartElement("appearance"); stream.writeAttribute("backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : QString()); stream.writeAttribute("backgroundImage", backgroundImageName()); stream.writeAttribute("textColor", textColorSetting().isValid() ? textColorSetting().name() : QString()); stream.writeEndElement(); stream.writeStartElement("disposition"); stream.writeAttribute("columnCount", QString::number(columnsCount())); stream.writeAttribute("free", XMLWork::trueOrFalse(isFreeLayout())); stream.writeAttribute("mindMap", XMLWork::trueOrFalse(isMindMap())); stream.writeEndElement(); stream.writeStartElement("shortcut"); QString actionStrings[] = {"show", "globalShow", "globalSwitch"}; stream.writeAttribute("action", actionStrings[shortcutAction()]); stream.writeAttribute("combination", m_action->shortcut().toString()); stream.writeEndElement(); stream.writeStartElement("protection"); stream.writeAttribute("key", m_encryptionKey); stream.writeAttribute("type", QString::number(m_encryptionType)); stream.writeEndElement(); stream.writeEndElement(); } void BasketScene::subscribeBackgroundImages() { if (!m_backgroundImageName.isEmpty()) { Global::backgroundManager->subscribe(m_backgroundImageName); Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = Global::backgroundManager->pixmap(m_backgroundImageName); m_opaqueBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor()); m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor()); m_backgroundTiled = Global::backgroundManager->tiled(m_backgroundImageName); } } void BasketScene::unsubscribeBackgroundImages() { if (hasBackgroundImage()) { Global::backgroundManager->unsubscribe(m_backgroundImageName); Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = nullptr; m_opaqueBackgroundPixmap = nullptr; m_selectedBackgroundPixmap = nullptr; } } void BasketScene::setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor) { unsubscribeBackgroundImages(); m_icon = icon; m_basketName = name; m_backgroundImageName = backgroundImage; m_backgroundColorSetting = backgroundColor; m_textColorSetting = textColor; // Where is this shown? m_action->setText("BASKET SHORTCUT: " + name); // Basket should ALWAYS have an icon (the "basket" icon by default): QPixmap iconTest = KIconLoader::global()->loadIcon(m_icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/true); if (iconTest.isNull()) m_icon = "basket"; // We don't request the background images if it's not loaded yet (to make the application startup fast). // When the basket is loading (because requested by the user: he/she want to access it) // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image, // load all the notes and it's done! if (m_loadingLaunched) subscribeBackgroundImages(); recomputeAllStyles(); // If a note have a tag with the same background color as the basket one, then display a "..." recomputeBlankRects(); // See the drawing of blank areas in BasketScene::drawContents() unbufferizeAll(); if (isDuringEdit() && m_editor->graphicsWidget()) { QPalette palette; palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor()); palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor()); m_editor->graphicsWidget()->setPalette(palette); } emit propertiesChanged(this); } void BasketScene::setDisposition(int disposition, int columnCount) { static const int COLUMNS_LAYOUT = 0; static const int FREE_LAYOUT = 1; static const int MINDMAPS_LAYOUT = 2; int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT); if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) { if (firstNote() && columnCount > m_columnsCount) { // Insert each new columns: for (int i = m_columnsCount; i < columnCount; ++i) { Note *newColumn = new Note(this); insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); } } else if (firstNote() && columnCount < m_columnsCount) { Note *column = firstNote(); Note *cuttedNotes = nullptr; for (int i = 1; i <= m_columnsCount; ++i) { Note *columnToRemove = column; column = column->next(); if (i > columnCount) { // Remove the columns that are too much: unplugNote(columnToRemove); // "Cut" the content in the columns to be deleted: if (columnToRemove->firstChild()) { for (Note *it = columnToRemove->firstChild(); it; it = it->next()) it->setParentNote(nullptr); if (!cuttedNotes) cuttedNotes = columnToRemove->firstChild(); else { Note *lastCuttedNote = cuttedNotes; while (lastCuttedNote->next()) lastCuttedNote = lastCuttedNote->next(); lastCuttedNote->setNext(columnToRemove->firstChild()); columnToRemove->firstChild()->setPrev(lastCuttedNote); } columnToRemove->setFirstChild(nullptr); } delete columnToRemove; } } // Paste the content in the last column: if (cuttedNotes) insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true); unselectAll(); } if (columnCount != m_columnsCount) { m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) { Note *column = firstNote(); m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns! while (column) { // Move all childs on the first level: Note *nextColumn = column->next(); ungroupNote(column); column = nextColumn; } unselectAll(); m_mindMap = (disposition == MINDMAPS_LAYOUT); relayoutNotes(); } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) { if (firstNote()) { // TODO: Reorder notes! // Remove all notes (but keep a reference to them, we're not crazy ;-) ): Note *notes = m_firstNote; m_firstNote = nullptr; m_count = 0; m_countFounds = 0; // Insert the number of columns that is needed: Note *lastInsertedColumn = nullptr; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } // Reinsert the old notes in the first column: insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true); unselectAll(); } else { // Insert the number of columns that is needed: Note *lastInsertedColumn = nullptr; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } } m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } void BasketScene::equalizeColumnSizes() { if (!firstNote()) return; // Necessary to know the available space; relayoutNotes(); int availableSpace = m_view->viewport()->width(); int columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnsCount(); int columnCount = columnsCount(); Note *column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) { availableSpace -= minGroupWidth; --columnCount; } column = column->next(); } columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnCount; column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) column->setGroupWidth(minGroupWidth); else column->setGroupWidth(columnWidth); column = column->next(); } relayoutNotes(); } void BasketScene::enableActions() { Global::bnpView->enableActions(); m_view->setFocusPolicy(isLocked() ? Qt::NoFocus : Qt::StrongFocus); if (isLocked()) m_view->viewport()->setCursor(Qt::ArrowCursor); // When locking, the cursor stays the last form it was } bool BasketScene::save() { if (!m_loaded) return false; DEBUG_WIN << "Basket[" + folderName() + "]: Saving..."; QString data; QXmlStreamWriter stream(&data); XMLWork::setupXmlStream(stream, "basket"); // Create Properties Element and Populate It: saveProperties(stream); // Create Notes Element and Populate It: stream.writeStartElement("notes"); saveNotes(stream, nullptr); stream.writeEndElement(); stream.writeEndElement(); stream.writeEndDocument(); // Write to Disk: if (!saveToFile(fullPath() + ".basket", data)) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to save!"; return false; } Global::bnpView->setUnsavedStatus(false); m_commitdelay.start(10000); // delay is 10 seconds return true; } void BasketScene::commitEdit() { GitWrapper::commitBasket(this); } void BasketScene::aboutToBeActivated() { if (m_finishLoadOnFirstShow) { FOR_EACH_NOTE(note) note->finishLazyLoad(); // relayoutNotes(/*animate=*/false); setFocusedNote(nullptr); // So that during the focusInEvent that will come shortly, the FIRST note is focused. m_finishLoadOnFirstShow = false; m_loaded = true; } } void BasketScene::reload() { closeEditor(); unbufferizeAll(); // Keep the memory footprint low m_firstNote = nullptr; m_loaded = false; m_loadingLaunched = false; invalidate(); } void BasketScene::load() { // Load only once: if (m_loadingLaunched) return; m_loadingLaunched = true; DEBUG_WIN << "Basket[" + folderName() + "]: Loading..."; QDomDocument *doc = nullptr; QString content; // Load properties if (loadFromFile(fullPath() + ".basket", &content)) { doc = new QDomDocument("basket"); if (!doc->setContent(content)) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to parse XML!"; delete doc; doc = nullptr; } } if (isEncrypted()) DEBUG_WIN << "Basket is encrypted."; if (!doc) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to load!"; m_loadingLaunched = false; if (isEncrypted()) m_locked = true; Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar return; } m_locked = false; QDomElement docElem = doc->documentElement(); QDomElement properties = XMLWork::getElement(docElem, "properties"); loadProperties(properties); // Since we are loading, this time the background image will also be loaded! // Now that the background image is loaded and subscribed, we display it during the load process: delete doc; // BEGIN Compatibility with 0.6.0 Pre-Alpha versions: QDomElement notes = XMLWork::getElement(docElem, "notes"); if (notes.isNull()) notes = XMLWork::getElement(docElem, "items"); m_watcher->stopScan(); m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! // Load notes m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this); loadNotes(notes, nullptr); if (m_shouldConvertPlainTextNotes) convertTexts(); m_watcher->startScan(); signalCountsChanged(); if (isColumnsLayout()) { // Count the number of columns: int columnsCount = 0; Note *column = firstNote(); while (column) { ++columnsCount; column = column->next(); } m_columnsCount = columnsCount; } relayoutNotes(); // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote(): if (Global::bnpView->currentBasket() == this) setFocus(); focusANote(); m_loaded = true; enableActions(); } void BasketScene::filterAgain(bool andEnsureVisible /* = true*/) { newFilter(decoration()->filterData(), andEnsureVisible); } void BasketScene::filterAgainDelayed() { QTimer::singleShot(0, this, SLOT(filterAgain())); } void BasketScene::newFilter(const FilterData &data, bool andEnsureVisible /* = true*/) { if (!isLoaded()) return; // StopWatch::start(20); m_countFounds = 0; for (Note *note = firstNote(); note; note = note->next()) m_countFounds += note->newFilter(data); relayoutNotes(); signalCountsChanged(); if (hasFocus()) // if (!hasFocus()), focusANote() will be called at focusInEvent() focusANote(); // so, we avoid de-focus a note if it will be re-shown soon if (andEnsureVisible && m_focusedNote != nullptr) ensureNoteVisible(m_focusedNote); Global::bnpView->setFiltering(data.isFiltering); // StopWatch::check(20); } bool BasketScene::isFiltering() { return decoration()->filterBar()->filterData().isFiltering; } QString BasketScene::fullPath() { return Global::basketsFolder() + folderName(); } QString BasketScene::fullPathForFileName(const QString &fileName) { return fullPath() + fileName; } /*static*/ QString BasketScene::fullPathForFolderName(const QString &folderName) { return Global::basketsFolder() + folderName; } void BasketScene::setShortcut(QKeySequence shortcut, int action) { QList shortcuts {shortcut}; if (action > 0) { KGlobalAccel::self()->setShortcut(m_action, shortcuts, KGlobalAccel::Autoloading); KGlobalAccel::self()->setDefaultShortcut(m_action, shortcuts, KGlobalAccel::Autoloading); } m_shortcutAction = action; } void BasketScene::activatedShortcut() { Global::bnpView->setCurrentBasket(this); if (m_shortcutAction == 1) Global::bnpView->setActive(true); } void BasketScene::signalCountsChanged() { if (!m_timerCountsChanged.isActive()) { m_timerCountsChanged.setSingleShot(true); m_timerCountsChanged.start(0); } } void BasketScene::countsChangedTimeOut() { emit countsChanged(this); } BasketScene::BasketScene(QWidget *parent, const QString &folderName) //: Q3ScrollView(parent) : QGraphicsScene(parent) , m_noActionOnMouseRelease(false) , m_ignoreCloseEditorOnNextMouseRelease(false) , m_pressPos(-100, -100) , m_canDrag(false) , m_firstNote(nullptr) , m_columnsCount(1) , m_mindMap(false) , m_resizingNote(nullptr) , m_pickedResizer(0) , m_movingNote(nullptr) , m_pickedHandle(0, 0) , m_notesToBeDeleted() , m_clickedToInsert(nullptr) , m_zoneToInsert(0) , m_posToInsert(-1, -1) , m_isInsertPopupMenu(false) , m_insertMenuTitle(nullptr) , m_loaded(false) , m_loadingLaunched(false) , m_locked(false) , m_decryptBox(nullptr) , m_button(nullptr) , m_encryptionType(NoEncryption) #ifdef HAVE_LIBGPGME , m_gpg(0) #endif , m_backgroundPixmap(nullptr) , m_opaqueBackgroundPixmap(nullptr) , m_selectedBackgroundPixmap(nullptr) , m_action(nullptr) , m_shortcutAction(0) , m_hoveredNote(nullptr) , m_hoveredZone(Note::None) , m_lockedHovering(false) , m_underMouse(false) , m_inserterRect() , m_inserterShown(false) , m_inserterSplit(true) , m_inserterTop(false) , m_inserterGroup(false) , m_lastDisableClick(QTime::currentTime()) , m_isSelecting(false) , m_selectionStarted(false) , m_count(0) , m_countFounds(0) , m_countSelecteds(0) , m_folderName(folderName) , m_editor(nullptr) , m_leftEditorBorder(nullptr) , m_rightEditorBorder(nullptr) , m_redirectEditActions(false) , m_editorTrackMouseEvent(false) , m_editorWidth(-1) , m_editorHeight(-1) , m_doNotCloseEditor(false) , m_isDuringDrag(false) , m_draggedNotes() , m_focusedNote(nullptr) , m_startOfShiftSelectionNote(nullptr) , m_finishLoadOnFirstShow(false) , m_relayoutOnNextShow(false) { m_view = new BasketView(this); m_view->setFocusPolicy(Qt::StrongFocus); m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_action = new QAction(this); - connect(m_action, SIGNAL(triggered()), this, SLOT(activatedShortcut())); + connect(m_action, &QAction::triggered, this, &BasketScene::activatedShortcut); m_action->setObjectName(folderName); KGlobalAccel::self()->setGlobalShortcut(m_action, (QKeySequence())); // We do this in the basket properties dialog (and keep it in sync with the // global one) KActionCollection *ac = Global::bnpView->actionCollection(); ac->setShortcutsConfigurable(m_action, false); if (!m_folderName.endsWith('/')) m_folderName += '/'; // setDragAutoScroll(true); // By default, there is no corner widget: we set one for the corner area to be painted! // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area! m_cornerWidget = new QWidget(m_view); m_view->setCornerWidget(m_cornerWidget); m_view->viewport()->setAcceptDrops(true); m_view->viewport()->setMouseTracking(true); m_view->viewport()->setAutoFillBackground(false); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free) // File Watcher: m_watcher = new KDirWatch(this); - connect(m_watcher, SIGNAL(dirty(const QString &)), this, SLOT(watchedFileModified(const QString &))); - // connect(m_watcher, SIGNAL(deleted(const QString&)), this, SLOT(watchedFileDeleted(const QString&))); - connect(&m_watcherTimer, SIGNAL(timeout()), this, SLOT(updateModifiedNotes())); + connect(m_watcher, &KDirWatch::dirty, this, &BasketScene::watchedFileModified); + //connect(m_watcher, &KDirWatch::deleted, this, &BasketScene::watchedFileDeleted); + connect(&m_watcherTimer, &QTimer::timeout, this, &BasketScene::updateModifiedNotes); // Various Connections: - connect(&m_autoScrollSelectionTimer, SIGNAL(timeout()), this, SLOT(doAutoScrollSelection())); - connect(&m_timerCountsChanged, SIGNAL(timeout()), this, SLOT(countsChangedTimeOut())); - connect(&m_inactivityAutoSaveTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoSaveTimeout())); - connect(&m_inactivityAutoLockTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoLockTimeout())); + connect(&m_autoScrollSelectionTimer, &QTimer::timeout, this, &BasketScene::doAutoScrollSelection); + connect(&m_timerCountsChanged, &QTimer::timeout, this, &BasketScene::countsChangedTimeOut); + connect(&m_inactivityAutoSaveTimer, &QTimer::timeout, this, &BasketScene::inactivityAutoSaveTimeout); + connect(&m_inactivityAutoLockTimer, &QTimer::timeout, this, &BasketScene::inactivityAutoLockTimeout); #ifdef HAVE_LIBGPGME m_gpg = new KGpgMe(); #endif m_locked = isFileEncrypted(); // setup the delayed commit timer m_commitdelay.setSingleShot(true); - connect(&m_commitdelay, SIGNAL(timeout()), this, SLOT(commitEdit())); + connect(&m_commitdelay, &QTimer::timeout, this, &BasketScene::commitEdit); } void BasketScene::enterEvent(QEvent *) { m_underMouse = true; doHoverEffects(); } void BasketScene::leaveEvent(QEvent *) { m_underMouse = false; doHoverEffects(); if (m_lockedHovering) return; removeInserter(); if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); m_hoveredNote->update(); } m_hoveredNote = nullptr; } void BasketScene::setFocusIfNotInPopupMenu() { if (!qApp->activePopupWidget()) { if (isDuringEdit()) m_editor->graphicsWidget()->setFocus(); else setFocus(); } } void BasketScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { // If user click the basket, focus it! // The focus is delayed because if the click results in showing a popup menu, // the interface flicker by showing the focused rectangle (as the basket gets focus) // and immediately removing it (because the popup menu now have focus). if (!isDuringEdit()) QTimer::singleShot(0, this, SLOT(setFocusIfNotInPopupMenu())); // Convenient variables: bool controlPressed = event->modifiers() & Qt::ControlModifier; bool shiftPressed = event->modifiers() & Qt::ShiftModifier; // Do nothing if we disabled the click some milliseconds sooner. // For instance when a popup menu has been closed with click, we should not do action: if (event->button() == Qt::LeftButton && (qApp->activePopupWidget() || m_lastDisableClick.msecsTo(QTime::currentTime()) <= 80)) { doHoverEffects(); m_noActionOnMouseRelease = true; // But we allow to select: // The code is the same as at the bottom of this method: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->scenePos(); m_selectionInvert = controlPressed || shiftPressed; } return; } // if we are editing and no control key are pressed if (m_editor && !shiftPressed && !controlPressed) { // if the mouse is over the editor QPoint view_shift(m_view->horizontalScrollBar()->value(), m_view->verticalScrollBar()->value()); QGraphicsWidget *widget = dynamic_cast(m_view->itemAt((event->scenePos() - view_shift).toPoint())); if (widget && m_editor->graphicsWidget() == widget) { if (event->button() == Qt::LeftButton) { m_editorTrackMouseEvent = true; m_editor->startSelection(event->scenePos()); return; } else if (event->button() == Qt::MiddleButton) { m_editor->paste(event->scenePos(), QClipboard::Selection); return; } } } // Figure out what is the clicked note and zone: Note *clicked = noteAt(event->scenePos()); if (m_editor && (!clicked || (clicked && !(editedNote() == clicked)))) { closeEditor(); } Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); // Popup Tags menu: if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; m_noActionOnMouseRelease = true; popupTagsMenu(clicked); return; } if (event->button() == Qt::LeftButton) { // Prepare to allow drag and drop when moving mouse further: if ((zone == Note::Handle || zone == Note::Group) || (clicked && clicked->allSelected() && (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/))) { if (!shiftPressed && !controlPressed) { m_pressPos = event->scenePos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!! m_canDrag = true; // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position: if (m_editor && m_editor->textEdit()) { KTextEdit *editor = m_editor->textEdit(); m_textCursor = editor->textCursor(); } } } // Initializing Resizer move: if (zone == Note::Resizer) { m_resizingNote = clicked; m_pickedResizer = event->scenePos().x() - clicked->rightLimit(); m_noActionOnMouseRelease = true; m_lockedHovering = true; return; } // Select note(s): if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) { // closeEditor(); Note *end = clicked; if (clicked->isGroup() && shiftPressed) { if (clicked->containsNote(m_startOfShiftSelectionNote)) { m_startOfShiftSelectionNote = clicked->firstRealChild(); end = clicked->lastRealChild(); } else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote)) { end = clicked->lastRealChild(); } else { end = clicked->firstRealChild(); } } if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, end); else if (controlPressed) clicked->setSelectedRecursively(!clicked->allSelected()); else if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(end); /// /// /// m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end); // m_noActionOnMouseRelease = false; m_noActionOnMouseRelease = true; return; } // Folding/Unfolding group: if (zone == Note::GroupExpander) { clicked->toggleFolded(); if (/*m_animationTimeLine == 0 && */ Settings::playAnimations()) { qWarning() << "Folding animation to be done"; } relayoutNotes(); m_noActionOnMouseRelease = true; return; } } // Popup menu for tag emblems: if (event->button() == Qt::RightButton && zone >= Note::Emblem0) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; popupEmblemMenu(clicked, zone - Note::Emblem0); m_noActionOnMouseRelease = true; return; } // Insertion Popup Menu: if ((event->button() == Qt::RightButton) && ((!clicked && isFreeLayout()) || (clicked && (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn)))) { unselectAll(); m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); QMenu menu(m_view); menu.addActions(Global::bnpView->popupMenu("insert_popup")->actions()); // If we already added a title, remove it because it would be kept and // then added several times. if (m_insertMenuTitle && menu.actions().contains(m_insertMenuTitle)) menu.removeAction(m_insertMenuTitle); QAction *first = menu.actions().value(0); // i18n: Verbs (for the "insert" menu) if (zone == Note::TopGroup || zone == Note::BottomGroup) m_insertMenuTitle = menu.insertSection(first, i18n("Group")); else m_insertMenuTitle = menu.insertSection(first, i18n("Insert")); setInsertPopupMenu(); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu())); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(hideInsertPopupMenu())); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::delayedCancelInsertPopupMenu); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::hideInsertPopupMenu); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu.exec(QCursor::pos()); m_noActionOnMouseRelease = true; return; } // Note Context Menu: if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) { if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// if (editedNote() == clicked) { closeEditor(false); clicked->setSelected(true); } m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); QMenu *menu = Global::bnpView->popupMenu("note_popup"); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(QCursor::pos()); m_noActionOnMouseRelease = true; return; } // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease): if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); // closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 1: m_isInsertPopupMenu = true; pasteNote(); break; case 2: type = NoteType::Image; break; case 3: type = NoteType::Link; break; case 4: type = NoteType::Launcher; break; default: m_noActionOnMouseRelease = false; return; } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); } } else { if (clicked) zone = clicked->zoneAt(event->scenePos() - QPoint(clicked->x(), clicked->y()), true); // closeEditor(); clickedToInsert(event, clicked, zone); save(); } m_noActionOnMouseRelease = true; return; } // Finally, no action has been done during pressEvent, so an action can be done on releaseEvent: m_noActionOnMouseRelease = false; /* Selection scenario: * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point. * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect, * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle. */ // Prepare selection: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->scenePos(); // We usually invert the selection with the Ctrl key, but some environments (like GNOME or The Gimp) do it with the Shift key. // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people m_selectionInvert = controlPressed || shiftPressed; } } void BasketScene::delayedCancelInsertPopupMenu() { QTimer::singleShot(0, this, SLOT(cancelInsertPopupMenu())); } void BasketScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { if (event->reason() == QGraphicsSceneContextMenuEvent::Keyboard) { if (countFounds /*countShown*/ () == 0) { // TODO: Count shown!! QMenu *menu = Global::bnpView->popupMenu("insert_popup"); setInsertPopupMenu(); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu())); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::delayedCancelInsertPopupMenu); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick); removeInserter(); m_lockedHovering = true; menu->exec(m_view->mapToGlobal(QPoint(0, 0))); } else { if (!m_focusedNote->isSelected()) unselectAllBut(m_focusedNote); setFocusedNote(m_focusedNote); /// /// /// m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote); // Popup at bottom (or top) of the focused note, if visible : QMenu *menu = Global::bnpView->popupMenu("note_popup"); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering); + connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick); doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(noteVisibleRect(m_focusedNote).bottomLeft().toPoint()); } } } QRectF BasketScene::noteVisibleRect(Note *note) { QRectF rect(QPointF(note->x(), note->y()), QSizeF(note->width(), note->height())); QPoint basketPoint = m_view->mapToGlobal(QPoint(0, 0)); rect.moveTopLeft(rect.topLeft() + basketPoint + QPoint(m_view->frameWidth(), m_view->frameWidth())); // Now, rect contain the global note rectangle on the screen. // We have to clip it by the basket widget : // if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom // rect.setBottom(basketPoint.y() + visibleHeight() + 1); if (rect.bottom() > basketPoint.y() + m_view->viewport()->height() + 1) { // Bottom too... bottom rect.setBottom(basketPoint.y() + m_view->viewport()->height() + 1); if (rect.height() <= 0) // Have at least one visible pixel of height rect.setTop(rect.bottom()); } if (rect.top() < basketPoint.y() + m_view->frameWidth()) { // Top too... top rect.setTop(basketPoint.y() + m_view->frameWidth()); if (rect.height() <= 0) rect.setBottom(rect.top()); } // if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right // rect.setRight(basketPoint.x() + visibleWidth() + 1); if (rect.right() > basketPoint.x() + m_view->viewport()->width() + 1) { // Right too... right rect.setRight(basketPoint.x() + m_view->viewport()->width() + 1); if (rect.width() <= 0) // Have at least one visible pixel of width rect.setLeft(rect.right()); } if (rect.left() < basketPoint.x() + m_view->frameWidth()) { // Left too... left rect.setLeft(basketPoint.x() + m_view->frameWidth()); if (rect.width() <= 0) rect.setRight(rect.left()); } return rect; } void BasketScene::disableNextClick() { m_lastDisableClick = QTime::currentTime(); } void BasketScene::recomputeAllStyles() { FOR_EACH_NOTE(note) note->recomputeAllStyles(); } void BasketScene::removedStates(const QList &deletedStates) { bool modifiedBasket = false; FOR_EACH_NOTE(note) if (note->removedStates(deletedStates)) modifiedBasket = true; if (modifiedBasket) save(); } void BasketScene::insertNote(Note *note, Note *clicked, int zone, const QPointF &pos, bool animateNewPosition) { if (!note) { qDebug() << "Wanted to insert NO note"; return; } if (clicked && zone == Note::BottomColumn) { // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags. // We ensure that by changing the insertion point after the last note of the column: Note *last = clicked->lastChild(); if (last) { clicked = last; zone = Note::BottomInsert; } } /// Insertion at the bottom of a column: if (clicked && zone == Note::BottomColumn) { note->setWidth(clicked->rightLimit() - clicked->x()); Note *lastChild = clicked->lastChild(); for (Note *n = note; n; n = n->next()) { n->setXRecursively(clicked->x()); n->setYRecursively((lastChild ? lastChild : clicked)->bottom() + 1); } appendNoteIn(note, clicked); /// Insertion relative to a note (top/bottom, insert/group): } else if (clicked) { note->setWidth(clicked->width()); for (Note *n = note; n; n = n->next()) { if (zone == Note::TopGroup || zone == Note::BottomGroup) { n->setXRecursively(clicked->x() + Note::GROUP_WIDTH); } else { n->setXRecursively(clicked->x()); } if (zone == Note::TopInsert || zone == Note::TopGroup) { n->setYRecursively(clicked->y()); } else { n->setYRecursively(clicked->bottom() + 1); } } if (zone == Note::TopInsert) { appendNoteBefore(note, clicked); } else if (zone == Note::BottomInsert) { appendNoteAfter(note, clicked); } else if (zone == Note::TopGroup) { groupNoteBefore(note, clicked); } else if (zone == Note::BottomGroup) { groupNoteAfter(note, clicked); } /// Free insertion: } else if (isFreeLayout()) { // Group if note have siblings: if (note->next()) { Note *group = new Note(this); for (Note *n = note; n; n = n->next()) n->setParentNote(group); group->setFirstChild(note); note = group; } // Insert at cursor position: const int initialWidth = 250; note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth); if (note->isGroup() && note->firstChild()) note->setInitialHeight(note->firstChild()->height()); // note->setGroupWidth(initialWidth); note->setXRecursively(pos.x()); note->setYRecursively(pos.y()); appendNoteAfter(note, lastNote()); } relayoutNotes(); } void BasketScene::clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked, /*Note::Zone*/ int zone) { Note *note; if (event->button() == Qt::MidButton) note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(QClipboard::Selection), this); else note = NoteFactory::createNoteText(QString(), this); if (!note) return; insertNote(note, clicked, zone, QPointF(event->scenePos()), /*animateNewPosition=*/false); // ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote() if (event->button() != Qt::MidButton) { removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. closeEditor(); noteEdit(note, /*justAdded=*/true); } } void BasketScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { m_isDuringDrag = true; Global::bnpView->updateStatusBarHint(); if (NoteDrag::basketOf(event->mimeData()) == this) { m_draggedNotes = NoteDrag::notesOf(event); NoteDrag::saveNoteSelectionToList(selectedNotes()); } event->accept(); } void BasketScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { // m_isDuringDrag = true; // if (isLocked()) // return; // FIXME: viewportToContents does NOT work !!! // QPoint pos = viewportToContents(event->pos()); // QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() ); // if (insertAtCursorPos()) // computeInsertPlace(pos); doHoverEffects(event->scenePos()); // showFrameInsertTo(); if (isFreeLayout() || noteAt(event->scenePos())) // Cursor before rightLimit() or hovering the dragged source notes acceptDropEvent(event); else { event->setAccepted(false); } /* Note *hoveredNote = noteAt(event->pos().x(), event->pos().y()); if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) { event->acceptAction(false); event->accept(false); } else acceptDropEvent(event);*/ // A workaround since QScrollView::dragAutoScroll seem to have no effect : // ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30); // QScrollView::dragMoveEvent(event); } void BasketScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *) { // resetInsertTo(); m_isDuringDrag = false; m_draggedNotes.clear(); NoteDrag::selectedNotes.clear(); m_noActionOnMouseRelease = true; emit resetStatusBarText(); doHoverEffects(); } void BasketScene::dropEvent(QGraphicsSceneDragDropEvent *event) { QPointF pos = event->scenePos(); qDebug() << "Drop Event at position " << pos.x() << ":" << pos.y(); m_isDuringDrag = false; emit resetStatusBarText(); // if (isLocked()) // return; // Do NOT check the bottom&right borders. // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // the note is first removed, and relayoutNotes() compute the new height that is smaller // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // Should, of course, not return 0: Note *clicked = noteAt(pos); if (NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()) && event->dropAction() == Qt::MoveAction) { m_doNotCloseEditor = true; } Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast(event->source())); if (note) { Note::Zone zone = (clicked ? clicked->zoneAt(pos - QPointF(clicked->x(), clicked->y()), /*toAdd=*/true) : Note::None); bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()); if (animateNewPosition) { FOR_EACH_NOTE(n) n->setOnTop(false); // FOR_EACH_NOTE_IN_CHUNK(note) for (Note *n = note; n; n = n->next()) n->setOnTop(true); } insertNote(note, clicked, zone, pos, animateNewPosition); // If moved a note on bottom, contentsHeight has been diminished, then view scrolled up, and we should re-scroll the view down: ensureNoteVisible(note); // if (event->button() != Qt::MidButton) { // removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. // } // resetInsertTo(); // doHoverEffects(); called by insertNote() save(); } m_draggedNotes.clear(); NoteDrag::selectedNotes.clear(); m_doNotCloseEditor = false; // When starting the drag, we saved where we were editing. // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor. // So we re-show the cursor, and re-position it at the right place: if (m_editor && m_editor->textEdit()) { KTextEdit *editor = m_editor->textEdit(); editor->setTextCursor(m_textCursor); } } // handles dropping of a note to basket that is not shown // (usually through its entry in the basket list) void BasketScene::blindDrop(QGraphicsSceneDragDropEvent *event) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast(event->source())); if (note) { insertCreatedNote(note); // unselectAllBut(note); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName)); } } save(); } void BasketScene::blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(mimeData, this, true, dropAction, dynamic_cast(source)); if (note) { insertCreatedNote(note); // unselectAllBut(note); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName)); } } save(); } void BasketScene::insertEmptyNote(int type) { if (!isLoaded()) load(); if (isDuringEdit()) closeEditor(); Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this); insertCreatedNote(note /*, / *edit=* /true*/); noteEdit(note, /*justAdded=*/true); } void BasketScene::insertWizard(int type) { saveInsertionData(); Note *note = nullptr; switch (type) { default: case 1: note = NoteFactory::importKMenuLauncher(this); break; case 2: note = NoteFactory::importIcon(this); break; case 3: note = NoteFactory::importFileContent(this); break; } if (!note) return; restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::insertColor(const QColor &color) { Note *note = NoteFactory::createNoteColor(color, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::insertImage(const QPixmap &image) { Note *note = NoteFactory::createNoteImage(image, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void BasketScene::pasteNote(QClipboard::Mode mode) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(mode), this); if (note) { insertCreatedNote(note); // unselectAllBut(note); } } } void BasketScene::insertCreatedNote(Note *note) { // Get the insertion data if the user clicked inside the basket: Note *clicked = m_clickedToInsert; int zone = m_zoneToInsert; QPointF pos = m_posToInsert; // If it isn't the case, use the default position: if (!clicked && (pos.x() < 0 || pos.y() < 0)) { // Insert right after the focused note: focusANote(); if (m_focusedNote) { clicked = m_focusedNote; zone = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert); pos = QPointF(m_focusedNote->x(), m_focusedNote->bottom()); // Insert at the end of the last column: } else if (isColumnsLayout()) { Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/; /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column clicked = column->firstChild(); zone = Note::TopInsert; } else { // On Bottom*/ clicked = column; zone = Note::BottomColumn; /*}*/ // Insert at free position: } else { pos = QPointF(0, 0); } } insertNote(note, clicked, zone, pos); // ensureNoteVisible(lastInsertedNote()); removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it. // resetInsertTo(); save(); } void BasketScene::saveInsertionData() { m_savedClickedToInsert = m_clickedToInsert; m_savedZoneToInsert = m_zoneToInsert; m_savedPosToInsert = m_posToInsert; } void BasketScene::restoreInsertionData() { m_clickedToInsert = m_savedClickedToInsert; m_zoneToInsert = m_savedZoneToInsert; m_posToInsert = m_savedPosToInsert; } void BasketScene::resetInsertionData() { m_clickedToInsert = nullptr; m_zoneToInsert = 0; m_posToInsert = QPoint(-1, -1); } void BasketScene::hideInsertPopupMenu() { QTimer::singleShot(50 /*ms*/, this, SLOT(timeoutHideInsertPopupMenu())); } void BasketScene::timeoutHideInsertPopupMenu() { resetInsertionData(); } void BasketScene::acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond) { // FIXME: Should not accept all actions! Or not all actions (link not supported?!) // event->acceptAction(preCond && 1); // event->accept(preCond); event->setAccepted(preCond); } void BasketScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // Now disallow drag and mouse redirection m_canDrag = false; if (m_editorTrackMouseEvent) { m_editorTrackMouseEvent = false; m_editor->endSelection(m_pressPos); return; } // Cancel Resizer move: if (m_resizingNote) { m_resizingNote = nullptr; m_pickedResizer = 0; m_lockedHovering = false; doHoverEffects(); save(); } // Cancel Note move: /* if (m_movingNote) { m_movingNote = 0; m_pickedHandle = QPoint(0, 0); m_lockedHovering = false; //doHoverEffects(); save(); }*/ // Cancel Selection rectangle: if (m_isSelecting) { m_isSelecting = false; stopAutoScrollSelection(); resetWasInLastSelectionRect(); doHoverEffects(); invalidate(m_selectionRect); } m_selectionStarted = false; Note *clicked = noteAt(event->scenePos()); Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) { if (m_ignoreCloseEditorOnNextMouseRelease) m_ignoreCloseEditorOnNextMouseRelease = false; else { bool editedNoteStillThere = closeEditor(); if (editedNoteStillThere) // clicked->setSelected(true); unselectAllBut(clicked); } } /* if (event->buttons() == 0 && (zone == Note::Group || zone == Note::Handle)) { closeEditor(); unselectAllBut(clicked); } */ // Do nothing if an action has already been made during mousePressEvent, // or if user made a selection and canceled it by regressing to a very small rectangle. if (m_noActionOnMouseRelease) return; // We immediately set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered. // This is the case when a popup menu is shown, and user click to the basket area to close it: // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event. // Obviously, nothing should be done in this case: m_noActionOnMouseRelease = true; if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->scenePos(); closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 5: type = NoteType::Color; break; case 6: Global::bnpView->grabScreenshot(); return; case 7: Global::bnpView->slotColorFromScreen(); return; case 8: Global::bnpView->insertWizard(3); // loadFromFile return; case 9: Global::bnpView->insertWizard(1); // importKMenuLauncher return; case 10: Global::bnpView->insertWizard(2); // importIcon return; } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); return; } } } // Note *clicked = noteAt(event->pos().x(), event->pos().y()); if (!clicked) { if (isFreeLayout() && event->button() == Qt::LeftButton) { clickedToInsert(event); save(); } return; } // Note::Zone zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) ); // Convenient variables: bool controlPressed = event->modifiers() & Qt::ControlModifier; bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) { if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked); else if (controlPressed) clicked->setSelectedRecursively(!clicked->allSelected()); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); m_noActionOnMouseRelease = true; return; } // Switch tag states: if (zone >= Note::Emblem0) { if (event->button() == Qt::LeftButton) { int icons = -1; for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) { if (!(*it)->emblem().isEmpty()) icons++; if (icons == zone - Note::Emblem0) { State *state = (*it)->nextState(); if (!state) return; it = clicked->states().insert(it, state); ++it; clicked->states().erase(it); clicked->recomputeStyle(); clicked->unbufferize(); clicked->update(); updateEditorAppearance(); filterAgain(); save(); break; } } return; } /* else if (event->button() == Qt::RightButton) { popupEmblemMenu(clicked, zone - Note::Emblem0); return; }*/ } // Insert note or past clipboard: QString text; // Note *note; QString link; // int zone = zone; if (event->button() == Qt::MidButton && zone == Note::Resizer) return; // zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true ); if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer)) return; if (clicked->isGroup() && zone == Note::None) return; switch (zone) { case Note::Handle: case Note::Group: // We select note on mousePress if it was unselected or Ctrl is pressed. // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease: if (event->buttons() == 0) { qDebug() << "EXEC"; if (!(event->modifiers() & Qt::ControlModifier) && clicked->allSelected()) unselectAllBut(clicked); if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) { closeEditor(); clicked->setSelected(true); } } break; case Note::Custom0: // unselectAllBut(clicked); setFocusedNote(clicked); noteOpen(clicked); break; case Note::GroupExpander: case Note::TagsArrow: break; case Note::Link: link = clicked->linkAt(event->scenePos() - QPoint(clicked->x(), clicked->y())); if (!link.isEmpty()) { if (link == "basket-internal-remove-basket") { // TODO: ask confirmation: "Do you really want to delete the welcome baskets?\n You can re-add them at any time in the Help menu." Global::bnpView->doBasketDeletion(this); } else if (link == "basket-internal-import") { QMenu *menu = Global::bnpView->popupMenu("fileimport"); menu->exec(event->screenPos()); } else if (link.startsWith("basket://")) { emit crossReference(link); } else { KRun *run = new KRun(QUrl::fromUserInput(link), m_view->window()); // open the URL. run->setAutoDelete(true); } break; } // If there is no link, edit note content case Note::Content: { if (m_editor && m_editor->note() == clicked && m_editor->graphicsWidget()) { m_editor->setCursorTo(event->scenePos()); } else { closeEditor(); unselectAllBut(clicked); noteEdit(clicked, /*justAdded=*/false, event->scenePos()); QGraphicsScene::mouseReleaseEvent(event); } break; } case Note::TopInsert: case Note::TopGroup: case Note::BottomInsert: case Note::BottomGroup: case Note::BottomColumn: clickedToInsert(event, clicked, zone); save(); break; case Note::None: default: KMessageBox::information(m_view->viewport(), i18n("This message should never appear. If it does, this program is buggy! " "Please report the bug to the developer.")); break; } } void BasketScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { Note *clicked = noteAt(event->scenePos()); Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None); if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) { doCopy(CopyToSelection); m_noActionOnMouseRelease = true; } else mousePressEvent(event); } void BasketScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // redirect this event to the editor if track mouse event is active if (m_editorTrackMouseEvent && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) { m_editor->updateSelection(event->scenePos()); return; } // Drag the notes: if (m_canDrag && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) { m_canDrag = false; m_isSelecting = false; // Don't draw selection rectangle after drag! m_selectionStarted = false; NoteSelection *selection = selectedNotes(); if (selection->firstStacked()) { QDrag *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/m_view); // d will be deleted by QT /*bool shouldRemove = */ d->exec(); // delete selection; // Never delete because URL is dragged and the file must be available for the extern application // if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note // emit wantDelete(this); } return; } // Moving a Resizer: if (m_resizingNote) { qreal groupWidth = event->scenePos().x() - m_resizingNote->x() - m_pickedResizer; qreal minRight = m_resizingNote->minRight(); // int maxRight = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts. qreal maxRight = 100 * sceneRect().width(); // A big enough value (+infinity) for free layouts. Note *nextColumn = m_resizingNote->next(); if (m_resizingNote->isColumn()) { if (nextColumn) maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH; else // maxRight = contentsWidth(); maxRight = sceneRect().width(); } if (groupWidth > maxRight - m_resizingNote->x()) groupWidth = maxRight - m_resizingNote->x(); if (groupWidth < minRight - m_resizingNote->x()) groupWidth = minRight - m_resizingNote->x(); qreal delta = groupWidth - m_resizingNote->groupWidth(); m_resizingNote->setGroupWidth(groupWidth); // If resizing columns: if (m_resizingNote->isColumn()) { Note *column = m_resizingNote; if ((column = column->next())) { // Next columns should not have them X coordinate animated, because it would flicker: column->setXRecursively(column->x() + delta); // And the resizer should resize the TWO sibling columns, and not push the other columns on th right: column->setGroupWidth(column->groupWidth() - delta); } } relayoutNotes(); } // Moving a Note: /* if (m_movingNote) { int x = event->pos().x() - m_pickedHandle.x(); int y = event->pos().y() - m_pickedHandle.y(); if (x < 0) x = 0; if (y < 0) y = 0; m_movingNote->setX(x); m_movingNote->setY(y); m_movingNote->relayoutAt(x, y, / *animate=* /false); relayoutNotes(true); } */ // Dragging the selection rectangle: if (m_selectionStarted) doAutoScrollSelection(); doHoverEffects(event->scenePos()); } void BasketScene::doAutoScrollSelection() { static const int AUTO_SCROLL_MARGIN = 50; // pixels static const int AUTO_SCROLL_DELAY = 100; // milliseconds QPoint pos = m_view->mapFromGlobal(QCursor::pos()); // Do the selection: if (m_isSelecting) invalidate(m_selectionRect); m_selectionEndPoint = m_view->mapToScene(pos); m_selectionRect = QRectF(m_selectionBeginPoint, m_selectionEndPoint).normalized(); if (m_selectionRect.left() < 0) m_selectionRect.setLeft(0); if (m_selectionRect.top() < 0) m_selectionRect.setTop(0); // if (m_selectionRect.right() >= contentsWidth()) m_selectionRect.setRight(contentsWidth() - 1); // if (m_selectionRect.bottom() >= contentsHeight()) m_selectionRect.setBottom(contentsHeight() - 1); if (m_selectionRect.right() >= sceneRect().width()) m_selectionRect.setRight(sceneRect().width() - 1); if (m_selectionRect.bottom() >= sceneRect().height()) m_selectionRect.setBottom(sceneRect().height() - 1); if ((m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > QApplication::startDragDistance()) { m_isSelecting = true; selectNotesIn(m_selectionRect, m_selectionInvert); invalidate(m_selectionRect); m_noActionOnMouseRelease = true; } else { // If the user was selecting but cancel by making the rectangle too small, cancel it really!!! if (m_isSelecting) { if (m_selectionInvert) selectNotesIn(QRectF(), m_selectionInvert); else unselectAllBut(nullptr); // TODO: unselectAll(); } if (m_isSelecting) resetWasInLastSelectionRect(); m_isSelecting = false; stopAutoScrollSelection(); return; } // Do the auto-scrolling: // FIXME: It's still flickering // QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN); QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, m_view->viewport()->width() - 2 * AUTO_SCROLL_MARGIN, m_view->viewport()->height() - 2 * AUTO_SCROLL_MARGIN); int dx = 0; int dy = 0; if (pos.y() < AUTO_SCROLL_MARGIN) dy = pos.y() - AUTO_SCROLL_MARGIN; else if (pos.y() > m_view->viewport()->height() - AUTO_SCROLL_MARGIN) dy = pos.y() - m_view->viewport()->height() + AUTO_SCROLL_MARGIN; // else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN) // dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN; if (pos.x() < AUTO_SCROLL_MARGIN) dx = pos.x() - AUTO_SCROLL_MARGIN; else if (pos.x() > m_view->viewport()->width() - AUTO_SCROLL_MARGIN) dx = pos.x() - m_view->viewport()->width() + AUTO_SCROLL_MARGIN; // else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN) // dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN; if (dx || dy) { qApp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong // scrollBy(dx, dy); if (!m_autoScrollSelectionTimer.isActive()) m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY); } else stopAutoScrollSelection(); } void BasketScene::stopAutoScrollSelection() { m_autoScrollSelectionTimer.stop(); } void BasketScene::resetWasInLastSelectionRect() { Note *note = m_firstNote; while (note) { note->resetWasInLastSelectionRect(); note = note->next(); } } void BasketScene::selectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->selectAll(); else if (m_editor->lineEdit()) m_editor->lineEdit()->selectAll(); } else { // First select all in the group, then in the parent group... Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr); while (parent) { if (!parent->allSelected()) { parent->setSelectedRecursively(true); return; } child = parent; parent = parent->parentNote(); } // Then, select all: FOR_EACH_NOTE(note) note->setSelectedRecursively(true); } } void BasketScene::unselectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) { QTextCursor cursor = m_editor->textEdit()->textCursor(); cursor.clearSelection(); m_editor->textEdit()->setTextCursor(cursor); selectionChangedInEditor(); // THIS IS NOT EMITTED BY Qt!!! } else if (m_editor->lineEdit()) m_editor->lineEdit()->deselect(); } else { if (countSelecteds() > 0) // Optimization FOR_EACH_NOTE(note) note->setSelectedRecursively(false); } } void BasketScene::invertSelection() { FOR_EACH_NOTE(note) note->invertSelectionRecursively(); } void BasketScene::unselectAllBut(Note *toSelect) { FOR_EACH_NOTE(note) note->unselectAllBut(toSelect); } void BasketScene::invertSelectionOf(Note *toSelect) { FOR_EACH_NOTE(note) note->invertSelectionOf(toSelect); } void BasketScene::selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/) { FOR_EACH_NOTE(note) note->selectIn(rect, invertSelection, unselectOthers); } void BasketScene::doHoverEffects() { doHoverEffects(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos()))); } void BasketScene::doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos) { // Inform the old and new hovered note (if any): Note *oldHoveredNote = m_hoveredNote; if (note != m_hoveredNote) { if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); m_hoveredNote->update(); } m_hoveredNote = note; if (m_hoveredNote) { m_hoveredNote->setHovered(true); } } // If we are hovering a note, compute which zone is hovered and inform the note: if (m_hoveredNote) { if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) { m_hoveredZone = zone; m_hoveredNote->setHoveredZone(zone); m_view->viewport()->setCursor(m_hoveredNote->cursorFromZone(zone)); m_hoveredNote->update(); } // If we are hovering an insert line zone, update this thing: if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn) { placeInserter(m_hoveredNote, zone); } else { removeInserter(); } // If we are hovering an embedded link in a rich text element, show the destination in the statusbar: if (zone == Note::Link) emit setStatusBarText(m_hoveredNote->linkAt(pos - QPoint(m_hoveredNote->x(), m_hoveredNote->y()))); else if (m_hoveredNote->content()) emit setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone)); // resetStatusBarText(); // If we aren't hovering a note, reset all: } else { if (isFreeLayout() && !isSelecting()) m_view->viewport()->setCursor(Qt::CrossCursor); else m_view->viewport()->unsetCursor(); m_hoveredZone = Note::None; removeInserter(); emit resetStatusBarText(); } } void BasketScene::doHoverEffects(const QPointF &pos) { // if (isDuringEdit()) // viewport()->unsetCursor(); // Do we have the right to do hover effects? if (!m_loaded || m_lockedHovering) { return; } // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false. // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar: // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false. // User need to leave the area and re-enter it to get effects. // This hack solve that by dismissing the m_underMouse variable: // Don't do hover effects when a popup menu is opened. // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent. // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state: bool underMouse = !qApp->activePopupWidget(); // if (qApp->activePopupWidget()) // underMouse = false; // Compute which note is hovered: Note *note = (m_isSelecting || !underMouse ? nullptr : noteAt(pos)); Note::Zone zone = (note ? note->zoneAt(pos - QPointF(note->x(), note->y()), isDuringDrag()) : Note::None); // Inform the old and new hovered note (if any) and update the areas: doHoverEffects(note, zone, pos); } void BasketScene::mouseEnteredEditorWidget() { if (!m_lockedHovering && !qApp->activePopupWidget()) doHoverEffects(editedNote(), Note::Content, QPoint()); } void BasketScene::removeInserter() { if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden! m_inserterShown = false; invalidate(m_inserterRect); } } void BasketScene::placeInserter(Note *note, int zone) { // Remove the inserter: if (!note) { removeInserter(); return; } // Update the old position: if (inserterShown()) { invalidate(m_inserterRect); } // Some commodities: m_inserterShown = true; m_inserterTop = (zone == Note::TopGroup || zone == Note::TopInsert); m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup); // X and width: qreal groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH); qreal x = note->x(); qreal width = (note->isGroup() ? note->rightLimit() - note->x() : note->width()); if (m_inserterGroup) { x += groupIndent; width -= groupIndent; } m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn()); // if (note->isGroup()) // width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0); // Y: qreal y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3); if (!m_inserterTop) y += (note->isColumn() ? note->height() : note->height()); // Assigning result: m_inserterRect = QRectF(x, y, width, 6 - (m_inserterGroup ? 2 : 0)); // Update the new position: invalidate(m_inserterRect); } inline void drawLineByRect(QPainter &painter, qreal x, qreal y, qreal width, qreal height) { painter.drawLine(x, y, x + width - 1, y + height - 1); } void BasketScene::drawInserter(QPainter &painter, qreal xPainter, qreal yPainter) { if (!m_inserterShown) return; QRectF rect = m_inserterRect; // For shorter code-lines when drawing! rect.translate(-xPainter, -yPainter); int lineY = (m_inserterGroup && m_inserterTop ? 0 : 2); int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1); KStatefulBrush statefulBrush(KColorScheme::View, KColorScheme::HoverColor); const QColor highlightColor = palette().color(QPalette::Highlight).lighter(); painter.setPen(highlightColor); // The horizontal line: // painter.drawRect( rect.x(), rect.y() + lineY, rect.width(), 2); int width = rect.width() - 4; painter.fillRect(rect.x() + 2, rect.y() + lineY, width, 2, highlightColor); // The left-most and right-most edges (biggest vertical lines): drawLineByRect(painter, rect.x(), rect.y(), 1, (m_inserterGroup ? 4 : 6)); drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(), 1, (m_inserterGroup ? 4 : 6)); // The left and right mid vertical lines: drawLineByRect(painter, rect.x() + 1, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); // Draw the split as a feedback to know where is the limit between insert and group: if (m_inserterSplit) { int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0); int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2; painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2); painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2); } } void BasketScene::helpEvent(QGraphicsSceneHelpEvent *event) { if (!m_loaded || !Settings::showNotesToolTip()) return; QString message; QRectF rect; QPointF contentPos = event->scenePos(); Note *note = noteAt(contentPos); if (!note && isFreeLayout()) { message = i18n("Insert note here\nRight click for more options"); QRectF itRect; for (QList::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) { itRect = QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()).intersected(*it); if (itRect.contains(contentPos)) { rect = itRect; rect.moveLeft(rect.left() - sceneRect().x()); rect.moveTop(rect.top() - sceneRect().y()); break; } } } else { if (!note) return; Note::Zone zone = note->zoneAt(contentPos - QPointF(note->x(), note->y())); switch (zone) { case Note::Resizer: message = (note->isColumn() ? i18n("Resize those columns") : (note->isGroup() ? i18n("Resize this group") : i18n("Resize this note"))); break; case Note::Handle: message = i18n("Select or move this note"); break; case Note::Group: message = i18n("Select or move this group"); break; case Note::TagsArrow: message = i18n("Assign or remove tags from this note"); if (note->states().count() > 0) { QString tagsString; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) { QString tagName = "" + Tools::textToHTMLWithoutP((*it)->fullName()) + ""; if (tagsString.isEmpty()) tagsString = tagName; else tagsString = i18n("%1, %2", tagsString, tagName); } message = "" + message + "
" + i18n("Assigned Tags: %1", tagsString); } break; case Note::Custom0: message = note->content()->zoneTip(zone); break; //"Open this link/Open this file/Open this sound file/Launch this application" case Note::GroupExpander: message = (note->isFolded() ? i18n("Expand this group") : i18n("Collapse this group")); break; case Note::Link: case Note::Content: message = note->content()->editToolTipText(); break; case Note::TopInsert: case Note::BottomInsert: message = i18n("Insert note here\nRight click for more options"); break; case Note::TopGroup: message = i18n("Group note with the one below\nRight click for more options"); break; case Note::BottomGroup: message = i18n("Group note with the one above\nRight click for more options"); break; case Note::BottomColumn: message = i18n("Insert note here\nRight click for more options"); break; case Note::None: message = "** Zone NONE: internal error **"; break; default: if (zone >= Note::Emblem0) message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName(); else message = QString(); break; } if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) { QStringList keys; QStringList values; note->content()->toolTipInfos(&keys, &values); keys.append(i18n("Added")); keys.append(i18n("Last Modification")); values.append(note->addedStringDate()); values.append(note->lastModificationStringDate()); message = "" + message; QStringList::iterator key; QStringList::iterator value; for (key = keys.begin(), value = values.begin(); key != keys.end() && value != values.end(); ++key, ++value) message += "
" + i18nc("of the form 'key: value'", "%1: %2", *key, *value); message += "
"; } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert)) message += '\n' + i18n("Click on the right to group instead of insert"); else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup)) message += '\n' + i18n("Click on the left to insert instead of group"); rect = note->zoneRect(zone, contentPos - QPoint(note->x(), note->y())); rect.moveLeft(rect.left() - sceneRect().x()); rect.moveTop(rect.top() - sceneRect().y()); rect.moveLeft(rect.left() + note->x()); rect.moveTop(rect.top() + note->y()); } QToolTip::showText(event->screenPos(), message, m_view, rect.toRect()); } Note *BasketScene::lastNote() { Note *note = firstNote(); while (note && note->next()) note = note->next(); return note; } void BasketScene::deleteNotes() { Note *note = m_firstNote; while (note) { Note *tmp = note->next(); delete note; note = tmp; } m_firstNote = nullptr; m_resizingNote = nullptr; m_movingNote = nullptr; m_focusedNote = nullptr; m_startOfShiftSelectionNote = nullptr; m_tagPopupNote = nullptr; m_clickedToInsert = nullptr; m_savedClickedToInsert = nullptr; m_hoveredNote = nullptr; m_count = 0; m_countFounds = 0; m_countSelecteds = 0; emit resetStatusBarText(); emit countsChanged(this); } Note *BasketScene::noteAt(QPointF pos) { qreal x = pos.x(); qreal y = pos.y(); // NO: // // Do NOT check the bottom&right borders. // // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // // the note is first removed, and relayoutNotes() compute the new height that is smaller // // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // // Should, of course, not return 0: if (x < 0 || x > sceneRect().width() || y < 0 || y > sceneRect().height()) return nullptr; // When resizing a note/group, keep it highlighted: if (m_resizingNote) return m_resizingNote; // Search and return the hovered note: Note *note = m_firstNote; Note *possibleNote; while (note) { possibleNote = note->noteAt(pos); if (possibleNote) { if (NoteDrag::selectedNotes.contains(possibleNote) || draggedNotes().contains(possibleNote)) return nullptr; else return possibleNote; } note = note->next(); } // If the basket is layouted in columns, return one of the columns to be able to add notes in them: if (isColumnsLayout()) { Note *column = m_firstNote; while (column) { if (x >= column->x() && x < column->rightLimit()) return column; column = column->next(); } } // Nothing found, no note is hovered: return nullptr; } BasketScene::~BasketScene() { m_commitdelay.stop(); // we don't know how long deleteNotes() last so we want to make extra sure that nobody will commit in between if (m_decryptBox) delete m_decryptBox; #ifdef HAVE_LIBGPGME delete m_gpg; #endif deleteNotes(); if (m_view) delete m_view; } QColor BasketScene::selectionRectInsideColor() { return Tools::mixColor(Tools::mixColor(backgroundColor(), palette().color(QPalette::Highlight)), backgroundColor()); } QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a) { // normal button... QRgb rgb = bgColor.rgb(); QRgb rgb_b = fgColor.rgb(); int alpha = a; if (alpha > 255) alpha = 255; if (alpha < 0) alpha = 0; int inv_alpha = 255 - alpha; QColor result = QColor(qRgb(qRed(rgb_b) * inv_alpha / 255 + qRed(rgb) * alpha / 255, qGreen(rgb_b) * inv_alpha / 255 + qGreen(rgb) * alpha / 255, qBlue(rgb_b) * inv_alpha / 255 + qBlue(rgb) * alpha / 255)); return result; } void BasketScene::unlock() { QTimer::singleShot(0, this, SLOT(load())); } void BasketScene::inactivityAutoLockTimeout() { lock(); } void BasketScene::drawBackground(QPainter *painter, const QRectF &rect) { if (!m_loadingLaunched) { if (!m_locked) { QTimer::singleShot(0, this, SLOT(load())); return; } else { Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar } } if (!hasBackgroundImage()) { painter->fillRect(rect, backgroundColor()); // It's either a background pixmap to draw or a background color to fill: } else if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height())) { painter->fillRect(rect, backgroundColor()); blendBackground(*painter, rect, 0, 0, /*opaque=*/true); } else { painter->fillRect(rect, backgroundColor()); } } void BasketScene::drawForeground(QPainter *painter, const QRectF &rect) { if (m_locked) { if (!m_decryptBox) { m_decryptBox = new QFrame(m_view); m_decryptBox->setFrameShape(QFrame::StyledPanel); m_decryptBox->setFrameShadow(QFrame::Plain); m_decryptBox->setLineWidth(1); QGridLayout *layout = new QGridLayout(m_decryptBox); layout->setContentsMargins(11, 11, 11, 11); layout->setSpacing(6); #ifdef HAVE_LIBGPGME m_button = new QPushButton(m_decryptBox); m_button->setText(i18n("&Unlock")); layout->addWidget(m_button, 1, 2); - connect(m_button, SIGNAL(clicked()), this, SLOT(unlock())); + connect(m_button, &QButton::clicked, this, &BasketScene::unlock); #endif QLabel *label = new QLabel(m_decryptBox); QString text = "" + i18n("Password protected basket.") + "
"; #ifdef HAVE_LIBGPGME label->setText(text + i18n("Press Unlock to access it.")); #else label->setText(text + i18n("Encryption is not supported by
this version of %1.", QGuiApplication::applicationDisplayName())); #endif label->setAlignment(Qt::AlignTop); layout->addWidget(label, 0, 1, 1, 2); QLabel *pixmap = new QLabel(m_decryptBox); pixmap->setPixmap(KIconLoader::global()->loadIcon("encrypted", KIconLoader::NoGroup, KIconLoader::SizeHuge)); layout->addWidget(pixmap, 0, 0, 2, 1); QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); layout->addItem(spacer, 1, 1); label = new QLabel("" + i18n("To make baskets stay unlocked, change the automatic
" "locking duration in the application settings.") + "
", m_decryptBox); label->setAlignment(Qt::AlignTop); layout->addWidget(label, 2, 0, 1, 3); m_decryptBox->resize(layout->sizeHint()); } if (m_decryptBox->isHidden()) { m_decryptBox->show(); } #ifdef HAVE_LIBGPGME m_button->setFocus(); #endif m_decryptBox->move((m_view->viewport()->width() - m_decryptBox->width()) / 2, (m_view->viewport()->height() - m_decryptBox->height()) / 2); } else { if (m_decryptBox && !m_decryptBox->isHidden()) m_decryptBox->hide(); } if (!m_loaded) { setSceneRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height()); QBrush brush(backgroundColor()); QPixmap pixmap(m_view->viewport()->width(), m_view->viewport()->height()); // TODO: Clip it to asked size only! QPainter painter2(&pixmap); QTextDocument rt; rt.setHtml(QString("
%1
").arg(i18n("Loading..."))); rt.setTextWidth(m_view->viewport()->width()); int hrt = rt.size().height(); painter2.fillRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height(), brush); blendBackground(painter2, QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()), -1, -1, /*opaque=*/true); QPalette pal = palette(); pal.setColor(QPalette::WindowText, textColor()); painter2.translate(0, (m_view->viewport()->height() - hrt) / 2); QAbstractTextDocumentLayout::PaintContext context; context.palette = pal; rt.documentLayout()->draw(&painter2, context); painter2.end(); painter->drawPixmap(0, 0, pixmap); return; // TODO: Clip to the wanted rectangle } enableActions(); if ((inserterShown() && rect.intersects(inserterRect())) || (m_isSelecting && rect.intersects(m_selectionRect))) { // Draw inserter: if (inserterShown() && rect.intersects(inserterRect())) { drawInserter(*painter, 0, 0); } // Draw selection rect: if (m_isSelecting && rect.intersects(m_selectionRect)) { QRectF selectionRect = m_selectionRect; QRectF selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2); if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) { QColor insideColor = selectionRectInsideColor(); painter->fillRect(selectionRectInside, QBrush(insideColor, Qt::Dense4Pattern)); } painter->setPen(palette().color(QPalette::Highlight).darker()); painter->drawRect(selectionRect); painter->setPen(Tools::mixColor(palette().color(QPalette::Highlight).darker(), backgroundColor())); painter->drawPoint(selectionRect.topLeft()); painter->drawPoint(selectionRect.topRight()); painter->drawPoint(selectionRect.bottomLeft()); painter->drawPoint(selectionRect.bottomRight()); } } } /* rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw) */ void BasketScene::blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter, qreal yPainter, bool opaque, QPixmap *bg) { painter.save(); if (xPainter == -1 && yPainter == -1) { xPainter = rect.x(); yPainter = rect.y(); } if (hasBackgroundImage()) { const QPixmap *bgPixmap = (bg ? /* * */ bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap)); if (isTiledBackground()) { painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y()); } else { painter.drawPixmap(QPointF(rect.x() - xPainter, rect.y() - yPainter), *bgPixmap, rect); } } painter.restore(); } void BasketScene::recomputeBlankRects() { m_blankAreas.clear(); return; m_blankAreas.append(QRectF(0, 0, sceneRect().width(), sceneRect().height())); FOR_EACH_NOTE(note) note->recomputeBlankRects(m_blankAreas); // See the drawing of blank areas in BasketScene::drawContents() if (hasBackgroundImage() && !isTiledBackground()) substractRectOnAreas(QRectF(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false); } void BasketScene::unsetNotesWidth() { Note *note = m_firstNote; while (note) { note->unsetWidth(); note = note->next(); } } void BasketScene::relayoutNotes() { if (Global::bnpView->currentBasket() != this) return; // Optimize load time, and basket will be relaid out when activated, anyway int h = 0; tmpWidth = 0; tmpHeight = 0; Note *note = m_firstNote; while (note) { if (note->matching()) { note->relayoutAt(0, h); if (note->hasResizer()) { int minGroupWidth = note->minRight() - note->x(); if (note->groupWidth() < minGroupWidth) { note->setGroupWidth(minGroupWidth); relayoutNotes(); // Redo the thing, but this time it should not recurse return; } } h += note->height(); } note = note->next(); } if (isFreeLayout()) tmpHeight += 100; else tmpHeight += 15; setSceneRect(0, 0, qMax((qreal)m_view->viewport()->width(), tmpWidth), qMax((qreal)m_view->viewport()->height(), tmpHeight)); recomputeBlankRects(); placeEditor(); doHoverEffects(); invalidate(); } void BasketScene::popupEmblemMenu(Note *note, int emblemNumber) { m_tagPopupNote = note; State *state = note->stateForEmblemNumber(emblemNumber); State *nextState = state->nextState(/*cycle=*/false); Tag *tag = state->parentTag(); m_tagPopup = tag; QKeySequence sequence = tag->shortcut(); bool sequenceOnDelete = (nextState == nullptr && !tag->shortcut().isEmpty()); QMenu menu(m_view); if (tag->countStates() == 1) { menu.addSection(/*SmallIcon(state->icon()), */ tag->name()); QAction *act; act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove"), &menu); act->setData(1); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(2); menu.addAction(act); menu.addSeparator(); act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu); act->setData(3); menu.addAction(act); } else { menu.addSection(tag->name()); QList::iterator it; State *currentState; int i = 10; // QActionGroup makes the actions exclusive; turns checkboxes into radio // buttons on some styles. QActionGroup *emblemGroup = new QActionGroup(&menu); for (it = tag->states().begin(); it != tag->states().end(); ++it) { currentState = *it; QKeySequence sequence; if (currentState == nextState && !tag->shortcut().isEmpty()) sequence = tag->shortcut(); StateAction *sa = new StateAction(currentState, QKeySequence(sequence), nullptr, false); sa->setChecked(state == currentState); sa->setActionGroup(emblemGroup); sa->setData(i); menu.addAction(sa); if (currentState == nextState && !tag->shortcut().isEmpty()) sa->setShortcut(sequence); ++i; } menu.addSeparator(); QAction *act = new QAction(&menu); act->setIcon(QIcon::fromTheme("edit-delete")); act->setText(i18n("&Remove")); act->setShortcut(sequenceOnDelete ? sequence : QKeySequence()); act->setData(1); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(2); menu.addAction(act); menu.addSeparator(); act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu); act->setData(3); menu.addAction(act); act = new QAction(QIcon::fromTheme("search-filter"), i18n("Filter by this &State"), &menu); act->setData(4); menu.addAction(act); } - connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(toggledStateInMenu(QAction *))); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering())); - connect(&menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick())); + connect(&menu, &QMenu::triggered, this, &BasketScene::toggledStateInMenu); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering); + connect(&menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick); m_lockedHovering = true; menu.exec(QCursor::pos()); } void BasketScene::toggledStateInMenu(QAction *action) { int id = action->data().toInt(); if (id == 1) { removeTagFromSelectedNotes(m_tagPopup); // m_tagPopupNote->removeTag(m_tagPopup); // m_tagPopupNote->setWidth(0); // To force a new layout computation updateEditorAppearance(); filterAgain(); save(); return; } if (id == 2) { // Customize this State: TagsEditDialog dialog(m_view, m_tagPopupNote->stateOfTag(m_tagPopup)); dialog.exec(); return; } if (id == 3) { // Filter by this Tag decoration()->filterBar()->filterTag(m_tagPopup); return; } if (id == 4) { // Filter by this State decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup)); return; } /*addStateToSelectedNotes*/ changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/); // m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true); filterAgain(); save(); } State *BasketScene::stateForTagFromSelectedNotes(Tag *tag) { State *state = nullptr; FOR_EACH_NOTE(note) if (note->stateForTagFromSelectedNotes(tag, &state) && state == nullptr) return nullptr; return state; } void BasketScene::activatedTagShortcut(Tag *tag) { // Compute the next state to set: State *state = stateForTagFromSelectedNotes(tag); if (state) state = state->nextState(/*cycle=*/false); else state = tag->states().first(); // Set or unset it: if (state) { FOR_EACH_NOTE(note) note->addStateToSelectedNotes(state, /*orReplace=*/true); updateEditorAppearance(); } else removeTagFromSelectedNotes(tag); filterAgain(); save(); } void BasketScene::popupTagsMenu(Note *note) { m_tagPopupNote = note; QMenu menu(m_view); menu.addSection(i18n("Tags")); Global::bnpView->populateTagsMenu(menu, note); m_lockedHovering = true; menu.exec(QCursor::pos()); } void BasketScene::unlockHovering() { m_lockedHovering = false; doHoverEffects(); } void BasketScene::toggledTagInMenu(QAction *act) { int id = act->data().toInt(); if (id == 1) { // Assign new Tag... TagsEditDialog dialog(m_view, /*stateToEdit=*/nullptr, /*addNewTag=*/true); dialog.exec(); if (!dialog.addedStates().isEmpty()) { State::List states = dialog.addedStates(); for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState) FOR_EACH_NOTE(note) note->addStateToSelectedNotes(*itState); updateEditorAppearance(); filterAgain(); save(); } return; } if (id == 2) { // Remove All removeAllTagsFromSelectedNotes(); filterAgain(); save(); return; } if (id == 3) { // Customize... TagsEditDialog dialog(m_view); dialog.exec(); return; } Tag *tag = Tag::all[id - 10]; if (!tag) return; if (m_tagPopupNote->hasTag(tag)) removeTagFromSelectedNotes(tag); else addTagToSelectedNotes(tag); m_tagPopupNote->setWidth(0); // To force a new layout computation filterAgain(); save(); } void BasketScene::addTagToSelectedNotes(Tag *tag) { FOR_EACH_NOTE(note) note->addTagToSelectedNotes(tag); updateEditorAppearance(); } void BasketScene::removeTagFromSelectedNotes(Tag *tag) { FOR_EACH_NOTE(note) note->removeTagFromSelectedNotes(tag); updateEditorAppearance(); } void BasketScene::addStateToSelectedNotes(State *state) { FOR_EACH_NOTE(note) note->addStateToSelectedNotes(state); updateEditorAppearance(); } void BasketScene::updateEditorAppearance() { if (isDuringEdit() && m_editor->graphicsWidget()) { m_editor->graphicsWidget()->setFont(m_editor->note()->font()); if (m_editor->graphicsWidget()->widget()) { QPalette palette; palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor()); palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor()); m_editor->graphicsWidget()->setPalette(palette); } // Ugly Hack around Qt bugs: placeCursor() don't call any signal: HtmlEditor *htmlEditor = dynamic_cast(m_editor); if (htmlEditor) { if (m_editor->textEdit()->textCursor().atStart()) { m_editor->textEdit()->moveCursor(QTextCursor::Right); m_editor->textEdit()->moveCursor(QTextCursor::Left); } else { m_editor->textEdit()->moveCursor(QTextCursor::Left); m_editor->textEdit()->moveCursor(QTextCursor::Right); } htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text) } } } void BasketScene::editorPropertiesChanged() { if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) { m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone); } } void BasketScene::changeStateOfSelectedNotes(State *state) { FOR_EACH_NOTE(note) note->changeStateOfSelectedNotes(state); updateEditorAppearance(); } void BasketScene::removeAllTagsFromSelectedNotes() { FOR_EACH_NOTE(note) note->removeAllTagsFromSelectedNotes(); updateEditorAppearance(); } bool BasketScene::selectedNotesHaveTags() { FOR_EACH_NOTE(note) if (note->selectedNotesHaveTags()) return true; return false; } QColor BasketScene::backgroundColor() const { if (m_backgroundColorSetting.isValid()) return m_backgroundColorSetting; else return palette().color(QPalette::Base); } QColor BasketScene::textColor() const { if (m_textColorSetting.isValid()) return m_textColorSetting; else return palette().color(QPalette::Text); } void BasketScene::unbufferizeAll() { FOR_EACH_NOTE(note) note->unbufferizeAll(); } Note *BasketScene::editedNote() { if (m_editor) return m_editor->note(); else return nullptr; } bool BasketScene::hasTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) return !m_editor->textEdit()->document()->isEmpty(); else if (m_editor->lineEdit()) return !m_editor->lineEdit()->displayText().isEmpty(); else return false; } bool BasketScene::hasSelectedTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) { // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter: // Qt mysteriously tell us there is an invisible selection!! // return m_editor->textEdit()->hasSelectedText(); return !m_editor->textEdit()->textCursor().selectedText().isEmpty(); } else if (m_editor->lineEdit()) return m_editor->lineEdit()->hasSelectedText(); else return false; } bool BasketScene::selectedAllTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) { return m_editor->textEdit()->document()->isEmpty() || m_editor->textEdit()->toPlainText() == m_editor->textEdit()->textCursor().selectedText(); } else if (m_editor->lineEdit()) return m_editor->lineEdit()->displayText().isEmpty() || m_editor->lineEdit()->displayText() == m_editor->lineEdit()->selectedText(); else return false; } void BasketScene::selectionChangedInEditor() { Global::bnpView->notesStateChanged(); } void BasketScene::contentChangedInEditor() { // Do not wait 3 seconds, because we need the note to expand as needed (if a line is too wider... the note should grow wider): if (m_editor->textEdit()) m_editor->autoSave(/*toFileToo=*/false); // else { if (m_inactivityAutoSaveTimer.isActive()) m_inactivityAutoSaveTimer.stop(); m_inactivityAutoSaveTimer.setSingleShot(true); m_inactivityAutoSaveTimer.start(3 * 1000); Global::bnpView->setUnsavedStatus(true); // } } void BasketScene::inactivityAutoSaveTimeout() { if (m_editor) m_editor->autoSave(/*toFileToo=*/true); } void BasketScene::placeEditorAndEnsureVisible() { placeEditor(/*andEnsureVisible=*/true); } // TODO: [kw] Oh boy, this will probably require some tweaking. void BasketScene::placeEditor(bool /*andEnsureVisible*/ /*= false*/) { if (!isDuringEdit()) return; QFrame *editorQFrame = dynamic_cast(m_editor->graphicsWidget()->widget()); KTextEdit *textEdit = m_editor->textEdit(); Note *note = m_editor->note(); qreal frameWidth = (editorQFrame ? editorQFrame->frameWidth() : 0); qreal x = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth; qreal y; qreal maxHeight = qMax((qreal)m_view->viewport()->height(), sceneRect().height()); qreal height, width; if (textEdit) { // Need to do it 2 times, because it's wrong otherwise // (sometimes, width depends on height, and sometimes, height depends on with): for (int i = 0; i < 2; i++) { // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called: // editor->sync() CRASH!! // editor->sync(); y = note->y() + Note::NOTE_MARGIN - frameWidth; height = note->height() - 2 * frameWidth - 2 * Note::NOTE_MARGIN; width = note->x() + note->width() - x + 1; if (y + height > maxHeight) y = maxHeight - height; m_editor->graphicsWidget()->setMaximumSize(width, height); textEdit->setFixedSize(width, height); textEdit->viewport()->setFixedSize(width, height); } } else { height = note->height() - 2 * Note::NOTE_MARGIN + 2 * frameWidth; width = note->x() + note->width() - x; // note->rightLimit() - x + 2*m_view->frameWidth; if (m_editor->graphicsWidget()) m_editor->graphicsWidget()->widget()->setFixedSize(width, height); x -= 1; y = note->y() + Note::NOTE_MARGIN - frameWidth; } if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) { m_editorWidth = width; // Avoid infinite recursion!!! m_editorHeight = height; m_editor->autoSave(/*toFileToo=*/true); } m_editorWidth = width; m_editorHeight = height; m_editor->graphicsWidget()->setPos(x, y); m_editorX = x; m_editorY = y; // if (andEnsureVisible) // ensureNoteVisible(note); } void BasketScene::editorCursorPositionChanged() { if (!isDuringEdit()) return; FocusedTextEdit *textEdit = dynamic_cast(m_editor->textEdit()); if (textEdit) { QPoint cursorPoint = textEdit->viewport()->mapToGlobal(textEdit->cursorRect().center()); // QPointF contentsCursor = m_view->mapToScene( m_view->viewport()->mapFromGlobal(cursorPoint) ); // m_view->ensureVisible(contentsCursor.x(), contentsCursor.y(),1,1); } } void BasketScene::closeEditorDelayed() { setFocus(); QTimer::singleShot(0, this, SLOT(closeEditor())); } bool BasketScene::closeEditor(bool deleteEmptyNote /* =true*/) { if (!isDuringEdit()) return true; if (m_doNotCloseEditor) return true; if (m_redirectEditActions) { if (m_editor->textEdit()) { - disconnect(m_editor->textEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor())); - disconnect(m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(selectionChangedInEditor())); - disconnect(m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(contentChangedInEditor())); + disconnect(m_editor->textEdit(), &KTextEdit::selectionChanged, this, &BasketScene::selectionChangedInEditor); + disconnect(m_editor->textEdit(), &KTextEdit::textChanged, this, &BasketScene::selectionChangedInEditor); + disconnect(m_editor->textEdit(), &KTextEdit::textChanged, this, &BasketScene::contentChangedInEditor); } else if (m_editor->lineEdit()) { - disconnect(m_editor->lineEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor())); - disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(selectionChangedInEditor())); - disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString &)), this, SLOT(contentChangedInEditor())); + disconnect(m_editor->lineEdit(), &QLineEdit::selectionChanged, this, &BasketScene::selectionChangedInEditor); + disconnect(m_editor->lineEdit(), &QLineEdit::textChanged, this, &BasketScene::selectionChangedInEditor); + disconnect(m_editor->lineEdit(), &QLineEdit::textChanged, this, &BasketScene::contentChangedInEditor); } } m_editorTrackMouseEvent = false; m_editor->graphicsWidget()->widget()->disconnect(); removeItem(m_editor->graphicsWidget()); m_editor->validate(); Note *note = m_editor->note(); // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly: bool isEmpty = m_editor->isEmpty(); delete m_editor; m_editor = nullptr; m_redirectEditActions = false; m_editorWidth = -1; m_editorHeight = -1; m_inactivityAutoSaveTimer.stop(); // Delete the note if it is now empty: if (isEmpty && deleteEmptyNote) { focusANonSelectedNoteAboveOrThenBelow(); note->setSelected(true); note->deleteSelectedNotes(); if (m_hoveredNote == note) m_hoveredNote = nullptr; if (m_focusedNote == note) m_focusedNote = nullptr; delete note; save(); note = nullptr; } unlockHovering(); filterAgain(/*andEnsureVisible=*/false); // Does not work: // if (Settings::playAnimations()) // note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving if (note) note->setSelected(false); // unselectAll(); doHoverEffects(); // save(); Global::bnpView->m_actEditNote->setEnabled(!isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/); emit resetStatusBarText(); // Remove the "Editing. ... to validate." text. // if (qApp->activeWindow() == Global::mainContainer) // Set focus to the basket, unless the user pressed a letter key in the filter bar and the currently edited note came hidden, then editing closed: if (!decoration()->filterBar()->lineEdit()->hasFocus()) setFocus(); // Return true if the note is still there: return (note != nullptr); } void BasketScene::closeBasket() { closeEditor(); unbufferizeAll(); // Keep the memory footprint low if (isEncrypted()) { if (Settings::enableReLockTimeout()) { int seconds = Settings::reLockTimeoutMinutes() * 60; m_inactivityAutoLockTimer.setSingleShot(true); m_inactivityAutoLockTimer.start(seconds * 1000); } } } void BasketScene::openBasket() { if (m_inactivityAutoLockTimer.isActive()) m_inactivityAutoLockTimer.stop(); } Note *BasketScene::theSelectedNote() { if (countSelecteds() != 1) { qDebug() << "NO SELECTED NOTE !!!!"; return nullptr; } Note *selectedOne; FOR_EACH_NOTE(note) { selectedOne = note->theSelectedNote(); if (selectedOne) return selectedOne; } qDebug() << "One selected note, BUT NOT FOUND !!!!"; return nullptr; } NoteSelection *BasketScene::selectedNotes() { NoteSelection selection; FOR_EACH_NOTE(note) selection.append(note->selectedNotes()); if (!selection.firstChild) return nullptr; for (NoteSelection *node = selection.firstChild; node; node = node->next) node->parent = nullptr; // If the top-most groups are columns, export only children of those groups // (because user is not aware that columns are groups, and don't care: it's not what she want): if (selection.firstChild->note->isColumn()) { NoteSelection tmpSelection; NoteSelection *nextNode; NoteSelection *nextSubNode; for (NoteSelection *node = selection.firstChild; node; node = nextNode) { nextNode = node->next; if (node->note->isColumn()) { for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) { nextSubNode = subNode->next; tmpSelection.append(subNode); subNode->parent = nullptr; subNode->next = nullptr; } } else { tmpSelection.append(node); node->parent = nullptr; node->next = nullptr; } } // debugSel(tmpSelection.firstChild); return tmpSelection.firstChild; } else { // debugSel(selection.firstChild); return selection.firstChild; } } void BasketScene::showEditedNoteWhileFiltering() { if (m_editor) { Note *note = m_editor->note(); filterAgain(); note->setSelected(true); relayoutNotes(); note->setX(note->x()); note->setY(note->y()); filterAgainDelayed(); } } void BasketScene::noteEdit(Note *note, bool justAdded, const QPointF &clickedPoint) // TODO: Remove the first parameter!!! { if (!note) note = theSelectedNote(); // TODO: Or pick the focused note! if (!note) return; if (isDuringEdit()) { closeEditor(); // Validate the noteeditors in QLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict) return; } if (note != m_focusedNote) { setFocusedNote(note); m_startOfShiftSelectionNote = note; } if (justAdded && isFiltering()) { QTimer::singleShot(0, this, SLOT(showEditedNoteWhileFiltering())); } doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback! NoteEditor *editor = NoteEditor::editNoteContent(note->content(), nullptr); if (editor->graphicsWidget()) { m_editor = editor; addItem(m_editor->graphicsWidget()); placeEditorAndEnsureVisible(); // placeEditor(); // FIXME: After? m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit(); if (m_redirectEditActions) { // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text: // selection has not changed but "Select All" should be re-enabled: m_editor->connectActions(this); } m_editor->graphicsWidget()->setFocus(); - connect(m_editor, SIGNAL(askValidation()), this, SLOT(closeEditorDelayed())); - connect(m_editor, SIGNAL(mouseEnteredEditorWidget()), this, SLOT(mouseEnteredEditorWidget())); + connect(m_editor, &NoteEditor::askValidation, this, &BasketScene::closeEditorDelayed); + connect(m_editor, &NoteEditor::mouseEnteredEditorWidget, this, &BasketScene::mouseEnteredEditorWidget); if (clickedPoint != QPoint()) { m_editor->setCursorTo(clickedPoint); updateEditorAppearance(); } // qApp->processEvents(); // Show the editor toolbar before ensuring the note is visible ensureNoteVisible(note); // because toolbar can create a new line and then partially hide the note m_editor->graphicsWidget()->setFocus(); // When clicking in the basket, a QTimer::singleShot(0, ...) focus the basket! So we focus the widget after qApp->processEvents() emit resetStatusBarText(); // Display "Editing. ... to validate." } else { // Delete the note user have canceled the addition: if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) { focusANonSelectedNoteAboveOrThenBelow(); editor->note()->setSelected(true); editor->note()->deleteSelectedNotes(); if (m_hoveredNote == editor->note()) m_hoveredNote = nullptr; if (m_focusedNote == editor->note()) m_focusedNote = nullptr; delete editor->note(); save(); } editor->deleteLater(); unlockHovering(); filterAgain(); unselectAll(); } // Must set focus to the editor, otherwise edit cursor is not seen and precomposed characters cannot be entered if (m_editor != nullptr && m_editor->textEdit() != nullptr) m_editor->textEdit()->setFocus(); Global::bnpView->m_actEditNote->setEnabled(false); } void BasketScene::noteDelete() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->textCursor().deleteChar(); else if (m_editor->lineEdit()) m_editor->lineEdit()->del(); return; } if (countSelecteds() <= 0) return; int really = KMessageBox::Yes; if (Settings::confirmNoteDeletion()) really = KMessageBox::questionYesNo(m_view, i18np("Do you really want to delete this note?", "Do you really want to delete these %1 notes?", countSelecteds()), i18np("Delete Note", "Delete Notes", countSelecteds()), KStandardGuiItem::del(), KStandardGuiItem::cancel()); if (really == KMessageBox::No) return; noteDeleteWithoutConfirmation(); } void BasketScene::focusANonSelectedNoteBelow(bool inSameColumn) { // First focus another unselected one below it...: if (m_focusedNote && m_focusedNote->isSelected()) { Note *next = m_focusedNote->nextShownInStack(); while (next && next->isSelected()) next = next->nextShownInStack(); if (next) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) { setFocusedNote(next); m_startOfShiftSelectionNote = next; } } } } void BasketScene::focusANonSelectedNoteAbove(bool inSameColumn) { // ... Or above it: if (m_focusedNote && m_focusedNote->isSelected()) { Note *prev = m_focusedNote->prevShownInStack(); while (prev && prev->isSelected()) prev = prev->prevShownInStack(); if (prev) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) { setFocusedNote(prev); m_startOfShiftSelectionNote = prev; } } } } void BasketScene::focusANonSelectedNoteBelowOrThenAbove() { focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/false); focusANonSelectedNoteAbove(/*inSameColumn=*/false); } void BasketScene::focusANonSelectedNoteAboveOrThenBelow() { focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/false); focusANonSelectedNoteBelow(/*inSameColumn=*/false); } void BasketScene::noteDeleteWithoutConfirmation(bool deleteFilesToo) { // If the currently focused note is selected, it will be deleted. focusANonSelectedNoteBelowOrThenAbove(); // Do the deletion: Note *note = firstNote(); Note *next; while (note) { next = note->next(); // If we delete 'note' on the next line, note->next() will be 0! note->deleteSelectedNotes(deleteFilesToo, &m_notesToBeDeleted); note = next; } if (!m_notesToBeDeleted.isEmpty()) { doCleanUp(); } relayoutNotes(); // FIXME: filterAgain()? save(); } void BasketScene::doCopy(CopyMode copyMode) { QClipboard *cb = QApplication::clipboard(); QClipboard::Mode mode = ((copyMode == CopyToSelection) ? QClipboard::Selection : QClipboard::Clipboard); NoteSelection *selection = selectedNotes(); int countCopied = countSelecteds(); if (selection->firstStacked()) { QDrag *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/nullptr); // d will be deleted by QT // /*bool shouldRemove = */d->drag(); // delete selection; cb->setMimeData(d->mimeData(), mode); // NoteMultipleDrag will be deleted by QT // if (copyMode == CutToClipboard && !note->useFile()) // If useFile(), NoteDrag::dragObject() will delete it TODO // note->slotDelete(); if (copyMode == CutToClipboard) { noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false); focusANote(); } switch (copyMode) { default: case CopyToClipboard: emit postMessage(i18np("Copied note to clipboard.", "Copied notes to clipboard.", countCopied)); break; case CutToClipboard: emit postMessage(i18np("Cut note to clipboard.", "Cut notes to clipboard.", countCopied)); break; case CopyToSelection: emit postMessage(i18np("Copied note to selection.", "Copied notes to selection.", countCopied)); break; } } } void BasketScene::noteCopy() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->copy(); else if (m_editor->lineEdit()) m_editor->lineEdit()->copy(); } else doCopy(CopyToClipboard); } void BasketScene::noteCut() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->cut(); else if (m_editor->lineEdit()) m_editor->lineEdit()->cut(); } else doCopy(CutToClipboard); } void BasketScene::noteOpen(Note *note) { /* GetSelectedNotes NoSelectedNote || Count == 0 ? return AllTheSameType ? Get { url, message(count) } */ // TODO: Open ALL selected notes! if (!note) note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/false); QString message = note->content()->messageWhenOpening(NoteContent::OpenOne /*NoteContent::OpenSeveral*/); if (url.isEmpty()) { if (message.isEmpty()) emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); else { int result = KMessageBox::warningContinueCancel(m_view, message, /*caption=*/QString(), KGuiItem(i18n("&Edit"), "edit")); if (result == KMessageBox::Continue) noteEdit(note); } } else { emit postMessage(message); // "Opening link target..." / "Launching application..." / "Opening note file..." // Finally do the opening job: QString customCommand = note->content()->customOpenCommand(); if (url.url().startsWith("basket://")) { emit crossReference(url.url()); } else if (customCommand.isEmpty()) { KRun *run = new KRun(url, m_view->window()); run->setAutoDelete(true); } else { QList urls {url}; KRun::run(customCommand, urls, m_view->window()); } } } /** Code from bool KRun::displayOpenWithDialog(const KUrl::List& lst, bool tempFiles) * It does not allow to set a text, so I ripped it to do that: */ bool KRun__displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &text) { if (qApp && !KAuthorized::authorizeAction("openwith")) { KMessageBox::sorry(window, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-( return false; } KOpenWithDialog l(lst, text, QString(), nullptr); if (l.exec()) { KService::Ptr service = l.service(); if (!!service) return KRun::runApplication(*service, lst, window, tempFiles ? KRun::DeleteTemporaryFiles : KRun::RunFlags()); // qDebug(250) << "No service set, running " << l.text() << endl; return KRun::run(l.text(), lst, window); // TODO handle tempFiles } return false; } void BasketScene::noteOpenWith(Note *note) { if (!note) note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/true); QString message = note->content()->messageWhenOpening(NoteContent::OpenOneWith /*NoteContent::OpenSeveralWith*/); QString text = note->content()->messageWhenOpening(NoteContent::OpenOneWithDialog /*NoteContent::OpenSeveralWithDialog*/); if (url.isEmpty()) { emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); } else { QList urls {url}; if (KRun__displayOpenWithDialog(urls, m_view->window(), false, text)) emit postMessage(message); // "Opening link target with..." / "Opening note file with..." } } void BasketScene::noteSaveAs() { // if (!note) // note = theSelectedNote(); Note *note = theSelectedNote(); if (!note) return; QUrl url = note->content()->urlToOpen(/*with=*/false); if (url.isEmpty()) return; QString fileName = QFileDialog::getSaveFileName(m_view, i18n("Save to File"), url.fileName(), note->content()->saveAsFilters()); // TODO: Ask to overwrite ! if (fileName.isEmpty()) return; // TODO: Convert format, etc. (use NoteContent::saveAs(fileName)) KIO::copy(url, QUrl::fromLocalFile(fileName)); } Note *BasketScene::selectedGroup() { FOR_EACH_NOTE(note) { Note *selectedGroup = note->selectedGroup(); if (selectedGroup) { // If the selected group is one group in a column, then return that group, and not the column, // because the column is not ungrouppage, and the Ungroup action would be disabled. if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) { return selectedGroup->firstChild(); } return selectedGroup; } } return nullptr; } bool BasketScene::selectionIsOneGroup() { return (selectedGroup() != nullptr); } Note *BasketScene::firstSelected() { Note *first = nullptr; FOR_EACH_NOTE(note) { first = note->firstSelected(); if (first) return first; } return nullptr; } Note *BasketScene::lastSelected() { Note *last = nullptr, *tmp = nullptr; FOR_EACH_NOTE(note) { tmp = note->lastSelected(); if (tmp) last = tmp; } return last; } bool BasketScene::convertTexts() { m_watcher->stopScan(); bool convertedNotes = false; if (!isLoaded()) load(); FOR_EACH_NOTE(note) if (note->convertTexts()) convertedNotes = true; if (convertedNotes) save(); m_watcher->startScan(); return convertedNotes; } void BasketScene::noteGroup() { /* // Nothing to do? if (isLocked() || countSelecteds() <= 1) return; // If every selected notes are ALREADY in one group, then don't touch anything: Note *selectedGroup = this->selectedGroup(); if (selectedGroup && !selectedGroup->isColumn()) return; */ // Copied from BNPView::updateNotesActions() bool severalSelected = countSelecteds() >= 2; Note *selectedGroup = (severalSelected ? this->selectedGroup() : nullptr); bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn()); if (!enabled) return; // Get the first selected note: we will group selected items just before: Note *first = firstSelected(); // if (selectedGroup != 0 || first == 0) // return; m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected: // Create and insert the receiving group: Note *group = new Note(this); if (first->isFree()) { insertNote(group, nullptr, Note::BottomColumn, QPointF(first->x(), first->y()), /*animateNewPosition=*/false); } else { insertNote(group, first, Note::TopInsert, QPointF(), /*animateNewPosition=*/false); } // Put a FAKE UNSELECTED note in the new group, so if the new group is inside an allSelected() group, the parent group is not moved inside the new group! Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); // Group the notes: Note *nextNote; Note *note = firstNote(); while (note) { nextNote = note->next(); note->groupIn(group); note = nextNote; } m_loaded = true; // Part 2 / 2 of the workaround! // Do cleanup: unplugNote(fakeNote); delete fakeNote; unselectAll(); group->setSelectedRecursively(true); // Notes were unselected by unplugging relayoutNotes(); save(); } void BasketScene::noteUngroup() { Note *group = selectedGroup(); if (group && !group->isColumn()) { ungroupNote(group); relayoutNotes(); } save(); } void BasketScene::unplugSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { unplugNote(toUnplug->note); } } void BasketScene::insertSelection(NoteSelection *selection, Note *after) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) { Note *group = new Note(this); insertNote(group, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/false); Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); insertSelection(toUnplug->firstChild, fakeNote); unplugNote(fakeNote); delete fakeNote; after = group; } else { Note *note = toUnplug->note; note->setPrev(nullptr); note->setNext(nullptr); insertNote(note, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/true); after = note; } } } void BasketScene::selectSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) selectSelection(toUnplug); else toUnplug->note->setSelected(true); } } void BasketScene::noteMoveOnTop() { // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); if (isColumnsLayout()) { if (firstNote()->firstChild()) insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert, QPointF(), /*animateNewPosition=*/false); else insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); } else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, nullptr, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(); save(); } void BasketScene::noteMoveOnBottom() { // TODO: Duplicate code: void noteMoveOn(); // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); if (isColumnsLayout()) insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false); else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, nullptr, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(); save(); } void BasketScene::moveSelectionTo(Note *here, bool below /* = true*/) { NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this); // if (isColumnsLayout()) insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert), QPointF(), /*animateNewPosition=*/false); // else { // // TODO: Also allow to move notes on top of a group!!!!!!! // insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false); // } insertSelection(selection, fakeNote); unplugNote(fakeNote); delete fakeNote; selectSelection(selection); relayoutNotes(); save(); } void BasketScene::noteMoveNoteUp() { // TODO: Move between columns, even if they are empty !!!!!!! // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!! Note *first = firstSelected(); Note *previous = first->prevShownInStack(); if (previous) moveSelectionTo(previous, /*below=*/false); } void BasketScene::noteMoveNoteDown() { Note *first = lastSelected(); Note *next = first->nextShownInStack(); if (next) moveSelectionTo(next, /*below=*/true); } void BasketScene::wheelEvent(QGraphicsSceneWheelEvent *event) { // Q3ScrollView::wheelEvent(event); QGraphicsScene::wheelEvent(event); } void BasketScene::linkLookChanged() { Note *note = m_firstNote; while (note) { note->linkLookChanged(); note = note->next(); } relayoutNotes(); } void BasketScene::slotCopyingDone2(KIO::Job *job, const QUrl & /*from*/, const QUrl &to) { if (job->error()) { DEBUG_WIN << "Copy finished, ERROR"; return; } Note *note = noteForFullPath(to.path()); DEBUG_WIN << "Copy finished, load note: " + to.path() + (note ? QString() : " --- NO CORRESPONDING NOTE"); if (note != nullptr) { note->content()->loadFromFile(/*lazyLoad=*/false); if (isEncrypted()) note->content()->saveToFile(); if (m_focusedNote == note) // When inserting a new note we ensure it visible ensureNoteVisible(note); // But after loading it has certainly grown and if it was } // on bottom of the basket it's not visible entirely anymore } Note *BasketScene::noteForFullPath(const QString &path) { Note *note = firstNote(); Note *found; while (note) { found = note->noteForFullPath(path); if (found) return found; note = note->next(); } return nullptr; } void BasketScene::deleteFiles() { m_watcher->stopScan(); Tools::deleteRecursively(fullPath()); } QList BasketScene::usedStates() { QList states; FOR_EACH_NOTE(note) note->usedStates(states); return states; } void BasketScene::listUsedTags(QList &list) { if (!isLoaded()) { load(); } FOR_EACH_NOTE(child) child->listUsedTags(list); } /** Unfocus the previously focused note (unless it was null) * and focus the new @param note (unless it is null) if hasFocus() * Update m_focusedNote to the new one */ void BasketScene::setFocusedNote(Note *note) // void BasketScene::changeFocusTo(Note *note) { // Don't focus an hidden note: if (note != nullptr && !note->isShown()) return; // When clicking a group, this group gets focused. But only content-based notes should be focused: if (note && note->isGroup()) note = note->firstRealChild(); // The first time a note is focused, it becomes the start of the Shift selection: if (m_startOfShiftSelectionNote == nullptr) m_startOfShiftSelectionNote = note; // Unfocus the old focused note: if (m_focusedNote != nullptr) m_focusedNote->setFocused(false); // Notify the new one to draw a focus rectangle... only if the basket is focused: if (hasFocus() && note != nullptr) note->setFocused(true); // Save the new focused note: m_focusedNote = note; } /** If no shown note is currently focused, try to find a shown note and focus it * Also update m_focusedNote to the new one (or null if there isn't) */ void BasketScene::focusANote() { if (countFounds() == 0) { // No note to focus setFocusedNote(nullptr); // m_startOfShiftSelectionNote = 0; return; } if (m_focusedNote == nullptr) { // No focused note yet : focus the first shown Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = m_focusedNote; return; } // Search a visible note to focus if the focused one isn't shown : Note *toFocus = m_focusedNote; if (toFocus && !toFocus->isShown()) toFocus = toFocus->nextShownInStack(); if (!toFocus && m_focusedNote) toFocus = m_focusedNote->prevShownInStack(); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = toFocus; } Note *BasketScene::firstNoteInStack() { if (!firstNote()) return nullptr; if (firstNote()->content()) return firstNote(); else return firstNote()->nextInStack(); } Note *BasketScene::lastNoteInStack() { Note *note = lastNote(); while (note) { if (note->content()) return note; Note *possibleNote = note->lastRealChild(); if (possibleNote && possibleNote->content()) return possibleNote; note = note->prev(); } return nullptr; } Note *BasketScene::firstNoteShownInStack() { Note *first = firstNoteInStack(); while (first && !first->isShown()) first = first->nextInStack(); return first; } Note *BasketScene::lastNoteShownInStack() { Note *last = lastNoteInStack(); while (last && !last->isShown()) last = last->prevInStack(); return last; } Note *BasketScene::noteOn(NoteOn side) { Note *bestNote = nullptr; int distance = -1; // int bestDistance = contentsWidth() * contentsHeight() * 10; int bestDistance = sceneRect().width() * sceneRect().height() * 10; Note *note = firstNoteShownInStack(); Note *primary = m_focusedNote->parentPrimaryNote(); while (note) { switch (side) { case LEFT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE); break; case RIGHT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE); break; case TOP_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE); break; case BOTTOM_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE); break; } if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) { bestNote = note; bestDistance = distance; } note = note->nextShownInStack(); } return bestNote; } Note *BasketScene::firstNoteInGroup() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr); while (parent) { if (parent->firstChild() != child && !parent->isColumn()) return parent->firstRealChild(); child = parent; parent = parent->parentNote(); } return nullptr; } Note *BasketScene::noteOnHome() { // First try to find the first note of the group containing the focused note: Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr); while (parent) { if (parent->nextShownInStack() != m_focusedNote) return parent->nextShownInStack(); child = parent; parent = parent->parentNote(); } // If it was not found, then focus the very first note in the basket: if (isFreeLayout()) { Note *first = firstNoteShownInStack(); // The effective first note found Note *note = first; // The current note, to compare with the previous first note, if this new note is more on top if (note) note = note->nextShownInStack(); while (note) { if (note->y() < first->y() || (note->y() == first->y() && note->x() < first->x())) first = note; note = note->nextShownInStack(); } return first; } else return firstNoteShownInStack(); } Note *BasketScene::noteOnEnd() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr); Note *lastChild; while (parent) { lastChild = parent->lastRealChild(); if (lastChild && lastChild != m_focusedNote) { if (lastChild->isShown()) return lastChild; lastChild = lastChild->prevShownInStack(); if (lastChild && lastChild->isShown() && lastChild != m_focusedNote) return lastChild; } child = parent; parent = parent->parentNote(); } if (isFreeLayout()) { Note *last; Note *note; last = note = firstNoteShownInStack(); note = note->nextShownInStack(); while (note) { if (note->bottom() > last->bottom() || (note->bottom() == last->bottom() && note->x() > last->x())) last = note; note = note->nextShownInStack(); } return last; } else return lastNoteShownInStack(); } void BasketScene::keyPressEvent(QKeyEvent *event) { if (isDuringEdit()) { QGraphicsScene::keyPressEvent(event); /*if( event->key() == Qt::Key_Return ) { m_editor->graphicsWidget()->setFocus(); } else if( event->key() == Qt::Key_Escape) { closeEditor(); }*/ event->accept(); return; } if (event->key() == Qt::Key_Escape) { if (decoration()->filterData().isFiltering) decoration()->filterBar()->reset(); else unselectAll(); } if (countFounds() == 0) return; if (!m_focusedNote) return; Note *toFocus = nullptr; switch (event->key()) { case Qt::Key_Down: toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack()); if (toFocus) break; // scrollBy(0, 30); // This cases do not move focus to another note... return; case Qt::Key_Up: toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack()); if (toFocus) break; // scrollBy(0, -30); // This cases do not move focus to another note... return; case Qt::Key_PageDown: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(BOTTOM_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->nextShownInStack(); } if (toFocus == nullptr) toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; // scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note... // scrollBy(0, viewport()->height() / 2); // This cases do not move focus to another note... return; case Qt::Key_PageUp: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(TOP_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->prevShownInStack(); } if (toFocus == nullptr) toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; // scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note... // scrollBy(0, - viewport()->height() / 2); // This cases do not move focus to another note... return; case Qt::Key_Home: toFocus = noteOnHome(); break; case Qt::Key_End: toFocus = noteOnEnd(); break; case Qt::Key_Left: if (m_focusedNote->tryFoldParent()) return; if ((toFocus = noteOn(LEFT_SIDE))) break; if ((toFocus = firstNoteInGroup())) break; // scrollBy(-30, 0); // This cases do not move focus to another note... return; case Qt::Key_Right: if (m_focusedNote->tryExpandParent()) return; if ((toFocus = noteOn(RIGHT_SIDE))) break; // scrollBy(30, 0); // This cases do not move focus to another note... return; case Qt::Key_Space: // This case do not move focus to another note... if (m_focusedNote) { m_focusedNote->setSelected(!m_focusedNote->isSelected()); event->accept(); } else event->ignore(); return; // ... so we return after the process default: return; } if (toFocus == nullptr) { // If no direction keys have been pressed OR reached out the begin or end event->ignore(); // Important !! return; } if (event->modifiers() & Qt::ShiftModifier) { // Shift+arrowKeys selection if (m_startOfShiftSelectionNote == nullptr) m_startOfShiftSelectionNote = toFocus; ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! selectRange(m_startOfShiftSelectionNote, toFocus); setFocusedNote(toFocus); event->accept(); return; } else /*if (toFocus != m_focusedNote)*/ { // Move focus to ANOTHER note... ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! setFocusedNote(toFocus); m_startOfShiftSelectionNote = toFocus; if (!(event->modifiers() & Qt::ControlModifier)) // ... select only current note if Control unselectAllBut(m_focusedNote); event->accept(); return; } event->ignore(); // Important !! } /** Select a range of notes and deselect the others. * The order between start and end has no importance (end could be before start) */ void BasketScene::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/) { Note *cur; Note *realEnd = nullptr; // Avoid crash when start (or end) is null if (start == nullptr) start = end; else if (end == nullptr) end = start; // And if *both* are null if (start == nullptr) { if (unselectOthers) unselectAll(); return; } // In case there is only one note to select if (start == end) { if (unselectOthers) unselectAllBut(start); else start->setSelected(true); return; } // Free layout baskets should select range as if we were drawing a rectangle between start and end: if (isFreeLayout()) { QRectF startRect(start->x(), start->y(), start->width(), start->height()); QRectF endRect(end->x(), end->y(), end->width(), end->height()); QRectF toSelect = startRect.united(endRect); selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers); return; } // Search the REAL first (and deselect the others before it) : for (cur = firstNoteInStack(); cur != nullptr; cur = cur->nextInStack()) { if (cur == start || cur == end) break; if (unselectOthers) cur->setSelected(false); } // Select the notes after REAL start, until REAL end : if (cur == start) realEnd = end; else if (cur == end) realEnd = start; for (/*cur = cur*/; cur != nullptr; cur = cur->nextInStack()) { cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown if (cur == realEnd) break; } if (!unselectOthers) return; // Deselect the remaining notes : if (cur) cur = cur->nextInStack(); for (/*cur = cur*/; cur != nullptr; cur = cur->nextInStack()) cur->setSelected(false); } void BasketScene::focusInEvent(QFocusEvent *event) { // Focus cannot be get with Tab when locked, but a click can focus the basket! if (isLocked()) { if (m_button) { QGraphicsScene::focusInEvent(event); QTimer::singleShot(0, m_button, SLOT(setFocus())); } } else { QGraphicsScene::focusInEvent(event); focusANote(); // hasFocus() is true at this stage, note will be focused } } void BasketScene::focusOutEvent(QFocusEvent *) { if (m_focusedNote != nullptr) m_focusedNote->setFocused(false); } void BasketScene::ensureNoteVisible(Note *note) { if (!note->isShown()) // Logical! return; if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls return; m_view->ensureVisible(note); /*// int bottom = note->y() + qMin(note->height(), visibleHeight()); // int finalRight = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), visibleWidth()); qreal bottom = note->y() + qMin(note->height(), (qreal)m_view->viewport()->height()); qreal finalRight = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), (qreal)m_view->viewport()->width()); m_view->ensureVisible(finalRight, bottom, 0, 0); m_view->ensureVisible(note->x(), note->y(), 0, 0);*/ } void BasketScene::addWatchedFile(const QString &fullPath) { // DEBUG_WIN << "Watcher>Add Monitoring Of : " + fullPath + ""; m_watcher->addFile(fullPath); } void BasketScene::removeWatchedFile(const QString &fullPath) { // DEBUG_WIN << "Watcher>Remove Monitoring Of : " + fullPath + ""; m_watcher->removeFile(fullPath); } void BasketScene::watchedFileModified(const QString &fullPath) { if (!m_modifiedFiles.contains(fullPath)) m_modifiedFiles.append(fullPath); // If a big file is saved by an application, notifications are send several times. // We wait they are not sent anymore to consider the file complete! m_watcherTimer.setSingleShot(true); m_watcherTimer.start(200); DEBUG_WIN << "Watcher>Modified : " + fullPath + ""; } void BasketScene::watchedFileDeleted(const QString &fullPath) { Note *note = noteForFullPath(fullPath); removeWatchedFile(fullPath); if (note) { NoteSelection *selection = selectedNotes(); unselectAllBut(note); noteDeleteWithoutConfirmation(); while (selection) { selection->note->setSelected(true); selection = selection->nextStacked(); } } DEBUG_WIN << "Watcher>Removed : " + fullPath + ""; } void BasketScene::updateModifiedNotes() { for (QList::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) { Note *note = noteForFullPath(*it); if (note) note->content()->loadFromFile(/*lazyLoad=*/false); } m_modifiedFiles.clear(); } bool BasketScene::setProtection(int type, QString key) { #ifdef HAVE_LIBGPGME if (type == PasswordEncryption || // Ask a new password m_encryptionType != type || m_encryptionKey != key) { int savedType = m_encryptionType; QString savedKey = m_encryptionKey; m_encryptionType = type; m_encryptionKey = key; m_gpg->clearCache(); if (saveAgain()) { emit propertiesChanged(this); } else { m_encryptionType = savedType; m_encryptionKey = savedKey; m_gpg->clearCache(); return false; } } return true; #else m_encryptionType = type; m_encryptionKey = key; return false; #endif } bool BasketScene::saveAgain() { bool result = false; m_watcher->stopScan(); // Re-encrypt basket file: result = save(); // Re-encrypt every note files recursively: if (result) { FOR_EACH_NOTE(note) { result = note->saveAgain(); if (!result) break; } } m_watcher->startScan(); return result; } bool BasketScene::loadFromFile(const QString &fullPath, QString *string) { QByteArray array; if (loadFromFile(fullPath, &array)) { *string = QString::fromUtf8(array.data(), array.size()); return true; } else return false; } bool BasketScene::isEncrypted() { return (m_encryptionType != NoEncryption); } bool BasketScene::isFileEncrypted() { QFile file(fullPath() + ".basket"); if (file.open(QIODevice::ReadOnly)) { // Should be ASCII anyways QString line = file.readLine(32); if (line.startsWith("-----BEGIN PGP MESSAGE-----")) return true; } return false; } bool BasketScene::loadFromFile(const QString &fullPath, QByteArray *array) { QFile file(fullPath); bool encrypted = false; if (file.open(QIODevice::ReadOnly)) { *array = file.readAll(); QByteArray magic = "-----BEGIN PGP MESSAGE-----"; int i = 0; if (array->size() > magic.size()) for (i = 0; array->at(i) == magic[i]; ++i) ; if (i == magic.size()) { encrypted = true; } file.close(); #ifdef HAVE_LIBGPGME if (encrypted) { QByteArray tmp(*array); tmp.detach(); // Only use gpg-agent for private key encryption since it doesn't // cache password used in symmetric encryption. m_gpg->setUseGnuPGAgent(Settings::useGnuPGAgent() && m_encryptionType == PrivateKeyEncryption); if (m_encryptionType == PrivateKeyEncryption) m_gpg->setText(i18n("Please enter the password for the following private key:"), false); else m_gpg->setText(i18n("Please enter the password for the basket %1:", basketName()), false); // Used when decrypting return m_gpg->decrypt(tmp, array); } #else if (encrypted) { return false; } #endif return true; } else return false; } bool BasketScene::saveToFile(const QString &fullPath, const QString &string) { QByteArray array = string.toUtf8(); return saveToFile(fullPath, array); } bool BasketScene::saveToFile(const QString &fullPath, const QByteArray &array) { ulong length = array.size(); bool success = true; QByteArray tmp; #ifdef HAVE_LIBGPGME if (isEncrypted()) { QString key; // We only use gpg-agent for private key encryption and saving without // public key doesn't need one. m_gpg->setUseGnuPGAgent(false); if (m_encryptionType == PrivateKeyEncryption) { key = m_encryptionKey; // public key doesn't need password m_gpg->setText(QString(), false); } else m_gpg->setText(i18n("Please assign a password to the basket %1:", basketName()), true); // Used when defining a new password success = m_gpg->encrypt(array, length, &tmp, key); length = tmp.size(); } else tmp = array; #else success = !isEncrypted(); if (success) tmp = array; #endif /*if (success && (success = file.open(QIODevice::WriteOnly))){ success = (file.write(tmp) == (Q_LONG)tmp.size()); file.close(); }*/ if (success) return safelySaveToFile(fullPath, tmp, length); else return false; } /** * A safer version of saveToFile, that doesn't perform encryption. To save a * file owned by a basket (i.e. a basket or a note file), use saveToFile(), but * to save to another file, (e.g. the basket hierarchy), use this function * instead. */ /*static*/ bool BasketScene::safelySaveToFile(const QString &fullPath, const QByteArray &array, unsigned long length) { // Modulus operandi: // 1. Use QSaveFile to try and save the file // 2. Show a modal dialog (with the error) when bad things happen // 3. We keep trying (at increasing intervals, up until every minute) // until we finally save the file. // The error dialog is static to make sure we never show the dialog twice, static DiskErrorDialog *dialog = nullptr; static const uint maxDelay = 60 * 1000; // ms uint retryDelay = 1000; // ms bool success = false; do { QSaveFile saveFile(fullPath); if (saveFile.open(QIODevice::WriteOnly)) { saveFile.write(array, length); if (saveFile.commit()) success = true; } if (!success) { if (!dialog) { dialog = new DiskErrorDialog(i18n("Error while saving"), saveFile.errorString(), qApp->activeWindow()); } if (!dialog->isVisible()) dialog->show(); static const uint sleepDelay = 50; // ms for (uint i = 0; i < retryDelay / sleepDelay; ++i) { qApp->processEvents(); } // Double the retry delay, but don't go over the max. retryDelay = qMin(maxDelay, retryDelay * 2); // ms } } while (!success); if (dialog) dialog->deleteLater(); dialog = nullptr; return true; // Guess we can't really return a fail } /*static*/ bool BasketScene::safelySaveToFile(const QString &fullPath, const QString &string) { QByteArray bytes = string.toUtf8(); return safelySaveToFile(fullPath, bytes, bytes.length()); } void BasketScene::lock() { #ifdef HAVE_LIBGPGME closeEditor(); m_gpg->clearCache(); m_locked = true; enableActions(); deleteNotes(); m_loaded = false; m_loadingLaunched = false; #endif } diff --git a/src/bnpview.cpp b/src/bnpview.cpp index 903a7e0..f870657 100644 --- a/src/bnpview.cpp +++ b/src/bnpview.cpp @@ -1,2839 +1,2839 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "bnpview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BASKET_USE_DRKONQI #include #endif // BASKET_USE_DRKONQI #include #include // usleep #include "archive.h" #include "backgroundmanager.h" #include "backup.h" #include "basketfactory.h" #include "basketlistview.h" #include "basketproperties.h" #include "basketscene.h" #include "basketstatusbar.h" #include "colorpicker.h" #include "crashhandler.h" #include "debugwindow.h" #include "decoratedbasket.h" #include "formatimporter.h" #include "gitwrapper.h" #include "history.h" #include "htmlexporter.h" #include "icon_names.h" #include "newbasketdialog.h" #include "notedrag.h" #include "noteedit.h" // To launch InlineEditors::initToolBars() #include "notefactory.h" #include "password.h" #include "regiongrabber.h" #include "settings.h" #include "softwareimporters.h" #include "tools.h" #include "xmlwork.h" #include #include #include #include //#include "bnpviewadaptor.h" /** class BNPView: */ const int BNPView::c_delayTooltipTime = 275; BNPView::BNPView(QWidget *parent, const char *name, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, BasketStatusBar *bar) : QSplitter(Qt::Horizontal, parent) , m_actLockBasket(nullptr) , m_actPassBasket(nullptr) , m_loading(true) , m_newBasketPopup(false) , m_firstShow(true) , m_colorPicker(new DesktopColorPicker()) , m_regionGrabber(nullptr) , m_passiveDroppedSelection(nullptr) , m_actionCollection(actionCollection) , m_guiClient(aGUIClient) , m_statusbar(bar) , m_tryHideTimer(nullptr) , m_hideTimer(nullptr) { // new BNPViewAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject("/BNPView", this); setObjectName(name); /* Settings */ Settings::loadConfig(); Global::bnpView = this; // Needed when loading the baskets: Global::backgroundManager = new BackgroundManager(); setupGlobalShortcuts(); m_history = new QUndoStack(this); initialize(); QTimer::singleShot(0, this, SLOT(lateInit())); } BNPView::~BNPView() { int treeWidth = Global::bnpView->sizes()[Settings::treeOnLeft() ? 0 : 1]; Settings::setBasketTreeWidth(treeWidth); if (currentBasket() && currentBasket()->isDuringEdit()) currentBasket()->closeEditor(); Settings::saveConfig(); Global::bnpView = nullptr; delete Global::systemTray; Global::systemTray = nullptr; delete m_statusbar; delete m_history; m_history = nullptr; NoteDrag::createAndEmptyCuttingTmpFolder(); // Clean the temporary folder we used } void BNPView::lateInit() { /* InlineEditors* instance = InlineEditors::instance(); if(instance) { KToolBar* toolbar = instance->richTextToolBar(); if(toolbar) toolbar->hide(); } */ // If the main window is hidden when session is saved, Container::queryClose() // isn't called and the last value would be kept Settings::setStartDocked(true); Settings::saveConfig(); /* System tray icon */ Global::systemTray = new SystemTray(Global::activeMainWindow()); Global::systemTray->setIconByName(":/images/22-apps-basket"); - connect(Global::systemTray, SIGNAL(showPart()), this, SIGNAL(showPart())); + connect(Global::systemTray, &SystemTray::showPart, this, &BNPView::showPart); /*if (Settings::useSystray()) Global::systemTray->show();*/ // Load baskets DEBUG_WIN << "Baskets are loaded from " + Global::basketsFolder(); NoteDrag::createAndEmptyCuttingTmpFolder(); // If last exec hasn't done it: clean the temporary folder we will use Tag::loadTags(); // Tags should be ready before loading baskets, but tags need the mainContainer to be ready to create KActions! load(); // If no basket has been found, try to import from an older version, if (topLevelItemCount() <= 0) { QDir dir; dir.mkdir(Global::basketsFolder()); if (FormatImporter::shouldImportBaskets()) { FormatImporter::importBaskets(); load(); } if (topLevelItemCount() <= 0) { // Create first basket: BasketFactory::newBasket(QString(), i18n("General")); GitWrapper::commitBasket(currentBasket()); GitWrapper::commitTagsXml(); } } // Load the Welcome Baskets if it is the First Time: if (!Settings::welcomeBasketsAdded()) { addWelcomeBaskets(); Settings::setWelcomeBasketsAdded(true); Settings::saveConfig(); } m_tryHideTimer = new QTimer(this); m_hideTimer = new QTimer(this); - connect(m_tryHideTimer, SIGNAL(timeout()), this, SLOT(timeoutTryHide())); - connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(timeoutHide())); + connect(m_tryHideTimer, &QTimer::timeout, this, &BNPView::timeoutTryHide); + connect(m_hideTimer, &QTimer::timeout, this, &BNPView::timeoutHide); // Preload every baskets for instant filtering: /*StopWatch::start(100); QListViewItemIterator it(m_tree); while (it.current()) { BasketListViewItem *item = ((BasketListViewItem*)it.current()); item->basket()->load(); qApp->processEvents(); ++it; } StopWatch::check(100);*/ } void BNPView::addWelcomeBaskets() { // Possible paths where to find the welcome basket archive, trying the translated one, and falling back to the English one: QStringList possiblePaths; if (QString(Tools::systemCodeset()) == QString("UTF-8")) { // Welcome baskets are encoded in UTF-8. If the system is not, then use the English version: QString lang = QLocale().languageToString(QLocale().language()); possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_" + lang + ".baskets")); possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_" + lang.split('_')[0] + ".baskets")); } possiblePaths.append(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/welcome/Welcome_en_US.baskets")); // Take the first EXISTING basket archive found: QDir dir; QString path; for (QStringList::Iterator it = possiblePaths.begin(); it != possiblePaths.end(); ++it) { if (dir.exists(*it)) { path = *it; break; } } // Extract: if (!path.isEmpty()) Archive::open(path); } void BNPView::onFirstShow() { // In late init, because we need qApp->mainWidget() to be set! connectTagsMenu(); m_statusbar->setupStatusBar(); int treeWidth = Settings::basketTreeWidth(); if (treeWidth < 0) treeWidth = m_tree->fontMetrics().maxWidth() * 11; QList splitterSizes; splitterSizes.append(treeWidth); setSizes(splitterSizes); } void BNPView::setupGlobalShortcuts() { KActionCollection *ac = new KActionCollection(this); QAction *a = nullptr; // Ctrl+Shift+W only works when started standalone: QWidget *basketMainWindow = qobject_cast(Global::bnpView->parent()); int modifier = Qt::CTRL + Qt::ALT + Qt::SHIFT; if (basketMainWindow) { a = ac->addAction("global_show_hide_main_window", Global::systemTray, SLOT(toggleActive())); a->setText(i18n("Show/hide main window")); a->setStatusTip( i18n("Allows you to show main Window if it is hidden, and to hide " "it if it is shown.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(modifier + Qt::Key_W))); } a = ac->addAction("global_paste", Global::bnpView, SLOT(globalPasteInCurrentBasket())); a->setText(i18n("Paste clipboard contents in current basket")); a->setStatusTip( i18n("Allows you to paste clipboard contents in the current basket " "without having to open the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, QKeySequence(modifier + Qt::Key_V)); a = ac->addAction("global_show_current_basket", Global::bnpView, SLOT(showPassiveContentForced())); a->setText(i18n("Show current basket name")); a->setStatusTip( i18n("Allows you to know basket is current without opening " "the main window.")); a = ac->addAction("global_paste_selection", Global::bnpView, SLOT(pasteSelInCurrentBasket())); a->setText(i18n("Paste selection in current basket")); a->setStatusTip( i18n("Allows you to paste clipboard selection in the current basket " "without having to open the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(Qt::CTRL + Qt::ALT + Qt::SHIFT + Qt::Key_S))); a = ac->addAction("global_new_basket", Global::bnpView, SLOT(askNewBasket())); a->setText(i18n("Create a new basket")); a->setStatusTip( i18n("Allows you to create a new basket without having to open the " "main window (you then can use the other global shortcuts to add " "a note, paste clipboard or paste selection in this new basket).")); a = ac->addAction("global_previous_basket", Global::bnpView, SLOT(goToPreviousBasket())); a->setText(i18n("Go to previous basket")); a->setStatusTip( i18n("Allows you to change current basket to the previous one without " "having to open the main window.")); a = ac->addAction("global_next_basket", Global::bnpView, SLOT(goToNextBasket())); a->setText(i18n("Go to next basket")); a->setStatusTip( i18n("Allows you to change current basket to the next one " "without having to open the main window.")); a = ac->addAction("global_note_add_html", Global::bnpView, SLOT(addNoteHtml())); a->setText(i18n("Insert text note")); a->setStatusTip( i18n("Add a text note to the current basket without having to open " "the main window.")); KGlobalAccel::self()->setGlobalShortcut(a, (QKeySequence(modifier + Qt::Key_T))); a = ac->addAction("global_note_add_image", Global::bnpView, SLOT(addNoteImage())); a->setText(i18n("Insert image note")); a->setStatusTip( i18n("Add an image note to the current basket without having to open " "the main window.")); a = ac->addAction("global_note_add_link", Global::bnpView, SLOT(addNoteLink())); a->setText(i18n("Insert link note")); a->setStatusTip( i18n("Add a link note to the current basket without having " "to open the main window.")); a = ac->addAction("global_note_add_color", Global::bnpView, SLOT(addNoteColor())); a->setText(i18n("Insert color note")); a->setStatusTip( i18n("Add a color note to the current basket without having to open " "the main window.")); a = ac->addAction("global_note_pick_color", Global::bnpView, SLOT(slotColorFromScreenGlobal())); a->setText(i18n("Pick color from screen")); a->setStatusTip( i18n("Add a color note picked from one pixel on screen to the current " "basket without " "having to open the main window.")); a = ac->addAction("global_note_grab_screenshot", Global::bnpView, SLOT(grabScreenshotGlobal())); a->setText(i18n("Grab screen zone")); a->setStatusTip( i18n("Grab a screen zone as an image in the current basket without " "having to open the main window.")); #if 0 a = ac->addAction("global_note_add_text", Global::bnpView, SLOT(addNoteText())); a->setText(i18n("Insert plain text note")); a->setStatusTip( i18n("Add a plain text note to the current basket without having to " "open the main window.")); #endif } void BNPView::initialize() { /// Configure the List View Columns: m_tree = new BasketTreeListView(this); m_tree->setHeaderLabel(i18n("Baskets")); m_tree->setSortingEnabled(false /*Disabled*/); m_tree->setRootIsDecorated(true); m_tree->setLineWidth(1); m_tree->setMidLineWidth(0); m_tree->setFocusPolicy(Qt::NoFocus); /// Configure the List View Drag and Drop: m_tree->setDragEnabled(true); m_tree->setDragDropMode(QAbstractItemView::DragDrop); m_tree->setAcceptDrops(true); m_tree->viewport()->setAcceptDrops(true); /// Configure the Splitter: m_stack = new QStackedWidget(this); setOpaqueResize(true); setCollapsible(indexOf(m_tree), true); setCollapsible(indexOf(m_stack), false); setStretchFactor(indexOf(m_tree), 0); setStretchFactor(indexOf(m_stack), 1); /// Configure the List View Signals: - connect(m_tree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); - connect(m_tree, SIGNAL(itemPressed(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); - connect(m_tree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(slotPressed(QTreeWidgetItem *, int))); + connect(m_tree, &BasketTreeListView::itemActivated, this, &BNPView::slotPressed); + connect(m_tree, &BasketTreeListView::itemPressed, this, &BNPView::slotPressed); + connect(m_tree, &BasketTreeListView::itemClicked, this, &BNPView::slotPressed); - connect(m_tree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(needSave(QTreeWidgetItem *))); - connect(m_tree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(needSave(QTreeWidgetItem *))); - connect(m_tree, SIGNAL(contextMenuRequested(const QPoint &)), this, SLOT(slotContextMenu(const QPoint &))); - connect(m_tree, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotShowProperties(QTreeWidgetItem *))); + connect(m_tree, &BasketTreeListView::itemExpanded, this, &BNPView::needSave); + connect(m_tree, &BasketTreeListView::itemCollapsed, this, &BNPView::needSave); + connect(m_tree, &BasketTreeListView::contextMenuRequested, this, &BNPView::slotContextMenu); + connect(m_tree, &BasketTreeListView::itemDoubleClicked, this, &BNPView::slotShowProperties); - connect(m_tree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SIGNAL(basketChanged())); - connect(m_tree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SIGNAL(basketChanged())); + connect(m_tree, &BasketTreeListView::itemExpanded, this, &BNPView::basketChanged); + connect(m_tree, &BasketTreeListView::itemCollapsed, this, &BNPView::basketChanged); - connect(this, SIGNAL(basketChanged()), this, SLOT(slotBasketChanged())); + connect(this, &BNPView::basketChanged, this, &BNPView::slotBasketChanged); - connect(m_history, SIGNAL(canRedoChanged(bool)), this, SLOT(canUndoRedoChanged())); - connect(m_history, SIGNAL(canUndoChanged(bool)), this, SLOT(canUndoRedoChanged())); + connect(m_history, &QUndoStack::canRedoChanged, this, &BNPView::canUndoRedoChanged); + connect(m_history, &QUndoStack::canUndoChanged, this, &BNPView::canUndoRedoChanged); setupActions(); /// What's This Help for the tree: m_tree->setWhatsThis( i18n("

Basket Tree

" "Here is the list of your baskets. " "You can organize your data by putting them in different baskets. " "You can group baskets by subject by creating new baskets inside others. " "You can browse between them by clicking a basket to open it, or reorganize them using drag and drop.")); setTreePlacement(Settings::treeOnLeft()); } void BNPView::setupActions() { QAction *a = nullptr; KActionCollection *ac = actionCollection(); a = ac->addAction("basket_export_basket_archive", this, SLOT(saveAsArchive())); a->setText(i18n("&Basket Archive...")); a->setIcon(QIcon::fromTheme("baskets")); a->setShortcut(0); m_actSaveAsArchive = a; a = ac->addAction("basket_import_basket_archive", this, SLOT(openArchive())); a->setText(i18n("&Basket Archive...")); a->setIcon(QIcon::fromTheme("baskets")); a->setShortcut(0); m_actOpenArchive = a; a = ac->addAction("window_hide", this, SLOT(hideOnEscape())); a->setText(i18n("&Hide Window")); m_actionCollection->setDefaultShortcut(a, KStandardShortcut::Close); m_actHideWindow = a; m_actHideWindow->setEnabled(Settings::useSystray()); // Init here ! a = ac->addAction("basket_export_html", this, SLOT(exportToHTML())); a->setText(i18n("&HTML Web Page...")); a->setIcon(QIcon::fromTheme("text-html")); a->setShortcut(0); m_actExportToHtml = a; a = ac->addAction("basket_import_text_file", this, &BNPView::importTextFile); a->setText(i18n("Text &File...")); a->setIcon(QIcon::fromTheme("text-plain")); a->setShortcut(0); a = ac->addAction("basket_backup_restore", this, SLOT(backupRestore())); a->setText(i18n("&Backup && Restore...")); a->setShortcut(0); a = ac->addAction("check_cleanup", this, SLOT(checkCleanup())); a->setText(i18n("&Check && Cleanup...")); a->setShortcut(0); if (Global::commandLineOpts->isSet("debug")) { a->setEnabled(true); } else { a->setEnabled(false); } /** Note : ****************************************************************/ a = ac->addAction("edit_delete", this, SLOT(delNote())); a->setText(i18n("D&elete")); a->setIcon(QIcon::fromTheme("edit-delete")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Delete")); m_actDelNote = a; m_actCutNote = ac->addAction(KStandardAction::Cut, this, SLOT(cutNote())); m_actCopyNote = ac->addAction(KStandardAction::Copy, this, SLOT(copyNote())); m_actSelectAll = ac->addAction(KStandardAction::SelectAll, this, SLOT(slotSelectAll())); m_actSelectAll->setStatusTip(i18n("Selects all notes")); a = ac->addAction("edit_unselect_all", this, SLOT(slotUnselectAll())); a->setText(i18n("U&nselect All")); m_actUnselectAll = a; m_actUnselectAll->setStatusTip(i18n("Unselects all selected notes")); a = ac->addAction("edit_invert_selection", this, SLOT(slotInvertSelection())); a->setText(i18n("&Invert Selection")); m_actionCollection->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Asterisk); m_actInvertSelection = a; m_actInvertSelection->setStatusTip(i18n("Inverts the current selection of notes")); a = ac->addAction("note_edit", this, SLOT(editNote())); a->setText(i18nc("Verb; not Menu", "&Edit...")); // a->setIcon(QIcon::fromTheme("edit")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Return")); m_actEditNote = a; m_actOpenNote = ac->addAction(KStandardAction::Open, "note_open", this, SLOT(openNote())); m_actOpenNote->setIcon(QIcon::fromTheme("window-new")); m_actOpenNote->setText(i18n("&Open")); m_actionCollection->setDefaultShortcut(m_actOpenNote, QKeySequence("F9")); a = ac->addAction("note_open_with", this, SLOT(openNoteWith())); a->setText(i18n("Open &With...")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Shift+F9")); m_actOpenNoteWith = a; m_actSaveNoteAs = ac->addAction(KStandardAction::SaveAs, "note_save_to_file", this, SLOT(saveNoteAs())); m_actSaveNoteAs->setText(i18n("&Save to File...")); m_actionCollection->setDefaultShortcut(m_actSaveNoteAs, QKeySequence("F10")); a = ac->addAction("note_group", this, SLOT(noteGroup())); a->setText(i18n("&Group")); a->setIcon(QIcon::fromTheme("mail-attachment")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+G")); m_actGroup = a; a = ac->addAction("note_ungroup", this, SLOT(noteUngroup())); a->setText(i18n("U&ngroup")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+G")); m_actUngroup = a; a = ac->addAction("note_move_top", this, SLOT(moveOnTop())); a->setText(i18n("Move on &Top")); a->setIcon(QIcon::fromTheme("arrow-up-double")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Home")); m_actMoveOnTop = a; a = ac->addAction("note_move_up", this, SLOT(moveNoteUp())); a->setText(i18n("Move &Up")); a->setIcon(QIcon::fromTheme("arrow-up")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Up")); m_actMoveNoteUp = a; a = ac->addAction("note_move_down", this, SLOT(moveNoteDown())); a->setText(i18n("Move &Down")); a->setIcon(QIcon::fromTheme("arrow-down")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+Down")); m_actMoveNoteDown = a; a = ac->addAction("note_move_bottom", this, SLOT(moveOnBottom())); a->setText(i18n("Move on &Bottom")); a->setIcon(QIcon::fromTheme("arrow-down-double")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+End")); m_actMoveOnBottom = a; m_actPaste = ac->addAction(KStandardAction::Paste, this, SLOT(pasteInCurrentBasket())); /** Insert : **************************************************************/ #if 0 a = ac->addAction("insert_text"); a->setText(i18n("Plai&n Text")); a->setIcon(QIcon::fromTheme("text")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+T")); m_actInsertText = a; #endif a = ac->addAction("insert_html"); a->setText(i18n("&Text")); a->setIcon(QIcon::fromTheme("text-html")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Insert")); m_actInsertHtml = a; a = ac->addAction("insert_link"); a->setText(i18n("&Link")); a->setIcon(QIcon::fromTheme(IconNames::LINK)); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Y")); m_actInsertLink = a; a = ac->addAction("insert_cross_reference"); a->setText(i18n("Cross &Reference")); a->setIcon(QIcon::fromTheme(IconNames::CROSS_REF)); m_actInsertCrossReference = a; a = ac->addAction("insert_image"); a->setText(i18n("&Image")); a->setIcon(QIcon::fromTheme(IconNames::IMAGE)); m_actInsertImage = a; a = ac->addAction("insert_color"); a->setText(i18n("&Color")); a->setIcon(QIcon::fromTheme(IconNames::COLOR)); m_actInsertColor = a; a = ac->addAction("insert_launcher"); a->setText(i18n("L&auncher")); a->setIcon(QIcon::fromTheme(IconNames::LAUNCH)); m_actInsertLauncher = a; a = ac->addAction("insert_kmenu"); a->setText(i18n("Import Launcher for &desktop application...")); a->setIcon(QIcon::fromTheme(IconNames::KMENU)); m_actImportKMenu = a; a = ac->addAction("insert_icon"); a->setText(i18n("Im&port Icon...")); a->setIcon(QIcon::fromTheme(IconNames::ICONS)); m_actImportIcon = a; a = ac->addAction("insert_from_file"); a->setText(i18n("Load From &File...")); a->setIcon(QIcon::fromTheme(IconNames::DOCUMENT_IMPORT)); m_actLoadFile = a; // connect( m_actInsertText, QAction::triggered, this, [this] () { insertEmpty(NoteType::Text); }); connect(m_actInsertHtml, &QAction::triggered, this, [this] () { insertEmpty(NoteType::Html); }); connect(m_actInsertImage, &QAction::triggered, this, [this] () { insertEmpty(NoteType::Image); }); connect(m_actInsertLink, &QAction::triggered, this, [this] () { insertEmpty(NoteType::Link); }); connect(m_actInsertCrossReference, &QAction::triggered, this, [this] () { insertEmpty(NoteType::CrossReference); }); connect(m_actInsertColor, &QAction::triggered, this, [this] () { insertEmpty(NoteType::Color); }); connect(m_actInsertLauncher, &QAction::triggered, this, [this] () { insertEmpty(NoteType::Launcher); }); connect(m_actImportKMenu, &QAction::triggered, this, [this] () { insertWizard(1); }); connect(m_actImportIcon, &QAction::triggered, this, [this] () { insertWizard(2); }); connect(m_actLoadFile, &QAction::triggered, this, [this] () { insertWizard(3); }); a = ac->addAction("insert_screen_color", this, &BNPView::slotColorFromScreen); a->setText(i18n("C&olor from Screen")); a->setIcon(QIcon::fromTheme("kcolorchooser")); m_actColorPicker = a; connect(m_colorPicker.get(), &DesktopColorPicker::pickedColor, this, &BNPView::colorPicked); a = ac->addAction("insert_screen_capture", this, SLOT(grabScreenshot())); a->setText(i18n("Grab Screen &Zone")); a->setIcon(QIcon::fromTheme("ksnapshot")); m_actGrabScreenshot = a; - // connect( m_actGrabScreenshot, SIGNAL(regionGrabbed(const QPixmap&)), this, SLOT(screenshotGrabbed(const QPixmap&)) ); - // connect( m_colorPicker, SIGNAL(canceledPick()), this, SLOT(colorPickingCanceled()) ); - + //connect(m_actGrabScreenshot, SIGNAL(regionGrabbed(const QPixmap&)), this, SLOT(screenshotGrabbed(const QPixmap&))); + //connect(m_colorPicker, SIGNAL(canceledPick()), this, SLOT(colorPickingCanceled())); + // m_insertActions.append( m_actInsertText ); m_insertActions.append(m_actInsertHtml); m_insertActions.append(m_actInsertLink); m_insertActions.append(m_actInsertCrossReference); m_insertActions.append(m_actInsertImage); m_insertActions.append(m_actInsertColor); m_insertActions.append(m_actImportKMenu); m_insertActions.append(m_actInsertLauncher); m_insertActions.append(m_actImportIcon); m_insertActions.append(m_actLoadFile); m_insertActions.append(m_actColorPicker); m_insertActions.append(m_actGrabScreenshot); /** Basket : **************************************************************/ // At this stage, main.cpp has not set qApp->mainWidget(), so Global::runInsideKontact() // returns true. We do it ourself: bool runInsideKontact = true; QWidget *parentWidget = (QWidget *)parent(); while (parentWidget) { if (parentWidget->inherits("MainWindow")) runInsideKontact = false; parentWidget = (QWidget *)parentWidget->parent(); } // Use the "basket" icon in Kontact so it is consistent with the Kontact "New..." icon a = ac->addAction("basket_new", this, SLOT(askNewBasket())); a->setText(i18n("&New Basket...")); a->setIcon(QIcon::fromTheme((runInsideKontact ? "basket" : "document-new"))); m_actionCollection->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::New)); actNewBasket = a; a = ac->addAction("basket_new_sub", this, SLOT(askNewSubBasket())); a->setText(i18n("New &Sub-Basket...")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+Shift+N")); actNewSubBasket = a; a = ac->addAction("basket_new_sibling", this, SLOT(askNewSiblingBasket())); a->setText(i18n("New Si&bling Basket...")); actNewSiblingBasket = a; KActionMenu *newBasketMenu = new KActionMenu(i18n("&New"), ac); newBasketMenu->setIcon(QIcon::fromTheme("document-new")); ac->addAction("basket_new_menu", newBasketMenu); newBasketMenu->addAction(actNewBasket); newBasketMenu->addAction(actNewSubBasket); newBasketMenu->addAction(actNewSiblingBasket); connect(newBasketMenu, SIGNAL(triggered()), this, SLOT(askNewBasket())); a = ac->addAction("basket_properties", this, SLOT(propBasket())); a->setText(i18n("&Properties...")); a->setIcon(QIcon::fromTheme("document-properties")); m_actionCollection->setDefaultShortcut(a, QKeySequence("F2")); m_actPropBasket = a; a = ac->addAction("basket_sort_children_asc", this, SLOT(sortChildrenAsc())); a->setText(i18n("Sort Children Ascending")); a->setIcon(QIcon::fromTheme("view-sort-ascending")); m_actSortChildrenAsc = a; a = ac->addAction("basket_sort_children_desc", this, SLOT(sortChildrenDesc())); a->setText(i18n("Sort Children Descending")); a->setIcon(QIcon::fromTheme("view-sort-descending")); m_actSortChildrenDesc = a; a = ac->addAction("basket_sort_siblings_asc", this, SLOT(sortSiblingsAsc())); a->setText(i18n("Sort Siblings Ascending")); a->setIcon(QIcon::fromTheme("view-sort-ascending")); m_actSortSiblingsAsc = a; a = ac->addAction("basket_sort_siblings_desc", this, SLOT(sortSiblingsDesc())); a->setText(i18n("Sort Siblings Descending")); a->setIcon(QIcon::fromTheme("view-sort-descending")); m_actSortSiblingsDesc = a; a = ac->addAction("basket_remove", this, SLOT(delBasket())); a->setText(i18nc("Remove Basket", "&Remove")); a->setShortcut(0); m_actDelBasket = a; #ifdef HAVE_LIBGPGME a = ac->addAction("basket_password", this, SLOT(password())); a->setText(i18nc("Password protection", "Pass&word...")); a->setShortcut(0); m_actPassBasket = a; a = ac->addAction("basket_lock", this, SLOT(lockBasket())); a->setText(i18nc("Lock Basket", "&Lock")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+L")); m_actLockBasket = a; #endif /** Edit : ****************************************************************/ // m_actUndo = KStandardAction::undo( this, SLOT(undo()), actionCollection() ); // m_actUndo->setEnabled(false); // Not yet implemented ! // m_actRedo = KStandardAction::redo( this, SLOT(redo()), actionCollection() ); // m_actRedo->setEnabled(false); // Not yet implemented ! KToggleAction *toggleAct = nullptr; toggleAct = new KToggleAction(i18n("&Filter"), ac); ac->addAction("edit_filter", toggleAct); toggleAct->setIcon(QIcon::fromTheme("view-filter")); m_actionCollection->setDefaultShortcuts(toggleAct, KStandardShortcut::shortcut(KStandardShortcut::Find)); m_actShowFilter = toggleAct; connect(m_actShowFilter, SIGNAL(toggled(bool)), this, SLOT(showHideFilterBar(bool))); toggleAct = new KToggleAction(ac); ac->addAction("edit_filter_all_baskets", toggleAct); toggleAct->setText(i18n("&Search All")); toggleAct->setIcon(QIcon::fromTheme("edit-find")); m_actionCollection->setDefaultShortcut(toggleAct, QKeySequence("Ctrl+Shift+F")); m_actFilterAllBaskets = toggleAct; - connect(m_actFilterAllBaskets, SIGNAL(toggled(bool)), this, SLOT(toggleFilterAllBaskets(bool))); + connect(m_actFilterAllBaskets, &KToggleAction::toggled, this, &BNPView::toggleFilterAllBaskets); a = ac->addAction("edit_filter_reset", this, SLOT(slotResetFilter())); a->setText(i18n("&Reset Filter")); a->setIcon(QIcon::fromTheme("edit-clear-locationbar-rtl")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Ctrl+R")); m_actResetFilter = a; /** Go : ******************************************************************/ a = ac->addAction("go_basket_previous", this, SLOT(goToPreviousBasket())); a->setText(i18n("&Previous Basket")); a->setIcon(QIcon::fromTheme("go-previous")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Left")); m_actPreviousBasket = a; a = ac->addAction("go_basket_next", this, SLOT(goToNextBasket())); a->setText(i18n("&Next Basket")); a->setIcon(QIcon::fromTheme("go-next")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Right")); m_actNextBasket = a; a = ac->addAction("go_basket_fold", this, SLOT(foldBasket())); a->setText(i18n("&Fold Basket")); a->setIcon(QIcon::fromTheme("go-up")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Up")); m_actFoldBasket = a; a = ac->addAction("go_basket_expand", this, SLOT(expandBasket())); a->setText(i18n("&Expand Basket")); a->setIcon(QIcon::fromTheme("go-down")); m_actionCollection->setDefaultShortcut(a, QKeySequence("Alt+Down")); m_actExpandBasket = a; #if 0 // FOR_BETA_PURPOSE: a = ac->addAction("beta_convert_texts", this, SLOT(convertTexts())); a->setText(i18n("Convert text notes to rich text notes")); a->setIcon(QIcon::fromTheme("run-build-file")); m_convertTexts = a; #endif InlineEditors::instance()->initToolBars(actionCollection()); /** Help : ****************************************************************/ a = ac->addAction("help_welcome_baskets", this, SLOT(addWelcomeBaskets())); a->setText(i18n("&Welcome Baskets")); } BasketListViewItem *BNPView::topLevelItem(int i) { return (BasketListViewItem *)m_tree->topLevelItem(i); } void BNPView::slotShowProperties(QTreeWidgetItem *item) { if (item) propBasket(); } void BNPView::slotContextMenu(const QPoint &pos) { QTreeWidgetItem *item; item = m_tree->itemAt(pos); QString menuName; if (item) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); setCurrentBasket(basket); menuName = "basket_popup"; } else { menuName = "tab_bar_popup"; /* * "File -> New" create a new basket with the same parent basket as the current one. * But when invoked when right-clicking the empty area at the bottom of the basket tree, * it is obvious the user want to create a new basket at the bottom of the tree (with no parent). * So we set a temporary variable during the time the popup menu is shown, * so the slot askNewBasket() will do the right thing: */ setNewBasketPopup(); } QMenu *menu = popupMenu(menuName); - connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideNewBasketPopup())); + connect(menu, &QMenu::aboutToHide, this, &BNPView::aboutToHideNewBasketPopup); menu->exec(m_tree->mapToGlobal(pos)); } /* this happens every time we switch the basket (but not if we tell the user we save the stuff */ void BNPView::save() { DEBUG_WIN << "Basket Tree: Saving..."; QString data; QXmlStreamWriter stream(&data); XMLWork::setupXmlStream(stream, "basketTree"); // Save Basket Tree: save(m_tree, nullptr, stream); stream.writeEndElement(); stream.writeEndDocument(); // Write to Disk: BasketScene::safelySaveToFile(Global::basketsFolder() + "baskets.xml", data); GitWrapper::commitBasketView(); } void BNPView::save(QTreeWidget *listView, QTreeWidgetItem *item, QXmlStreamWriter &stream) { if (item == nullptr) { if (listView == nullptr) { // This should not happen: we call either save(listView, 0) or save(0, item) DEBUG_WIN << "BNPView::save error: listView=NULL and item=NULL"; return; } // For each basket: for (int i = 0; i < listView->topLevelItemCount(); i++) { item = listView->topLevelItem(i); save(nullptr, item, stream); } } else { saveSubHierarchy(item, stream, true); } } void BNPView::writeBasketElement(QTreeWidgetItem *item, QXmlStreamWriter &stream) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); // Save Attributes: stream.writeAttribute("folderName", basket->folderName()); if (item->childCount() >= 0) // If it can be expanded/folded: stream.writeAttribute("folded", XMLWork::trueOrFalse(!item->isExpanded())); if (((BasketListViewItem *)item)->isCurrentBasket()) stream.writeAttribute("lastOpened", "true"); basket->saveProperties(stream); } void BNPView::saveSubHierarchy(QTreeWidgetItem *item, QXmlStreamWriter &stream, bool recursive) { stream.writeStartElement("basket"); writeBasketElement(item, stream); // create root if (recursive) { for (int i = 0; i < item->childCount(); i++) { saveSubHierarchy(item->child(i), stream, true); } } stream.writeEndElement(); } void BNPView::load() { QScopedPointer doc(XMLWork::openFile("basketTree", Global::basketsFolder() + "baskets.xml")); // BEGIN Compatibility with 0.6.0 Pre-Alpha versions: if (!doc) doc.reset(XMLWork::openFile("basketsTree", Global::basketsFolder() + "baskets.xml")); // END if (doc != nullptr) { QDomElement docElem = doc->documentElement(); load(nullptr, docElem); } m_loading = false; } void BNPView::load(QTreeWidgetItem *item, const QDomElement &baskets) { QDomNode n = baskets.firstChild(); while (!n.isNull()) { QDomElement element = n.toElement(); if ((!element.isNull()) && element.tagName() == "basket") { QString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { BasketScene *basket = loadBasket(folderName); BasketListViewItem *basketItem = appendBasket(basket, item); basketItem->setExpanded(!XMLWork::trueOrFalse(element.attribute("folded", "false"), false)); basket->loadProperties(XMLWork::getElement(element, "properties")); if (XMLWork::trueOrFalse(element.attribute("lastOpened", element.attribute("lastOpened", "false")), false)) // Compat with 0.6.0-Alphas setCurrentBasket(basket); // Load Sub-baskets: load(basketItem, element); } } n = n.nextSibling(); } } BasketScene *BNPView::loadBasket(const QString &folderName) { if (folderName.isEmpty()) return nullptr; DecoratedBasket *decoBasket = new DecoratedBasket(m_stack, folderName); BasketScene *basket = decoBasket->basket(); m_stack->addWidget(decoBasket); - connect(basket, SIGNAL(countsChanged(BasketScene *)), this, SLOT(countsChanged(BasketScene *))); + connect(basket, &BasketScene::countsChanged, this, &BNPView::countsChanged); // Important: Create listViewItem and connect signal BEFORE loadProperties(), so we get the listViewItem updated without extra work: - connect(basket, SIGNAL(propertiesChanged(BasketScene *)), this, SLOT(updateBasketListViewItem(BasketScene *))); + connect(basket, &BasketScene::propertiesChanged, this, &BNPView::updateBasketListViewItem); - connect(basket->decoration()->filterBar(), SIGNAL(newFilter(const FilterData &)), this, SLOT(newFilterFromFilterBar())); - connect(basket, SIGNAL(crossReference(QString)), this, SLOT(loadCrossReference(QString))); + connect(basket->decoration()->filterBar(), &FilterBar::newFilter, this, &BNPView::newFilterFromFilterBar); + connect(basket, &BasketScene::crossReference, this, &BNPView::loadCrossReference); return basket; } int BNPView::basketCount(QTreeWidgetItem *parent) { int count = 1; if (parent == nullptr) return 0; for (int i = 0; i < parent->childCount(); i++) { count += basketCount(parent->child(i)); } return count; } bool BNPView::canFold() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (!item) return false; return (item->childCount() > 0 && item->isExpanded()); } bool BNPView::canExpand() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (!item) return false; return (item->childCount() > 0 && !item->isExpanded()); } BasketListViewItem *BNPView::appendBasket(BasketScene *basket, QTreeWidgetItem *parentItem) { BasketListViewItem *newBasketItem; if (parentItem) newBasketItem = new BasketListViewItem(parentItem, parentItem->child(parentItem->childCount() - 1), basket); else { newBasketItem = new BasketListViewItem(m_tree, m_tree->topLevelItem(m_tree->topLevelItemCount() - 1), basket); } return newBasketItem; } void BNPView::loadNewBasket(const QString &folderName, const QDomElement &properties, BasketScene *parent) { BasketScene *basket = loadBasket(folderName); appendBasket(basket, (basket ? listViewItemForBasket(parent) : nullptr)); basket->loadProperties(properties); setCurrentBasketInHistory(basket); // save(); } int BNPView::topLevelItemCount() { return m_tree->topLevelItemCount(); } void BNPView::goToPreviousBasket() { if (m_history->canUndo()) m_history->undo(); } void BNPView::goToNextBasket() { if (m_history->canRedo()) m_history->redo(); } void BNPView::foldBasket() { BasketListViewItem *item = listViewItemForBasket(currentBasket()); if (item && item->childCount() <= 0) item->setExpanded(false); // If Alt+Left is hit and there is nothing to close, make sure the focus will go to the parent basket QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, nullptr, nullptr); QApplication::postEvent(m_tree, keyEvent); } void BNPView::expandBasket() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, nullptr, nullptr); QApplication::postEvent(m_tree, keyEvent); } void BNPView::closeAllEditors() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = (BasketListViewItem *)(*it); item->basket()->closeEditor(); ++it; } } bool BNPView::convertTexts() { bool convertedNotes = false; QProgressDialog dialog; dialog.setWindowTitle(i18n("Plain Text Notes Conversion")); dialog.setLabelText(i18n("Converting plain text notes to rich text ones...")); dialog.setModal(true); dialog.setRange(0, basketCount()); dialog.show(); // setMinimumDuration(50/*ms*/); QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = (BasketListViewItem *)(*it); if (item->basket()->convertTexts()) convertedNotes = true; dialog.setValue(dialog.value() + 1); if (dialog.wasCanceled()) break; ++it; } return convertedNotes; } void BNPView::toggleFilterAllBaskets(bool doFilter) { // If the filter isn't already showing, we make sure it does. if (doFilter) m_actShowFilter->setChecked(true); // currentBasket()->decoration()->filterBar()->setFilterAll(doFilter); if (doFilter) currentBasket()->decoration()->filterBar()->setEditFocus(); // Filter every baskets: newFilter(); } /** This function can be called recursively because we call qApp->processEvents(). * If this function is called whereas another "instance" is running, * this new "instance" leave and set up a flag that is read by the first "instance" * to know it should re-begin the work. * PS: Yes, that's a very lame pseudo-threading but that works, and it's programmer-efforts cheap :-) */ void BNPView::newFilter() { static bool alreadyEntered = false; static bool shouldRestart = false; if (alreadyEntered) { shouldRestart = true; return; } alreadyEntered = true; shouldRestart = false; BasketScene *current = currentBasket(); const FilterData &filterData = current->decoration()->filterBar()->filterData(); // Set the filter data for every other baskets, or reset the filter for every other baskets if we just disabled the filterInAllBaskets: QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() != current) { if (isFilteringAllBaskets()) item->basket()->decoration()->filterBar()->setFilterData(filterData); // Set the new FilterData for every other baskets else item->basket()->decoration()->filterBar()->setFilterData(FilterData()); // We just disabled the global filtering: remove the FilterData } ++it; } // Show/hide the "little filter icons" (during basket load) // or the "little numbers" (to show number of found notes in the baskets) is the tree: qApp->processEvents(); // Load every baskets for filtering, if they are not already loaded, and if necessary: if (filterData.isFiltering) { BasketScene *current = currentBasket(); QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() != current) { BasketScene *basket = item->basket(); if (!basket->loadingLaunched() && !basket->isLocked()) basket->load(); basket->filterAgain(); qApp->processEvents(); if (shouldRestart) { alreadyEntered = false; shouldRestart = false; newFilter(); return; } } ++it; } } // qApp->processEvents(); m_tree->viewport()->update(); // to see the "little numbers" alreadyEntered = false; shouldRestart = false; } void BNPView::newFilterFromFilterBar() { if (isFilteringAllBaskets()) QTimer::singleShot(0, this, SLOT(newFilter())); // Keep time for the QLineEdit to display the filtered character and refresh correctly! } bool BNPView::isFilteringAllBaskets() { return m_actFilterAllBaskets->isChecked(); } BasketListViewItem *BNPView::listViewItemForBasket(BasketScene *basket) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket() == basket) return item; ++it; } return nullptr; } BasketScene *BNPView::currentBasket() { DecoratedBasket *decoBasket = (DecoratedBasket *)m_stack->currentWidget(); if (decoBasket) return decoBasket->basket(); else return nullptr; } BasketScene *BNPView::parentBasketOf(BasketScene *basket) { BasketListViewItem *item = (BasketListViewItem *)(listViewItemForBasket(basket)->parent()); if (item) return item->basket(); else return nullptr; } void BNPView::setCurrentBasketInHistory(BasketScene *basket) { if (!basket) return; if (currentBasket() == basket) return; m_history->push(new HistorySetBasket(basket)); } void BNPView::setCurrentBasket(BasketScene *basket) { if (currentBasket() == basket) return; if (currentBasket()) currentBasket()->closeBasket(); if (basket) basket->aboutToBeActivated(); BasketListViewItem *item = listViewItemForBasket(basket); if (item) { m_tree->setCurrentItem(item); item->ensureVisible(); m_stack->setCurrentWidget(basket->decoration()); // If the window has changed size, only the current basket receive the event, // the others will receive ony one just before they are shown. // But this triggers unwanted animations, so we eliminate it: basket->relayoutNotes(); basket->openBasket(); setWindowTitle(item->basket()->basketName()); countsChanged(basket); updateStatusBarHint(); if (Global::systemTray) Global::systemTray->updateDisplay(); m_tree->scrollToItem(m_tree->currentItem()); item->basket()->setFocus(); } m_tree->viewport()->update(); emit basketChanged(); } void BNPView::removeBasket(BasketScene *basket) { if (basket->isDuringEdit()) basket->closeEditor(); // Find a new basket to switch to and select it. // Strategy: get the next sibling, or the previous one if not found. // If there is no such one, get the parent basket: BasketListViewItem *basketItem = listViewItemForBasket(basket); BasketListViewItem *nextBasketItem = (BasketListViewItem *)(m_tree->itemBelow(basketItem)); if (!nextBasketItem) nextBasketItem = (BasketListViewItem *)m_tree->itemAbove(basketItem); if (!nextBasketItem) nextBasketItem = (BasketListViewItem *)(basketItem->parent()); if (nextBasketItem) setCurrentBasketInHistory(nextBasketItem->basket()); // Remove from the view: basket->unsubscribeBackgroundImages(); m_stack->removeWidget(basket->decoration()); // delete basket->decoration(); delete basketItem; // delete basket; // If there is no basket anymore, add a new one: if (!nextBasketItem) { BasketFactory::newBasket(QString(), i18n("General")); } else { // No need to save two times if we add a basket save(); } } void BNPView::setTreePlacement(bool onLeft) { if (onLeft) insertWidget(0, m_tree); else addWidget(m_tree); // updateGeometry(); qApp->postEvent(this, new QResizeEvent(size(), size())); } void BNPView::relayoutAllBaskets() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); // item->basket()->unbufferizeAll(); item->basket()->unsetNotesWidth(); item->basket()->relayoutNotes(); ++it; } } void BNPView::recomputeAllStyles() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->recomputeAllStyles(); item->basket()->unsetNotesWidth(); item->basket()->relayoutNotes(); ++it; } } void BNPView::removedStates(const QList &deletedStates) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->removedStates(deletedStates); ++it; } } void BNPView::linkLookChanged() { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); item->basket()->linkLookChanged(); ++it; } } void BNPView::filterPlacementChanged(bool onTop) { QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = static_cast(*it); DecoratedBasket *decoration = static_cast(item->basket()->parent()); decoration->setFilterBarPosition(onTop); ++it; } } void BNPView::updateBasketListViewItem(BasketScene *basket) { BasketListViewItem *item = listViewItemForBasket(basket); if (item) item->setup(); if (basket == currentBasket()) { setWindowTitle(basket->basketName()); if (Global::systemTray) Global::systemTray->updateDisplay(); } // Don't save if we are loading! if (!m_loading) save(); } void BNPView::needSave(QTreeWidgetItem *) { if (!m_loading) // A basket has been collapsed/expanded or a new one is select: this is not urgent: QTimer::singleShot(500 /*ms*/, this, SLOT(save())); } void BNPView::slotPressed(QTreeWidgetItem *item, int column) { Q_UNUSED(column); BasketScene *basket = currentBasket(); if (basket == nullptr) return; // Impossible to Select no Basket: if (!item) m_tree->setCurrentItem(listViewItemForBasket(basket), true); else if (dynamic_cast(item) != nullptr && currentBasket() != ((BasketListViewItem *)item)->basket()) { setCurrentBasketInHistory(((BasketListViewItem *)item)->basket()); needSave(nullptr); } basket->graphicsView()->viewport()->setFocus(); } DecoratedBasket *BNPView::currentDecoratedBasket() { if (currentBasket()) return currentBasket()->decoration(); else return nullptr; } // Redirected actions : void BNPView::exportToHTML() { HTMLExporter exporter(currentBasket()); } void BNPView::editNote() { currentBasket()->noteEdit(); } void BNPView::cutNote() { currentBasket()->noteCut(); } void BNPView::copyNote() { currentBasket()->noteCopy(); } void BNPView::delNote() { currentBasket()->noteDelete(); } void BNPView::openNote() { currentBasket()->noteOpen(); } void BNPView::openNoteWith() { currentBasket()->noteOpenWith(); } void BNPView::saveNoteAs() { currentBasket()->noteSaveAs(); } void BNPView::noteGroup() { currentBasket()->noteGroup(); } void BNPView::noteUngroup() { currentBasket()->noteUngroup(); } void BNPView::moveOnTop() { currentBasket()->noteMoveOnTop(); } void BNPView::moveOnBottom() { currentBasket()->noteMoveOnBottom(); } void BNPView::moveNoteUp() { currentBasket()->noteMoveNoteUp(); } void BNPView::moveNoteDown() { currentBasket()->noteMoveNoteDown(); } void BNPView::slotSelectAll() { currentBasket()->selectAll(); } void BNPView::slotUnselectAll() { currentBasket()->unselectAll(); } void BNPView::slotInvertSelection() { currentBasket()->invertSelection(); } void BNPView::slotResetFilter() { currentDecoratedBasket()->resetFilter(); } void BNPView::importTextFile() { SoftwareImporters::importTextFile(); } void BNPView::backupRestore() { BackupDialog dialog; dialog.exec(); } void checkNote(Note *note, QList &fileList) { while (note) { note->finishLazyLoad(); if (note->isGroup()) { checkNote(note->firstChild(), fileList); } else if (note->content()->useFile()) { QString noteFileName = note->basket()->folderName() + note->content()->fileName(); int basketFileIndex = fileList.indexOf(noteFileName); if (basketFileIndex < 0) { DEBUG_WIN << "" + noteFileName + " NOT FOUND!"; } else { fileList.removeAt(basketFileIndex); } } note = note->next(); } } void checkBasket(BasketListViewItem *item, QList &dirList, QList &fileList) { BasketScene *basket = ((BasketListViewItem *)item)->basket(); QString basketFolderName = basket->folderName(); int basketFolderIndex = dirList.indexOf(basket->folderName()); if (basketFolderIndex < 0) { DEBUG_WIN << "" + basketFolderName + " NOT FOUND!"; } else { dirList.removeAt(basketFolderIndex); } int basketFileIndex = fileList.indexOf(basket->folderName() + ".basket"); if (basketFileIndex < 0) { DEBUG_WIN << ".basket file of " + basketFolderName + ".basket NOT FOUND!"; } else { fileList.removeAt(basketFileIndex); } if (!basket->loadingLaunched() && !basket->isLocked()) { basket->load(); } DEBUG_WIN << "\t********************************************************************************"; DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") loaded."; Note *note = basket->firstNote(); if (!note) { DEBUG_WIN << "\tHas NO notes!"; } else { checkNote(note, fileList); } basket->save(); qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 100); for (int i = 0; i < item->childCount(); i++) { checkBasket((BasketListViewItem *)item->child(i), dirList, fileList); } if (basket != Global::bnpView->currentBasket()) { DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") unloading..."; DEBUG_WIN << "\t********************************************************************************"; basket->unbufferizeAll(); } else { DEBUG_WIN << basket->basketName() << "(" << basketFolderName << ") is the current basket, not unloading."; DEBUG_WIN << "\t********************************************************************************"; } qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 100); } void BNPView::checkCleanup() { DEBUG_WIN << "Starting the check, cleanup and reindexing... (" + Global::basketsFolder() + ')'; QList dirList; QList fileList; QString topDirEntry; QString subDirEntry; QFileInfo fileInfo; QDir topDir(Global::basketsFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); foreach (topDirEntry, topDir.entryList()) { if (topDirEntry != QLatin1String(".") && topDirEntry != QLatin1String("..")) { fileInfo.setFile(Global::basketsFolder() + '/' + topDirEntry); if (fileInfo.isDir()) { dirList << topDirEntry + '/'; QDir basketDir(Global::basketsFolder() + '/' + topDirEntry, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); foreach (subDirEntry, basketDir.entryList()) { if (subDirEntry != "." && subDirEntry != "..") { fileList << topDirEntry + '/' + subDirEntry; } } } else if (topDirEntry != "." && topDirEntry != ".." && topDirEntry != "baskets.xml") { fileList << topDirEntry; } } } DEBUG_WIN << "Directories found: " + QString::number(dirList.count()); DEBUG_WIN << "Files found: " + QString::number(fileList.count()); DEBUG_WIN << "Checking Baskets:"; for (int i = 0; i < topLevelItemCount(); i++) { checkBasket(topLevelItem(i), dirList, fileList); } DEBUG_WIN << "Baskets checked."; DEBUG_WIN << "Directories remaining (not in any basket): " + QString::number(dirList.count()); DEBUG_WIN << "Files remaining (not in any basket): " + QString::number(fileList.count()); foreach (topDirEntry, dirList) { DEBUG_WIN << "" + topDirEntry + " does not belong to any basket!"; // Tools::deleteRecursively(Global::basketsFolder() + '/' + topDirEntry); // DEBUG_WIN << "\t" + topDirEntry + " removed!"; Tools::trashRecursively(Global::basketsFolder() + "/" + topDirEntry); DEBUG_WIN << "\t" + topDirEntry + " trashed!"; foreach (subDirEntry, fileList) { fileInfo.setFile(Global::basketsFolder() + '/' + subDirEntry); if (!fileInfo.isFile()) { fileList.removeAll(subDirEntry); DEBUG_WIN << "\t\t" + subDirEntry + " already removed!"; } } } foreach (subDirEntry, fileList) { DEBUG_WIN << "" + subDirEntry + " does not belong to any note!"; // Tools::deleteRecursively(Global::basketsFolder() + '/' + subDirEntry); // DEBUG_WIN << "\t" + subDirEntry + " removed!"; Tools::trashRecursively(Global::basketsFolder() + '/' + subDirEntry); DEBUG_WIN << "\t" + subDirEntry + " trashed!"; } DEBUG_WIN << "Check, cleanup and reindexing completed"; } void BNPView::countsChanged(BasketScene *basket) { if (basket == currentBasket()) notesStateChanged(); } void BNPView::notesStateChanged() { BasketScene *basket = currentBasket(); // Update statusbar message : if (currentBasket()->isLocked()) setSelectionStatus(i18n("Locked")); else if (!basket->isLoaded()) setSelectionStatus(i18n("Loading...")); else if (basket->count() == 0) setSelectionStatus(i18n("No notes")); else { QString count = i18np("%1 note", "%1 notes", basket->count()); QString selecteds = i18np("%1 selected", "%1 selected", basket->countSelecteds()); QString showns = (currentDecoratedBasket()->filterData().isFiltering ? i18n("all matches") : i18n("no filter")); if (basket->countFounds() != basket->count()) showns = i18np("%1 match", "%1 matches", basket->countFounds()); setSelectionStatus(i18nc("e.g. '18 notes, 10 matches, 5 selected'", "%1, %2, %3", count, showns, selecteds)); } if (currentBasket()->redirectEditActions()) { m_actSelectAll->setEnabled(!currentBasket()->selectedAllTextInEditor()); m_actUnselectAll->setEnabled(currentBasket()->hasSelectedTextInEditor()); } else { m_actSelectAll->setEnabled(basket->countSelecteds() < basket->countFounds()); m_actUnselectAll->setEnabled(basket->countSelecteds() > 0); } m_actInvertSelection->setEnabled(basket->countFounds() > 0); updateNotesActions(); } void BNPView::updateNotesActions() { bool isLocked = currentBasket()->isLocked(); bool oneSelected = currentBasket()->countSelecteds() == 1; bool oneOrSeveralSelected = currentBasket()->countSelecteds() >= 1; bool severalSelected = currentBasket()->countSelecteds() >= 2; // FIXME: m_actCheckNotes is also modified in void BNPView::areSelectedNotesCheckedChanged(bool checked) // bool BasketScene::areSelectedNotesChecked() should return false if bool BasketScene::showCheckBoxes() is false // m_actCheckNotes->setChecked( oneOrSeveralSelected && // currentBasket()->areSelectedNotesChecked() && // currentBasket()->showCheckBoxes() ); Note *selectedGroup = (severalSelected ? currentBasket()->selectedGroup() : nullptr); m_actEditNote->setEnabled(!isLocked && oneSelected && !currentBasket()->isDuringEdit()); if (currentBasket()->redirectEditActions()) { m_actCutNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); m_actCopyNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); m_actPaste->setEnabled(true); m_actDelNote->setEnabled(currentBasket()->hasSelectedTextInEditor()); } else { m_actCutNote->setEnabled(!isLocked && oneOrSeveralSelected); m_actCopyNote->setEnabled(oneOrSeveralSelected); m_actPaste->setEnabled(!isLocked); m_actDelNote->setEnabled(!isLocked && oneOrSeveralSelected); } m_actOpenNote->setEnabled(oneOrSeveralSelected); m_actOpenNoteWith->setEnabled(oneSelected); // TODO: oneOrSeveralSelected IF SAME TYPE m_actSaveNoteAs->setEnabled(oneSelected); // IDEM? m_actGroup->setEnabled(!isLocked && severalSelected && (!selectedGroup || selectedGroup->isColumn())); m_actUngroup->setEnabled(!isLocked && selectedGroup && !selectedGroup->isColumn()); m_actMoveOnTop->setEnabled(!isLocked && oneOrSeveralSelected && !currentBasket()->isFreeLayout()); m_actMoveNoteUp->setEnabled(!isLocked && oneOrSeveralSelected); // TODO: Disable when unavailable! m_actMoveNoteDown->setEnabled(!isLocked && oneOrSeveralSelected); m_actMoveOnBottom->setEnabled(!isLocked && oneOrSeveralSelected && !currentBasket()->isFreeLayout()); for (QList::const_iterator action = m_insertActions.constBegin(); action != m_insertActions.constEnd(); ++action) (*action)->setEnabled(!isLocked); // From the old Note::contextMenuEvent(...) : /* if (useFile() || m_type == Link) { m_type == Link ? i18n("&Open target") : i18n("&Open") m_type == Link ? i18n("Open target &with...") : i18n("Open &with...") m_type == Link ? i18n("&Save target as...") : i18n("&Save a copy as...") // If useFile() there is always a file to open / open with / save, but : if (m_type == Link) { if (url().toDisplayString().isEmpty() && runCommand().isEmpty()) // no URL nor runCommand : popupMenu->setItemEnabled(7, false); // no possible Open ! if (url().toDisplayString().isEmpty()) // no URL : popupMenu->setItemEnabled(8, false); // no possible Open with ! if (url().toDisplayString().isEmpty() || url().path().endsWith("/")) // no URL or target a folder : popupMenu->setItemEnabled(9, false); // not possible to save target file } } else if (m_type != Color) { popupMenu->insertSeparator(); popupMenu->insertItem( QIcon::fromTheme("document-save-as"), i18n("&Save a copy as..."), this, SLOT(slotSaveAs()), 0, 10 ); }*/ } // BEGIN Color picker (code from KColorEdit): /* Activate the mode */ void BNPView::slotColorFromScreen(bool global) { m_colorPickWasGlobal = global; currentBasket()->saveInsertionData(); m_colorPicker->pickColor(); } void BNPView::slotColorFromScreenGlobal() { slotColorFromScreen(true); } void BNPView::colorPicked(const QColor &color) { if (!currentBasket()->isLoaded()) { showPassiveLoading(currentBasket()); currentBasket()->load(); } currentBasket()->insertColor(color); if (Settings::usePassivePopup()) { showPassiveDropped(i18n("Picked color to basket %1")); } } void BNPView::slotConvertTexts() { /* int result = KMessageBox::questionYesNoCancel( this, i18n( "

This will convert every text notes into rich text notes.
" "The content of the notes will not change and you will be able to apply formatting to those notes.

" "

This process cannot be reverted back: you will not be able to convert the rich text notes to plain text ones later.

" "

As a beta-tester, you are strongly encouraged to do the convert process because it is to test if plain text notes are still needed.
" "If nobody complain about not having plain text notes anymore, then the final version is likely to not support plain text notes anymore.

" "

Which basket notes do you want to convert?

" ), i18n("Convert Text Notes"), KGuiItem(i18n("Only in the Current Basket")), KGuiItem(i18n("In Every Baskets")) ); if (result == KMessageBox::Cancel) return; */ bool conversionsDone; // if (result == KMessageBox::Yes) // conversionsDone = currentBasket()->convertTexts(); // else conversionsDone = convertTexts(); if (conversionsDone) KMessageBox::information(this, i18n("The plain text notes have been converted to rich text."), i18n("Conversion Finished")); else KMessageBox::information(this, i18n("There are no plain text notes to convert."), i18n("Conversion Finished")); } QMenu *BNPView::popupMenu(const QString &menuName) { QMenu *menu = nullptr; if (m_guiClient) { qDebug() << "m_guiClient"; KXMLGUIFactory *factory = m_guiClient->factory(); if (factory) { menu = (QMenu *)factory->container(menuName, m_guiClient); } } if (menu == nullptr) { QString basketDataPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket/"; KMessageBox::error(this, i18n("

The file basketui.rc seems to not exist or is too old.
" "%1 cannot run without it and will stop.

" "

Please check your installation of %2.

" "

If you do not have administrator access to install the application " "system wide, you can copy the file basketui.rc from the installation " "archive to the folder %4.

" "

As last resort, if you are sure the application is correctly installed " "but you had a preview version of it, try to remove the " "file %5basketui.rc

", QGuiApplication::applicationDisplayName(), QGuiApplication::applicationDisplayName(), basketDataPath, basketDataPath, basketDataPath), i18n("Resource not Found"), KMessageBox::AllowLink); exit(1); // We SHOULD exit right now and aboard everything because the caller except menu != 0 to not crash. } return menu; } void BNPView::showHideFilterBar(bool show, bool switchFocus) { // if (show != m_actShowFilter->isChecked()) // m_actShowFilter->setChecked(show); m_actShowFilter->setChecked(show); currentDecoratedBasket()->setFilterBarVisible(show, switchFocus); if (!show) currentDecoratedBasket()->resetFilter(); } void BNPView::insertEmpty(int type) { if (currentBasket()->isLocked()) { showPassiveImpossible(i18n("Cannot add note.")); return; } currentBasket()->insertEmptyNote(type); } void BNPView::insertWizard(int type) { if (currentBasket()->isLocked()) { showPassiveImpossible(i18n("Cannot add note.")); return; } currentBasket()->insertWizard(type); } // BEGIN Screen Grabbing: void BNPView::grabScreenshot(bool global) { if (m_regionGrabber) { KWindowSystem::activateWindow(m_regionGrabber->winId()); return; } // Delay before to take a screenshot because if we hide the main window OR the systray popup menu, // we should wait the windows below to be repainted!!! // A special case is where the action is triggered with the global keyboard shortcut. // In this case, global is true, and we don't wait. // In the future, if global is also defined for other cases, check for // enum QAction::ActivationReason { UnknownActivation, EmulatedActivation, AccelActivation, PopupMenuActivation, ToolBarActivation }; int delay = (isMainWindowActive() ? 500 : (global /*qApp->activePopupWidget()*/ ? 0 : 200)); m_colorPickWasGlobal = global; hideMainWindow(); currentBasket()->saveInsertionData(); usleep(delay * 1000); m_regionGrabber = new RegionGrabber; - connect(m_regionGrabber, SIGNAL(regionGrabbed(const QPixmap &)), this, SLOT(screenshotGrabbed(const QPixmap &))); + connect(m_regionGrabber, &RegionGrabber::regionGrabbed, this, &BNPView::screenshotGrabbed); } void BNPView::hideMainWindow() { if (isMainWindowActive()) { if (Global::activeMainWindow()) { m_HiddenMainWindow = Global::activeMainWindow(); m_HiddenMainWindow->hide(); } m_colorPickWasShown = true; } else m_colorPickWasShown = false; } void BNPView::grabScreenshotGlobal() { grabScreenshot(true); } void BNPView::screenshotGrabbed(const QPixmap &pixmap) { delete m_regionGrabber; m_regionGrabber = nullptr; // Cancelled (pressed Escape): if (pixmap.isNull()) { if (m_colorPickWasShown) showMainWindow(); return; } if (!currentBasket()->isLoaded()) { showPassiveLoading(currentBasket()); currentBasket()->load(); } currentBasket()->insertImage(pixmap); if (m_colorPickWasShown) showMainWindow(); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Grabbed screen zone to basket %1")); } BasketScene *BNPView::basketForFolderName(const QString &folderName) { /* QPtrList basketsList = listBaskets(); BasketScene *basket; for (basket = basketsList.first(); basket; basket = basketsList.next()) if (basket->folderName() == folderName) return basket; */ QString name = folderName; if (!name.endsWith('/')) name += '/'; QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); if (item->basket()->folderName() == name) return item->basket(); ++it; } return nullptr; } Note *BNPView::noteForFileName(const QString &fileName, BasketScene &basket, Note *note) { if (!note) note = basket.firstNote(); if (note->fullPath().endsWith(fileName)) return note; Note *child = note->firstChild(); Note *found; while (child) { found = noteForFileName(fileName, basket, child); if (found) return found; child = child->next(); } return nullptr; } void BNPView::setFiltering(bool filtering) { m_actShowFilter->setChecked(filtering); m_actResetFilter->setEnabled(filtering); if (!filtering) m_actFilterAllBaskets->setEnabled(false); } void BNPView::undo() { // TODO } void BNPView::redo() { // TODO } void BNPView::pasteToBasket(int /*index*/, QClipboard::Mode /*mode*/) { // TODO: REMOVE! // basketAt(index)->pasteNote(mode); } void BNPView::propBasket() { BasketPropertiesDialog dialog(currentBasket(), this); dialog.exec(); } void BNPView::delBasket() { // DecoratedBasket *decoBasket = currentDecoratedBasket(); BasketScene *basket = currentBasket(); int really = KMessageBox::questionYesNo(this, i18n("Do you really want to remove the basket %1 and its contents?", Tools::textToHTMLWithoutP(basket->basketName())), i18n("Remove Basket"), KGuiItem(i18n("&Remove Basket"), "edit-delete"), KStandardGuiItem::cancel()); if (really == KMessageBox::No) return; QStringList basketsList = listViewItemForBasket(basket)->childNamesTree(0); if (basketsList.count() > 0) { int deleteChilds = KMessageBox::questionYesNoList(this, i18n("%1 has the following children baskets.
Do you want to remove them too?
", Tools::textToHTMLWithoutP(basket->basketName())), basketsList, i18n("Remove Children Baskets"), KGuiItem(i18n("&Remove Children Baskets"), "edit-delete")); if (deleteChilds == KMessageBox::No) return; } QString basketFolderName = basket->folderName(); doBasketDeletion(basket); GitWrapper::commitDeleteBasket(basketFolderName); } void BNPView::doBasketDeletion(BasketScene *basket) { basket->closeEditor(); QTreeWidgetItem *basketItem = listViewItemForBasket(basket); for (int i = 0; i < basketItem->childCount(); i++) { // First delete the child baskets: doBasketDeletion(((BasketListViewItem *)basketItem->child(i))->basket()); } // Then, basket have no child anymore, delete it: DecoratedBasket *decoBasket = basket->decoration(); basket->deleteFiles(); removeBasket(basket); // Remove the action to avoid keyboard-shortcut clashes: delete basket->m_action; // FIXME: It's quick&dirty. In the future, the Basket should be deleted, and then the QAction deleted in the Basket destructor. delete decoBasket; // delete basket; } void BNPView::password() { #ifdef HAVE_LIBGPGME QPointer dlg = new PasswordDlg(qApp->activeWindow()); BasketScene *cur = currentBasket(); dlg->setType(cur->encryptionType()); dlg->setKey(cur->encryptionKey()); if (dlg->exec()) { cur->setProtection(dlg->type(), dlg->key()); if (cur->encryptionType() != BasketScene::NoEncryption) { // Clear metadata Tools::deleteMetadataRecursively(cur->fullPath()); cur->lock(); } } #endif } void BNPView::lockBasket() { #ifdef HAVE_LIBGPGME BasketScene *cur = currentBasket(); cur->lock(); #endif } void BNPView::saveAsArchive() { BasketScene *basket = currentBasket(); QDir dir; KConfigGroup config = KSharedConfig::openConfig()->group("Basket Archive"); QString folder = config.readEntry("lastFolder", QDir::homePath()) + "/"; QString url = folder + QString(basket->basketName()).replace('/', '_') + ".baskets"; QString filter = "*.baskets|" + i18n("Basket Archives") + "\n*|" + i18n("All Files"); QString destination = url; for (bool askAgain = true; askAgain;) { destination = QFileDialog::getSaveFileName(nullptr, i18n("Save as Basket Archive"), destination, filter); if (destination.isEmpty()) // User canceled return; if (dir.exists(destination)) { int result = KMessageBox::questionYesNoCancel( this, "" + i18n("The file %1 already exists. Do you really want to override it?", QUrl::fromLocalFile(destination).fileName()), i18n("Override File?"), KGuiItem(i18n("&Override"), "document-save")); if (result == KMessageBox::Cancel) return; else if (result == KMessageBox::Yes) askAgain = false; } else askAgain = false; } bool withSubBaskets = true; // KMessageBox::questionYesNo(this, i18n("Do you want to export sub-baskets too?"), i18n("Save as Basket Archive")) == KMessageBox::Yes; config.writeEntry("lastFolder", QUrl::fromLocalFile(destination).adjusted(QUrl::RemoveFilename).path()); config.sync(); Archive::save(basket, withSubBaskets, destination); } QString BNPView::s_fileToOpen; void BNPView::delayedOpenArchive() { Archive::open(s_fileToOpen); } QString BNPView::s_basketToOpen; void BNPView::delayedOpenBasket() { BasketScene *bv = this->basketForFolderName(s_basketToOpen); this->setCurrentBasketInHistory(bv); } void BNPView::openArchive() { QString filter = QStringLiteral("*.baskets|") + i18n("Basket Archives") + QStringLiteral("\n*|") + i18n("All Files"); QString path = QFileDialog::getOpenFileName(this, i18n("Open Basket Archive"), QString(), filter); if (!path.isEmpty()) { // User has not canceled Archive::open(path); } } void BNPView::activatedTagShortcut() { Tag *tag = Tag::tagForKAction((QAction *)sender()); currentBasket()->activatedTagShortcut(tag); } void BNPView::slotBasketChanged() { m_actFoldBasket->setEnabled(canFold()); m_actExpandBasket->setEnabled(canExpand()); if (currentBasket()->decoration()->filterData().isFiltering) currentBasket()->decoration()->filterBar()->show(); // especially important for Filter all setFiltering(currentBasket() && currentBasket()->decoration()->filterData().isFiltering); this->canUndoRedoChanged(); } void BNPView::canUndoRedoChanged() { if (m_history) { m_actPreviousBasket->setEnabled(m_history->canUndo()); m_actNextBasket->setEnabled(m_history->canRedo()); } } void BNPView::currentBasketChanged() { } void BNPView::isLockedChanged() { bool isLocked = currentBasket()->isLocked(); setLockStatus(isLocked); // m_actLockBasket->setChecked(isLocked); m_actPropBasket->setEnabled(!isLocked); m_actDelBasket->setEnabled(!isLocked); updateNotesActions(); } void BNPView::askNewBasket() { askNewBasket(nullptr, nullptr); GitWrapper::commitCreateBasket(); } void BNPView::askNewBasket(BasketScene *parent, BasketScene *pickProperties) { NewBasketDefaultProperties properties; if (pickProperties) { properties.icon = pickProperties->icon(); properties.backgroundImage = pickProperties->backgroundImageName(); properties.backgroundColor = pickProperties->backgroundColorSetting(); properties.textColor = pickProperties->textColorSetting(); properties.freeLayout = pickProperties->isFreeLayout(); properties.columnCount = pickProperties->columnsCount(); } NewBasketDialog(parent, properties, this).exec(); } void BNPView::askNewSubBasket() { askNewBasket(/*parent=*/currentBasket(), /*pickPropertiesOf=*/currentBasket()); } void BNPView::askNewSiblingBasket() { askNewBasket(/*parent=*/parentBasketOf(currentBasket()), /*pickPropertiesOf=*/currentBasket()); } void BNPView::globalPasteInCurrentBasket() { currentBasket()->setInsertPopupMenu(); pasteInCurrentBasket(); currentBasket()->cancelInsertPopupMenu(); } void BNPView::pasteInCurrentBasket() { currentBasket()->pasteNote(); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Clipboard content pasted to basket %1")); } void BNPView::pasteSelInCurrentBasket() { currentBasket()->pasteNote(QClipboard::Selection); if (Settings::usePassivePopup()) showPassiveDropped(i18n("Selection pasted to basket %1")); } void BNPView::showPassiveDropped(const QString &title) { if (!currentBasket()->isLocked()) { // TODO: Keep basket, so that we show the message only if something was added to a NOT visible basket m_passiveDroppedTitle = title; m_passiveDroppedSelection = currentBasket()->selectedNotes(); QTimer::singleShot(c_delayTooltipTime, this, SLOT(showPassiveDroppedDelayed())); // DELAY IT BELOW: } else showPassiveImpossible(i18n("No note was added.")); } void BNPView::showPassiveDroppedDelayed() { if (isMainWindowActive() || m_passiveDroppedSelection == nullptr) return; QString title = m_passiveDroppedTitle; QImage contentsImage = NoteDrag::feedbackPixmap(m_passiveDroppedSelection).toImage(); QResource::registerResource(contentsImage.bits(), QStringLiteral(":/images/passivepopup_image")); if (Settings::useSystray()) { /*Uncomment after switching to QSystemTrayIcon or port to KStatusNotifierItem See also other occurrences of Global::systemTray below*/ /*KPassivePopup::message(KPassivePopup::Boxed, title.arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), (contentsImage.isNull() ? QString() : QStringLiteral("")), KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, title.arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), (contentsImage.isNull() ? QString() : QStringLiteral("")), KIconLoader::global()->loadIcon(currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, true), (QWidget *)this); } } void BNPView::showPassiveImpossible(const QString &message) { if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, QString("%1") .arg(i18n("Basket %1 is locked")) .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { /*KPassivePopup::message(KPassivePopup::Boxed, QString("%1") .arg(i18n("Basket %1 is locked")) .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), (QWidget*)this);*/ } } void BNPView::showPassiveContentForced() { showPassiveContent(/*forceShow=*/true); } void BNPView::showPassiveContent(bool forceShow /* = false*/) { if (!forceShow && isMainWindowActive()) return; // FIXME: Duplicate code (2 times) QString message; if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, "" + Tools::makeStandardCaption( currentBasket()->isLocked() ? QString("%1 %2") .arg(Tools::textToHTMLWithoutP(currentBasket()->basketName()), i18n("(Locked)")) : Tools::textToHTMLWithoutP(currentBasket()->basketName()) ), message, KIconLoader::global()->loadIcon( currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, "" + Tools::makeStandardCaption(currentBasket()->isLocked() ? QString("%1 %2").arg(Tools::textToHTMLWithoutP(currentBasket()->basketName()), i18n("(Locked)")) : Tools::textToHTMLWithoutP(currentBasket()->basketName())), message, KIconLoader::global()->loadIcon(currentBasket()->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, true), (QWidget *)this); } } void BNPView::showPassiveLoading(BasketScene *basket) { if (isMainWindowActive()) return; if (Settings::useSystray()) { /*KPassivePopup::message(KPassivePopup::Boxed, Tools::textToHTMLWithoutP(basket->basketName()), i18n("Loading..."), KIconLoader::global()->loadIcon( basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), 0L, true ), Global::systemTray);*/ } else { KPassivePopup::message(KPassivePopup::Boxed, Tools::textToHTMLWithoutP(basket->basketName()), i18n("Loading..."), KIconLoader::global()->loadIcon(basket->icon(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, true), (QWidget *)this); } } void BNPView::addNoteText() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Text); } void BNPView::addNoteHtml() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Html); } void BNPView::addNoteImage() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Image); } void BNPView::addNoteLink() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Link); } void BNPView::addNoteCrossReference() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::CrossReference); } void BNPView::addNoteColor() { showMainWindow(); currentBasket()->insertEmptyNote(NoteType::Color); } void BNPView::aboutToHideNewBasketPopup() { QTimer::singleShot(0, this, SLOT(cancelNewBasketPopup())); } void BNPView::cancelNewBasketPopup() { m_newBasketPopup = false; } void BNPView::setNewBasketPopup() { m_newBasketPopup = true; } void BNPView::setWindowTitle(QString s) { emit setWindowCaption(s); } void BNPView::updateStatusBarHint() { m_statusbar->updateStatusBarHint(); } void BNPView::setSelectionStatus(QString s) { m_statusbar->setSelectionStatus(s); } void BNPView::setLockStatus(bool isLocked) { m_statusbar->setLockStatus(isLocked); } void BNPView::postStatusbarMessage(const QString &msg) { m_statusbar->postStatusbarMessage(msg); } void BNPView::setStatusBarHint(const QString &hint) { m_statusbar->setStatusBarHint(hint); } void BNPView::setUnsavedStatus(bool isUnsaved) { m_statusbar->setUnsavedStatus(isUnsaved); } void BNPView::setActive(bool active) { KMainWindow *win = Global::activeMainWindow(); if (!win) return; if (active == isMainWindowActive()) return; // qApp->updateUserTimestamp(); // If "activate on mouse hovering systray", or "on drag through systray" Global::systemTray->activate(); } void BNPView::hideOnEscape() { if (Settings::useSystray()) setActive(false); } bool BNPView::isMainWindowActive() { KMainWindow *main = Global::activeMainWindow(); if (main && main->isActiveWindow()) return true; return false; } void BNPView::newBasket() { askNewBasket(); } bool BNPView::createNoteHtml(const QString content, const QString basket) { BasketScene *b = basketForFolderName(basket); if (!b) return false; Note *note = NoteFactory::createNoteHtml(content, b); if (!note) return false; b->insertCreatedNote(note); return true; } bool BNPView::changeNoteHtml(const QString content, const QString basket, const QString noteName) { BasketScene *b = basketForFolderName(basket); if (!b) return false; Note *note = noteForFileName(noteName, *b); if (!note || note->content()->type() != NoteType::Html) return false; HtmlContent *noteContent = (HtmlContent *)note->content(); noteContent->setHtml(content); note->saveAgain(); return true; } bool BNPView::createNoteFromFile(const QString url, const QString basket) { BasketScene *b = basketForFolderName(basket); if (!b) return false; QUrl kurl(url); if (url.isEmpty()) return false; Note *n = NoteFactory::copyFileAndLoad(kurl, b); if (!n) return false; b->insertCreatedNote(n); return true; } QStringList BNPView::listBaskets() { QStringList basketList; QTreeWidgetItemIterator it(m_tree); while (*it) { BasketListViewItem *item = ((BasketListViewItem *)*it); basketList.append(item->basket()->basketName()); basketList.append(item->basket()->folderName()); ++it; } return basketList; } void BNPView::handleCommandLine() { QCommandLineParser *parser = Global::commandLineOpts; /* Custom data folder */ QString customDataFolder = parser->value("data-folder"); if (!customDataFolder.isNull() && !customDataFolder.isEmpty()) { Global::setCustomSavesFolder(customDataFolder); } /* Debug window */ if (parser->isSet("debug")) { new DebugWindow(); Global::debugWindow->show(); } /* Crash Handler to Mail Developers when Crashing: */ #ifndef BASKET_USE_DRKONQI if (!parser->isSet("use-drkonqi")) KCrash::setCrashHandler(Crash::crashHandler); #endif } void BNPView::reloadBasket(const QString &folderName) { basketForFolderName(folderName)->reload(); } /** Scenario of "Hide main window to system tray icon when mouse move out of the window" : * - At enterEvent() we stop m_tryHideTimer * - After that and before next, we are SURE cursor is hovering window * - At leaveEvent() we restart m_tryHideTimer * - Every 'x' ms, timeoutTryHide() seek if cursor hover a widget of the application or not * - If yes, we musn't hide the window * - But if not, we start m_hideTimer to hide main window after a configured elapsed time * - timeoutTryHide() continue to be called and if cursor move again to one widget of the app, m_hideTimer is stopped * - If after the configured time cursor hasn't go back to a widget of the application, timeoutHide() is called * - It then hide the main window to systray icon * - When the user will show it, enterEvent() will be called the first time he enter mouse to it * - ... */ /** Why do as this ? Problems with the use of only enterEvent() and leaveEvent() : * - Resize window or hover titlebar isn't possible : leave/enterEvent * are * > Use the grip or Alt+rightDND to resize window * > Use Alt+DND to move window * - Each menu trigger the leavEvent */ void BNPView::enterEvent(QEvent *) { if (m_tryHideTimer) m_tryHideTimer->stop(); if (m_hideTimer) m_hideTimer->stop(); } void BNPView::leaveEvent(QEvent *) { if (Settings::useSystray() && Settings::hideOnMouseOut() && m_tryHideTimer) m_tryHideTimer->start(50); } void BNPView::timeoutTryHide() { // If a menu is displayed, do nothing for the moment if (qApp->activePopupWidget() != nullptr) return; if (qApp->widgetAt(QCursor::pos()) != nullptr) m_hideTimer->stop(); else if (!m_hideTimer->isActive()) { // Start only one time m_hideTimer->setSingleShot(true); m_hideTimer->start(Settings::timeToHideOnMouseOut() * 100); } // If a subdialog is opened, we mustn't hide the main window: if (qApp->activeWindow() != nullptr && qApp->activeWindow() != Global::activeMainWindow()) m_hideTimer->stop(); } void BNPView::timeoutHide() { // We check that because the setting can have been set to off if (Settings::useSystray() && Settings::hideOnMouseOut()) setActive(false); m_tryHideTimer->stop(); } void BNPView::changedSelectedNotes() { // tabChanged(0); // FIXME: NOT OPTIMIZED } /*void BNPView::areSelectedNotesCheckedChanged(bool checked) { m_actCheckNotes->setChecked(checked && currentBasket()->showCheckBoxes()); }*/ void BNPView::enableActions() { BasketScene *basket = currentBasket(); if (!basket) return; if (m_actLockBasket) m_actLockBasket->setEnabled(!basket->isLocked() && basket->isEncrypted()); if (m_actPassBasket) m_actPassBasket->setEnabled(!basket->isLocked()); m_actPropBasket->setEnabled(!basket->isLocked()); m_actDelBasket->setEnabled(!basket->isLocked()); m_actExportToHtml->setEnabled(!basket->isLocked()); m_actShowFilter->setEnabled(!basket->isLocked()); m_actFilterAllBaskets->setEnabled(!basket->isLocked()); m_actResetFilter->setEnabled(!basket->isLocked()); basket->decoration()->filterBar()->setEnabled(!basket->isLocked()); } void BNPView::showMainWindow() { if (m_HiddenMainWindow) { m_HiddenMainWindow->show(); m_HiddenMainWindow = nullptr; } else { KMainWindow *win = Global::activeMainWindow(); if (win) { win->show(); } } setActive(true); emit showPart(); } void BNPView::populateTagsMenu() { QMenu *menu = (QMenu *)(popupMenu("tags")); if (menu == nullptr || currentBasket() == nullptr) // TODO: Display a messagebox. [menu is 0, surely because on first launch, the XMLGUI does not work!] return; menu->clear(); Note *referenceNote; if (currentBasket()->focusedNote() && currentBasket()->focusedNote()->isSelected()) referenceNote = currentBasket()->focusedNote(); else referenceNote = currentBasket()->firstSelected(); populateTagsMenu(*menu, referenceNote); m_lastOpenedTagsMenu = menu; - // connect( menu, SIGNAL(aboutToHide()), this, SLOT(disconnectTagsMenu()) ); + //connect(menu, &QMenu::aboutToHide, this, &BNPView::disconnectTagsMenu); } void BNPView::populateTagsMenu(QMenu &menu, Note *referenceNote) { if (currentBasket() == nullptr) return; currentBasket()->m_tagPopupNote = referenceNote; bool enable = currentBasket()->countSelecteds() > 0; QList::iterator it; Tag *currentTag; State *currentState; int i = 10; for (it = Tag::all.begin(); it != Tag::all.end(); ++it) { // Current tag and first state of it: currentTag = *it; currentState = currentTag->states().first(); QKeySequence sequence; if (!currentTag->shortcut().isEmpty()) sequence = currentTag->shortcut(); StateAction *mi = new StateAction(currentState, QKeySequence(sequence), this, true); // The previously set ID will be set in the actions themselves as data. mi->setData(i); if (referenceNote && referenceNote->hasTag(currentTag)) mi->setChecked(true); menu.addAction(mi); if (!currentTag->shortcut().isEmpty()) m_actionCollection->setDefaultShortcut(mi, sequence); mi->setEnabled(enable); ++i; } menu.addSeparator(); // I don't like how this is implemented; but I can't think of a better way // to do this, so I will have to leave it for now QAction *act = new QAction(i18n("&Assign new Tag..."), &menu); act->setData(1); act->setEnabled(enable); menu.addAction(act); act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove All"), &menu); act->setData(2); if (!currentBasket()->selectedNotesHaveTags()) act->setEnabled(false); menu.addAction(act); act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu); act->setData(3); menu.addAction(act); connect(&menu, SIGNAL(triggered(QAction *)), currentBasket(), SLOT(toggledTagInMenu(QAction *))); connect(&menu, SIGNAL(aboutToHide()), currentBasket(), SLOT(unlockHovering())); connect(&menu, SIGNAL(aboutToHide()), currentBasket(), SLOT(disableNextClick())); } void BNPView::connectTagsMenu() { - connect(popupMenu("tags"), SIGNAL(aboutToShow()), this, SLOT(populateTagsMenu())); - connect(popupMenu("tags"), SIGNAL(aboutToHide()), this, SLOT(disconnectTagsMenu())); + connect(popupMenu("tags"), SIGNAL(aboutToShow()), this, SLOT(populateTagsMenu())); + connect(popupMenu("tags"), &QMenu::aboutToHide, this, &BNPView::disconnectTagsMenu); } void BNPView::showEvent(QShowEvent *) { if (m_firstShow) { m_firstShow = false; onFirstShow(); } } void BNPView::disconnectTagsMenu() { QTimer::singleShot(0, this, SLOT(disconnectTagsMenuDelayed())); } void BNPView::disconnectTagsMenuDelayed() { disconnect(m_lastOpenedTagsMenu, SIGNAL(triggered(QAction *)), currentBasket(), SLOT(toggledTagInMenu(QAction *))); disconnect(m_lastOpenedTagsMenu, SIGNAL(aboutToHide()), currentBasket(), SLOT(unlockHovering())); disconnect(m_lastOpenedTagsMenu, SIGNAL(aboutToHide()), currentBasket(), SLOT(disableNextClick())); } void BNPView::loadCrossReference(QString link) { // remove "basket://" and any encoding. QString folderName = link.mid(9, link.length() - 9); folderName = QUrl::fromPercentEncoding(folderName.toUtf8()); BasketScene *basket = this->basketForFolderName(folderName); if (!basket) return; this->setCurrentBasketInHistory(basket); } QString BNPView::folderFromBasketNameLink(QStringList pages, QTreeWidgetItem *parent) { QString found; QString page = pages.first(); page = QUrl::fromPercentEncoding(page.toUtf8()); pages.removeFirst(); if (page == "..") { QTreeWidgetItem *p; if (parent) p = parent->parent(); else p = m_tree->currentItem()->parent(); found = this->folderFromBasketNameLink(pages, p); } else if (!parent && page.isEmpty()) { parent = m_tree->invisibleRootItem(); found = this->folderFromBasketNameLink(pages, parent); } else { if (!parent && (page == "." || !page.isEmpty())) { parent = m_tree->currentItem(); } QRegExp re(":\\{([0-9]+)\\}"); re.setMinimal(true); int pos = 0; pos = re.indexIn(page, pos); int basketNum = 1; if (pos != -1) basketNum = re.cap(1).toInt(); page = page.left(page.length() - re.matchedLength()); for (int i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *child = parent->child(i); if (child->text(0).toLower() == page.toLower()) { basketNum--; if (basketNum == 0) { if (pages.count() > 0) { found = this->folderFromBasketNameLink(pages, child); break; } else { found = ((BasketListViewItem *)child)->basket()->folderName(); break; } } } else found = QString(); } } return found; } void BNPView::sortChildrenAsc() { m_tree->currentItem()->sortChildren(0, Qt::AscendingOrder); } void BNPView::sortChildrenDesc() { m_tree->currentItem()->sortChildren(0, Qt::DescendingOrder); } void BNPView::sortSiblingsAsc() { QTreeWidgetItem *parent = m_tree->currentItem()->parent(); if (!parent) m_tree->sortItems(0, Qt::AscendingOrder); else parent->sortChildren(0, Qt::AscendingOrder); } void BNPView::sortSiblingsDesc() { QTreeWidgetItem *parent = m_tree->currentItem()->parent(); if (!parent) m_tree->sortItems(0, Qt::DescendingOrder); else parent->sortChildren(0, Qt::DescendingOrder); } diff --git a/src/decoratedbasket.cpp b/src/decoratedbasket.cpp index 97a57c8..30c2404 100644 --- a/src/decoratedbasket.cpp +++ b/src/decoratedbasket.cpp @@ -1,82 +1,82 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "decoratedbasket.h" #include #include #include "basketscene.h" #include "filter.h" #include "settings.h" /** Class DecoratedBasket: */ DecoratedBasket::DecoratedBasket(QWidget *parent, const QString &folderName, Qt::WindowFlags fl) : QWidget(parent, fl) { m_layout = new QVBoxLayout(this); m_filter = new FilterBar(this); m_basket = new BasketScene(this, folderName); m_basket->graphicsView()->setParent(this); m_layout->addWidget(m_basket->graphicsView()); setFilterBarPosition(Settings::filterOnTop()); m_filter->hide(); m_basket->setFocus(); // To avoid the filter bar have focus on load connect(m_filter, SIGNAL(newFilter(const FilterData &)), m_basket, SLOT(newFilter(const FilterData &))); - connect(m_basket, SIGNAL(postMessage(const QString &)), Global::bnpView, SLOT(postStatusbarMessage(const QString &))); - connect(m_basket, SIGNAL(setStatusBarText(const QString &)), Global::bnpView, SLOT(setStatusBarHint(const QString &))); - connect(m_basket, SIGNAL(resetStatusBarText()), Global::bnpView, SLOT(updateStatusBarHint())); + connect(m_basket, &BasketScene::postMessage, Global::bnpView, &BNPView::postStatusbarMessage); + connect(m_basket, &BasketScene::setStatusBarText, Global::bnpView, &BNPView::setStatusBarHint); + connect(m_basket, &BasketScene::resetStatusBarText, Global::bnpView, &BNPView::updateStatusBarHint); } DecoratedBasket::~DecoratedBasket() { } void DecoratedBasket::setFilterBarPosition(bool onTop) { m_layout->removeWidget(m_filter); if (onTop) { m_layout->insertWidget(0, m_filter); setTabOrder(this /*(QWidget*)parent()*/, m_filter); setTabOrder(m_filter, m_basket->graphicsView()); setTabOrder(m_basket->graphicsView(), (QWidget *)parent()); } else { m_layout->addWidget(m_filter); setTabOrder(this /*(QWidget*)parent()*/, m_basket->graphicsView()); setTabOrder(m_basket->graphicsView(), m_filter); setTabOrder(m_filter, (QWidget *)parent()); } } void DecoratedBasket::setFilterBarVisible(bool show, bool switchFocus) { // m_basket->setShowFilterBar(true);//show); // m_basket->save(); // In this order (m_basket and then m_filter) because setShown(false) // will call resetFilter() that will update actions, and then check the // Ctrl+F action whereas it should be unchecked // FIXME: It's very uggly all those things m_filter->setVisible(show); if (show) { if (switchFocus) m_filter->setEditFocus(); } else if (m_filter->hasEditFocus()) m_basket->setFocus(); } void DecoratedBasket::resetFilter() { m_filter->reset(); } void DecoratedBasket::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); m_basket->relayoutNotes(); } diff --git a/src/filter.cpp b/src/filter.cpp index 306f2e4..9f0f109 100644 --- a/src/filter.cpp +++ b/src/filter.cpp @@ -1,300 +1,300 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "filter.h" #include #include #include #include #include #include #include #include #include #include #include "bnpview.h" #include "focusedwidgets.h" #include "global.h" #include "tag.h" #include "tools.h" /** FilterBar */ FilterBar::FilterBar(QWidget *parent) : QWidget(parent) /*, m_blinkTimer(this), m_blinkedTimes(0)*/ { QHBoxLayout *hBox = new QHBoxLayout(this); // Create every widgets: // (Aaron Seigo says we don't need to worry about the // "Toolbar group" stuff anymore.) QIcon resetIcon = QIcon::fromTheme("dialog-close"); QIcon inAllIcon = QIcon::fromTheme("edit-find"); m_resetButton = new QToolButton(this); m_resetButton->setIcon(resetIcon); m_resetButton->setText(i18n("Reset Filter")); //, /*groupText=*/QString(), this, SLOT(reset()), 0); m_resetButton->setAutoRaise(true); // new KToolBarButton("locationbar_erase", /*id=*/1230, this, /*name=*/0, i18n("Reset Filter")); m_lineEdit = new QLineEdit(this); QLabel *label = new QLabel(this); label->setText(i18n("&Filter: ")); label->setBuddy(m_lineEdit); m_tagsBox = new KComboBox(this); QLabel *label2 = new QLabel(this); label2->setText(i18n("T&ag: ")); label2->setBuddy(m_tagsBox); m_inAllBasketsButton = new QToolButton(this); m_inAllBasketsButton->setIcon(inAllIcon); m_inAllBasketsButton->setText(i18n("Filter All Baskets")); //, /*groupText=*/QString(), this, SLOT(inAllBaskets()), 0); m_inAllBasketsButton->setAutoRaise(true); // Configure the Tags combobox: repopulateTagsCombo(); // Configure the Search in all Baskets button: m_inAllBasketsButton->setCheckable(true); // m_inAllBasketsButton->setChecked(true); // Global::bnpView->toggleFilterAllBaskets(true); // m_lineEdit->setMaximumWidth(150); m_lineEdit->setClearButtonEnabled(true); // Layout all those widgets: hBox->addWidget(m_resetButton); hBox->addWidget(label); hBox->addWidget(m_lineEdit); hBox->addWidget(label2); hBox->addWidget(m_tagsBox); hBox->addWidget(m_inAllBasketsButton); - // connect( &m_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkBar()) ); - connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); - connect(m_lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(changeFilter())); + //connect(&m_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkBar())); + connect(m_resetButton, &QToolButton::clicked, this, &FilterBar::reset); + connect(m_lineEdit, &QLineEdit::textChanged, this, &FilterBar::changeFilter); connect(m_tagsBox, SIGNAL(activated(int)), this, SLOT(tagChanged(int))); - // connect( m_inAllBasketsButton, SIGNAL(clicked()), this, SLOT(inAllBaskets()) ); + //connect(m_inAllBasketsButton, SIGNAL(clicked()), this, SLOT(inAllBaskets())); m_inAllBasketsButton->setDefaultAction(Global::bnpView->m_actFilterAllBaskets); FocusWidgetFilter *lineEditF = new FocusWidgetFilter(m_lineEdit); m_tagsBox->installEventFilter(lineEditF); - connect(lineEditF, SIGNAL(escapePressed()), SLOT(reset())); - connect(lineEditF, SIGNAL(returnPressed()), SLOT(changeFilter())); + connect(lineEditF, &FocusWidgetFilter::escapePressed, this, &FilterBar::reset); + connect(lineEditF, &FocusWidgetFilter::returnPressed, this, &FilterBar::changeFilter); } FilterBar::~FilterBar() { } void FilterBar::setFilterData(const FilterData &data) { m_lineEdit->setText(data.string); int index = 0; switch (data.tagFilterType) { default: case FilterData::DontCareTagsFilter: index = 0; break; case FilterData::NotTaggedFilter: index = 1; break; case FilterData::TaggedFilter: index = 2; break; case FilterData::TagFilter: filterTag(data.tag); return; case FilterData::StateFilter: filterState(data.state); return; } if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::repopulateTagsCombo() { static const int ICON_SIZE = 16; m_tagsBox->clear(); m_tagsMap.clear(); m_statesMap.clear(); m_tagsBox->addItem(QString()); m_tagsBox->addItem(i18n("(Not tagged)")); m_tagsBox->addItem(i18n("(Tagged)")); int index = 3; Tag *tag; State *state; QString icon; QString text; QPixmap emblem; for (Tag::List::iterator it = Tag::all.begin(); it != Tag::all.end(); ++it) { tag = *it; state = tag->states().first(); // Insert the tag in the combo-box: if (tag->countStates() > 1) { text = tag->name(); icon = QString(); } else { text = state->name(); icon = state->emblem(); } emblem = KIconLoader::global()->loadIcon(icon, KIconLoader::Desktop, ICON_SIZE, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/true); m_tagsBox->insertItem(index, emblem, text); // Update the mapping: m_tagsMap.insert(index, tag); ++index; // Insert sub-states, if needed: if (tag->countStates() > 1) { for (State::List::iterator it2 = tag->states().begin(); it2 != tag->states().end(); ++it2) { state = *it2; // Insert the state: text = state->name(); icon = state->emblem(); emblem = KIconLoader::global()->loadIcon(icon, KIconLoader::Desktop, ICON_SIZE, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/true); // Indent the emblem to show the hierarchy relation: if (!emblem.isNull()) emblem = Tools::indentPixmap(emblem, /*depth=*/1, /*deltaX=*/2 * ICON_SIZE / 3); m_tagsBox->insertItem(index, emblem, text); // Update the mapping: m_statesMap.insert(index, state); ++index; } } } } void FilterBar::reset() { m_lineEdit->setText(QString()); // m_data->isFiltering will be set to false; m_lineEdit->clearFocus(); changeFilter(); if (m_tagsBox->currentIndex() != 0) { m_tagsBox->setCurrentIndex(0); tagChanged(0); } hide(); emit newFilter(m_data); } void FilterBar::filterTag(Tag *tag) { int index = 0; for (QMap::Iterator it = m_tagsMap.begin(); it != m_tagsMap.end(); ++it) if (it.value() == tag) { index = it.key(); break; } if (index <= 0) return; if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::filterState(State *state) { int index = 0; for (QMap::Iterator it = m_statesMap.begin(); it != m_statesMap.end(); ++it) if (it.value() == state) { index = it.key(); break; } if (index <= 0) return; if (m_tagsBox->currentIndex() != index) { m_tagsBox->setCurrentIndex(index); tagChanged(index); } } void FilterBar::inAllBaskets() { // TODO! } void FilterBar::setEditFocus() { m_lineEdit->setFocus(); } bool FilterBar::hasEditFocus() { return m_lineEdit->hasFocus() || m_tagsBox->hasFocus(); } const FilterData &FilterBar::filterData() { return m_data; } void FilterBar::changeFilter() { m_data.string = m_lineEdit->text(); m_data.isFiltering = (!m_data.string.isEmpty() || m_data.tagFilterType != FilterData::DontCareTagsFilter); if (hasEditFocus()) m_data.isFiltering = true; emit newFilter(m_data); } void FilterBar::tagChanged(int index) { m_data.tag = nullptr; m_data.state = nullptr; switch (index) { case 0: m_data.tagFilterType = FilterData::DontCareTagsFilter; break; case 1: m_data.tagFilterType = FilterData::NotTaggedFilter; break; case 2: m_data.tagFilterType = FilterData::TaggedFilter; break; default: // Try to find if we are filtering a tag: QMap::iterator it = m_tagsMap.find(index); if (it != m_tagsMap.end()) { m_data.tagFilterType = FilterData::TagFilter; m_data.tag = *it; } else { // If not, try to find if we are filtering a state: QMap::iterator it2 = m_statesMap.find(index); if (it2 != m_statesMap.end()) { m_data.tagFilterType = FilterData::StateFilter; m_data.state = *it2; } else { // If not (should never happens), do as if the tags filter was reset: m_data.tagFilterType = FilterData::DontCareTagsFilter; } } break; } m_data.isFiltering = (!m_data.string.isEmpty() || m_data.tagFilterType != FilterData::DontCareTagsFilter); if (hasEditFocus()) m_data.isFiltering = true; emit newFilter(m_data); } diff --git a/src/focusedwidgets.cpp b/src/focusedwidgets.cpp index 7bd356a..501f9e5 100644 --- a/src/focusedwidgets.cpp +++ b/src/focusedwidgets.cpp @@ -1,135 +1,135 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "focusedwidgets.h" #include #include #include #include #include #include #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "settings.h" #ifdef KeyPress #undef KeyPress #endif /** class FocusedTextEdit */ FocusedTextEdit::FocusedTextEdit(bool disableUpdatesOnKeyPress, QWidget *parent) : KTextEdit(parent) , m_disableUpdatesOnKeyPress(disableUpdatesOnKeyPress) { - connect(this, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); + connect(this, &FocusedTextEdit::selectionChanged, this, &FocusedTextEdit::onSelectionChanged); } FocusedTextEdit::~FocusedTextEdit() { } void FocusedTextEdit::paste(QClipboard::Mode mode) { const QMimeData *md = QApplication::clipboard()->mimeData(mode); if (md) insertFromMimeData(md); } void FocusedTextEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { emit escapePressed(); return; } if (m_disableUpdatesOnKeyPress) setUpdatesEnabled(false); KTextEdit::keyPressEvent(event); // Workaround (for ensuring the cursor to be visible): signal not emitted when pressing those keys: if (event->key() == Qt::Key_Home || event->key() == Qt::Key_End || event->key() == Qt::Key_PageUp || event->key() == Qt::Key_PageDown) emit cursorPositionChanged(); if (m_disableUpdatesOnKeyPress) { setUpdatesEnabled(true); if (!document()->isEmpty()) ensureCursorVisible(); } } void FocusedTextEdit::wheelEvent(QWheelEvent *event) { // If we're already scrolled all the way to the top or bottom, we pass the // wheel event onto the basket. QScrollBar *sb = verticalScrollBar(); if ((event->delta() > 0 && sb->value() > sb->minimum()) || (event->delta() < 0 && sb->value() < sb->maximum())) KTextEdit::wheelEvent(event); // else // Global::bnpView->currentBasket()->graphicsView()->wheelEvent(event); } void FocusedTextEdit::enterEvent(QEvent *event) { emit mouseEntered(); KTextEdit::enterEvent(event); } void FocusedTextEdit::insertFromMimeData(const QMimeData *source) { // When user always wants plaintext pasting, if both HTML and text data is // present, only send plain text data (the provided source is readonly and I // also can't just pass it to QMimeData constructor as the latter is 'private') if (Settings::pasteAsPlainText() && source->hasHtml() && source->hasText()) { QMimeData alteredSource; alteredSource.setData("text/plain", source->data("text/plain")); KTextEdit::insertFromMimeData(&alteredSource); } else KTextEdit::insertFromMimeData(source); } void FocusedTextEdit::onSelectionChanged() { if (textCursor().selectedText().length() > 0) { QMimeData *md = createMimeDataFromSelection(); QApplication::clipboard()->setMimeData(md, QClipboard::Selection); } } /** class FocusWidgetFilter */ FocusWidgetFilter::FocusWidgetFilter(QWidget *parent) : QObject(parent) { if (parent) parent->installEventFilter(this); } bool FocusWidgetFilter::eventFilter(QObject *, QEvent *e) { switch (e->type()) { case QEvent::KeyPress: { QKeyEvent *ke = static_cast(e); switch (ke->key()) { case Qt::Key_Return: emit returnPressed(); return true; case Qt::Key_Escape: emit escapePressed(); return true; default: return false; }; } case QEvent::Enter: emit mouseEntered(); // pass through default: return false; }; } diff --git a/src/kcolorcombo2.cpp b/src/kcolorcombo2.cpp index 636cdcd..1f64918 100644 --- a/src/kcolorcombo2.cpp +++ b/src/kcolorcombo2.cpp @@ -1,754 +1,754 @@ /** * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "kcolorcombo2.h" #ifndef USE_OLD_KCOLORCOMBO #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_COLOR_ARRAY //#define OUTPUT_GIMP_PALETTE /** class KColorPopup: */ const int KColorPopup::MARGIN = 1; const int KColorPopup::FRAME_WIDTH = 1; KColorPopup::KColorPopup(KColorCombo2 *parent) : QWidget(/*parent=*/nullptr, Qt::Popup) , m_selector(parent) , m_pixmap(nullptr) { hide(); setMouseTracking(true); // resize(20, 20); } KColorPopup::~KColorPopup() { delete m_pixmap; } #include void KColorPopup::relayout() // FIXME: relayout should NOT redraw the pixmap! { int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); bool haveDefault = m_selector->defaultColor().isValid(); int width = 2 + MARGIN + (colorWidth + MARGIN) * columnCount; int height = 2 + MARGIN + (colorHeight + MARGIN) * rowCount + (colorHeight + MARGIN); resize(width, height); // Initialize the pixmap: delete m_pixmap; m_pixmap = new QPixmap(width, height); QPainter painter(m_pixmap); painter.fillRect(0, 0, width, height, palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, width, height); // Needed to draw: int x, y; QRect selectionRect; // Draw the color array: for (int i = 0; i < columnCount; ++i) { for (int j = 0; j < rowCount; ++j) { x = 1 + MARGIN + (colorWidth + MARGIN) * i; y = 1 + MARGIN + (colorHeight + MARGIN) * j; if (i == m_selectedColumn && j == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, colorWidth + 4, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); } m_selector->drawColorRect(painter, x, y, m_selector->colorAt(i, j), /*isDefault=*/false, colorWidth, colorHeight); } } m_columnOther = (haveDefault ? columnCount / 2 : 0); // "(Default)" is allowed, paint "Other..." on the right int defaultCellWidth = (colorWidth + MARGIN) * m_columnOther; int otherCellWidth = (colorWidth + MARGIN) * (columnCount - m_columnOther); // Draw the "(Default)" and "Other..." colors: y = height - (colorHeight + MARGIN) - 1; QColor textColor; if (m_selector->defaultColor().isValid()) { x = 1 + MARGIN; if (m_selectedColumn < m_columnOther && rowCount == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, defaultCellWidth, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); textColor = palette().color(QPalette::HighlightedText); } else textColor = palette().color(QPalette::Text); m_selector->drawColorRect(painter, x, y, m_selector->defaultColor(), /*isDefault=*/true, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, i18n("(Default)")); } x = 1 + MARGIN + m_columnOther * (colorWidth + MARGIN); if (m_selectedColumn >= m_columnOther && rowCount == m_selectedRow) { selectionRect = QRect(x - 2, y - 2, otherCellWidth, colorHeight + 4); painter.fillRect(selectionRect, palette().color(QPalette::Highlight)); textColor = palette().color(QPalette::HighlightedText); } else textColor = palette().color(QPalette::Text); m_selector->drawColorRect(painter, x, y, m_otherColor, /*isDefault=*/false, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, i18n("Other...")); // QPoint pos = mapFromGlobal(QCursor::pos()); // painter.drawRect(pos.x(), pos.y(), 5000, 5000); } void KColorPopup::updateCell(int column, int row) { int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); int x = 1 + MARGIN + -2 + column * (colorWidth + MARGIN); int y = 1 + MARGIN + -2 + row * (colorHeight + MARGIN); int width = colorWidth + MARGIN; int height = colorHeight + MARGIN; if (row == m_selector->rowCount()) { if (m_selectedColumn < m_columnOther) // The "(Default)" cell: width = (colorWidth + MARGIN) * m_columnOther; else // The "Other..." cell: width = (colorWidth + MARGIN) * (m_selector->columnCount() - m_columnOther); } update(x, y, width, height); } void KColorPopup::doSelection() { m_otherColor = QColor(); // If the selected color is not the default one, try to find it in the array: if (m_selector->color().isValid()) { bool isInArray = false; for (int column = 0; column < m_selector->columnCount(); ++column) for (int row = 0; row < m_selector->rowCount(); ++row) if (m_selector->color() == m_selector->colorAt(column, row)) { m_selectedColumn = column; m_selectedRow = row; isInArray = true; } // If not found in array, it's another one: if (!isInArray) { m_selectedColumn = m_columnOther; m_selectedRow = m_selector->rowCount(); m_otherColor = m_selector->color(); } // If it's the default one: } else { m_selectedColumn = 0; m_selectedRow = m_selector->rowCount(); } } void KColorPopup::validate() { hide(); close(); emit closed(); if (m_selectedRow != m_selector->rowCount()) // A normal row: m_selector->setColor(m_selector->colorAt(m_selectedColumn, m_selectedRow)); else if (m_selectedColumn < m_columnOther) // The default color: m_selector->setColor(QColor()); else { // The user want to choose one: QColor color = m_selector->effectiveColor(); color = QColorDialog::getColor(color, this); if (color.isValid()) m_selector->setColor(color); } } void KColorPopup::mousePressEvent(QMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < 0 || y < 0 || x >= width() || y >= height()) { hide(); close(); emit closed(); } else validate(); event->accept(); } void KColorPopup::paintEvent(QPaintEvent *event) { QPainter painter(this); if (m_pixmap) painter.drawPixmap(0, 0, *m_pixmap); painter.setPen(Qt::black); painter.drawRect(event->rect()); } void KColorPopup::mouseMoveEvent(QMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < FRAME_WIDTH + 2 || y < FRAME_WIDTH + 2 || x > width() - 2 - 2 * FRAME_WIDTH || y > height() - 2 - 2 * FRAME_WIDTH) return; int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); // int oldSelectedColumn = m_selectedColumn; // int oldSelectedRow = m_selectedRow; m_selectedColumn = (x - FRAME_WIDTH - MARGIN + 2) / (colorWidth + MARGIN); m_selectedRow = (y - FRAME_WIDTH - MARGIN + 2) / (colorHeight + MARGIN); relayout(); update(); } void KColorPopup::keyPressEvent(QKeyEvent *event) { int column = m_selectedColumn; int row = m_selectedRow; int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); switch (event->key()) { case Qt::Key_Right: if (m_selectedRow != rowCount) // A normal row: column = (column + 1) % columnCount; else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case Qt::Key_Left: if (m_selectedRow != rowCount) { // A normal row: column = (column - 1); if (column < 0) column = columnCount - 1; } else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case Qt::Key_Up: row = (row - 1); if (row < 0) row = rowCount; break; case Qt::Key_Down: row = (row + 1) % (rowCount + 1); break; case Qt::Key_PageDown: row += 10; if (row > rowCount) row = rowCount; break; case Qt::Key_PageUp: row -= 10; if (row < 0) row = 0; break; case Qt::Key_Home: row = 0; column = 0; break; case Qt::Key_End: row = rowCount; column = columnCount - 1; break; case Qt::Key_Return: validate(); break; default: QWidget::keyPressEvent(event); } if (row != m_selectedRow || column != m_selectedColumn) { m_selectedRow = row; m_selectedColumn = column; relayout(); update(); } } /** Helper function: */ QColor Tool_mixColors(const QColor &color1, const QColor &color2) { QColor mixedColor; mixedColor.setRgb((color1.red() + color2.red()) / 2, (color1.green() + color2.green()) / 2, (color1.blue() + color2.blue()) / 2); return mixedColor; } /** class KColorCombo2Private */ class KColorCombo2::KColorCombo2Private { }; /** class KColorCombo2: */ /* All code for the popup management (including the constructor, popup() and eventFilter()) * has been copied from the KDateEdit widget (in libkdepim). * * Some other piece of code comes from KColorButton (in libkdeui) to enable color drag, drop, copy and paste. */ KColorCombo2::KColorCombo2(const QColor &color, const QColor &defaultColor, QWidget *parent) : KComboBox(parent) , m_color(color) , m_defaultColor(defaultColor) { setEditable(false); init(); } KColorCombo2::KColorCombo2(const QColor &color, QWidget *parent) : KComboBox(parent) , m_color(color) , m_defaultColor() { setEditable(false); init(); } void KColorCombo2::init() { m_colorArray = nullptr; d = new KColorCombo2Private(); setDefaultColor(m_defaultColor); insertItem(/*index=*/0, QString()); updateComboBox(); // It need an item of index 0 to exists, so we created it. setAcceptDrops(true); m_popup = new KColorPopup(this); m_popup->installEventFilter(this); - connect(m_popup, SIGNAL(closed()), SLOT(popupClosed())); + connect(m_popup, &KColorPopup::closed, this, &KColorCombo2::popupClosed); // By default, the array is filled with setRainbowPreset(). // But we allocate it on demand (the later as possible) to avoid performances issues if the developer set another array. // However, to keep columnCount() rowCount() const, we define theme here: m_columnCount = 13; m_rowCount = 9; } KColorCombo2::~KColorCombo2() { deleteColorArray(); } void KColorCombo2::setColor(const QColor &color) { // Do nothing if the color should be set to the default one and there is no such default color allowed: if (!color.isValid() && !m_defaultColor.isValid()) { // kdebug << this::FUNCTION << "Trying to assign the default color (an invalid one) whereas no such default color is allowed"; return; } if (m_color != color) { m_color = color; updateComboBox(); emit changed(color); } } QColor KColorCombo2::color() const { return m_color; } QColor KColorCombo2::effectiveColor() const { if (m_color.isValid()) return m_color; else return m_defaultColor; } void KColorCombo2::setRainbowPreset(int colorColumnCount, int lightRowCount, int darkRowCount, bool withGray) { // At least one row and one column: if (colorColumnCount < 1 - (withGray ? 1 : 0)) colorColumnCount = 1 - (withGray ? 1 : 0); if (lightRowCount < 0) lightRowCount = 0; if (darkRowCount < 0) darkRowCount = 0; // Create the array: int columnCount = colorColumnCount + (withGray ? 1 : 0); int rowCount = lightRowCount + 1 + darkRowCount; newColorArray(columnCount, rowCount); // Fill the array: for (int i = 0; i < colorColumnCount; ++i) { int hue = i * 360 / colorColumnCount; // With light colors: for (int j = 1; j <= lightRowCount; ++j) { // Start to 1 because we don't want a row full of white! int saturation = j * 255 / (lightRowCount + 1); setColorAt(i, j - 1, QColor::fromHsv(hue, saturation, 255)); } // With pure colors: setColorAt(i, lightRowCount, QColor::fromHsv(hue, 255, 255)); // With dark colors: for (int j = 1; j <= darkRowCount; ++j) { int value = 255 - j * 255 / (darkRowCount + 1); setColorAt(i, lightRowCount + j, QColor::fromHsv(hue, 255, value)); } } // Fill the gray column: if (withGray) { for (int i = 0; i < rowCount; ++i) { int gray = (rowCount == 1 ? 128 : 255 - (i * 255 / (rowCount - 1))); setColorAt(columnCount - 1, i, QColor(gray, gray, gray)); } } #ifdef DEBUG_COLOR_ARRAY qDebug() << "KColorCombo2::setColorPreset"; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { int h, s, v; m_colorArray[i][j].getHsv(&h, &s, &v); qDebug() << QString("(%1,%2,%3)").arg(h, 3).arg(s, 3).arg(v, 3); // qDebug() << colorArray[i][j].name() << " "; } qDebug(); } #endif #ifdef OUTPUT_GIMP_PALETTE qDebug() << "GIMP Palette"; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { qDebug() << QString("(%1,%2,%3)").arg(m_colorArray[i][j].red(), 3).arg(m_colorArray[i][j].green(), 3).arg(m_colorArray[i][j].blue(), 3); } } #endif } int KColorCombo2::columnCount() const { return m_columnCount; } int KColorCombo2::rowCount() const { return m_rowCount; } QColor KColorCombo2::colorAt(int column, int row) /* const*/ { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) return QColor(); return m_colorArray[column][row]; } QColor KColorCombo2::defaultColor() const { return m_defaultColor; } void KColorCombo2::newColorArray(int columnCount, int rowCount) { if (columnCount <= 0 || rowCount <= 0) { // kdebug << this::FUNCTION << "Trying to create an empty new color array (with %d columns and %d rows)"; return; } // Delete any previous array (if any): deleteColorArray(); // Create a new array of the wanted dimensions: m_columnCount = columnCount; m_rowCount = rowCount; m_colorArray = new QColor *[columnCount]; for (int i = 0; i < columnCount; ++i) m_colorArray[i] = new QColor[rowCount]; } void KColorCombo2::setColorAt(int column, int row, const QColor &color) { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) { // kdebug << this::FUNCTION << "Trying to set a color at an invalid index (at column %d and row %d, whereas the array have %d columns and %d rows)"; return; } m_colorArray[column][row] = color; } void KColorCombo2::setDefaultColor(const QColor &color) { m_defaultColor = color; if (!m_defaultColor.isValid() && !m_color.isValid()) m_color = Qt::white; // FIXME: Use the first one. } QPixmap KColorCombo2::colorRectPixmap(const QColor &color, bool isDefault, int width, int height) { // Prepare to draw: QPixmap pixmap(width, height); QBitmap mask(width, height); QPainter painter(&pixmap); QPainter maskPainter(&mask); // Draw pixmap: drawColorRect(painter, 0, 0, color, isDefault, width, height); // Draw mask (make the four corners transparent): maskPainter.fillRect(0, 0, width, height, Qt::color1); // opaque maskPainter.setPen(Qt::color0); // transparent maskPainter.drawPoint(0, 0); maskPainter.drawPoint(0, height - 1); maskPainter.drawPoint(width - 1, height - 1); maskPainter.drawPoint(width - 1, 0); // Finish: painter.end(); maskPainter.end(); pixmap.setMask(mask); return pixmap; } void KColorCombo2::drawColorRect(QPainter &painter, int x, int y, const QColor &color, bool isDefault, int width, int height) { // Fill: if (color.isValid()) painter.fillRect(x /*+ 1*/, y /*+ 1*/, width /*- 2*/, height /*- 2*/, color); else { // If it's an invalid color, it's for the "Other..." entry: draw a rainbow. // If it wasn't for the "Other..." entry, the programmer made a fault, so (s)he will be informed about that visually. for (int i = 0; i < width - 2; ++i) { int hue = i * 360 / (width - 2); for (int j = 0; j < height - 2; ++j) { int saturation = 255 - (j * 255 / (height - 2)); painter.setPen(QColor::fromHsv(hue, saturation, /*value=*/255)); painter.drawPoint(x + i + 1, y + j + 1); } } } // Stroke: int dontCare, value; color.getHsv(/*hue:*/ &dontCare, /*saturation:*/ &dontCare, &value); QColor stroke = (color.isValid() ? color.darker(125) : palette().color(QPalette::Text)); painter.setPen(/*color);//*/ stroke); painter.drawLine(x + 1, y, x + width - 2, y); painter.drawLine(x, y + 1, x, y + height - 2); painter.drawLine(x + 1, y + height - 1, x + width - 2, y + height - 1); painter.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2); // Round corners: QColor antialiasing; if (color.isValid()) { antialiasing = Tool_mixColors(color, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); painter.drawPoint(x + width - 2, y + 1); } else { // The two top corners: antialiasing = Tool_mixColors(Qt::red, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + width - 2, y + 1); // The two bottom ones: antialiasing = Tool_mixColors(Qt::white, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); } // Mark default color: if (isDefault) { painter.setPen(stroke); painter.drawLine(x + 1, y + height - 2, x + width - 2, y + 1); } } int KColorCombo2::colorRectHeight() const { return (fontMetrics().boundingRect(i18n("(Default)")).height() + 2) * 3 / 2; } int KColorCombo2::colorRectWidthForHeight(int height) const { return height * 14 / 10; // 1.4 times the height, like A4 papers. } void KColorCombo2::deleteColorArray() { if (m_colorArray) { for (int i = 0; i < m_columnCount; ++i) delete[] m_colorArray[i]; delete[] m_colorArray; m_colorArray = nullptr; } } void KColorCombo2::updateComboBox() { int height = colorRectHeight() * 2 / 3; // fontMetrics().boundingRect(i18n("(Default)")).height() + 2 QPixmap pixmap = colorRectPixmap(effectiveColor(), !m_color.isValid(), height, height); // TODO: isDefaultColorSelected() setItemIcon(/*index=*/0, pixmap); setItemText(/*index=*/0, (m_color.isValid() ? QString(i18n("R:%1, G:%2, B:%3", m_color.red(), m_color.green(), m_color.blue())) : i18nc("color", "(Default)"))); } void KColorCombo2::showPopup() { if (!m_colorArray) setRainbowPreset(); // Compute where to show the popup: QRect desk = qApp->desktop()->screenGeometry(this); QPoint popupPoint = mapToGlobal(QPoint(0, 0)); int popupHeight = m_popup->size().height(); if (popupPoint.y() + height() + popupHeight > desk.bottom()) popupPoint.setY(popupPoint.y() - popupHeight); else popupPoint.setY(popupPoint.y() + height()); int popupWidth = m_popup->size().width(); if (popupPoint.x() + popupWidth > desk.right()) popupPoint.setX(desk.right() - popupWidth); if (popupPoint.x() < desk.left()) popupPoint.setX(desk.left()); if (popupPoint.y() < desk.top()) popupPoint.setY(desk.top()); // Configure the popup: m_popup->move(popupPoint); // m_popup->setColor(m_color); m_popup->doSelection(); m_popup->relayout(); // FIXME: In aboutToShow() ? #if 0 //#ifndef QT_NO_EFFECTS if (QApplication::isEffectEnabled(UI_AnimateCombo)) { if (m_popup->y() < mapToGlobal(QPoint(0, 0)).y()) qScrollEffect(m_popup, QEffects::UpScroll); else qScrollEffect(m_popup); } else #endif m_popup->show(); // The combo box is now shown pressed. Make it show not pressed again // by causing its (invisible) list box to emit a 'selected' signal. // Simulate an Enter to unpress it: /*QListWidget *lb = listBox(); if (lb) { lb->setCurrentItem(0); QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, 0, 0); QApplication::postEvent(lb, keyEvent); }*/ } void KColorCombo2::mouseMoveEvent(QMouseEvent *event) { if ((event->buttons() & Qt::LeftButton) && (event->pos() - m_dragStartPos).manhattanLength() > qApp->startDragDistance()) { // Drag color object: QMimeData *mimeData = new QMimeData; QDrag *colorDrag = new QDrag(this); mimeData->setColorData(effectiveColor()); // Replace the drag pixmap with our own rounded one, at the same position and dimensions: QPixmap pixmap = colorDrag->pixmap(); pixmap = colorRectPixmap(effectiveColor(), /*isDefault=*/false, pixmap.width(), pixmap.height()); colorDrag->setPixmap(pixmap); colorDrag->setHotSpot(colorDrag->hotSpot()); colorDrag->exec(Qt::CopyAction, Qt::CopyAction); // setDown(false); } } void KColorCombo2::dragEnterEvent(QDragEnterEvent *event) { if (isEnabled() && event->mimeData()->hasColor()) event->accept(); } void KColorCombo2::dropEvent(QDropEvent *event) { QColor color; color = qvariant_cast(event->mimeData()->colorData()); if (color.isValid()) setColor(color); } void KColorCombo2::keyPressEvent(QKeyEvent *event) { QKeySequence key(event->key()); if (KStandardShortcut::copy().contains(key)) { QMimeData *mime = new QMimeData; mime->setColorData(effectiveColor()); QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard); } else if (KStandardShortcut::paste().contains(key)) { QColor color; color = qvariant_cast(QApplication::clipboard()->mimeData(QClipboard::Clipboard)->colorData()); setColor(color); } else KComboBox::keyPressEvent(event); } void KColorCombo2::fontChange(const QFont &) { // Since the color-rectangle is the same height of the text, we should resize it if the font change: updateComboBox(); } void KColorCombo2::virtual_hook(int /*id*/, void * /*data*/) { /* KBASE::virtual_hook(id, data); */ } void KColorCombo2::popupClosed() { hidePopup(); } #endif // USE_OLD_KCOLORCOMBO diff --git a/src/kgpgme.cpp b/src/kgpgme.cpp index 20cdc46..7758bd6 100644 --- a/src/kgpgme.cpp +++ b/src/kgpgme.cpp @@ -1,449 +1,449 @@ /** * SPDX-FileCopyrightText: (C) 2006 Petri Damsten * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #ifdef HAVE_LIBGPGME #include "debugwindow.h" #include "global.h" #include "kgpgme.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //For errno #include //For LC_ALL, etc. #include //For write // KGpgSelKey class based on class in KGpg with the same name class KGpgSelKey : public QDialog { private: QTreeWidget *keysListpr; public: KGpgSelKey(QWidget *parent, const char *name, QString preselected, const KGpgMe &gpg) : QDialog(parent) { // Dialog options setObjectName(name); setModal(true); setWindowTitle(i18n("Private Key List")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QString keyname; QVBoxLayout *vbox; QWidget *page = new QWidget(this); QLabel *labeltxt; QPixmap keyPair = QIcon::fromTheme("kgpg_key2").pixmap(20, 20); setMinimumSize(350, 100); keysListpr = new QTreeWidget(page); keysListpr->setRootIsDecorated(true); keysListpr->setColumnCount(3); QStringList headers; headers << i18n("Name") << i18n("Email") << i18n("ID"); keysListpr->setHeaderLabels(headers); keysListpr->setAllColumnsShowFocus(true); labeltxt = new QLabel(i18n("Choose a secret key:"), page); vbox = new QVBoxLayout(page); KGpgKeyList list = gpg.keys(true); for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) { QString name = gpg.checkForUtf8((*it).name); QStringList values; values << name << (*it).email << (*it).id; QTreeWidgetItem *item = new QTreeWidgetItem(keysListpr, values, 3); item->setIcon(0, keyPair); if (preselected == (*it).id) { item->setSelected(true); keysListpr->setCurrentItem(item); } } if (!keysListpr->currentItem() && keysListpr->topLevelItemCount() > 0) { keysListpr->topLevelItem(0)->setSelected(true); keysListpr->setCurrentItem(keysListpr->topLevelItem(0)); } vbox->addWidget(labeltxt); vbox->addWidget(keysListpr); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &KGpgMe::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &KGpgMe::reject); mainLayout->addWidget(buttonBox); }; QString key() { QTreeWidgetItem *item = keysListpr->currentItem(); if (item) return item->text(2); return QString(); } }; KGpgMe::KGpgMe() : m_ctx(0) , m_useGnuPGAgent(true) { init(GPGME_PROTOCOL_OpenPGP); if (gpgme_new(&m_ctx) == GPG_ERR_NO_ERROR) { gpgme_set_armor(m_ctx, 1); setPassphraseCb(); // Set gpg version gpgme_engine_info_t info; gpgme_get_engine_info(&info); while (info != NULL && info->protocol != gpgme_get_protocol(m_ctx)) { info = info->next; } if (info != NULL) { QByteArray gpgPath = info->file_name; gpgPath.replace("gpg2", "gpg"); // require GnuPG v1 gpgme_ctx_set_engine_info(m_ctx, GPGME_PROTOCOL_OpenPGP, gpgPath.data(), NULL); } } else { m_ctx = 0; } } KGpgMe::~KGpgMe() { if (m_ctx) gpgme_release(m_ctx); clearCache(); } void KGpgMe::clearCache() { if (m_cache.size() > 0) { m_cache.fill('\0'); m_cache.truncate(0); } } void KGpgMe::init(gpgme_protocol_t proto) { gpgme_error_t err; gpgme_check_version("1.0.0"); // require GnuPG v1 setlocale(LC_ALL, ""); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); #ifndef Q_OS_WIN gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL)); #endif err = gpgme_engine_check_version(proto); if (err) { static QString lastErrorText; QString text = QString("%1: %2").arg(gpgme_strsource(err), gpgme_strerror(err)); if (text != lastErrorText) { KMessageBox::error(qApp->activeWindow(), text); lastErrorText = text; } } } QString KGpgMe::checkForUtf8(QString txt) { // code borrowed from KGpg which borrowed it from gpa const char *s; // Make sure the encoding is UTF-8. // Test structure suggested by Werner Koch if (txt.isEmpty()) return QString(); for (s = txt.toLatin1(); *s && !(*s & 0x80); s++) ; if (*s && !strchr(txt.toLatin1(), 0xc3) && (txt.indexOf("\\x") == -1)) return txt; // The string is not in UTF-8 // if (strchr (txt.toLatin1(), 0xc3)) return (txt+" +++"); if (txt.indexOf("\\x") == -1) return QString::fromUtf8(txt.toLatin1()); // if (!strchr (txt.toLatin1(), 0xc3) || (txt.indexOf("\\x")!=-1)) { for (int idx = 0; (idx = txt.indexOf("\\x", idx)) >= 0; ++idx) { char str[2] = "x"; str[0] = (char)QString(txt.mid(idx + 2, 2)).toShort(0, 16); txt.replace(idx, 4, str); } if (!strchr(txt.toLatin1(), 0xc3)) return QString::fromUtf8(txt.toLatin1()); else return QString::fromUtf8(QString::fromUtf8(txt.toLatin1()).toLatin1()); // perform Utf8 twice, or some keys display badly return txt; } QString KGpgMe::selectKey(QString previous) { QPointer dlg = new KGpgSelKey(qApp->activeWindow(), "", previous, *this); if (dlg->exec()) return dlg->key(); return QString(); } // Rest of the code is mainly based in gpgme examples KGpgKeyList KGpgMe::keys(bool privateKeys /* = false */) const { KGpgKeyList keys; gpgme_error_t err = 0, err2 = 0; gpgme_key_t key = 0; gpgme_keylist_result_t result = 0; if (m_ctx) { err = gpgme_op_keylist_start(m_ctx, NULL, privateKeys); if (!err) { while (!(err = gpgme_op_keylist_next(m_ctx, &key))) { KGpgKey gpgkey; if (!key->subkeys) continue; gpgkey.id = key->subkeys->keyid; if (key->uids) { gpgkey.name = key->uids->name; gpgkey.email = key->uids->email; } keys.append(gpgkey); gpgme_key_unref(key); } if (gpg_err_code(err) == GPG_ERR_EOF) err = 0; err2 = gpgme_op_keylist_end(m_ctx); if (!err) err = err2; } } if (err) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } else { result = gpgme_op_keylist_result(m_ctx); if (result->truncated) { KMessageBox::error(qApp->activeWindow(), i18n("Key listing unexpectedly truncated.")); } } return keys; } bool KGpgMe::encrypt(const QByteArray &inBuffer, unsigned long length, QByteArray *outBuffer, QString keyid /* = QString() */) { gpgme_error_t err = 0; gpgme_data_t in = 0, out = 0; gpgme_key_t keys[2] = {NULL, NULL}; gpgme_key_t *key = NULL; gpgme_encrypt_result_t result = 0; outBuffer->resize(0); if (m_ctx) { err = gpgme_data_new_from_mem(&in, inBuffer.data(), length, 1); if (!err) { err = gpgme_data_new(&out); if (!err) { if (keyid.isNull()) { key = NULL; } else { err = gpgme_get_key(m_ctx, keyid.toLatin1(), &keys[0], 0); key = keys; } if (!err) { err = gpgme_op_encrypt(m_ctx, key, GPGME_ENCRYPT_ALWAYS_TRUST, in, out); if (!err) { result = gpgme_op_encrypt_result(m_ctx); if (result->invalid_recipients) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("That public key is not meant for encryption")).arg(result->invalid_recipients->fpr)); } else { err = readToBuffer(out, outBuffer); } } } } } } if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } if (err != GPG_ERR_NO_ERROR) { DEBUG_WIN << "KGpgMe::encrypt error: " + QString::number(err); clearCache(); } if (keys[0]) gpgme_key_unref(keys[0]); if (in) gpgme_data_release(in); if (out) gpgme_data_release(out); return (err == GPG_ERR_NO_ERROR); } bool KGpgMe::decrypt(const QByteArray &inBuffer, QByteArray *outBuffer) { gpgme_error_t err = 0; gpgme_data_t in = 0, out = 0; gpgme_decrypt_result_t result = 0; outBuffer->resize(0); if (m_ctx) { err = gpgme_data_new_from_mem(&in, inBuffer.data(), inBuffer.size(), 1); if (!err) { err = gpgme_data_new(&out); if (!err) { err = gpgme_op_decrypt(m_ctx, in, out); if (!err) { result = gpgme_op_decrypt_result(m_ctx); if (result->unsupported_algorithm) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("Unsupported algorithm")).arg(result->unsupported_algorithm)); } else { err = readToBuffer(out, outBuffer); } } } } } if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) { KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err))); } if (err != GPG_ERR_NO_ERROR) clearCache(); if (in) gpgme_data_release(in); if (out) gpgme_data_release(out); return (err == GPG_ERR_NO_ERROR); } #define BUF_SIZE (32 * 1024) gpgme_error_t KGpgMe::readToBuffer(gpgme_data_t in, QByteArray *outBuffer) const { int ret; gpgme_error_t err = GPG_ERR_NO_ERROR; ret = gpgme_data_seek(in, 0, SEEK_SET); if (ret) { err = gpgme_err_code_from_errno(errno); } else { char *buf = new char[BUF_SIZE + 2]; if (buf) { while ((ret = gpgme_data_read(in, buf, BUF_SIZE)) > 0) { uint size = outBuffer->size(); outBuffer->resize(size + ret); memcpy(outBuffer->data() + size, buf, ret); } if (ret < 0) err = gpgme_err_code_from_errno(errno); delete[] buf; } } return err; } bool KGpgMe::isGnuPGAgentAvailable() { QString agent_info = qgetenv("GPG_AGENT_INFO"); if (agent_info.indexOf(':') > 0) return true; return false; } void KGpgMe::setPassphraseCb() { bool agent = false; QString agent_info; agent_info = qgetenv("GPG_AGENT_INFO"); if (m_useGnuPGAgent) { if (agent_info.indexOf(':')) agent = true; if (agent_info.startsWith(QLatin1String("disable:"))) setenv("GPG_AGENT_INFO", agent_info.mid(8).toLatin1(), 1); } else { if (!agent_info.startsWith(QLatin1String("disable:"))) setenv("GPG_AGENT_INFO", "disable:" + agent_info.toLatin1(), 1); } if (agent) gpgme_set_passphrase_cb(m_ctx, 0, 0); else gpgme_set_passphrase_cb(m_ctx, passphraseCb, this); } gpgme_error_t KGpgMe::passphraseCb(void *hook, const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd) { KGpgMe *gpg = static_cast(hook); return gpg->passphrase(uid_hint, passphrase_info, last_was_bad, fd); } gpgme_error_t KGpgMe::passphrase(const char *uid_hint, const char * /*passphrase_info*/, int last_was_bad, int fd) { QString s; QString gpg_hint = checkForUtf8(uid_hint); bool canceled = false; if (last_was_bad) { s += "" + i18n("Wrong password.") + "

\n\n"; clearCache(); } if (!m_text.isEmpty()) s += m_text + "
"; if (!gpg_hint.isEmpty()) s += gpg_hint; if (m_cache.isEmpty()) { KPasswordDialog dlg; dlg.setPrompt(s); if (m_saving) dlg.setWindowTitle(i18n("Please enter a new password:")); if (dlg.exec()) m_cache = dlg.password(); else canceled = true; } if (!canceled) write(fd, m_cache.data(), m_cache.length()); write(fd, "\n", 1); return canceled ? GPG_ERR_CANCELED : GPG_ERR_NO_ERROR; } #endif // HAVE_LIBGPGME diff --git a/src/linklabel.cpp b/src/linklabel.cpp index 1a45c87..948f9a3 100644 --- a/src/linklabel.cpp +++ b/src/linklabel.cpp @@ -1,731 +1,731 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "linklabel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "htmlexporter.h" #include "kcolorcombo2.h" #include "tools.h" #include "variouswidgets.h" /** LinkLook */ LinkLook *LinkLook::soundLook = new LinkLook(/*useLinkColor=*/false, /*canPreview=*/false); LinkLook *LinkLook::fileLook = new LinkLook(/*useLinkColor=*/false, /*canPreview=*/true); LinkLook *LinkLook::localLinkLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/true); LinkLook *LinkLook::networkLinkLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook *LinkLook::launcherLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook *LinkLook::crossReferenceLook = new LinkLook(/*useLinkColor=*/true, /*canPreview=*/false); LinkLook::LinkLook(bool useLinkColor, bool canPreview) { m_useLinkColor = useLinkColor; m_canPreview = canPreview; m_iconSize = 0; } LinkLook::LinkLook(const LinkLook &other) { m_useLinkColor = other.useLinkColor(); m_canPreview = other.canPreview(); setLook(other.italic(), other.bold(), other.underlining(), other.color(), other.hoverColor(), other.iconSize(), other.preview()); } void LinkLook::setLook(bool italic, bool bold, int underlining, QColor color, QColor hoverColor, int iconSize, int preview) { m_italic = italic; m_bold = bold; m_underlining = underlining; m_color = color; m_hoverColor = hoverColor; m_iconSize = iconSize; m_preview = (canPreview() ? preview : None); } int LinkLook::previewSize() const { if (previewEnabled()) { switch (preview()) { default: case None: return 0; case IconSize: return iconSize(); case TwiceIconSize: return iconSize() * 2; case ThreeIconSize: return iconSize() * 3; } } else return 0; } QColor LinkLook::effectiveColor() const { if (m_color.isValid()) return m_color; else return defaultColor(); } QColor LinkLook::effectiveHoverColor() const { if (m_hoverColor.isValid()) return m_hoverColor; else return defaultHoverColor(); } QColor LinkLook::defaultColor() const { if (m_useLinkColor) return qApp->palette().color(QPalette::Link); else return qApp->palette().color(QPalette::Text); } QColor LinkLook::defaultHoverColor() const { return Qt::red; } LinkLook *LinkLook::lookForURL(const QUrl &url) { return url.isLocalFile() ? localLinkLook : networkLinkLook; } QString LinkLook::toCSS(const QString &cssClass, const QColor &defaultTextColor) const { // Set the link class: QString css = QString("{ display: block; width: 100%;"); if (underlineOutside()) css += " text-decoration: underline;"; else css += " text-decoration: none;"; if (m_italic == true) css += " font-style: italic;"; if (m_bold == true) css += " font-weight: bold;"; QColor textColor = (color().isValid() || m_useLinkColor ? effectiveColor() : defaultTextColor); css += QString(" color: %1; }\n").arg(textColor.name()); QString css2 = css; css.prepend(QString(" .%1 a").arg(cssClass)); css2.prepend(QString(" a.%1").arg(cssClass)); // Set the hover state class: QString hover; if (m_underlining == OnMouseHover) hover = "text-decoration: underline;"; else if (m_underlining == OnMouseOutside) hover = "text-decoration: none;"; if (effectiveHoverColor() != effectiveColor()) { if (!hover.isEmpty()) hover += ' '; hover += QString("color: %4;").arg(effectiveHoverColor().name()); } // But include it only if it contain a different style than non-hover state: if (!hover.isEmpty()) { css += QString(" .%1 a:hover { %2 }\n").arg(cssClass, hover); css2 += QString(" a:hover.%1 { %2 }\n").arg(cssClass, hover); } return css + css2; } /** LinkLabel */ LinkLabel::LinkLabel(int hAlign, int vAlign, QWidget *parent, Qt::WindowFlags f) : QFrame(parent, f) , m_isSelected(false) , m_isHovered(false) , m_look(nullptr) { initLabel(hAlign, vAlign); } LinkLabel::LinkLabel(const QString &title, const QString &icon, LinkLook *look, int hAlign, int vAlign, QWidget *parent, Qt::WindowFlags f) : QFrame(parent, f) , m_isSelected(false) , m_isHovered(false) , m_look(nullptr) { initLabel(hAlign, vAlign); setLink(title, icon, look); } void LinkLabel::initLabel(int hAlign, int vAlign) { m_layout = new QBoxLayout(QBoxLayout::LeftToRight, this); m_icon = new QLabel(this); m_title = new QLabel(this); m_spacer1 = new QSpacerItem(0, 0, QSizePolicy::Preferred /*Expanding*/, QSizePolicy::Preferred /*Expanding*/); m_spacer2 = new QSpacerItem(0, 0, QSizePolicy::Preferred /*Expanding*/, QSizePolicy::Preferred /*Expanding*/); m_hAlign = hAlign; m_vAlign = vAlign; m_title->setTextFormat(Qt::PlainText); // DEGUB: // m_icon->setPaletteBackgroundColor("lightblue"); // m_title->setPaletteBackgroundColor("lightyellow"); } LinkLabel::~LinkLabel() { } void LinkLabel::setLink(const QString &title, const QString &icon, LinkLook *look) { if (look) m_look = look; // Needed for icon size m_title->setText(title); m_title->setVisible(!title.isEmpty()); if (icon.isEmpty()) m_icon->clear(); else { QPixmap pixmap = QIcon::fromTheme(icon).pixmap(m_look->iconSize()); if (!pixmap.isNull()) m_icon->setPixmap(pixmap); } m_icon->setVisible(!icon.isEmpty()); if (look) setLook(look); } void LinkLabel::setLook(LinkLook *look) // FIXME: called externally (so, without setLink()) it's buggy (icon not { m_look = look; QFont font; font.setBold(look->bold()); font.setUnderline(look->underlineOutside()); font.setItalic(look->italic()); m_title->setFont(font); QPalette palette; if (m_isSelected) palette.setColor(m_title->foregroundRole(), QApplication::palette().color(QPalette::Text)); else palette.setColor(m_title->foregroundRole(), look->effectiveColor()); m_title->setPalette(palette); m_icon->setVisible(m_icon->pixmap() && !m_icon->pixmap()->isNull()); setAlign(m_hAlign, m_vAlign); } void LinkLabel::setAlign(int hAlign, int vAlign) { m_hAlign = hAlign; m_vAlign = vAlign; if (!m_look) return; // Define alignment flags : Qt::Alignment hFlag, vFlag; switch (hAlign) { default: case 0: hFlag = Qt::AlignLeft; break; case 1: hFlag = Qt::AlignHCenter; break; case 2: hFlag = Qt::AlignRight; break; } switch (vAlign) { case 0: vFlag = Qt::AlignTop; break; default: case 1: vFlag = Qt::AlignVCenter; break; case 2: vFlag = Qt::AlignBottom; break; } // Clear the widget : m_layout->removeItem(m_spacer1); m_layout->removeWidget(m_icon); m_layout->removeWidget(m_title); m_layout->removeItem(m_spacer2); // Otherwise, minimumSize will be incoherent (last size ? ) m_layout->setSizeConstraint(QLayout::SetMinimumSize); // And re-populate the widget with the appropriates things and order bool addSpacers = (hAlign == 1); m_layout->setDirection(QBoxLayout::LeftToRight); // m_title->setSizePolicy( QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum/*Expanding*/, 0, 0, false) ); m_icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); m_spacer1->changeSize(QSizePolicy::Expanding, QSizePolicy::Preferred); m_spacer2->changeSize(QSizePolicy::Expanding, QSizePolicy::Preferred); m_icon->setAlignment(hFlag | vFlag); m_title->setAlignment(hFlag | vFlag); if (hAlign) m_title->setWordWrap(true); if ((addSpacers && (vAlign != 0)) || (m_title->text().isEmpty() && hAlign == 2)) m_layout->addItem(m_spacer1); if (hAlign == 2) { // If align at right, icon is at right m_layout->addWidget(m_title); m_layout->addWidget(m_icon); } else { m_layout->addWidget(m_icon); m_layout->addWidget(m_title); } if ((addSpacers && (vAlign != 2)) || (m_title->text().isEmpty() && hAlign == 0)) m_layout->addItem(m_spacer2); } void LinkLabel::enterEvent(QEvent *) { m_isHovered = true; if (!m_isSelected) { QPalette palette; palette.setColor(m_title->foregroundRole(), m_look->effectiveHoverColor()); m_title->setPalette(palette); } QFont font = m_title->font(); font.setUnderline(m_look->underlineInside()); m_title->setFont(font); } void LinkLabel::leaveEvent(QEvent *) { m_isHovered = false; if (!m_isSelected) { QPalette palette; palette.setColor(m_title->foregroundRole(), m_look->effectiveColor()); m_title->setPalette(palette); } QFont font = m_title->font(); font.setUnderline(m_look->underlineOutside()); m_title->setFont(font); } void LinkLabel::setSelected(bool selected) { m_isSelected = selected; QPalette palette; if (selected) palette.setColor(m_title->foregroundRole(), QApplication::palette().color(QPalette::HighlightedText)); else if (m_isHovered) palette.setColor(m_title->foregroundRole(), m_look->effectiveHoverColor()); else palette.setColor(m_title->foregroundRole(), m_look->effectiveColor()); m_title->setPalette(palette); } void LinkLabel::setPaletteBackgroundColor(const QColor &color) { QPalette framePalette; framePalette.setColor(QFrame::foregroundRole(), color); QFrame::setPalette(framePalette); QPalette titlePalette; titlePalette.setColor(m_title->foregroundRole(), color); m_title->setPalette(titlePalette); } int LinkLabel::heightForWidth(int w) const { int iconS = (m_icon->isVisible()) ? m_look->iconSize() : 0; // Icon size int iconW = iconS; // Icon width to remove to w int titleH = (m_title->isVisible()) ? m_title->heightForWidth(w - iconW) : 0; // Title height return (titleH >= iconS) ? titleH : iconS; // No margin for the moment ! } /** class LinkDisplay */ LinkDisplay::LinkDisplay() : m_title() , m_icon() , m_preview() , m_look(nullptr) , m_font() , m_minWidth(0) , m_width(0) , m_height(0) { } void LinkDisplay::setLink(const QString &title, const QString &icon, LinkLook *look, const QFont &font) { setLink(title, icon, m_preview, look, font); } void LinkDisplay::setLink(const QString &title, const QString &icon, const QPixmap &preview, LinkLook *look, const QFont &font) { m_title = title; m_icon = icon; m_preview = preview; m_look = look; m_font = font; // "Constants": int BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); int LINK_MARGIN = BUTTON_MARGIN + 2; // Recompute m_minWidth: QRect textRect = QFontMetrics(labelFont(font, false)).boundingRect(0, 0, /*width=*/1, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); int iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); m_minWidth = BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN + textRect.width(); // Recompute m_maxWidth: textRect = QFontMetrics(labelFont(font, false)).boundingRect(0, 0, /*width=*/50000000, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); m_maxWidth = BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN + textRect.width(); // Adjust m_width: if (m_width < m_minWidth) setWidth(m_minWidth); // Recompute m_height: m_height = heightForWidth(m_width); } void LinkDisplay::setWidth(qreal width) { if (width < m_minWidth) width = m_minWidth; if (width != m_width) { m_width = width; m_height = heightForWidth(m_width); } } /** Paint on @p painter * in (@p x, @p y, @p width, @p height) * using @p palette for the button drawing (if @p isHovered) * and the LinkLook color() for the text, * unless [the LinkLook !color.isValid() and it does not useLinkColor()] or [@p isDefaultColor is false]: in this case it will use @p palette's active text color. * It will draw the button if @p isIconButtonHovered. */ void LinkDisplay::paint(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QPalette &palette, bool isDefaultColor, bool isSelected, bool isHovered, bool isIconButtonHovered) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); qreal LINK_MARGIN = BUTTON_MARGIN + 2; QPixmap pixmap; // Load the preview...: if (!isHovered && m_look->previewEnabled() && !m_preview.isNull()) pixmap = m_preview; // ... Or the icon (if no preview or if the "Open" icon should be shown): else { qreal iconSize = m_look->iconSize(); QString iconName = (isHovered ? Global::openNoteIcon() : m_icon); KIconLoader::States iconState = (isIconButtonHovered ? KIconLoader::ActiveState : KIconLoader::DefaultState); pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Desktop, iconSize, iconState, QStringList(), nullptr, /*canReturnNull=*/false); } qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); qreal pixmapX = (iconPreviewWidth - pixmap.width()) / 2; qreal pixmapY = (height - pixmap.height()) / 2; // Draw the button (if any) and the icon: if (isHovered) { QStyleOption opt; opt.rect = QRect(-1, -1, iconPreviewWidth + 2 * BUTTON_MARGIN, height + 2); opt.state = isIconButtonHovered ? (QStyle::State_MouseOver | QStyle::State_Enabled) : QStyle::State_Enabled; qApp->style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, painter); } painter->drawPixmap(x + BUTTON_MARGIN - 1 + pixmapX, y + pixmapY, pixmap); // Figure out the text color: if (isSelected) { painter->setPen(qApp->palette().color(QPalette::HighlightedText)); } else if (isIconButtonHovered) painter->setPen(m_look->effectiveHoverColor()); else if (!isDefaultColor || (!m_look->color().isValid() && !m_look->useLinkColor())) // If the color is FORCED or if the link color default to the text color: painter->setPen(palette.color(QPalette::Active, QPalette::WindowText)); else painter->setPen(m_look->effectiveColor()); // Draw the text: painter->setFont(labelFont(m_font, isIconButtonHovered)); painter->drawText(x + BUTTON_MARGIN - 1 + iconPreviewWidth + LINK_MARGIN, y, width - BUTTON_MARGIN + 1 - iconPreviewWidth - LINK_MARGIN, height, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_title); } QPixmap LinkDisplay::feedbackPixmap(qreal width, qreal height, const QPalette &palette, bool isDefaultColor) { qreal theWidth = qMin(width, maxWidth()); qreal theHeight = qMin(height, heightForWidth(theWidth)); QPixmap pixmap(theWidth, theHeight); pixmap.fill(palette.color(QPalette::Active, QPalette::Background)); QPainter painter(&pixmap); paint(&painter, 0, 0, theWidth, theHeight, palette, isDefaultColor, /*isSelected=*/false, /*isHovered=*/false, /*isIconButtonHovered=*/false); painter.end(); return pixmap; } bool LinkDisplay::iconButtonAt(const QPointF &pos) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); // int LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); return pos.x() <= BUTTON_MARGIN - 1 + iconPreviewWidth + BUTTON_MARGIN; } QRectF LinkDisplay::iconButtonRect() const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); // int LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); return QRectF(0, 0, BUTTON_MARGIN - 1 + iconPreviewWidth + BUTTON_MARGIN, m_height); } QFont LinkDisplay::labelFont(QFont font, bool isIconButtonHovered) const { if (m_look->italic()) font.setItalic(true); if (m_look->bold()) font.setBold(true); if (isIconButtonHovered) { if (m_look->underlineInside()) font.setUnderline(true); } else { if (m_look->underlineOutside()) font.setUnderline(true); } return font; } qreal LinkDisplay::heightForWidth(qreal width) const { qreal BUTTON_MARGIN = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); qreal LINK_MARGIN = BUTTON_MARGIN + 2; qreal iconPreviewWidth = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.width() : 0)); qreal iconPreviewHeight = qMax(m_look->iconSize(), (m_look->previewEnabled() ? m_preview.height() : 0)); QRectF textRect = QFontMetrics(labelFont(m_font, false)).boundingRect(0, 0, width - BUTTON_MARGIN + 1 - iconPreviewWidth - LINK_MARGIN, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_title); return qMax(textRect.height(), iconPreviewHeight + 2 * BUTTON_MARGIN - 2); } QString LinkDisplay::toHtml(const QString & /*imageName*/) const { // TODO return QString(); } QString LinkDisplay::toHtml(HTMLExporter *exporter, const QUrl &url, const QString &title) { QString linkIcon; if (m_look->previewEnabled() && !m_preview.isNull()) { QString fileName = Tools::fileNameForNewFile("preview_" + url.fileName() + ".png", exporter->iconsFolderPath); QString fullPath = exporter->iconsFolderPath + fileName; m_preview.save(fullPath, "PNG"); linkIcon = QString("\"\"").arg(exporter->iconsFolderName + fileName, QString::number(m_preview.width()), QString::number(m_preview.height())); } else { linkIcon = exporter->iconsFolderName + exporter->copyIcon(m_icon, m_look->iconSize()); linkIcon = QString("\"\"").arg(linkIcon, QString::number(m_look->iconSize()), QString::number(m_look->iconSize())); } QString linkTitle = Tools::textToHTMLWithoutP(title.isEmpty() ? m_title : title); return QString("%2 %3").arg(url.toDisplayString(), linkIcon, linkTitle); } /** LinkLookEditWidget **/ LinkLookEditWidget::LinkLookEditWidget(KCModule *module, const QString exTitle, const QString exIcon, QWidget *parent, Qt::WindowFlags fl) : QWidget(parent, fl) { QLabel *label; QVBoxLayout *layout = new QVBoxLayout(this); m_italic = new QCheckBox(i18n("I&talic"), this); layout->addWidget(m_italic); m_bold = new QCheckBox(i18n("&Bold"), this); layout->addWidget(m_bold); QGridLayout *gl = new QGridLayout; layout->addLayout(gl); gl->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 1, /*2*/ 3); m_underlining = new KComboBox(this); m_underlining->addItem(i18n("Always")); m_underlining->addItem(i18n("Never")); m_underlining->addItem(i18n("On mouse hovering")); m_underlining->addItem(i18n("When mouse is outside")); label = new QLabel(this); label->setText(i18n("&Underline:")); label->setBuddy(m_underlining); gl->addWidget(label, 0, 0); gl->addWidget(m_underlining, 0, 1); m_color = new KColorCombo2(QRgb(), this); label = new QLabel(this); label->setText(i18n("Colo&r:")); label->setBuddy(m_color); gl->addWidget(label, 1, 0); gl->addWidget(m_color, 1, 1); m_hoverColor = new KColorCombo2(QRgb(), this); label = new QLabel(this); label->setText(i18n("&Mouse hover color:")); label->setBuddy(m_hoverColor); gl->addWidget(label, 2, 0); gl->addWidget(m_hoverColor, 2, 1); QHBoxLayout *icoLay = new QHBoxLayout(nullptr); m_iconSize = new IconSizeCombo(this); icoLay->addWidget(m_iconSize); label = new QLabel(this); label->setText(i18n("&Icon size:")); label->setBuddy(m_iconSize); gl->addWidget(label, 3, 0); gl->addItem(icoLay, 3, 1); m_preview = new KComboBox(this); m_preview->addItem(i18n("None")); m_preview->addItem(i18n("Icon size")); m_preview->addItem(i18n("Twice the icon size")); m_preview->addItem(i18n("Three times the icon size")); m_label = new QLabel(this); m_label->setText(i18n("&Preview:")); m_label->setBuddy(m_preview); m_hLabel = new HelpLabel(i18n("You disabled preview but still see images?"), i18n("

This is normal because there are several type of notes.
" "This setting only applies to file and local link notes.
" "The images you see are image notes, not file notes.
" "File notes are generic documents, whereas image notes are pictures you can draw in.

" "

When dropping files to baskets, %1 detects their type and shows you the content of the files.
" "For instance, when dropping image or text files, image and text notes are created for them.
" "For type of files %2 does not understand, they are shown as generic file notes with just an icon or file preview and a filename.

" "

If you do not want the application to create notes depending on the content of the files you drop, " "go to the \"General\" page and uncheck \"Image or animation\" in the \"View Content of Added Files for the Following Types\" group.

", // TODO: Note: you can resize down maximum size of images... QGuiApplication::applicationDisplayName(), QGuiApplication::applicationDisplayName()), this); gl->addWidget(m_label, 4, 0); gl->addWidget(m_preview, 4, 1); gl->addWidget(m_hLabel, 5, 1, 1, 2); QGroupBox *gb = new QGroupBox(i18n("Example"), this); QHBoxLayout *gbLayout = new QHBoxLayout; gb->setLayout(gbLayout); m_exLook = new LinkLook; m_example = new LinkLabel(exTitle, exIcon, m_exLook, 1, 1); gbLayout->addWidget(m_example); m_example->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_example->setCursor(QCursor(Qt::PointingHandCursor)); layout->addWidget(gb); m_exTitle = exTitle; m_exIcon = exIcon; - connect(m_italic, SIGNAL(stateChanged(int)), this, SLOT(slotChangeLook())); - connect(m_bold, SIGNAL(stateChanged(int)), this, SLOT(slotChangeLook())); + connect(m_italic, &QCheckBox::stateChanged, this, &LinkLookEditWidget::slotChangeLook); + connect(m_bold, &QCheckBox::stateChanged, this, &LinkLookEditWidget::slotChangeLook); connect(m_underlining, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_color, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_hoverColor, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_iconSize, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_preview, SIGNAL(activated(int)), this, SLOT(slotChangeLook())); connect(m_italic, SIGNAL(stateChanged(int)), module, SLOT(changed())); connect(m_bold, SIGNAL(stateChanged(int)), module, SLOT(changed())); connect(m_underlining, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_color, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_hoverColor, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_iconSize, SIGNAL(activated(int)), module, SLOT(changed())); connect(m_preview, SIGNAL(activated(int)), module, SLOT(changed())); } void LinkLookEditWidget::set(LinkLook *look) { m_look = look; m_italic->setChecked(look->italic()); m_bold->setChecked(look->bold()); m_underlining->setCurrentIndex(look->underlining()); m_preview->setCurrentIndex(look->preview()); m_color->setDefaultColor(m_look->defaultColor()); m_color->setColor(m_look->color()); m_hoverColor->setDefaultColor(m_look->defaultHoverColor()); m_hoverColor->setColor(m_look->hoverColor()); m_iconSize->setSize(look->iconSize()); m_exLook = new LinkLook(*look); m_example->setLook(m_exLook); if (!look->canPreview()) { m_label->setEnabled(false); m_hLabel->setEnabled(false); m_preview->setEnabled(false); } slotChangeLook(); } void LinkLookEditWidget::slotChangeLook() { saveToLook(m_exLook); m_example->setLink(m_exTitle, m_exIcon, m_exLook); // and can't reload it at another size } LinkLookEditWidget::~LinkLookEditWidget() { } void LinkLookEditWidget::saveChanges() { saveToLook(m_look); } void LinkLookEditWidget::saveToLook(LinkLook *look) { look->setLook(m_italic->isChecked(), m_bold->isChecked(), m_underlining->currentIndex(), m_color->color(), m_hoverColor->color(), m_iconSize->iconSize(), (look->canPreview() ? m_preview->currentIndex() : LinkLook::None)); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b29309c..4dce1c4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,275 +1,275 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketstatusbar.h" #include "bnpview.h" #include "global.h" #include "settings.h" /** Container */ MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) , m_settings(nullptr) , m_quit(false) { BasketStatusBar *bar = new BasketStatusBar(statusBar()); m_baskets = new BNPView(this, "BNPViewApp", this, actionCollection(), bar); setCentralWidget(m_baskets); setupActions(); statusBar()->show(); statusBar()->setSizeGripEnabled(true); setAutoSaveSettings(/*groupName=*/QString::fromLatin1("MainWindow"), /*saveWindowSize=*//*FIXME:false:Why was it false??*/ true); // m_actShowToolbar->setChecked( toolBar()->isVisible() ); m_actShowStatusbar->setChecked(statusBar()->isVisible()); - connect(m_baskets, SIGNAL(setWindowCaption(const QString &)), this, SLOT(setWindowTitle(const QString &))); + connect(m_baskets, &BNPView::setWindowCaption, this, &MainWindow::setWindowTitle); // InlineEditors::instance()->richTextToolBar(); setStandardToolBarMenuEnabled(true); createGUI("basketui.rc"); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); applyMainWindowSettings(group); } MainWindow::~MainWindow() { KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); delete m_settings; delete m_baskets; } void MainWindow::setupActions() { actQuit = KStandardAction::quit(this, SLOT(quit()), actionCollection()); QAction *a = nullptr; a = actionCollection()->addAction("minimizeRestore", this, SLOT(minimizeRestore())); a->setText(i18n("Minimize")); a->setIcon(QIcon::fromTheme(QString())); a->setShortcut(0); /** Settings : ************************************************************/ // m_actShowToolbar = KStandardAction::showToolbar( this, SLOT(toggleToolBar()), actionCollection()); m_actShowStatusbar = KStandardAction::showStatusbar(this, SLOT(toggleStatusBar()), actionCollection()); // m_actShowToolbar->setCheckedState( KGuiItem(i18n("Hide &Toolbar")) ); (void)KStandardAction::keyBindings(this, SLOT(showShortcutsSettingsDialog()), actionCollection()); (void)KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); // QAction *actCfgNotifs = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection() ); // actCfgNotifs->setEnabled(false); // Not yet implemented ! actAppConfig = KStandardAction::preferences(this, SLOT(showSettingsDialog()), actionCollection()); } /*void MainWindow::toggleToolBar() { if (toolBar()->isVisible()) toolBar()->hide(); else toolBar()->show(); saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); }*/ void MainWindow::toggleStatusBar() { if (statusBar()->isVisible()) statusBar()->hide(); else statusBar()->show(); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); } void MainWindow::configureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); saveMainWindowSettings(group); KEditToolBar dlg(actionCollection()); - connect(&dlg, SIGNAL(newToolbarConfig()), this, SLOT(slotNewToolbarConfig())); + connect(&dlg, &KEditToolBar::newToolbarConfig, this, &MainWindow::slotNewToolbarConfig); dlg.exec(); } void MainWindow::configureNotifications() { // TODO // KNotifyDialog *dialog = new KNotifyDialog(this, "KNotifyDialog", false); // dialog->show(); } void MainWindow::slotNewToolbarConfig() // This is called when OK or Apply is clicked { // ...if you use any action list, use plugActionList on each here... createGUI("basketui.rc"); // TODO: Reconnect tags menu aboutToShow() ?? // TODO: Does this do anything? plugActionList(QString::fromLatin1("go_baskets_list"), actBasketsList); KConfigGroup group = KSharedConfig::openConfig()->group(autoSaveGroup()); applyMainWindowSettings(group); } void MainWindow::showSettingsDialog() { if (m_settings == nullptr) m_settings = new KSettings::Dialog(qApp->activeWindow()); if (Global::activeMainWindow()) { // Help, RestoreDefaults buttons not implemented! m_settings->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); m_settings->exec(); } else m_settings->show(); } void MainWindow::showShortcutsSettingsDialog() { KShortcutsDialog::configure(actionCollection()); //.setWindowTitle(..) // actionCollection()->writeSettings(); } void MainWindow::ensurePolished() { bool shouldSave = false; // If position and size has never been set, set nice ones: // - Set size to sizeHint() // - Keep the window manager placing the window where it want and save this if (Settings::mainWindowSize().isEmpty()) { // qDebug() << "Main Window Position: Initial Set in show()"; int defaultWidth = qApp->desktop()->width() * 5 / 6; int defaultHeight = qApp->desktop()->height() * 5 / 6; resize(defaultWidth, defaultHeight); // sizeHint() is bad (too small) and we want the user to have a good default area size shouldSave = true; } else { // qDebug() << "Main Window Position: Recall in show(x=" // << Settings::mainWindowPosition().x() << ", y=" << Settings::mainWindowPosition().y() // << ", width=" << Settings::mainWindowSize().width() << ", height=" << Settings::mainWindowSize().height() // << ")"; // move(Settings::mainWindowPosition()); // resize(Settings::mainWindowSize()); } KXmlGuiWindow::ensurePolished(); if (shouldSave) { // qDebug() << "Main Window Position: Save size and position in show(x=" // << pos().x() << ", y=" << pos().y() // << ", width=" << size().width() << ", height=" << size().height() // << ")"; Settings::setMainWindowPosition(pos()); Settings::setMainWindowSize(size()); Settings::saveConfig(); } } void MainWindow::resizeEvent(QResizeEvent *event) { // qDebug() << "Main Window Position: Save size in resizeEvent(width=" << size().width() << ", height=" << size().height() << ") ; isMaximized=" // << (isMaximized() ? "true" : "false"); Settings::setMainWindowSize(size()); Settings::saveConfig(); // Added to make it work (previous lines do not work): // saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); KXmlGuiWindow::resizeEvent(event); } void MainWindow::moveEvent(QMoveEvent *event) { // qDebug() << "Main Window Position: Save position in moveEvent(x=" << pos().x() << ", y=" << pos().y() << ")"; Settings::setMainWindowPosition(pos()); Settings::saveConfig(); // Added to make it work (previous lines do not work): // saveMainWindowSettings( KSharedConfig::openConfig(), autoSaveGroup() ); KXmlGuiWindow::moveEvent(event); } bool MainWindow::queryExit() { hide(); return true; } void MainWindow::quit() { m_quit = true; close(); } bool MainWindow::queryClose() { /* if (m_shuttingDown) // Set in askForQuit(): we don't have to ask again return true;*/ if (qApp->isSavingSession()) { Settings::setStartDocked(false); // If queryClose() is called it's because the window is shown Settings::saveConfig(); return true; } if (Settings::useSystray() && !m_quit /*&& Global::systemTray->parentWidgetTrayClose()*/) { hide(); return false; } else return askForQuit(); } bool MainWindow::askForQuit() { QString message = i18n("

Do you really want to quit %1?

", QGuiApplication::applicationDisplayName()); if (Settings::useSystray()) message += i18n( "

Notice that you do not have to quit the application before ending your desktop session. " "If you end your session while the application is still running, the application will be reloaded the next time you log in.

"); int really = KMessageBox::warningContinueCancel(this, message, i18n("Quit Confirm"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), "confirmQuitAsking"); if (really == KMessageBox::Cancel) { m_quit = false; return false; } return true; } void MainWindow::minimizeRestore() { if (isVisible()) hide(); else show(); } diff --git a/src/newbasketdialog.cpp b/src/newbasketdialog.cpp index a9cd84f..076e8bc 100644 --- a/src/newbasketdialog.cpp +++ b/src/newbasketdialog.cpp @@ -1,351 +1,351 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "newbasketdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For Global::mainWindow() #include #include "basketfactory.h" #include "basketlistview.h" #include "basketscene.h" #include "bnpview.h" #include "global.h" #include "kcolorcombo2.h" #include "tools.h" #include "variouswidgets.h" //For HelpLabel /** class SingleSelectionKIconView: */ SingleSelectionKIconView::SingleSelectionKIconView(QWidget *parent) : QListWidget(parent) , m_lastSelected(nullptr) { setViewMode(QListView::IconMode); - connect(this, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(slotSelectionChanged(QListWidgetItem *))); + connect(this, &SingleSelectionKIconView::currentItemChanged, this, &SingleSelectionKIconView::slotSelectionChanged); } QMimeData *SingleSelectionKIconView::dragObject() { return nullptr; } void SingleSelectionKIconView::slotSelectionChanged(QListWidgetItem *cur) { if (cur) m_lastSelected = cur; } /** class NewBasketDefaultProperties: */ NewBasketDefaultProperties::NewBasketDefaultProperties() : icon(QString()) , backgroundImage(QString()) , backgroundColor() , textColor() , freeLayout(false) , columnCount(1) { } /** class NewBasketDialog: */ NewBasketDialog::NewBasketDialog(BasketScene *parentBasket, const NewBasketDefaultProperties &defaultProperties, QWidget *parent) : QDialog(parent) , m_defaultProperties(defaultProperties) { // QDialog options setWindowTitle(i18n("New Basket")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("NewBasket"); setModal(true); QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); // Icon, Name and Background Color: QHBoxLayout *nameLayout = new QHBoxLayout; // QHBoxLayout *nameLayout = new QHBoxLayout(this); m_icon = new KIconButton(page); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::Action); m_icon->setIconSize(16); m_icon->setIcon(m_defaultProperties.icon.isEmpty() ? "basket" : m_defaultProperties.icon); int size = qMax(m_icon->sizeHint().width(), m_icon->sizeHint().height()); m_icon->setFixedSize(size, size); // Make it square! m_icon->setToolTip(i18n("Icon")); m_name = new QLineEdit(/*i18n("Basket"), */ page); m_name->setMinimumWidth(m_name->fontMetrics().maxWidth() * 20); - connect(m_name, SIGNAL(textChanged(const QString &)), this, SLOT(nameChanged(const QString &))); + connect(m_name, &QLineEdit::textChanged, this, &NewBasketDialog::nameChanged); m_name->setToolTip(i18n("Name")); m_backgroundColor = new KColorCombo2(QColor(), palette().color(QPalette::Base), page); m_backgroundColor->setColor(QColor()); m_backgroundColor->setFixedSize(m_backgroundColor->sizeHint()); m_backgroundColor->setColor(m_defaultProperties.backgroundColor); m_backgroundColor->setToolTip(i18n("Background color")); nameLayout->addWidget(m_icon); nameLayout->addWidget(m_name); nameLayout->addWidget(m_backgroundColor); topLayout->addLayout(nameLayout); QHBoxLayout *layout = new QHBoxLayout; QPushButton *button = new QPushButton(page); KGuiItem::assign(button, KGuiItem(i18n("&Manage Templates..."), "configure")); - connect(button, SIGNAL(clicked()), this, SLOT(manageTemplates())); + connect(button, &QPushButton::clicked, this, &NewBasketDialog::manageTemplates); button->hide(); // Compute the right template to use as the default: QString defaultTemplate = "free"; if (!m_defaultProperties.freeLayout) { if (m_defaultProperties.columnCount == 1) defaultTemplate = "1column"; else if (m_defaultProperties.columnCount == 2) defaultTemplate = "2columns"; else defaultTemplate = "3columns"; } // Empty: // * * * * * // Personal: // *To Do // Professional: // *Meeting Summary // Hobbies: // * m_templates = new SingleSelectionKIconView(page); m_templates->setSelectionMode(QAbstractItemView::SingleSelection); QListWidgetItem *lastTemplate = nullptr; QPixmap icon(40, 53); QPainter painter(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("One column"), m_templates); if (defaultTemplate == "1column") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawLine(icon.width() / 2, 0, icon.width() / 2, icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Two columns"), m_templates); if (defaultTemplate == "2columns") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawLine(icon.width() / 3, 0, icon.width() / 3, icon.height()); painter.drawLine(icon.width() * 2 / 3, 0, icon.width() * 2 / 3, icon.height()); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Three columns"), m_templates); if (defaultTemplate == "3columns") m_templates->setCurrentItem(lastTemplate); painter.begin(&icon); painter.fillRect(0, 0, icon.width(), icon.height(), palette().color(QPalette::Base)); painter.setPen(palette().color(QPalette::Text)); painter.drawRect(0, 0, icon.width(), icon.height()); painter.drawRect(icon.width() / 5, icon.width() / 5, icon.width() / 4, icon.height() / 8); painter.drawRect(icon.width() * 2 / 5, icon.width() * 2 / 5, icon.width() / 4, icon.height() / 8); painter.end(); lastTemplate = new QListWidgetItem(icon, i18n("Free"), m_templates); if (defaultTemplate == "free") m_templates->setCurrentItem(lastTemplate); m_templates->setMinimumHeight(topLayout->minimumSize().width() * 9 / 16); QLabel *label = new QLabel(page); label->setText(i18n("&Template:")); label->setBuddy(m_templates); layout->addWidget(label, /*stretch=*/0, Qt::AlignBottom); layout->addStretch(); layout->addWidget(button, /*stretch=*/0, Qt::AlignBottom); topLayout->addLayout(layout); topLayout->addWidget(m_templates); layout = new QHBoxLayout; m_createIn = new KComboBox(page); m_createIn->addItem(i18n("(Baskets)")); label = new QLabel(page); label->setText(i18n("C&reate in:")); label->setBuddy(m_createIn); HelpLabel *helpLabel = new HelpLabel(i18n("How is it useful?"), i18n("

Creating baskets inside of other baskets to form a hierarchy allows you to be more organized by eg.:

    " "
  • Grouping baskets by themes or topics;
  • " "
  • Grouping baskets in folders for different projects;
  • " "
  • Making sections with sub-baskets representing chapters or pages;
  • " "
  • Making a group of baskets to export together (to eg. email them to people).
"), page); layout->addWidget(label); layout->addWidget(m_createIn); layout->addWidget(helpLabel); layout->addStretch(); topLayout->addLayout(layout); m_basketsMap.clear(); int index; m_basketsMap.insert(/*index=*/0, /*basket=*/0L); index = 1; for (int i = 0; i < Global::bnpView->topLevelItemCount(); i++) { index = populateBasketsList(Global::bnpView->topLevelItem(i), /*indent=*/1, /*index=*/index); } - connect(m_templates, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotOk())); - connect(m_templates, SIGNAL(itemActivated(QListWidgetItem *)), this, SLOT(returnPressed())); + connect(m_templates, &QListWidget::itemDoubleClicked, this, &NewBasketDialog::slotOk); + connect(m_templates, &QListWidget::itemActivated, this, &NewBasketDialog::returnPressed); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); + connect(okButton, &QPushButton::clicked, this, &NewBasketDialog::slotOk); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); okButton->setEnabled(false); if (parentBasket) { int index = 0; for (QMap::Iterator it = m_basketsMap.begin(); it != m_basketsMap.end(); ++it) { if (it.value() == parentBasket) { index = it.key(); break; } } if (index <= 0) return; if (m_createIn->currentIndex() != index) m_createIn->setCurrentIndex(index); } m_name->setFocus(); } void NewBasketDialog::returnPressed() { okButton->animateClick(); } int NewBasketDialog::populateBasketsList(QTreeWidgetItem *item, int indent, int index) { static const int ICON_SIZE = 16; // Get the basket data: BasketScene *basket = ((BasketListViewItem *)item)->basket(); QPixmap icon = KIconLoader::global()->loadIcon(basket->icon(), KIconLoader::NoGroup, ICON_SIZE, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/false); icon = Tools::indentPixmap(icon, indent, 2 * ICON_SIZE / 3); m_createIn->addItem(icon, basket->basketName()); m_basketsMap.insert(index, basket); ++index; for (int i = 0; i < item->childCount(); i++) { // Append children of item to the list: index = populateBasketsList(item->child(i), indent + 1, index); } return index; } NewBasketDialog::~NewBasketDialog() { } void NewBasketDialog::ensurePolished() { QDialog::ensurePolished(); m_name->setFocus(); } void NewBasketDialog::nameChanged(const QString &newName) { okButton->setEnabled(!newName.isEmpty()); } void NewBasketDialog::slotOk() { QListWidgetItem *item = ((SingleSelectionKIconView *)m_templates)->selectedItem(); QString templateName; if (!item) return; if (item->text() == i18n("One column")) templateName = "1column"; if (item->text() == i18n("Two columns")) templateName = "2columns"; if (item->text() == i18n("Three columns")) templateName = "3columns"; if (item->text() == i18n("Free-form")) templateName = "free"; if (item->text() == i18n("Mind map")) templateName = "mindmap"; Global::bnpView->closeAllEditors(); QString backgroundImage; QColor textColor; if (m_backgroundColor->color() == m_defaultProperties.backgroundColor) { backgroundImage = m_defaultProperties.backgroundImage; textColor = m_defaultProperties.textColor; } BasketFactory::newBasket(m_icon->icon(), m_name->text(), m_basketsMap[m_createIn->currentIndex()], backgroundImage, m_backgroundColor->color(), textColor, templateName); if (Global::activeMainWindow()) Global::activeMainWindow()->show(); } void NewBasketDialog::manageTemplates() { KMessageBox::information(this, "Wait a minute! There is no template for now: they will come with time... :-D"); } diff --git a/src/noteedit.cpp b/src/noteedit.cpp index e0244d4..899849d 100644 --- a/src/noteedit.cpp +++ b/src/noteedit.cpp @@ -1,1322 +1,1322 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "noteedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketlistview.h" #include "basketscene.h" #include "focusedwidgets.h" #include "icon_names.h" #include "note.h" #include "notecontent.h" #include "notefactory.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" /** class NoteEditor: */ NoteEditor::NoteEditor(NoteContent *noteContent) { m_isEmpty = false; m_canceled = false; m_widget = nullptr; m_textEdit = nullptr; m_lineEdit = nullptr; m_noteContent = noteContent; } NoteEditor::~NoteEditor() { delete m_widget; } Note *NoteEditor::note() { return m_noteContent->note(); } void NoteEditor::setCursorTo(const QPointF &pos) { // clicked comes from the QMouseEvent, which is in item's coordinate system. if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); m_textEdit->setTextCursor(m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint())); } } void NoteEditor::startSelection(const QPointF &pos) { if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); m_textEdit->setTextCursor(m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint())); } } void NoteEditor::updateSelection(const QPointF &pos) { if (m_textEdit) { QPointF currentPos = note()->mapFromScene(pos); QPointF deltaPos = m_textEdit->pos() - note()->pos(); QTextCursor cursor = m_textEdit->cursorForPosition((currentPos - deltaPos).toPoint()); QTextCursor currentCursor = m_textEdit->textCursor(); // select the text currentCursor.setPosition(cursor.position(), QTextCursor::KeepAnchor); // update the cursor m_textEdit->setTextCursor(currentCursor); } } void NoteEditor::endSelection(const QPointF & /*pos*/) { // For TextEdit inside GraphicsScene selectionChanged() is only generated for the first selected char - // thus we need to call it manually after selection is finished if (FocusedTextEdit *textEdit = dynamic_cast(m_textEdit)) textEdit->onSelectionChanged(); } void NoteEditor::paste(const QPointF &pos, QClipboard::Mode mode) { if (FocusedTextEdit *textEdit = dynamic_cast(m_textEdit)) { setCursorTo(pos); textEdit->paste(mode); } } void NoteEditor::connectActions(BasketScene *scene) { if (m_textEdit) { connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(selectionChangedInEditor())); connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(contentChangedInEditor())); - connect(m_textEdit, SIGNAL(textChanged()), scene, SLOT(placeEditorAndEnsureVisible())); + connect(m_textEdit, &KTextEdit::textChanged, scene, &BasketScene::placeEditorAndEnsureVisible); connect(m_textEdit, SIGNAL(selectionChanged()), scene, SLOT(selectionChangedInEditor())); } else if (m_lineEdit) { connect(m_lineEdit, SIGNAL(textChanged(const QString &)), scene, SLOT(selectionChangedInEditor())); connect(m_lineEdit, SIGNAL(textChanged(const QString &)), scene, SLOT(contentChangedInEditor())); connect(m_lineEdit, SIGNAL(selectionChanged()), scene, SLOT(selectionChangedInEditor())); } } NoteEditor *NoteEditor::editNoteContent(NoteContent *noteContent, QWidget *parent) { TextContent *textContent = dynamic_cast(noteContent); if (textContent) return new TextEditor(textContent, parent); HtmlContent *htmlContent = dynamic_cast(noteContent); if (htmlContent) return new HtmlEditor(htmlContent, parent); ImageContent *imageContent = dynamic_cast(noteContent); if (imageContent) return new ImageEditor(imageContent, parent); AnimationContent *animationContent = dynamic_cast(noteContent); if (animationContent) return new AnimationEditor(animationContent, parent); FileContent *fileContent = dynamic_cast(noteContent); // Same for SoundContent if (fileContent) return new FileEditor(fileContent, parent); LinkContent *linkContent = dynamic_cast(noteContent); if (linkContent) return new LinkEditor(linkContent, parent); CrossReferenceContent *crossReferenceContent = dynamic_cast(noteContent); if (crossReferenceContent) return new CrossReferenceEditor(crossReferenceContent, parent); LauncherContent *launcherContent = dynamic_cast(noteContent); if (launcherContent) return new LauncherEditor(launcherContent, parent); ColorContent *colorContent = dynamic_cast(noteContent); if (colorContent) return new ColorEditor(colorContent, parent); UnknownContent *unknownContent = dynamic_cast(noteContent); if (unknownContent) return new UnknownEditor(unknownContent, parent); return nullptr; } void NoteEditor::setInlineEditor(QWidget *inlineEditor) { if (!m_widget) { m_widget = new QGraphicsProxyWidget(); } m_widget->setWidget(inlineEditor); m_widget->setZValue(500); // m_widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); m_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_textEdit = nullptr; m_lineEdit = nullptr; KTextEdit *textEdit = dynamic_cast(inlineEditor); if (textEdit) { m_textEdit = textEdit; } else { QLineEdit *lineEdit = dynamic_cast(inlineEditor); if (lineEdit) { m_lineEdit = lineEdit; } } } /** class TextEditor: */ TextEditor::TextEditor(TextContent *textContent, QWidget *parent) : NoteEditor(textContent) , m_textContent(textContent) { FocusedTextEdit *textEdit = new FocusedTextEdit(/*disableUpdatesOnKeyPress=*/true, parent); textEdit->setLineWidth(0); textEdit->setMidLineWidth(0); textEdit->setFrameStyle(QFrame::Box); QPalette palette; palette.setColor(textEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(textEdit->foregroundRole(), note()->textColor()); textEdit->setPalette(palette); textEdit->setFont(note()->font()); textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if (Settings::spellCheckTextNotes()) textEdit->setCheckSpellingEnabled(true); textEdit->setPlainText(m_textContent->text()); // Not sure if the following comment is still true // FIXME: Sometimes, the cursor flicker at ends before being positioned where clicked (because qApp->processEvents() I think) textEdit->moveCursor(QTextCursor::End); textEdit->verticalScrollBar()->setCursor(Qt::ArrowCursor); setInlineEditor(textEdit); - connect(textEdit, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); - connect(textEdit, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); + connect(textEdit, &FocusedTextEdit::escapePressed, this, &TextEditor::askValidation); + connect(textEdit, &FocusedTextEdit::mouseEntered, this, &TextEditor::mouseEnteredEditorWidget); - connect(textEdit, SIGNAL(cursorPositionChanged()), textContent->note()->basket(), SLOT(editorCursorPositionChanged())); + connect(textEdit, &FocusedTextEdit::cursorPositionChanged, textContent->note()->basket(), &BasketScene::editorCursorPositionChanged); // In case it is a very big note, the top is displayed and Enter is pressed: the cursor is on bottom, we should enure it visible: QTimer::singleShot(0, textContent->note()->basket(), SLOT(editorCursorPositionChanged())); } TextEditor::~TextEditor() { delete graphicsWidget()->widget(); // TODO: delete that in validate(), so we can remove one method } void TextEditor::autoSave(bool toFileToo) { bool autoSpellCheck = true; if (toFileToo) { if (Settings::spellCheckTextNotes() != textEdit()->checkSpellingEnabled()) { Settings::setSpellCheckTextNotes(textEdit()->checkSpellingEnabled()); Settings::saveConfig(); } autoSpellCheck = textEdit()->checkSpellingEnabled(); textEdit()->setCheckSpellingEnabled(false); } m_textContent->setText(textEdit()->toPlainText()); if (toFileToo) { m_textContent->saveToFile(); m_textContent->setEdited(); textEdit()->setCheckSpellingEnabled(autoSpellCheck); } } void TextEditor::validate() { if (Settings::spellCheckTextNotes() != textEdit()->checkSpellingEnabled()) { Settings::setSpellCheckTextNotes(textEdit()->checkSpellingEnabled()); Settings::saveConfig(); } textEdit()->setCheckSpellingEnabled(false); if (textEdit()->document()->isEmpty()) setEmpty(); m_textContent->setText(textEdit()->toPlainText()); m_textContent->saveToFile(); m_textContent->setEdited(); note()->setWidth(0); } /** class HtmlEditor: */ HtmlEditor::HtmlEditor(HtmlContent *htmlContent, QWidget *parent) : NoteEditor(htmlContent) , m_htmlContent(htmlContent) { FocusedTextEdit *textEdit = new FocusedTextEdit(/*disableUpdatesOnKeyPress=*/true, parent); textEdit->setLineWidth(0); textEdit->setMidLineWidth(0); textEdit->setFrameStyle(QFrame::Box); textEdit->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone); QPalette palette; palette.setColor(textEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(textEdit->foregroundRole(), note()->textColor()); textEdit->setPalette(palette); textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); textEdit->setHtml(Tools::tagCrossReferences(m_htmlContent->html(), /*userLink=*/true)); textEdit->moveCursor(QTextCursor::End); textEdit->verticalScrollBar()->setCursor(Qt::ArrowCursor); setInlineEditor(textEdit); - connect(textEdit, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); - connect(textEdit, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); + connect(textEdit, &FocusedTextEdit::mouseEntered, this, &HtmlEditor::mouseEnteredEditorWidget); + connect(textEdit, &FocusedTextEdit::escapePressed, this, &HtmlEditor::askValidation); - connect(InlineEditors::instance()->richTextFont, SIGNAL(currentFontChanged(const QFont &)), this, SLOT(onFontSelectionChanged(const QFont &))); - connect(InlineEditors::instance()->richTextFontSize, SIGNAL(sizeChanged(qreal)), textEdit, SLOT(setFontPointSize(qreal))); - connect(InlineEditors::instance()->richTextColor, SIGNAL(activated(const QColor &)), textEdit, SLOT(setTextColor(const QColor &))); + connect(InlineEditors::instance()->richTextFont, &QFontComboBox::currentFontChanged, this, &HtmlEditor::onFontSelectionChanged); + connect(InlineEditors::instance()->richTextFontSize, &FontSizeCombo::sizeChanged, textEdit, &FocusedTextEdit::setFontPointSize); + connect(InlineEditors::instance()->richTextColor, &KColorCombo::activated, textEdit, &FocusedTextEdit::setTextColor); connect(InlineEditors::instance()->focusWidgetFilter, SIGNAL(escapePressed()), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->focusWidgetFilter, SIGNAL(returnPressed()), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->richTextFont, SIGNAL(activated(int)), textEdit, SLOT(setFocus())); connect(InlineEditors::instance()->richTextFontSize, SIGNAL(activated(int)), textEdit, SLOT(setFocus())); connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged())); connect(textEdit, SIGNAL(currentCharFormatChanged(const QTextCharFormat &)), this, SLOT(charFormatChanged(const QTextCharFormat &))); // connect( textEdit, SIGNAL(currentVerticalAlignmentChanged(VerticalAlignment)), this, SLOT(slotVerticalAlignmentChanged()) ); connect(InlineEditors::instance()->richTextBold, SIGNAL(triggered(bool)), this, SLOT(setBold(bool))); connect(InlineEditors::instance()->richTextItalic, SIGNAL(triggered(bool)), textEdit, SLOT(setFontItalic(bool))); connect(InlineEditors::instance()->richTextUnderline, SIGNAL(triggered(bool)), textEdit, SLOT(setFontUnderline(bool))); connect(InlineEditors::instance()->richTextLeft, SIGNAL(triggered()), this, SLOT(setLeft())); connect(InlineEditors::instance()->richTextCenter, SIGNAL(triggered()), this, SLOT(setCentered())); connect(InlineEditors::instance()->richTextRight, SIGNAL(triggered()), this, SLOT(setRight())); connect(InlineEditors::instance()->richTextJustified, SIGNAL(triggered()), this, SLOT(setBlock())); // InlineEditors::instance()->richTextToolBar()->show(); cursorPositionChanged(); charFormatChanged(textEdit->currentCharFormat()); // QTimer::singleShot( 0, this, SLOT(cursorPositionChanged()) ); InlineEditors::instance()->enableRichTextToolBar(); connect(InlineEditors::instance()->richTextUndo, SIGNAL(triggered()), textEdit, SLOT(undo())); connect(InlineEditors::instance()->richTextRedo, SIGNAL(triggered()), textEdit, SLOT(redo())); connect(textEdit, SIGNAL(undoAvailable(bool)), InlineEditors::instance()->richTextUndo, SLOT(setEnabled(bool))); connect(textEdit, SIGNAL(redoAvailable(bool)), InlineEditors::instance()->richTextRedo, SLOT(setEnabled(bool))); connect(textEdit, SIGNAL(textChanged()), this, SLOT(editTextChanged())); InlineEditors::instance()->richTextUndo->setEnabled(false); InlineEditors::instance()->richTextRedo->setEnabled(false); connect(textEdit, SIGNAL(cursorPositionChanged()), htmlContent->note()->basket(), SLOT(editorCursorPositionChanged())); // In case it is a very big note, the top is displayed and Enter is pressed: the cursor is on bottom, we should enure it visible: QTimer::singleShot(0, htmlContent->note()->basket(), SLOT(editorCursorPositionChanged())); } void HtmlEditor::cursorPositionChanged() { InlineEditors::instance()->richTextFont->setCurrentFont(textEdit()->currentFont().family()); if (InlineEditors::instance()->richTextColor->color() != textEdit()->textColor()) InlineEditors::instance()->richTextColor->setColor(textEdit()->textColor()); InlineEditors::instance()->richTextBold->setChecked((textEdit()->fontWeight() >= QFont::Bold)); InlineEditors::instance()->richTextItalic->setChecked(textEdit()->fontItalic()); InlineEditors::instance()->richTextUnderline->setChecked(textEdit()->fontUnderline()); switch (textEdit()->alignment()) { default: case 1 /*Qt::AlignLeft*/: InlineEditors::instance()->richTextLeft->setChecked(true); break; case 2 /*Qt::AlignRight*/: InlineEditors::instance()->richTextRight->setChecked(true); break; case 4 /*Qt::AlignHCenter*/: InlineEditors::instance()->richTextCenter->setChecked(true); break; case 8 /*Qt::AlignJustify*/: InlineEditors::instance()->richTextJustified->setChecked(true); break; } } void HtmlEditor::editTextChanged() { // The following is a workaround for an apparent Qt bug. // When I start typing in a textEdit, the undo&redo actions are not enabled until I click // or move the cursor - probably, the signal undoAvailable() is not emitted. // So, I had to intervene and do that manually. InlineEditors::instance()->richTextUndo->setEnabled(textEdit()->document()->isUndoAvailable()); InlineEditors::instance()->richTextRedo->setEnabled(textEdit()->document()->isRedoAvailable()); } void HtmlEditor::charFormatChanged(const QTextCharFormat &format) { InlineEditors::instance()->richTextFontSize->setFontSize(format.font().pointSize()); } /*void HtmlEditor::slotVerticalAlignmentChanged(QTextEdit::VerticalAlignment align) { QTextEdit::VerticalAlignment align = textEdit() switch (align) { case KTextEdit::AlignSuperScript: InlineEditors::instance()->richTextSuper->setChecked(true); InlineEditors::instance()->richTextSub->setChecked(false); break; case KTextEdit::AlignSubScript: InlineEditors::instance()->richTextSuper->setChecked(false); InlineEditors::instance()->richTextSub->setChecked(true); break; default: InlineEditors::instance()->richTextSuper->setChecked(false); InlineEditors::instance()->richTextSub->setChecked(false); } NoteHtmlEditor::buttonToggled(int id) : case 106: if (isChecked && m_toolbar->isButtonOn(107)) m_toolbar->setButton(107, false); m_text->setVerticalAlignment(isChecked ? KTextEdit::AlignSuperScript : KTextEdit::AlignNormal); break; case 107: if (isChecked && m_toolbar->isButtonOn(106)) m_toolbar->setButton(106, false); m_text->setVerticalAlignment(isChecked ? KTextEdit::AlignSubScript : KTextEdit::AlignNormal); break; }*/ void HtmlEditor::setLeft() { textEdit()->setAlignment(Qt::AlignLeft); } void HtmlEditor::setRight() { textEdit()->setAlignment(Qt::AlignRight); } void HtmlEditor::setCentered() { textEdit()->setAlignment(Qt::AlignHCenter); } void HtmlEditor::setBlock() { textEdit()->setAlignment(Qt::AlignJustify); } void HtmlEditor::onFontSelectionChanged(const QFont &font) { // Change font family only textEdit()->setFontFamily(font.family()); InlineEditors::instance()->richTextFont->clearFocus(); // textEdit()->setFocus(); } void HtmlEditor::setBold(bool isChecked) { qWarning() << "setBold " << isChecked; textEdit()->setFontWeight(isChecked ? QFont::Bold : QFont::Normal); } HtmlEditor::~HtmlEditor() { // delete graphicsWidget()->widget(); } void HtmlEditor::autoSave(bool toFileToo) { m_htmlContent->setHtml(textEdit()->document()->toHtml("utf-8")); if (toFileToo) { m_htmlContent->saveToFile(); m_htmlContent->setEdited(); } } void HtmlEditor::validate() { if (Tools::htmlToText(textEdit()->toHtml()).isEmpty()) setEmpty(); QString convert = textEdit()->document()->toHtml("utf-8"); if (note()->allowCrossReferences()) convert = Tools::tagCrossReferences(convert, /*userLink=*/true); m_htmlContent->setHtml(convert); m_htmlContent->saveToFile(); m_htmlContent->setEdited(); disconnect(); graphicsWidget()->disconnect(); if (InlineEditors::instance()) { InlineEditors::instance()->disableRichTextToolBar(); // if (InlineEditors::instance()->richTextToolBar()) // InlineEditors::instance()->richTextToolBar()->hide(); } if (graphicsWidget()) { note()->setZValue(1); delete graphicsWidget()->widget(); setInlineEditor(nullptr); } } /** class ImageEditor: */ ImageEditor::ImageEditor(ImageContent *imageContent, QWidget *parent) : NoteEditor(imageContent) { int choice = KMessageBox::questionYesNoCancel(parent, i18n("Images can not be edited here at the moment (the next version of BasKet Note Pads will include an image editor).\n" "Do you want to open it with an application that understand it?"), i18n("Edit Image Note"), KStandardGuiItem::open(), KGuiItem(i18n("Load From &File..."), IconNames::DOCUMENT_IMPORT), KStandardGuiItem::cancel()); switch (choice) { case (KMessageBox::Yes): note()->basket()->noteOpen(note()); break; case (KMessageBox::No): // Load from file cancel(); Global::bnpView->insertWizard(3); // 3 maps to m_actLoadFile break; case (KMessageBox::Cancel): cancel(); break; } } /** class AnimationEditor: */ AnimationEditor::AnimationEditor(AnimationContent *animationContent, QWidget *parent) : NoteEditor(animationContent) { int choice = KMessageBox::questionYesNo(parent, i18n("This animated image can not be edited here.\n" "Do you want to open it with an application that understands it?"), i18n("Edit Animation Note"), KStandardGuiItem::open(), KStandardGuiItem::cancel()); if (choice == KMessageBox::Yes) note()->basket()->noteOpen(note()); } /** class FileEditor: */ FileEditor::FileEditor(FileContent *fileContent, QWidget *parent) : NoteEditor(fileContent) , m_fileContent(fileContent) { QLineEdit *lineEdit = new QLineEdit(parent); FocusWidgetFilter *filter = new FocusWidgetFilter(lineEdit); QPalette palette; palette.setColor(lineEdit->backgroundRole(), note()->backgroundColor()); palette.setColor(lineEdit->foregroundRole(), note()->textColor()); lineEdit->setPalette(palette); lineEdit->setFont(note()->font()); lineEdit->setText(m_fileContent->fileName()); lineEdit->selectAll(); setInlineEditor(lineEdit); connect(filter, SIGNAL(returnPressed()), this, SIGNAL(askValidation())); connect(filter, SIGNAL(escapePressed()), this, SIGNAL(askValidation())); connect(filter, SIGNAL(mouseEntered()), this, SIGNAL(mouseEnteredEditorWidget())); } FileEditor::~FileEditor() { delete graphicsWidget()->widget(); } void FileEditor::autoSave(bool toFileToo) { // FIXME: How to detect cancel? if (toFileToo && !lineEdit()->text().isEmpty() && m_fileContent->trySetFileName(lineEdit()->text())) { m_fileContent->setFileName(lineEdit()->text()); m_fileContent->setEdited(); } } void FileEditor::validate() { autoSave(/*toFileToo=*/true); } /** class LinkEditor: */ LinkEditor::LinkEditor(LinkContent *linkContent, QWidget *parent) : NoteEditor(linkContent) { QPointer dialog = new LinkEditDialog(linkContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (linkContent->url().isEmpty() && linkContent->title().isEmpty()) setEmpty(); } /** class CrossReferenceEditor: */ CrossReferenceEditor::CrossReferenceEditor(CrossReferenceContent *crossReferenceContent, QWidget *parent) : NoteEditor(crossReferenceContent) { QPointer dialog = new CrossReferenceEditDialog(crossReferenceContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (crossReferenceContent->url().isEmpty() && crossReferenceContent->title().isEmpty()) setEmpty(); } /** class LauncherEditor: */ LauncherEditor::LauncherEditor(LauncherContent *launcherContent, QWidget *parent) : NoteEditor(launcherContent) { QPointer dialog = new LauncherEditDialog(launcherContent, parent); if (dialog->exec() == QDialog::Rejected) cancel(); if (launcherContent->name().isEmpty() && launcherContent->exec().isEmpty()) setEmpty(); } /** class ColorEditor: */ ColorEditor::ColorEditor(ColorContent *colorContent, QWidget *parent) : NoteEditor(colorContent) { QPointer dialog = new QColorDialog(parent); dialog->setCurrentColor(colorContent->color()); dialog->setWindowTitle(i18n("Edit Color Note")); // dialog->setButtons(QDialog::Ok | QDialog::Cancel); if (dialog->exec() == QDialog::Accepted) { if (dialog->currentColor() != colorContent->color()) { colorContent->setColor(dialog->currentColor()); colorContent->setEdited(); } } else cancel(); /* This code don't allow to set a caption to the dialog: QColor color = colorContent()->color(); color = QColorDialog::getColor(parent)==QDialog::Accepted&&color!=m_color); if ( color.isValid() ) { colorContent()->setColor(color); setEdited(); }*/ } /** class UnknownEditor: */ UnknownEditor::UnknownEditor(UnknownContent *unknownContent, QWidget *parent) : NoteEditor(unknownContent) { KMessageBox::information(parent, i18n("The type of this note is unknown and can not be edited here.\n" "You however can drag or copy the note into an application that understands it."), i18n("Edit Unknown Note")); } /*********************************************************************/ /** class LinkEditDialog: */ LinkEditDialog::LinkEditDialog(LinkContent *contentNote, QWidget *parent /*, QKeyEvent *ke*/) : QDialog(parent) , m_noteContent(contentNote) { // QDialog options setWindowTitle(i18n("Edit Link Note")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("EditLink"); setModal(true); QWidget *page = new QWidget(this); mainLayout->addWidget(page); // QGridLayout *layout = new QGridLayout(page, /*nRows=*/4, /*nCols=*/2, /*margin=*/0, spacingHint()); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); QWidget *wid1 = new QWidget(page); mainLayout->addWidget(wid1); QHBoxLayout *titleLay = new QHBoxLayout(wid1); m_title = new QLineEdit(m_noteContent->title(), wid1); m_autoTitle = new QPushButton(i18n("Auto"), wid1); m_autoTitle->setCheckable(true); m_autoTitle->setChecked(m_noteContent->autoTitle()); titleLay->addWidget(m_title); titleLay->addWidget(m_autoTitle); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QHBoxLayout *hLay = new QHBoxLayout(wid); m_icon = new KIconButton(wid); QLabel *label3 = new QLabel(page); mainLayout->addWidget(label3); label3->setText(i18n("&Icon:")); label3->setBuddy(m_icon); if (m_noteContent->url().isEmpty()) { m_url = new KUrlRequester(QUrl(QString()), wid); m_url->setMode(KFile::File | KFile::ExistingOnly); } else { m_url = new KUrlRequester(m_noteContent->url().toDisplayString(), wid); m_url->setMode(KFile::File | KFile::ExistingOnly); } if (m_noteContent->title().isEmpty()) { m_title->setText(QString()); } else { m_title->setText(m_noteContent->title()); } QUrl filteredURL = NoteFactory::filteredURL(QUrl::fromUserInput(m_url->lineEdit()->text())); // KURIFilter::self()->filteredURI(KUrl(m_url->lineEdit()->text())); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::MimeType); m_icon->setIconSize(LinkLook::lookForURL(filteredURL)->iconSize()); m_autoIcon = new QPushButton(i18n("Auto"), wid); // Create before to know size here: /* Icon button: */ m_icon->setIcon(m_noteContent->icon()); int minSize = m_autoIcon->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square /* Auto button: */ m_autoIcon->setCheckable(true); m_autoIcon->setChecked(m_noteContent->autoIcon()); hLay->addWidget(m_icon); hLay->addWidget(m_autoIcon); hLay->addStretch(); m_url->lineEdit()->setMinimumWidth(m_url->lineEdit()->fontMetrics().maxWidth() * 20); m_title->setMinimumWidth(m_title->fontMetrics().maxWidth() * 20); // m_url->setShowLocalProtocol(true); QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Ta&rget:")); label1->setBuddy(m_url); QLabel *label2 = new QLabel(page); mainLayout->addWidget(label2); label2->setText(i18n("&Title:")); label2->setBuddy(m_title); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(label2, 1, 0, Qt::AlignVCenter); layout->addWidget(label3, 2, 0, Qt::AlignVCenter); layout->addWidget(m_url, 0, 1, Qt::AlignVCenter); layout->addWidget(wid1, 1, 1, Qt::AlignVCenter); layout->addWidget(wid, 2, 1, Qt::AlignVCenter); m_isAutoModified = false; connect(m_url, SIGNAL(textChanged(const QString &)), this, SLOT(urlChanged(const QString &))); connect(m_title, SIGNAL(textChanged(const QString &)), this, SLOT(doNotAutoTitle(const QString &))); connect(m_icon, SIGNAL(iconChanged(QString)), this, SLOT(doNotAutoIcon(QString))); connect(m_autoTitle, SIGNAL(clicked()), this, SLOT(guessTitle())); connect(m_autoIcon, SIGNAL(clicked()), this, SLOT(guessIcon())); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); // urlChanged(QString()); // if (ke) // qApp->postEvent(m_url->lineEdit(), ke); } LinkEditDialog::~LinkEditDialog() { } void LinkEditDialog::ensurePolished() { QDialog::ensurePolished(); if (m_url->lineEdit()->text().isEmpty()) { m_url->setFocus(); m_url->lineEdit()->end(false); } else { m_title->setFocus(); m_title->end(false); } } void LinkEditDialog::urlChanged(const QString &) { m_isAutoModified = true; // guessTitle(); // guessIcon(); // Optimization (filter only once): QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); if (m_autoIcon->isChecked()) m_icon->setIcon(NoteFactory::iconForURL(filteredURL)); if (m_autoTitle->isChecked()) { m_title->setText(NoteFactory::titleForURL(filteredURL)); m_autoTitle->setChecked(true); // Because the setText() will disable it! } } void LinkEditDialog::doNotAutoTitle(const QString &) { if (m_isAutoModified) m_isAutoModified = false; else m_autoTitle->setChecked(false); } void LinkEditDialog::doNotAutoIcon(QString) { m_autoIcon->setChecked(false); } void LinkEditDialog::guessIcon() { if (m_autoIcon->isChecked()) { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_icon->setIcon(NoteFactory::iconForURL(filteredURL)); } } void LinkEditDialog::guessTitle() { if (m_autoTitle->isChecked()) { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_title->setText(NoteFactory::titleForURL(filteredURL)); m_autoTitle->setChecked(true); // Because the setText() will disable it! } } void LinkEditDialog::slotOk() { QUrl filteredURL = NoteFactory::filteredURL(m_url->url()); // KURIFilter::self()->filteredURI(KUrl(m_url->url())); m_noteContent->setLink(filteredURL, m_title->text(), m_icon->icon(), m_autoTitle->isChecked(), m_autoIcon->isChecked()); m_noteContent->setEdited(); /* Change icon size if link look have changed */ LinkLook *linkLook = LinkLook::lookForURL(filteredURL); QString icon = m_icon->icon(); // When we change size, icon isn't changed and keep it's old size m_icon->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Reset size policy m_icon->setIconSize(linkLook->iconSize()); // So I store it's name and reload it after size change ! m_icon->setIcon(icon); int minSize = m_autoIcon->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square } /** class CrossReferenceEditDialog: */ CrossReferenceEditDialog::CrossReferenceEditDialog(CrossReferenceContent *contentNote, QWidget *parent /*, QKeyEvent *ke*/) : QDialog(parent) , m_noteContent(contentNote) { QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Edit Cross Reference")); QWidget *page = new QWidget(this); mainLayout->addWidget(page); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); m_targetBasket = new KComboBox(wid); this->generateBasketList(m_targetBasket); if (m_noteContent->url().isEmpty()) { BasketListViewItem *item = Global::bnpView->topLevelItem(0); m_noteContent->setCrossReference(QUrl::fromUserInput(item->data(0, Qt::UserRole).toString()), m_targetBasket->currentText(), "edit-copy"); this->urlChanged(0); } else { QString url = m_noteContent->url().url(); // cannot use findData because I'm using a StringList and I don't have the second // piece of data to make find work. for (int i = 0; i < m_targetBasket->count(); ++i) { if (url == m_targetBasket->itemData(i, Qt::UserRole).toStringList().first()) { m_targetBasket->setCurrentIndex(i); break; } } } QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Ta&rget:")); label1->setBuddy(m_targetBasket); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(m_targetBasket, 0, 1, Qt::AlignVCenter); connect(m_targetBasket, SIGNAL(activated(int)), this, SLOT(urlChanged(int))); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); setObjectName("EditCrossReference"); setModal(true); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); } CrossReferenceEditDialog::~CrossReferenceEditDialog() { } void CrossReferenceEditDialog::urlChanged(const int index) { if (m_targetBasket) m_noteContent->setCrossReference( QUrl::fromUserInput(m_targetBasket->itemData(index, Qt::UserRole).toStringList().first()), m_targetBasket->currentText().trimmed(), m_targetBasket->itemData(index, Qt::UserRole).toStringList().last()); } void CrossReferenceEditDialog::slotOk() { m_noteContent->setEdited(); } void CrossReferenceEditDialog::generateBasketList(KComboBox *targetList, BasketListViewItem *item, int indent) { if (!item) { // include ALL top level items and their children. for (int i = 0; i < Global::bnpView->topLevelItemCount(); ++i) this->generateBasketList(targetList, Global::bnpView->topLevelItem(i)); } else { BasketScene *bv = item->basket(); // TODO: add some fancy deco stuff to make it look like a tree list. QString pad; QString text = item->text(0); // user text text.prepend(pad.fill(' ', indent * 2)); // create the link text QString link = "basket://"; link.append(bv->folderName().toLower()); // unique ref. QStringList data; data.append(link); data.append(bv->icon()); targetList->addItem(item->icon(0), text, QVariant(data)); int subBasketCount = item->childCount(); if (subBasketCount > 0) { indent++; for (int i = 0; i < subBasketCount; ++i) { this->generateBasketList(targetList, (BasketListViewItem *)item->child(i), indent); } } } } /** class LauncherEditDialog: */ LauncherEditDialog::LauncherEditDialog(LauncherContent *contentNote, QWidget *parent) : QDialog(parent) , m_noteContent(contentNote) { QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Edit Launcher Note")); setObjectName("EditLauncher"); setModal(true); QWidget *page = new QWidget(this); mainLayout->addWidget(page); // QGridLayout *layout = new QGridLayout(page, /*nRows=*/4, /*nCols=*/2, /*margin=*/0, spacingHint()); QGridLayout *layout = new QGridLayout(page); mainLayout->addLayout(layout); KService service(contentNote->fullPath()); m_command = new RunCommandRequester(service.exec(), i18n("Choose a command to run:"), page); mainLayout->addWidget(m_command); m_name = new QLineEdit(service.name(), page); mainLayout->addWidget(m_name); QWidget *wid = new QWidget(page); mainLayout->addWidget(wid); QHBoxLayout *hLay = new QHBoxLayout(wid); m_icon = new KIconButton(wid); QLabel *label = new QLabel(page); mainLayout->addWidget(label); label->setText(i18n("&Icon:")); label->setBuddy(m_icon); m_icon->setIconType(KIconLoader::NoGroup, KIconLoader::Application); m_icon->setIconSize(LinkLook::launcherLook->iconSize()); QPushButton *guessButton = new QPushButton(i18n("&Guess"), wid); /* Icon button: */ m_icon->setIcon(service.icon()); int minSize = guessButton->sizeHint().height(); // Make the icon button at least the same height than the other buttons for a better alignment (nicer to the eyes): if (m_icon->sizeHint().height() < minSize) m_icon->setFixedSize(minSize, minSize); else m_icon->setFixedSize(m_icon->sizeHint().height(), m_icon->sizeHint().height()); // Make it square /* Guess button: */ hLay->addWidget(m_icon); hLay->addWidget(guessButton); hLay->addStretch(); m_command->lineEdit()->setMinimumWidth(m_command->lineEdit()->fontMetrics().maxWidth() * 20); QLabel *label1 = new QLabel(page); mainLayout->addWidget(label1); label1->setText(i18n("Comman&d:")); label1->setBuddy(m_command->lineEdit()); QLabel *label2 = new QLabel(page); mainLayout->addWidget(label2); label2->setText(i18n("&Name:")); label2->setBuddy(m_name); layout->addWidget(label1, 0, 0, Qt::AlignVCenter); layout->addWidget(label2, 1, 0, Qt::AlignVCenter); layout->addWidget(label, 2, 0, Qt::AlignVCenter); layout->addWidget(m_command, 0, 1, Qt::AlignVCenter); layout->addWidget(m_name, 1, 1, Qt::AlignVCenter); layout->addWidget(wid, 2, 1, Qt::AlignVCenter); QWidget *stretchWidget = new QWidget(page); mainLayout->addWidget(stretchWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Expanding); policy.setHorizontalStretch(1); policy.setVerticalStretch(255); stretchWidget->setSizePolicy(policy); // Make it fill ALL vertical space layout->addWidget(stretchWidget, 3, 1, Qt::AlignVCenter); connect(guessButton, SIGNAL(clicked()), this, SLOT(guessIcon())); } LauncherEditDialog::~LauncherEditDialog() { } void LauncherEditDialog::ensurePolished() { QDialog::ensurePolished(); if (m_command->runCommand().isEmpty()) { m_command->lineEdit()->setFocus(); m_command->lineEdit()->end(false); } else { m_name->setFocus(); m_name->end(false); } } void LauncherEditDialog::slotOk() { // TODO: Remember if a string has been modified AND IS DIFFERENT FROM THE // ORIGINAL! KDesktopFile dtFile(m_noteContent->fullPath()); KConfigGroup grp = dtFile.desktopGroup(); grp.writeEntry("Exec", m_command->runCommand()); grp.writeEntry("Name", m_name->text()); grp.writeEntry("Icon", m_icon->icon()); // Just for faster feedback: conf object will save to disk (and then // m_note->loadContent() called) m_noteContent->setLauncher(m_name->text(), m_icon->icon(), m_command->runCommand()); m_noteContent->setEdited(); } void LauncherEditDialog::guessIcon() { m_icon->setIcon(NoteFactory::iconForCommand(m_command->runCommand())); } /** class InlineEditors: */ InlineEditors::InlineEditors() { } InlineEditors::~InlineEditors() { } InlineEditors *InlineEditors::instance() { static InlineEditors *instance = nullptr; if (!instance) instance = new InlineEditors(); return instance; } void InlineEditors::initToolBars(KActionCollection *ac) { QFont defaultFont; QColor textColor = (Global::bnpView && Global::bnpView->currentBasket() ? Global::bnpView->currentBasket()->textColor() : palette().color(QPalette::Text)); // NOTE: currently it is NULL since initToolBars is called early. Could use different way to get MainWindow pointer from main KMainWindow *parent = Global::activeMainWindow(); // Init the RichTextEditor Toolbar: richTextFont = new QFontComboBox(Global::activeMainWindow()); focusWidgetFilter = new FocusWidgetFilter(richTextFont); richTextFont->setFixedWidth(richTextFont->sizeHint().width() * 2 / 3); richTextFont->setCurrentFont(defaultFont.family()); QWidgetAction *action = new QWidgetAction(parent); ac->addAction("richtext_font", action); action->setDefaultWidget(richTextFont); action->setText(i18n("Font")); ac->setDefaultShortcut(action, Qt::Key_F6); richTextFontSize = new FontSizeCombo(/*rw=*/true, Global::activeMainWindow()); richTextFontSize->setFontSize(defaultFont.pointSize()); action = new QWidgetAction(parent); ac->addAction("richtext_font_size", action); action->setDefaultWidget(richTextFontSize); action->setText(i18n("Font Size")); ac->setDefaultShortcut(action, Qt::Key_F7); richTextColor = new KColorCombo(Global::activeMainWindow()); richTextColor->installEventFilter(focusWidgetFilter); richTextColor->setFixedWidth(richTextColor->sizeHint().height() * 2); richTextColor->setColor(textColor); action = new QWidgetAction(parent); ac->addAction("richtext_color", action); action->setDefaultWidget(richTextColor); action->setText(i18n("Color")); KToggleAction *ta = nullptr; ta = new KToggleAction(ac); ac->addAction("richtext_bold", ta); ta->setText(i18n("Bold")); ta->setIcon(QIcon::fromTheme("format-text-bold")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+B")); richTextBold = ta; ta = new KToggleAction(ac); ac->addAction("richtext_italic", ta); ta->setText(i18n("Italic")); ta->setIcon(QIcon::fromTheme("format-text-italic")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+I")); richTextItalic = ta; ta = new KToggleAction(ac); ac->addAction("richtext_underline", ta); ta->setText(i18n("Underline")); ta->setIcon(QIcon::fromTheme("format-text-underline")); ac->setDefaultShortcut(ta, QKeySequence("Ctrl+U")); richTextUnderline = ta; #if 0 ta = new KToggleAction(ac); ac->addAction("richtext_super", ta); ta->setText(i18n("Superscript")); ta->setIcon(QIcon::fromTheme("text_super")); richTextSuper = ta; ta = new KToggleAction(ac); ac->addAction("richtext_sub", ta); ta->setText(i18n("Subscript")); ta->setIcon(QIcon::fromTheme("text_sub")); richTextSub = ta; #endif ta = new KToggleAction(ac); ac->addAction("richtext_left", ta); ta->setText(i18n("Align Left")); ta->setIcon(QIcon::fromTheme("format-justify-left")); richTextLeft = ta; ta = new KToggleAction(ac); ac->addAction("richtext_center", ta); ta->setText(i18n("Centered")); ta->setIcon(QIcon::fromTheme("format-justify-center")); richTextCenter = ta; ta = new KToggleAction(ac); ac->addAction("richtext_right", ta); ta->setText(i18n("Align Right")); ta->setIcon(QIcon::fromTheme("format-justify-right")); richTextRight = ta; ta = new KToggleAction(ac); ac->addAction("richtext_block", ta); ta->setText(i18n("Justified")); ta->setIcon(QIcon::fromTheme("format-justify-fill")); richTextJustified = ta; QActionGroup *alignmentGroup = new QActionGroup(this); alignmentGroup->addAction(richTextLeft); alignmentGroup->addAction(richTextCenter); alignmentGroup->addAction(richTextRight); alignmentGroup->addAction(richTextJustified); ta = new KToggleAction(ac); ac->addAction("richtext_undo", ta); ta->setText(i18n("Undo")); ta->setIcon(QIcon::fromTheme("edit-undo")); richTextUndo = ta; ta = new KToggleAction(ac); ac->addAction("richtext_redo", ta); ta->setText(i18n("Redo")); ta->setIcon(QIcon::fromTheme("edit-redo")); richTextRedo = ta; disableRichTextToolBar(); } KToolBar *InlineEditors::richTextToolBar() { if (Global::activeMainWindow()) { Global::activeMainWindow()->toolBar(); // Make sure we create the main toolbar FIRST, so it will be on top of the edit toolbar! return Global::activeMainWindow()->toolBar("richTextEditToolBar"); } else return nullptr; } void InlineEditors::enableRichTextToolBar() { richTextFont->setEnabled(true); richTextFontSize->setEnabled(true); richTextColor->setEnabled(true); richTextBold->setEnabled(true); richTextItalic->setEnabled(true); richTextUnderline->setEnabled(true); richTextLeft->setEnabled(true); richTextCenter->setEnabled(true); richTextRight->setEnabled(true); richTextJustified->setEnabled(true); richTextUndo->setEnabled(true); richTextRedo->setEnabled(true); } void InlineEditors::disableRichTextToolBar() { disconnect(richTextFont); disconnect(richTextFontSize); disconnect(richTextColor); disconnect(richTextBold); disconnect(richTextItalic); disconnect(richTextUnderline); disconnect(richTextLeft); disconnect(richTextCenter); disconnect(richTextRight); disconnect(richTextJustified); disconnect(richTextUndo); disconnect(richTextRedo); richTextFont->setEnabled(false); richTextFontSize->setEnabled(false); richTextColor->setEnabled(false); richTextBold->setEnabled(false); richTextItalic->setEnabled(false); richTextUnderline->setEnabled(false); richTextLeft->setEnabled(false); richTextCenter->setEnabled(false); richTextRight->setEnabled(false); richTextJustified->setEnabled(false); richTextUndo->setEnabled(false); richTextRedo->setEnabled(false); // Return to a "proper" state: QFont defaultFont; QColor textColor = (Global::bnpView && Global::bnpView->currentBasket() ? Global::bnpView->currentBasket()->textColor() : palette().color(QPalette::Text)); richTextFont->setCurrentFont(defaultFont.family()); richTextFontSize->setFontSize(defaultFont.pointSize()); richTextColor->setColor(textColor); richTextBold->setChecked(false); richTextItalic->setChecked(false); richTextUnderline->setChecked(false); richTextLeft->setChecked(false); richTextCenter->setChecked(false); richTextRight->setChecked(false); richTextJustified->setChecked(false); } QPalette InlineEditors::palette() const { return qApp->palette(); } diff --git a/src/password.cpp b/src/password.cpp index d1073bd..56d3a5f 100644 --- a/src/password.cpp +++ b/src/password.cpp @@ -1,127 +1,127 @@ /** * SPDX-FileCopyrightText: (C) 2006 Petri Damsten * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "password.h" #ifdef HAVE_LIBGPGME #include #include #include #include #include #include #include #include #include "basketscene.h" #include "kgpgme.h" PasswordDlg::PasswordDlg(QWidget *parent) : QDialog(parent) , w(0) { // QDialog options setWindowTitle(i18n("Password Protection")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &PasswordDlg::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &PasswordDlg::reject); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setModal(true); QHBoxLayout *toplayout = new QHBoxLayout(mainWidget); w = new Password; toplayout->addWidget(w, 1); } PasswordDlg::~PasswordDlg() { delete w; } void PasswordDlg::accept() { int n = type(); if (n == BasketScene::PrivateKeyEncryption && key().isEmpty()) KMessageBox::error(w, i18n("No private key selected.")); else QDialog::accept(); } QString PasswordDlg::key() const { QString s = w->keyCombo->currentText(); if (s.length() < 16) return QString(); int n = s.lastIndexOf(' '); if (n < 0) return QString(); return s.mid(n + 1); } int PasswordDlg::type() const { if (w->noPasswordRadioButton->isChecked()) return BasketScene::NoEncryption; else if (w->passwordRadioButton->isChecked()) return BasketScene::PasswordEncryption; else if (w->publicPrivateRadioButton->isChecked()) return BasketScene::PrivateKeyEncryption; return -1; } void PasswordDlg::setKey(const QString &key) { for (int i = 0; i < w->keyCombo->count(); ++i) { if (w->keyCombo->itemText(i).contains(key)) { w->keyCombo->setCurrentIndex(i); return; } } } void PasswordDlg::setType(int type) { if (type == BasketScene::NoEncryption) w->noPasswordRadioButton->setChecked(true); else if (type == BasketScene::PasswordEncryption) w->passwordRadioButton->setChecked(true); else if (type == BasketScene::PrivateKeyEncryption) w->publicPrivateRadioButton->setChecked(true); } Password::Password(QWidget *parent) : QWidget(parent) { // Setup from the UI file setupUi(this); KGpgMe gpg; KGpgKeyList list = gpg.keys(true); for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) { QString name = gpg.checkForUtf8((*it).name); keyCombo->addItem(QString("%1 <%2> %3").arg(name).arg((*it).email).arg((*it).id)); } publicPrivateRadioButton->setEnabled(keyCombo->count() > 0); keyCombo->setEnabled(keyCombo->count() > 0); } Password::~Password() { } #endif diff --git a/src/regiongrabber.cpp b/src/regiongrabber.cpp index 8eb26a8..eb16592 100644 --- a/src/regiongrabber.cpp +++ b/src/regiongrabber.cpp @@ -1,322 +1,322 @@ /** * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann * * SPDX-License-Identifier: LGPL-2.0-only */ #include "regiongrabber.h" #include #include #include #include #include #include #include #include RegionGrabber::RegionGrabber() : QWidget(nullptr) , selection() , mouseDown(false) , newSelection(false) , handleSize(10) , mouseOverHandle(nullptr) , idleTimer() , showHelp(true) , grabbing(false) , TLHandle(0, 0, handleSize, handleSize) , TRHandle(0, 0, handleSize, handleSize) , BLHandle(0, 0, handleSize, handleSize) , BRHandle(0, 0, handleSize, handleSize) , LHandle(0, 0, handleSize, handleSize) , THandle(0, 0, handleSize, handleSize) , RHandle(0, 0, handleSize, handleSize) , BHandle(0, 0, handleSize, handleSize) { handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle << &LHandle << &THandle << &RHandle << &BHandle; setMouseTracking(true); setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); int timeout = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout, this, SLOT(init())); - connect(&idleTimer, SIGNAL(timeout()), this, SLOT(displayHelp())); + connect(&idleTimer, &QTimer::timeout, this, &RegionGrabber::displayHelp); idleTimer.start(3000); } RegionGrabber::~RegionGrabber() { } void RegionGrabber::init() { pixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); showFullScreen(); // krazy:exclude=qmethods -- Necessary for proper screenshot capture. resize(pixmap.size()); move(0, 0); setCursor(Qt::CrossCursor); grabKeyboard(); } void RegionGrabber::displayHelp() { showHelp = true; update(); } void RegionGrabber::paintEvent(QPaintEvent *e) { Q_UNUSED(e); if (grabbing) // grabWindow() should just get the background return; QPainter painter(this); QPalette pal(QToolTip::palette()); QFont font = QToolTip::font(); QColor handleColor = pal.color(QPalette::Active, QPalette::Highlight); handleColor.setAlpha(160); QColor overlayColor(0, 0, 0, 160); QColor textColor = pal.color(QPalette::Active, QPalette::Text); QColor textBackgroundColor = pal.color(QPalette::Active, QPalette::Base); painter.drawPixmap(0, 0, pixmap); painter.setFont(font); QRect r = selection.normalized().adjusted(0, 0, -1, -1); if (!selection.isNull()) { QRegion grey(rect()); grey = grey.subtracted(r); painter.setPen(handleColor); painter.setBrush(overlayColor); painter.setClipRegion(grey); painter.drawRect(-1, -1, rect().width() + 1, rect().height() + 1); painter.setClipRect(rect()); painter.setBrush(Qt::NoBrush); painter.drawRect(r); } if (showHelp) { painter.setPen(textColor); painter.setBrush(textBackgroundColor); QString helpText = i18n("Select a region using the mouse. To take the snapshot, press the Enter key. Press Esc to quit."); QRect textRect = painter.boundingRect(rect().adjusted(2, 2, -2, -2), Qt::TextWordWrap, helpText); textRect.adjust(-2, -2, 4, 2); painter.drawRect(textRect); textRect.moveTopLeft(QPoint(3, 3)); painter.drawText(textRect, helpText); } if (selection.isNull()) { return; } // The grabbed region is everything which is covered by the drawn // rectangles (border included). This means that there is no 0px // selection, since a 0px wide rectangle will always be drawn as a line. QString txt = QString("%1x%2").arg(selection.width() == 0 ? 2 : selection.width()).arg(selection.height() == 0 ? 2 : selection.height()); QRect textRect = painter.boundingRect(rect(), Qt::AlignLeft, txt); QRect boundingRect = textRect.adjusted(-4, 0, 0, 0); if (textRect.width() < r.width() - 2 * handleSize && textRect.height() < r.height() - 2 * handleSize && (r.width() > 100 && r.height() > 100)) { // center, unsuitable for small selections boundingRect.moveCenter(r.center()); textRect.moveCenter(r.center()); } else if (r.y() - 3 > textRect.height() && r.x() + textRect.width() < rect().right()) { // on top, left aligned boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)); textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3)); } else if (r.x() - 3 > textRect.width()) { // left, top aligned boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())); textRect.moveTopRight(QPoint(r.x() - 5, r.y())); } else if (r.bottom() + 3 + textRect.height() < rect().bottom() && r.right() > textRect.width()) { // at bottom, right aligned boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)); textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3)); } else if (r.right() + textRect.width() + 3 < rect().width()) { // right, bottom aligned boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())); textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())); } // if the above didn't catch it, you are running on a very tiny screen... painter.setPen(textColor); painter.setBrush(textBackgroundColor); painter.drawRect(boundingRect); painter.drawText(textRect, txt); if ((r.height() > handleSize * 2 && r.width() > handleSize * 2) || !mouseDown) { updateHandles(); painter.setPen(handleColor); handleColor.setAlpha(60); painter.setBrush(handleColor); painter.drawRects(handleMask().rects()); } } void RegionGrabber::resizeEvent(QResizeEvent *e) { Q_UNUSED(e); if (selection.isNull()) return; QRect r = selection; r.setTopLeft(limitPointToRect(r.topLeft(), rect())); r.setBottomRight(limitPointToRect(r.bottomRight(), rect())); if (r.width() <= 1 || r.height() <= 1) // this just results in ugly drawing... r = QRect(); selection = r; } void RegionGrabber::mousePressEvent(QMouseEvent *e) { showHelp = false; idleTimer.stop(); if (e->button() == Qt::LeftButton) { mouseDown = true; dragStartPoint = e->pos(); selectionBeforeDrag = selection; if (!selection.contains(e->pos())) { newSelection = true; selection = QRect(); showHelp = true; } else { setCursor(Qt::ClosedHandCursor); } } else if (e->button() == Qt::RightButton) { newSelection = false; selection = QRect(); setCursor(Qt::CrossCursor); } update(); } void RegionGrabber::mouseMoveEvent(QMouseEvent *e) { if (mouseDown) { if (newSelection) { QPoint p = e->pos(); QRect r = rect(); selection = QRect(dragStartPoint, limitPointToRect(p, r)).normalized(); } else if (mouseOverHandle == nullptr) { // moving the whole selection QRect r = rect().normalized(), s = selectionBeforeDrag.normalized(); QPoint p = s.topLeft() + e->pos() - dragStartPoint; r.setBottomRight(r.bottomRight() - QPoint(s.width(), s.height())); if (!r.isNull() && r.isValid()) selection.moveTo(limitPointToRect(p, r)); } else { // dragging a handle QRect r = selectionBeforeDrag; QPoint offset = e->pos() - dragStartPoint; if (mouseOverHandle == &TLHandle || mouseOverHandle == &THandle || mouseOverHandle == &TRHandle) { // dragging one of the top handles r.setTop(r.top() + offset.y()); } if (mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle || mouseOverHandle == &BLHandle) { // dragging one of the left handles r.setLeft(r.left() + offset.x()); } if (mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle || mouseOverHandle == &BRHandle) { // dragging one of the bottom handles r.setBottom(r.bottom() + offset.y()); } if (mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle || mouseOverHandle == &BRHandle) { // dragging one of the right handles r.setRight(r.right() + offset.x()); } r = r.normalized(); r.setTopLeft(limitPointToRect(r.topLeft(), rect())); r.setBottomRight(limitPointToRect(r.bottomRight(), rect())); selection = r; } update(); } else { if (selection.isNull()) return; bool found = false; foreach (QRect *r, handles) { if (r->contains(e->pos())) { mouseOverHandle = r; found = true; break; } } if (!found) { mouseOverHandle = nullptr; if (selection.contains(e->pos())) setCursor(Qt::OpenHandCursor); else setCursor(Qt::CrossCursor); } else { if (mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle) setCursor(Qt::SizeFDiagCursor); if (mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle) setCursor(Qt::SizeBDiagCursor); if (mouseOverHandle == &LHandle || mouseOverHandle == &RHandle) setCursor(Qt::SizeHorCursor); if (mouseOverHandle == &THandle || mouseOverHandle == &BHandle) setCursor(Qt::SizeVerCursor); } } } void RegionGrabber::mouseReleaseEvent(QMouseEvent *e) { mouseDown = false; newSelection = false; idleTimer.start(); if (mouseOverHandle == nullptr && selection.contains(e->pos())) setCursor(Qt::OpenHandCursor); update(); } void RegionGrabber::mouseDoubleClickEvent(QMouseEvent *) { grabRect(); } void RegionGrabber::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { emit regionGrabbed(QPixmap()); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { grabRect(); } else { e->ignore(); } } void RegionGrabber::grabRect() { QRect r = selection.normalized(); if (!r.isNull() && r.isValid()) { grabbing = true; emit regionGrabbed(pixmap.copy(r)); } } void RegionGrabber::updateHandles() { QRect r = selection.normalized().adjusted(0, 0, -1, -1); int s2 = handleSize / 2; TLHandle.moveTopLeft(r.topLeft()); TRHandle.moveTopRight(r.topRight()); BLHandle.moveBottomLeft(r.bottomLeft()); BRHandle.moveBottomRight(r.bottomRight()); LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() / 2 - s2)); THandle.moveTopLeft(QPoint(r.x() + r.width() / 2 - s2, r.y())); RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() / 2 - s2)); BHandle.moveBottomLeft(QPoint(r.x() + r.width() / 2 - s2, r.bottom())); } QRegion RegionGrabber::handleMask() const { // note: not normalized QRects are bad here, since they will not be drawn QRegion mask; foreach (QRect *rect, handles) mask += QRegion(*rect); return mask; } QPoint RegionGrabber::limitPointToRect(const QPoint &p, const QRect &r) const { QPoint q; q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right()); q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom()); return q; } diff --git a/src/softwareimporters.cpp b/src/softwareimporters.cpp index a3ff2a5..cf7a852 100644 --- a/src/softwareimporters.cpp +++ b/src/softwareimporters.cpp @@ -1,236 +1,236 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "softwareimporters.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basketfactory.h" #include "basketscene.h" #include "bnpview.h" #include "debugwindow.h" #include "global.h" #include "icon_names.h" #include "notefactory.h" #include "tools.h" #include "xmlwork.h" /** class TreeImportDialog: */ TreeImportDialog::TreeImportDialog(QWidget *parent) : QDialog(parent) { QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); // QDialog options setWindowTitle(i18n("Import Hierarchy")); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setObjectName("ImportHeirachy"); setModal(true); m_choices = new QGroupBox(i18n("How to Import the Notes?"), page); m_choiceLayout = new QVBoxLayout(); m_choices->setLayout(m_choiceLayout); m_hierarchy_choice = new QRadioButton(i18n("&Keep original hierarchy (all notes in separate baskets)"), m_choices); m_separate_baskets_choice = new QRadioButton(i18n("&First level notes in separate baskets"), m_choices); m_one_basket_choice = new QRadioButton(i18n("&All notes in one basket"), m_choices); m_hierarchy_choice->setChecked(true); m_choiceLayout->addWidget(m_hierarchy_choice); m_choiceLayout->addWidget(m_separate_baskets_choice); m_choiceLayout->addWidget(m_one_basket_choice); topLayout->addWidget(m_choices); topLayout->addStretch(10); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &TreeImportDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &TreeImportDialog::reject); mainLayout->addWidget(buttonBox); } TreeImportDialog::~TreeImportDialog() { } int TreeImportDialog::choice() { if (m_hierarchy_choice->isChecked()) return 1; else if (m_separate_baskets_choice->isChecked()) return 2; else return 3; } /** class TextFileImportDialog: */ TextFileImportDialog::TextFileImportDialog(QWidget *parent) : QDialog(parent) { QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // QDialog options setWindowTitle(i18n("Import Text File")); setObjectName("ImportTextFile"); setModal(true); m_choices = new QGroupBox(i18n("Format of the Text File"), page); mainLayout->addWidget(m_choices); m_choiceLayout = new QVBoxLayout; m_choices->setLayout(m_choiceLayout); m_emptyline_choice = new QRadioButton(i18n("Notes separated by an &empty line"), m_choices); m_newline_choice = new QRadioButton(i18n("One ¬e per line"), m_choices); m_dash_choice = new QRadioButton(i18n("Notes begin with a &dash (-)"), m_choices); m_star_choice = new QRadioButton(i18n("Notes begin with a &star (*)"), m_choices); m_anotherSeparator = new QRadioButton(i18n("&Use another separator:"), m_choices); m_choiceLayout->addWidget(m_emptyline_choice); m_choiceLayout->addWidget(m_newline_choice); m_choiceLayout->addWidget(m_dash_choice); m_choiceLayout->addWidget(m_star_choice); m_choiceLayout->addWidget(m_anotherSeparator); QWidget *indentedTextEdit = new QWidget(m_choices); m_choiceLayout->addWidget(indentedTextEdit); QHBoxLayout *hLayout = new QHBoxLayout(indentedTextEdit); hLayout->addSpacing(20); m_customSeparator = new KTextEdit(indentedTextEdit); hLayout->addWidget(m_customSeparator); m_all_in_one_choice = new QRadioButton(i18n("&All in one note"), m_choices); m_choiceLayout->addWidget(m_all_in_one_choice); m_emptyline_choice->setChecked(true); topLayout->addWidget(m_choices); - connect(m_customSeparator, SIGNAL(textChanged()), this, SLOT(customSeparatorChanged())); + connect(m_customSeparator, &KTextEdit::textChanged, this, &TextFileImportDialog::customSeparatorChanged); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &TextFileImportDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &TextFileImportDialog::reject); mainLayout->addWidget(buttonBox); } TextFileImportDialog::~TextFileImportDialog() { } QString TextFileImportDialog::separator() { if (m_emptyline_choice->isChecked()) return "\n\n"; else if (m_newline_choice->isChecked()) return "\n"; else if (m_dash_choice->isChecked()) return "\n-"; else if (m_star_choice->isChecked()) return "\n*"; else if (m_anotherSeparator->isChecked()) return m_customSeparator->toPlainText(); else if (m_all_in_one_choice->isChecked()) return QString(); else return "\n\n"; } void TextFileImportDialog::customSeparatorChanged() { if (!m_anotherSeparator->isChecked()) m_anotherSeparator->toggle(); } /** namespace SoftwareImporters: */ void SoftwareImporters::finishImport(BasketScene *basket) { // Unselect the last inserted group: basket->unselectAll(); // Focus the FIRST note (the last inserted note is currently focused!): basket->setFocusedNote(basket->firstNoteShownInStack()); // Relayout every notes at their new place and simulate a load animation (because already loaded just after the creation). // Without a relayouting, notes on the bottom would comes from the top (because they were inserted on top) and clutter the animation load: basket->relayoutNotes(); basket->save(); } void SoftwareImporters::importTextFile() { QString fileName = QFileDialog::getOpenFileName(nullptr, QString(), "kfiledialog:///:ImportTextFile", "*|All files"); if (fileName.isEmpty()) return; TextFileImportDialog dialog; if (dialog.exec() == QDialog::Rejected) return; QString separator = dialog.separator(); QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); QString content = stream.readAll(); QStringList list = (separator.isEmpty() ? QStringList(content) : content.split(separator)); // First create a basket for it: QString title = i18nc("From TextFile.txt", "From %1", QUrl::fromLocalFile(fileName).fileName()); BasketFactory::newBasket(QStringLiteral("txt"), title); BasketScene *basket = Global::bnpView->currentBasket(); basket->load(); // Import every notes: for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { Note *note = NoteFactory::createNoteFromText((*it).trimmed(), basket); basket->insertNote(note, basket->firstNote(), Note::BottomColumn, QPoint(), /*animate=*/false); } // Finish the export: finishImport(basket); } } diff --git a/src/tagsedit.cpp b/src/tagsedit.cpp index 79ee93e..ecc6f2f 100644 --- a/src/tagsedit.cpp +++ b/src/tagsedit.cpp @@ -1,1288 +1,1288 @@ /** * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "tagsedit.h" #include #include #include #include #include #include #include #include #include //For m_tags->header() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bnpview.h" #include "global.h" #include "kcolorcombo2.h" #include "tag.h" #include "variouswidgets.h" //For FontSizeCombo /** class StateCopy: */ StateCopy::StateCopy(State *old /* = 0*/) { oldState = old; newState = new State(); if (oldState) oldState->copyTo(newState); } StateCopy::~StateCopy() { delete newState; } void StateCopy::copyBack() { } /** class TagCopy: */ TagCopy::TagCopy(Tag *old /* = 0*/) { oldTag = old; newTag = new Tag(); if (oldTag) oldTag->copyTo(newTag); if (old) for (State::List::iterator it = old->states().begin(); it != old->states().end(); ++it) stateCopies.append(new StateCopy(*it)); else stateCopies.append(new StateCopy()); } TagCopy::~TagCopy() { delete newTag; } void TagCopy::copyBack() { } bool TagCopy::isMultiState() { return (stateCopies.count() > 1); } /** class TagListViewItem: */ TagListViewItem::TagListViewItem(QTreeWidget *parent, TagCopy *tagCopy) : QTreeWidgetItem(parent) , m_tagCopy(tagCopy) , m_stateCopy(nullptr) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, TagCopy *tagCopy) : QTreeWidgetItem(parent) , m_tagCopy(tagCopy) , m_stateCopy(nullptr) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, TagCopy *tagCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(tagCopy) , m_stateCopy(nullptr) { setText(0, tagCopy->newTag->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, TagCopy *tagCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(tagCopy) , m_stateCopy(nullptr) { setText(0, tagCopy->newTag->name()); } /* */ TagListViewItem::TagListViewItem(QTreeWidget *parent, StateCopy *stateCopy) : QTreeWidgetItem(parent) , m_tagCopy(nullptr) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, StateCopy *stateCopy) : QTreeWidgetItem(parent) , m_tagCopy(nullptr) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, StateCopy *stateCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(nullptr) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } TagListViewItem::TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, StateCopy *stateCopy) : QTreeWidgetItem(parent, after) , m_tagCopy(nullptr) , m_stateCopy(stateCopy) { setText(0, stateCopy->newState->name()); } /* */ TagListViewItem::~TagListViewItem() { } TagListViewItem *TagListViewItem::lastChild() { if (childCount() <= 0) return nullptr; return (TagListViewItem *)child(childCount() - 1); } bool TagListViewItem::isEmblemObligatory() { return m_stateCopy != nullptr; // It's a state of a multi-state } TagListViewItem *TagListViewItem::prevSibling() { TagListViewItem *item = this; int idx = 0; if (!parent()) { idx = treeWidget()->indexOfTopLevelItem(item); if (idx <= 0) return nullptr; return (TagListViewItem *)treeWidget()->topLevelItem(idx - 1); } else { idx = parent()->indexOfChild(item); if (idx <= 0) return nullptr; return (TagListViewItem *)parent()->child(idx - 1); } } TagListViewItem *TagListViewItem::nextSibling() { TagListViewItem *item = this; int idx = 0; if (!parent()) { idx = treeWidget()->indexOfTopLevelItem(item); if (idx >= treeWidget()->topLevelItemCount()) return nullptr; return (TagListViewItem *)treeWidget()->topLevelItem(idx + 1); } else { idx = parent()->indexOfChild(item); if (idx >= parent()->childCount()) return nullptr; return (TagListViewItem *)parent()->child(idx + 1); } } TagListViewItem *TagListViewItem::parent() const { return (TagListViewItem *)QTreeWidgetItem::parent(); } // TODO: TagListViewItem:: int TAG_ICON_SIZE = 16; int TAG_MARGIN = 1; int TagListViewItem::width(const QFontMetrics & /* fontMetrics */, const QTreeWidget * /*listView*/, int /* column */) const { return treeWidget()->width(); } void TagListViewItem::setup() { QString text = (m_tagCopy ? m_tagCopy->newTag->name() : m_stateCopy->newState->name()); State *state = (m_tagCopy ? m_tagCopy->stateCopies[0]->newState : m_stateCopy->newState); QFont font = state->font(treeWidget()->font()); setText(0, text); QBrush brush; bool withIcon = m_stateCopy || (m_tagCopy && !m_tagCopy->isMultiState()); brush.setColor(isSelected() ? qApp->palette().color(QPalette::Highlight) : (withIcon && state->backgroundColor().isValid() ? state->backgroundColor() : treeWidget()->viewport()->palette().color(treeWidget()->viewport()->backgroundRole()))); setBackground(1, brush); } /** class TagListView: */ TagListView::TagListView(QWidget *parent) : QTreeWidget(parent) { setItemDelegate(new TagListDelegate); } TagListView::~TagListView() { } void TagListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Delete) emit deletePressed(); else if (event->key() != Qt::Key_Left || (currentItem() && currentItem()->parent())) // Do not allow to open/close first-level items QTreeWidget::keyPressEvent(event); } void TagListView::mouseDoubleClickEvent(QMouseEvent *event) { // Ignore this event! Do not open/close first-level items! // But trigger edit (change focus to name) when double-click an item: if (itemAt(event->pos()) != nullptr) emit doubleClickedItem(); } void TagListView::mousePressEvent(QMouseEvent *event) { // When clicking on an empty space, QListView would unselect the current item! We forbid that! if (itemAt(event->pos()) != nullptr) QTreeWidget::mousePressEvent(event); } void TagListView::mouseReleaseEvent(QMouseEvent *event) { // When clicking on an empty space, QListView would unselect the current item! We forbid that! if (itemAt(event->pos()) != nullptr) QTreeWidget::mouseReleaseEvent(event); } TagListViewItem *TagListView::currentItem() const { return (TagListViewItem *)QTreeWidget::currentItem(); } TagListViewItem *TagListView::firstChild() const { if (topLevelItemCount() <= 0) return nullptr; return (TagListViewItem *)topLevelItem(0); } TagListViewItem *TagListView::lastItem() const { if (topLevelItemCount() <= 0) return nullptr; return (TagListViewItem *)topLevelItem(topLevelItemCount() - 1); } /** class TagsEditDialog: */ TagsEditDialog::TagsEditDialog(QWidget *parent, State *stateToEdit, bool addNewTag) : QDialog(parent) , m_loading(false) { // QDialog options setWindowTitle(i18n("Customize Tags")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &TagsEditDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &TagsEditDialog::reject); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setObjectName("CustomizeTags"); setModal(true); - connect(okButton, SIGNAL(clicked()), SLOT(slotOk())); - connect(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); + connect(okButton, &QPushButton::clicked, this, &TagsEditDialog::slotOk); + connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &TagsEditDialog::slotCancel); QHBoxLayout *layout = new QHBoxLayout(mainWidget); /* Left part: */ QPushButton *newTag = new QPushButton(i18n("Ne&w Tag"), mainWidget); QPushButton *newState = new QPushButton(i18n("New St&ate"), mainWidget); - connect(newTag, SIGNAL(clicked()), this, SLOT(newTag())); - connect(newState, SIGNAL(clicked()), this, SLOT(newState())); + connect(newTag, &QPushButton::clicked, this, &TagsEditDialog::newTag); + connect(newState, &QPushButton::clicked, this, &TagsEditDialog::newState); m_tags = new TagListView(mainWidget); m_tags->header()->hide(); m_tags->setRootIsDecorated(false); // m_tags->addColumn(QString()); // m_tags->setSorting(-1); // Sort column -1, so disabled sorting // m_tags->setResizeMode(QTreeWidget::LastColumn); m_tags->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_moveUp = new QPushButton(mainWidget); KGuiItem::assign(m_moveUp, KGuiItem(QString(), "arrow-up")); m_moveDown = new QPushButton(mainWidget); KGuiItem::assign(m_moveDown, KGuiItem(QString(), "arrow-down")); m_deleteTag = new QPushButton(mainWidget); KGuiItem::assign(m_deleteTag, KGuiItem(QString(), "edit-delete")); m_moveUp->setToolTip(i18n("Move Up (Ctrl+Shift+Up)")); m_moveDown->setToolTip(i18n("Move Down (Ctrl+Shift+Down)")); m_deleteTag->setToolTip(i18n("Delete")); - connect(m_moveUp, SIGNAL(clicked()), this, SLOT(moveUp())); - connect(m_moveDown, SIGNAL(clicked()), this, SLOT(moveDown())); - connect(m_deleteTag, SIGNAL(clicked()), this, SLOT(deleteTag())); + connect(m_moveUp, &QPushButton::clicked, this, &TagsEditDialog::moveUp); + connect(m_moveDown, &QPushButton::clicked, this, &TagsEditDialog::moveDown); + connect(m_deleteTag, &QPushButton::clicked, this, &TagsEditDialog::deleteTag); QHBoxLayout *topLeftLayout = new QHBoxLayout; topLeftLayout->addWidget(m_moveUp); topLeftLayout->addWidget(m_moveDown); topLeftLayout->addWidget(m_deleteTag); QVBoxLayout *leftLayout = new QVBoxLayout; leftLayout->addWidget(newTag); leftLayout->addWidget(newState); leftLayout->addWidget(m_tags); leftLayout->addLayout(topLeftLayout); layout->addLayout(leftLayout); /* Right part: */ QWidget *rightWidget = new QWidget(mainWidget); m_tagBox = new QGroupBox(i18n("Tag"), rightWidget); m_tagBoxLayout = new QHBoxLayout; m_tagBox->setLayout(m_tagBoxLayout); QWidget *tagWidget = new QWidget; m_tagBoxLayout->addWidget(tagWidget); m_tagName = new QLineEdit(tagWidget); QLabel *tagNameLabel = new QLabel(i18n("&Name:"), tagWidget); tagNameLabel->setBuddy(m_tagName); m_shortcut = new KShortcutWidget(tagWidget); m_removeShortcut = new QPushButton(i18nc("Remove tag shortcut", "&Remove"), tagWidget); QLabel *shortcutLabel = new QLabel(i18n("S&hortcut:"), tagWidget); shortcutLabel->setBuddy(m_shortcut); - // connect( m_shortcut, SIGNAL(shortcutChanged(const QKeySequence&)), this, SLOT(capturedShortcut(const QKeySequence&)) ); - connect(m_removeShortcut, SIGNAL(clicked()), this, SLOT(removeShortcut())); + //connect(m_shortcut, &KShortcutWidget::shortcutChanged, this, &TagsEditDialog::capturedShortcut); + connect(m_removeShortcut, &QPushButton::clicked, this, &TagsEditDialog::removeShortcut); m_inherit = new QCheckBox(i18n("&Inherited by new sibling notes"), tagWidget); m_allowCrossRefernce = new QCheckBox(i18n("Allow Cross Reference Links"), tagWidget); HelpLabel *allowCrossReferenceHelp = new HelpLabel(i18n("What does this do?"), "

" + i18n("This option will enable you to type a cross reference link directly into a text note. Cross Reference links can have the following syntax:") + "

" + "

" + i18n("From the top of the tree (Absolute path):") + "
" + i18n("[[/top level item/child|optional title]]") + "

" + "

" + i18n("Relative to the current basket:") + "
" + i18n("[[../sibling|optional title]]") + "
" + i18n("[[child|optional title]]") + "
" + i18n("[[./child|optional title]]") + "

", tagWidget); QGridLayout *tagGrid = new QGridLayout(tagWidget); tagGrid->addWidget(tagNameLabel, 0, 0); tagGrid->addWidget(m_tagName, 0, 1, 1, 3); tagGrid->addWidget(shortcutLabel, 1, 0); tagGrid->addWidget(m_shortcut, 1, 1); tagGrid->addWidget(m_removeShortcut, 1, 2); tagGrid->addWidget(m_inherit, 2, 0, 1, 4); tagGrid->addWidget(m_allowCrossRefernce, 3, 0); tagGrid->addWidget(allowCrossReferenceHelp, 3, 1); tagGrid->setColumnStretch(/*col=*/3, /*stretch=*/255); m_stateBox = new QGroupBox(i18n("State"), rightWidget); m_stateBoxLayout = new QHBoxLayout; m_stateBox->setLayout(m_stateBoxLayout); QWidget *stateWidget = new QWidget; m_stateBoxLayout->addWidget(stateWidget); m_stateName = new QLineEdit(stateWidget); m_stateNameLabel = new QLabel(i18n("Na&me:"), stateWidget); m_stateNameLabel->setBuddy(m_stateName); QWidget *emblemWidget = new QWidget(stateWidget); m_emblem = new KIconButton(emblemWidget); m_emblem->setIconType(KIconLoader::NoGroup, KIconLoader::Action); m_emblem->setIconSize(16); m_emblem->setIcon("edit-delete"); m_removeEmblem = new QPushButton(i18nc("Remove tag emblem", "Remo&ve"), emblemWidget); QLabel *emblemLabel = new QLabel(i18n("&Emblem:"), stateWidget); emblemLabel->setBuddy(m_emblem); - connect(m_removeEmblem, SIGNAL(clicked()), this, SLOT(removeEmblem())); // m_emblem.resetIcon() is not a slot! + connect(m_removeEmblem, &QPushButton::clicked, this, &TagsEditDialog::removeEmblem); // m_emblem.resetIcon() is not a slot! // Make the icon button and the remove button the same height: int height = qMax(m_emblem->sizeHint().width(), m_emblem->sizeHint().height()); height = qMax(height, m_removeEmblem->sizeHint().height()); m_emblem->setFixedSize(height, height); // Make it square m_removeEmblem->setFixedHeight(height); m_emblem->resetIcon(); QHBoxLayout *emblemLayout = new QHBoxLayout(emblemWidget); emblemLayout->addWidget(m_emblem); emblemLayout->addWidget(m_removeEmblem); emblemLayout->addStretch(); m_backgroundColor = new KColorCombo2(QColor(), palette().color(QPalette::Base), stateWidget); QLabel *backgroundColorLabel = new QLabel(i18n("&Background:"), stateWidget); backgroundColorLabel->setBuddy(m_backgroundColor); QHBoxLayout *backgroundColorLayout = new QHBoxLayout(nullptr); backgroundColorLayout->addWidget(m_backgroundColor); backgroundColorLayout->addStretch(); QIcon boldIconSet = QIcon::fromTheme("format-text-bold"); m_bold = new QPushButton(boldIconSet, QString(), stateWidget); m_bold->setCheckable(true); int size = qMax(m_bold->sizeHint().width(), m_bold->sizeHint().height()); m_bold->setFixedSize(size, size); // Make it square! m_bold->setToolTip(i18n("Bold")); QIcon underlineIconSet = QIcon::fromTheme("format-text-underline"); m_underline = new QPushButton(underlineIconSet, QString(), stateWidget); m_underline->setCheckable(true); m_underline->setFixedSize(size, size); // Make it square! m_underline->setToolTip(i18n("Underline")); QIcon italicIconSet = QIcon::fromTheme("format-text-italic"); m_italic = new QPushButton(italicIconSet, QString(), stateWidget); m_italic->setCheckable(true); m_italic->setFixedSize(size, size); // Make it square! m_italic->setToolTip(i18n("Italic")); QIcon strikeIconSet = QIcon::fromTheme("format-text-strikethrough"); m_strike = new QPushButton(strikeIconSet, QString(), stateWidget); m_strike->setCheckable(true); m_strike->setFixedSize(size, size); // Make it square! m_strike->setToolTip(i18n("Strike Through")); QLabel *textLabel = new QLabel(i18n("&Text:"), stateWidget); textLabel->setBuddy(m_bold); QHBoxLayout *textLayout = new QHBoxLayout; textLayout->addWidget(m_bold); textLayout->addWidget(m_underline); textLayout->addWidget(m_italic); textLayout->addWidget(m_strike); textLayout->addStretch(); m_textColor = new KColorCombo2(QColor(), palette().color(QPalette::Text), stateWidget); QLabel *textColorLabel = new QLabel(i18n("Co&lor:"), stateWidget); textColorLabel->setBuddy(m_textColor); m_font = new QFontComboBox(stateWidget); m_font->addItem(i18n("(Default)"), 0); QLabel *fontLabel = new QLabel(i18n("&Font:"), stateWidget); fontLabel->setBuddy(m_font); m_fontSize = new FontSizeCombo(/*rw=*/true, /*withDefault=*/true, stateWidget); QLabel *fontSizeLabel = new QLabel(i18n("&Size:"), stateWidget); fontSizeLabel->setBuddy(m_fontSize); m_textEquivalent = new QLineEdit(stateWidget); QLabel *textEquivalentLabel = new QLabel(i18n("Te&xt equivalent:"), stateWidget); textEquivalentLabel->setBuddy(m_textEquivalent); QFont font = m_textEquivalent->font(); font.setFamily("monospace"); m_textEquivalent->setFont(font); HelpLabel *textEquivalentHelp = new HelpLabel(i18n("What is this for?"), "

" + i18n("When you copy and paste or drag and drop notes to a text editor, this text will be inserted as a textual equivalent of the tag.") + "

" + // "

" + i18n("If filled, this property lets you paste this tag or this state as textual equivalent.") + "
" + i18n("For instance, a list of notes with the To Do and Done tags are exported as lines preceded by [ ] or [x], " "representing an empty checkbox and a checked box.") + "

" + "

", stateWidget); QHBoxLayout *textEquivalentHelpLayout = new QHBoxLayout; textEquivalentHelpLayout->addWidget(textEquivalentHelp); textEquivalentHelpLayout->addStretch(255); m_onEveryLines = new QCheckBox(i18n("On ever&y line"), stateWidget); HelpLabel *onEveryLinesHelp = new HelpLabel(i18n("What does this mean?"), "

" + i18n("When a note has several lines, you can choose to export the tag or the state on the first line or on every line of the note.") + "

" + "

" + "

" + i18n("In the example above, the tag of the top note is only exported on the first line, while the tag of the bottom note is exported on every line of the note."), stateWidget); QHBoxLayout *onEveryLinesHelpLayout = new QHBoxLayout; onEveryLinesHelpLayout->addWidget(onEveryLinesHelp); onEveryLinesHelpLayout->addStretch(255); QGridLayout *textEquivalentGrid = new QGridLayout; textEquivalentGrid->addWidget(textEquivalentLabel, 0, 0); textEquivalentGrid->addWidget(m_textEquivalent, 0, 1); textEquivalentGrid->addLayout(textEquivalentHelpLayout, 0, 2); textEquivalentGrid->addWidget(m_onEveryLines, 1, 1); textEquivalentGrid->addLayout(onEveryLinesHelpLayout, 1, 2); textEquivalentGrid->setColumnStretch(/*col=*/3, /*stretch=*/255); KSeparator *separator = new KSeparator(Qt::Horizontal, stateWidget); QGridLayout *stateGrid = new QGridLayout(stateWidget); stateGrid->addWidget(m_stateNameLabel, 0, 0); stateGrid->addWidget(m_stateName, 0, 1, 1, 6); stateGrid->addWidget(emblemLabel, 1, 0); stateGrid->addWidget(emblemWidget, 1, 1, 1, 6); stateGrid->addWidget(backgroundColorLabel, 1, 5); stateGrid->addLayout(backgroundColorLayout, 1, 6, 1, 1); stateGrid->addWidget(textLabel, 2, 0); stateGrid->addLayout(textLayout, 2, 1, 1, 4); stateGrid->addWidget(textColorLabel, 2, 5); stateGrid->addWidget(m_textColor, 2, 6); stateGrid->addWidget(fontLabel, 3, 0); stateGrid->addWidget(m_font, 3, 1, 1, 4); stateGrid->addWidget(fontSizeLabel, 3, 5); stateGrid->addWidget(m_fontSize, 3, 6); stateGrid->addWidget(separator, 4, 0, 1, 7); stateGrid->addLayout(textEquivalentGrid, 5, 0, 1, 7); QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); rightLayout->addWidget(m_tagBox); rightLayout->addWidget(m_stateBox); rightLayout->addStretch(); layout->addWidget(rightWidget); rightWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); // Equalize the width of the first column of the two grids: int maxWidth = tagNameLabel->sizeHint().width(); maxWidth = qMax(maxWidth, shortcutLabel->sizeHint().width()); maxWidth = qMax(maxWidth, m_stateNameLabel->sizeHint().width()); maxWidth = qMax(maxWidth, emblemLabel->sizeHint().width()); maxWidth = qMax(maxWidth, textLabel->sizeHint().width()); maxWidth = qMax(maxWidth, fontLabel->sizeHint().width()); maxWidth = qMax(maxWidth, backgroundColorLabel->sizeHint().width()); maxWidth = qMax(maxWidth, textEquivalentLabel->sizeHint().width()); tagNameLabel->setFixedWidth(maxWidth); m_stateNameLabel->setFixedWidth(maxWidth); textEquivalentLabel->setFixedWidth(maxWidth); // Load Tags: for (Tag::List::iterator tagIt = Tag::all.begin(); tagIt != Tag::all.end(); ++tagIt) m_tagCopies.append(new TagCopy(*tagIt)); TagListViewItem *lastInsertedItem = nullptr; TagListViewItem *lastInsertedSubItem; TagListViewItem *item; TagListViewItem *subItem; for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { // New List View Item: if (lastInsertedItem) item = new TagListViewItem(m_tags, lastInsertedItem, *tagCopyIt); else item = new TagListViewItem(m_tags, *tagCopyIt); item->setExpanded(true); lastInsertedItem = item; // Load if ((*tagCopyIt)->isMultiState()) { lastInsertedSubItem = nullptr; StateCopy::List stateCopies = item->tagCopy()->stateCopies; for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { if (lastInsertedSubItem) subItem = new TagListViewItem(item, lastInsertedSubItem, *stateCopyIt); else subItem = new TagListViewItem(item, *stateCopyIt); lastInsertedSubItem = subItem; } } } // Connect Signals: - connect(m_tagName, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); - connect(m_shortcut, SIGNAL(shortcutChanged(const QList &)), this, SLOT(modified())); - connect(m_inherit, SIGNAL(stateChanged(int)), this, SLOT(modified())); - connect(m_allowCrossRefernce, SIGNAL(clicked(bool)), this, SLOT(modified())); - connect(m_stateName, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); - connect(m_emblem, SIGNAL(iconChanged(QString)), this, SLOT(modified())); - connect(m_backgroundColor, SIGNAL(changed(const QColor &)), this, SLOT(modified())); - connect(m_bold, SIGNAL(toggled(bool)), this, SLOT(modified())); - connect(m_underline, SIGNAL(toggled(bool)), this, SLOT(modified())); - connect(m_italic, SIGNAL(toggled(bool)), this, SLOT(modified())); - connect(m_strike, SIGNAL(toggled(bool)), this, SLOT(modified())); - connect(m_textColor, SIGNAL(activated(int)), this, SLOT(modified())); - connect(m_font, SIGNAL(editTextChanged(const QString &)), this, SLOT(modified())); - connect(m_fontSize, SIGNAL(editTextChanged(const QString &)), this, SLOT(modified())); - connect(m_textEquivalent, SIGNAL(textChanged(const QString &)), this, SLOT(modified())); - connect(m_onEveryLines, SIGNAL(stateChanged(int)), this, SLOT(modified())); + connect(m_tagName, &QLineEdit::textChanged, this, &TagsEditDialog::modified); + connect(m_shortcut, &KShortcutWidget::shortcutChanged, this, &TagsEditDialog::modified); + connect(m_inherit, &QCheckBox::stateChanged, this, &TagsEditDialog::modified); + connect(m_allowCrossRefernce, &QCheckBox::clicked, this, &TagsEditDialog::modified); + connect(m_stateName, &QLineEdit::textChanged, this, &TagsEditDialog::modified); + connect(m_emblem, &KIconButton::iconChanged, this, &TagsEditDialog::modified); + connect(m_backgroundColor, &KColorCombo2::changed, this, &TagsEditDialog::modified); + connect(m_bold, &QPushButton::toggled, this, &TagsEditDialog::modified); + connect(m_underline, &QPushButton::toggled, this, &TagsEditDialog::modified); + connect(m_italic, &QPushButton::toggled, this, &TagsEditDialog::modified); + connect(m_strike, &QPushButton::toggled, this, &TagsEditDialog::modified); + connect(m_textColor, &KColorCombo2::changed, this, &TagsEditDialog::modified); + connect(m_font, &QFontComboBox::editTextChanged, this, &TagsEditDialog::modified); + connect(m_fontSize, &FontSizeCombo::editTextChanged, this, &TagsEditDialog::modified); + connect(m_textEquivalent, &QLineEdit::textChanged, this, &TagsEditDialog::modified); + connect(m_onEveryLines, &QCheckBox::stateChanged, this, &TagsEditDialog::modified); connect(m_tags, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *))); connect(m_tags, SIGNAL(deletePressed()), this, SLOT(deleteTag())); connect(m_tags, SIGNAL(doubleClickedItem()), this, SLOT(renameIt())); QTreeWidgetItem *firstItem = m_tags->firstChild(); if (stateToEdit != nullptr) { TagListViewItem *item = itemForState(stateToEdit); if (item) firstItem = item; } // Select the first tag unless the first tag is a multi-state tag. // In this case, select the first state, as it let customize the state AND the associated tag. if (firstItem) { if (firstItem->childCount() > 0) firstItem = firstItem->child(0); firstItem->setSelected(true); m_tags->setCurrentItem(firstItem); currentItemChanged(firstItem); if (stateToEdit == nullptr) m_tags->scrollToItem(firstItem); m_tags->setFocus(); } else { m_moveUp->setEnabled(false); m_moveDown->setEnabled(false); m_deleteTag->setEnabled(false); m_tagBox->setEnabled(false); m_stateBox->setEnabled(false); } // TODO: Disabled both boxes if no tag!!! // Some keyboard shortcuts: // Ctrl+arrows instead of Alt+arrows (same as Go menu in the main window) because Alt+Down is for combo boxes QAction *selectAbove = new QAction(this); selectAbove->setShortcut(Qt::CTRL + Qt::Key_Up); - connect(selectAbove, SIGNAL(triggered()), this, SLOT(selectUp())); + connect(selectAbove, &QAction::triggered, this, &TagsEditDialog::selectUp); QAction *selectBelow = new QAction(this); selectBelow->setShortcut(Qt::CTRL + Qt::Key_Down); - connect(selectBelow, SIGNAL(triggered()), this, SLOT(selectDown())); + connect(selectBelow, &QAction::triggered, this, &TagsEditDialog::selectDown); QAction *selectLeft = new QAction(this); selectLeft->setShortcut(Qt::CTRL + Qt::Key_Left); - connect(selectLeft, SIGNAL(triggered()), this, SLOT(selectLeft())); + connect(selectLeft, &QAction::triggered, this, &TagsEditDialog::selectLeft); QAction *selectRight = new QAction(this); selectRight->setShortcut(Qt::CTRL + Qt::Key_Right); - connect(selectRight, SIGNAL(triggered()), this, SLOT(selectRight())); + connect(selectRight, &QAction::triggered, this, &TagsEditDialog::selectRight); QAction *moveAbove = new QAction(this); moveAbove->setShortcut(Qt::CTRL + Qt::Key_Up); - connect(moveAbove, SIGNAL(triggered()), this, SLOT(moveUp())); + connect(moveAbove, &QAction::triggered, this, &TagsEditDialog::moveUp); QAction *moveBelow = new QAction(this); moveBelow->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Down); - connect(moveBelow, SIGNAL(triggered()), this, SLOT(moveDown())); + connect(moveBelow, &QAction::triggered, this, &TagsEditDialog::moveDown); QAction *rename = new QAction(this); rename->setShortcut(Qt::Key_F2); - connect(rename, SIGNAL(triggered()), this, SLOT(renameIt())); + connect(rename, &QAction::triggered, this, &TagsEditDialog::renameIt); m_tags->setMinimumSize(m_tags->sizeHint().width() * 2, m_tagBox->sizeHint().height() + m_stateBox->sizeHint().height()); if (addNewTag) QTimer::singleShot(0, this, SLOT(newTag())); else // Once the window initial size is computed and the window show, allow the user to resize it down: QTimer::singleShot(0, this, SLOT(resetTreeSizeHint())); } TagsEditDialog::~TagsEditDialog() { } void TagsEditDialog::resetTreeSizeHint() { m_tags->setMinimumSize(m_tags->sizeHint()); } TagListViewItem *TagsEditDialog::itemForState(State *state) { // Browse all tags: QTreeWidgetItemIterator it(m_tags); while (*it) { QTreeWidgetItem *item = *it; // Return if we found the tag item: TagListViewItem *tagItem = (TagListViewItem *)item; if (tagItem->tagCopy() && tagItem->tagCopy()->oldTag && tagItem->tagCopy()->stateCopies[0]->oldState == state) return tagItem; // Browser all sub-states: QTreeWidgetItemIterator it2(item); while (*it2) { QTreeWidgetItem *subItem = *it2; // Return if we found the state item: TagListViewItem *stateItem = (TagListViewItem *)subItem; if (stateItem->stateCopy() && stateItem->stateCopy()->oldState && stateItem->stateCopy()->oldState == state) return stateItem; ++it2; } ++it; } return nullptr; } void TagsEditDialog::newTag() { // Add to the "model": TagCopy *newTagCopy = new TagCopy(); newTagCopy->stateCopies[0]->newState->setId("tag_state_" + QString::number(Tag::getNextStateUid())); // TODO: Check if it's really unique m_tagCopies.append(newTagCopy); m_addedStates.append(newTagCopy->stateCopies[0]->newState); // Add to the "view": TagListViewItem *item; if (m_tags->firstChild()) { // QListView::lastItem is the last item in the tree. If we the last item is a state item, the new tag gets appended to the begin of the list. TagListViewItem *last = m_tags->lastItem(); if (last->parent()) last = last->parent(); item = new TagListViewItem(m_tags, last, newTagCopy); } else item = new TagListViewItem(m_tags, newTagCopy); m_deleteTag->setEnabled(true); m_tagBox->setEnabled(true); // Add to the "controller": m_tags->setCurrentItem(item); currentItemChanged(item); item->setSelected(true); m_tagName->setFocus(); } void TagsEditDialog::newState() { TagListViewItem *tagItem = m_tags->currentItem(); if (tagItem->parent()) tagItem = ((TagListViewItem *)(tagItem->parent())); tagItem->setExpanded(true); // Show sub-states if we're adding them for the first time! State *firstState = tagItem->tagCopy()->stateCopies[0]->newState; // Add the first state to the "view". From now on, it's a multi-state tag: if (tagItem->childCount() <= 0) { firstState->setName(tagItem->tagCopy()->newTag->name()); // Force emblem to exists for multi-state tags: if (firstState->emblem().isEmpty()) firstState->setEmblem("empty"); new TagListViewItem(tagItem, tagItem->tagCopy()->stateCopies[0]); } // Add to the "model": StateCopy *newStateCopy = new StateCopy(); firstState->copyTo(newStateCopy->newState); newStateCopy->newState->setId("tag_state_" + QString::number(Tag::getNextStateUid())); // TODO: Check if it's really unique newStateCopy->newState->setName(QString()); // We copied it too but it's likely the name will not be the same tagItem->tagCopy()->stateCopies.append(newStateCopy); m_addedStates.append(newStateCopy->newState); // Add to the "view": TagListViewItem *item = new TagListViewItem(tagItem, tagItem->lastChild(), newStateCopy); // Add to the "controller": m_tags->setCurrentItem(item); currentItemChanged(item); m_stateName->setFocus(); } void TagsEditDialog::moveUp() { if (!m_moveUp->isEnabled()) // Trggered by keyboard shortcut return; TagListViewItem *tagItem = m_tags->currentItem(); // Move in the list view: int idx = 0; QList children; if (tagItem->parent()) { TagListViewItem *parentItem = tagItem->parent(); idx = parentItem->indexOfChild(tagItem); if (idx > 0) { tagItem = (TagListViewItem *)parentItem->takeChild(idx); children = tagItem->takeChildren(); parentItem->insertChild(idx - 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } else { idx = m_tags->indexOfTopLevelItem(tagItem); if (idx > 0) { tagItem = (TagListViewItem *)m_tags->takeTopLevelItem(idx); children = tagItem->takeChildren(); m_tags->insertTopLevelItem(idx - 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } m_tags->setCurrentItem(tagItem); // Move in the value list: if (tagItem->tagCopy()) { int pos = m_tagCopies.indexOf(tagItem->tagCopy()); m_tagCopies.removeAll(tagItem->tagCopy()); int i = 0; for (TagCopy::List::iterator it = m_tagCopies.begin(); it != m_tagCopies.end(); ++it, ++i) if (i == pos - 1) { m_tagCopies.insert(it, tagItem->tagCopy()); break; } } else { StateCopy::List &stateCopies = ((TagListViewItem *)(tagItem->parent()))->tagCopy()->stateCopies; int pos = stateCopies.indexOf(tagItem->stateCopy()); stateCopies.removeAll(tagItem->stateCopy()); int i = 0; for (StateCopy::List::iterator it = stateCopies.begin(); it != stateCopies.end(); ++it, ++i) if (i == pos - 1) { stateCopies.insert(it, tagItem->stateCopy()); break; } } ensureCurrentItemVisible(); m_moveDown->setEnabled(tagItem->nextSibling() != nullptr); m_moveUp->setEnabled(tagItem->prevSibling() != nullptr); } void TagsEditDialog::moveDown() { if (!m_moveDown->isEnabled()) // Trggered by keyboard shortcut return; TagListViewItem *tagItem = m_tags->currentItem(); // Move in the list view: int idx = 0; QList children; if (tagItem->parent()) { TagListViewItem *parentItem = tagItem->parent(); idx = parentItem->indexOfChild(tagItem); if (idx < parentItem->childCount() - 1) { children = tagItem->takeChildren(); tagItem = (TagListViewItem *)parentItem->takeChild(idx); parentItem->insertChild(idx + 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } else { idx = m_tags->indexOfTopLevelItem(tagItem); if (idx < m_tags->topLevelItemCount() - 1) { children = tagItem->takeChildren(); tagItem = (TagListViewItem *)m_tags->takeTopLevelItem(idx); m_tags->insertTopLevelItem(idx + 1, tagItem); tagItem->insertChildren(0, children); tagItem->setExpanded(true); } } m_tags->setCurrentItem(tagItem); // Move in the value list: if (tagItem->tagCopy()) { uint pos = m_tagCopies.indexOf(tagItem->tagCopy()); m_tagCopies.removeAll(tagItem->tagCopy()); if (pos == (uint)m_tagCopies.count() - 1) // Insert at end: iterator does not go there m_tagCopies.append(tagItem->tagCopy()); else { uint i = 0; for (TagCopy::List::iterator it = m_tagCopies.begin(); it != m_tagCopies.end(); ++it, ++i) if (i == pos + 1) { m_tagCopies.insert(it, tagItem->tagCopy()); break; } } } else { StateCopy::List &stateCopies = ((TagListViewItem *)(tagItem->parent()))->tagCopy()->stateCopies; uint pos = stateCopies.indexOf(tagItem->stateCopy()); stateCopies.removeAll(tagItem->stateCopy()); if (pos == (uint)stateCopies.count() - 1) // Insert at end: iterator does not go there stateCopies.append(tagItem->stateCopy()); else { uint i = 0; for (StateCopy::List::iterator it = stateCopies.begin(); it != stateCopies.end(); ++it, ++i) if (i == pos + 1) { stateCopies.insert(it, tagItem->stateCopy()); break; } } } ensureCurrentItemVisible(); m_moveDown->setEnabled(tagItem->nextSibling() != nullptr); m_moveUp->setEnabled(tagItem->prevSibling() != nullptr); } void TagsEditDialog::selectUp() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, nullptr, nullptr); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectDown() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, nullptr, nullptr); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectLeft() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, nullptr, nullptr); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::selectRight() { QKeyEvent *keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, nullptr, nullptr); QApplication::postEvent(m_tags, keyEvent); } void TagsEditDialog::deleteTag() { if (!m_deleteTag->isEnabled()) return; TagListViewItem *item = m_tags->currentItem(); int result = KMessageBox::Continue; if (item->tagCopy() && item->tagCopy()->oldTag) result = KMessageBox::warningContinueCancel(this, i18n("Deleting the tag will remove it from every note it is currently assigned to."), i18n("Confirm Delete Tag"), KGuiItem(i18n("Delete Tag"), "edit-delete")); else if (item->stateCopy() && item->stateCopy()->oldState) result = KMessageBox::warningContinueCancel(this, i18n("Deleting the state will remove the tag from every note the state is currently assigned to."), i18n("Confirm Delete State"), KGuiItem(i18n("Delete State"), "edit-delete")); if (result != KMessageBox::Continue) return; if (item->tagCopy()) { StateCopy::List stateCopies = item->tagCopy()->stateCopies; for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { StateCopy *stateCopy = *stateCopyIt; if (stateCopy->oldState) { m_deletedStates.append(stateCopy->oldState); m_addedStates.removeAll(stateCopy->oldState); } m_addedStates.removeAll(stateCopy->newState); } m_tagCopies.removeAll(item->tagCopy()); // Remove the new tag, to avoid keyboard-shortcut clashes: delete item->tagCopy()->newTag; } else { TagListViewItem *parentItem = item->parent(); // Remove the state: parentItem->tagCopy()->stateCopies.removeAll(item->stateCopy()); if (item->stateCopy()->oldState) { m_deletedStates.append(item->stateCopy()->oldState); m_addedStates.removeAll(item->stateCopy()->oldState); } m_addedStates.removeAll(item->stateCopy()->newState); delete item; item = nullptr; // Transform to single-state tag if needed: if (parentItem->childCount() == 1) { delete parentItem->child(0); m_tags->setCurrentItem(parentItem); } } delete item; if (m_tags->currentItem()) m_tags->currentItem()->setSelected(true); if (!m_tags->firstChild()) { m_deleteTag->setEnabled(false); m_tagBox->setEnabled(false); m_stateBox->setEnabled(false); } } void TagsEditDialog::renameIt() { if (m_tags->currentItem()->tagCopy()) m_tagName->setFocus(); else m_stateName->setFocus(); } void TagsEditDialog::capturedShortcut(const QKeySequence &shortcut) { Q_UNUSED(shortcut); // TODO: Validate it! // m_shortcut->setShortcut(shortcut); } void TagsEditDialog::removeShortcut() { // m_shortcut->setShortcut(QKeySequence()); modified(); } void TagsEditDialog::removeEmblem() { m_emblem->resetIcon(); modified(); } void TagsEditDialog::modified() { if (m_loading) return; TagListViewItem *tagItem = m_tags->currentItem(); if (tagItem == nullptr) return; if (tagItem->tagCopy()) { if (tagItem->tagCopy()->isMultiState()) { saveTagTo(tagItem->tagCopy()->newTag); } else { saveTagTo(tagItem->tagCopy()->newTag); saveStateTo(tagItem->tagCopy()->stateCopies[0]->newState); } } else if (tagItem->stateCopy()) { saveTagTo(((TagListViewItem *)(tagItem->parent()))->tagCopy()->newTag); saveStateTo(tagItem->stateCopy()->newState); } m_tags->currentItem()->setup(); if (m_tags->currentItem()->parent()) m_tags->currentItem()->parent()->setup(); m_removeShortcut->setEnabled(!m_shortcut->shortcut().isEmpty()); m_removeEmblem->setEnabled(!m_emblem->icon().isEmpty() && !m_tags->currentItem()->isEmblemObligatory()); m_onEveryLines->setEnabled(!m_textEquivalent->text().isEmpty()); } void TagsEditDialog::currentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *nextItem) { Q_UNUSED(nextItem); if (item == nullptr) return; m_loading = true; TagListViewItem *tagItem = (TagListViewItem *)item; if (tagItem->tagCopy()) { if (tagItem->tagCopy()->isMultiState()) { loadTagFrom(tagItem->tagCopy()->newTag); loadBlankState(); m_stateBox->setEnabled(false); m_stateBox->setTitle(i18n("State")); m_stateNameLabel->setEnabled(true); m_stateName->setEnabled(true); } else { loadTagFrom(tagItem->tagCopy()->newTag); // TODO: No duplicate loadStateFrom(tagItem->tagCopy()->stateCopies[0]->newState); m_stateBox->setEnabled(true); m_stateBox->setTitle(i18n("Appearance")); m_stateName->setText(QString()); m_stateNameLabel->setEnabled(false); m_stateName->setEnabled(false); } } else if (tagItem->stateCopy()) { loadTagFrom(((TagListViewItem *)(tagItem->parent()))->tagCopy()->newTag); loadStateFrom(tagItem->stateCopy()->newState); m_stateBox->setEnabled(true); m_stateBox->setTitle(i18n("State")); m_stateNameLabel->setEnabled(true); m_stateName->setEnabled(true); } ensureCurrentItemVisible(); m_loading = false; } void TagsEditDialog::ensureCurrentItemVisible() { TagListViewItem *tagItem = m_tags->currentItem(); // Ensure the tag and the states (if available) to be visible, but if there is a looooot of states, // ensure the tag is still visible, even if the last states are not... m_tags->scrollToItem(tagItem); int idx = 0; if (tagItem->parent()) { idx = ((QTreeWidgetItem *)tagItem->parent())->indexOfChild(tagItem); m_moveDown->setEnabled(idx < ((QTreeWidgetItem *)tagItem->parent())->childCount()); } else { idx = m_tags->indexOfTopLevelItem(tagItem); m_moveDown->setEnabled(idx < m_tags->topLevelItemCount()); } m_moveUp->setEnabled(idx > 0); } void TagsEditDialog::loadBlankState() { QFont defaultFont; m_stateName->setText(QString()); m_emblem->resetIcon(); m_removeEmblem->setEnabled(false); m_backgroundColor->setColor(QColor()); m_bold->setChecked(false); m_underline->setChecked(false); m_italic->setChecked(false); m_strike->setChecked(false); m_textColor->setColor(QColor()); // m_font->setCurrentIndex(0); m_font->setCurrentFont(defaultFont.family()); m_fontSize->setCurrentIndex(0); m_textEquivalent->setText(QString()); m_onEveryLines->setChecked(false); m_allowCrossRefernce->setChecked(false); } void TagsEditDialog::loadStateFrom(State *state) { m_stateName->setText(state->name()); if (state->emblem().isEmpty()) m_emblem->resetIcon(); else m_emblem->setIcon(state->emblem()); m_removeEmblem->setEnabled(!state->emblem().isEmpty() && !m_tags->currentItem()->isEmblemObligatory()); m_backgroundColor->setColor(state->backgroundColor()); m_bold->setChecked(state->bold()); m_underline->setChecked(state->underline()); m_italic->setChecked(state->italic()); m_strike->setChecked(state->strikeOut()); m_textColor->setColor(state->textColor()); m_textEquivalent->setText(state->textEquivalent()); m_onEveryLines->setChecked(state->onAllTextLines()); m_allowCrossRefernce->setChecked(state->allowCrossReferences()); QFont defaultFont; if (state->fontName().isEmpty()) m_font->setCurrentFont(defaultFont.family()); else m_font->setCurrentFont(state->fontName()); if (state->fontSize() == -1) m_fontSize->setCurrentIndex(0); else m_fontSize->setItemText(m_fontSize->currentIndex(), QString::number(state->fontSize())); } void TagsEditDialog::loadTagFrom(Tag *tag) { m_tagName->setText(tag->name()); QList shortcuts {tag->shortcut()}; m_shortcut->setShortcut(shortcuts); m_removeShortcut->setEnabled(!tag->shortcut().isEmpty()); m_inherit->setChecked(tag->inheritedBySiblings()); } void TagsEditDialog::saveStateTo(State *state) { state->setName(m_stateName->text()); state->setEmblem(m_emblem->icon()); state->setBackgroundColor(m_backgroundColor->color()); state->setBold(m_bold->isChecked()); state->setUnderline(m_underline->isChecked()); state->setItalic(m_italic->isChecked()); state->setStrikeOut(m_strike->isChecked()); state->setTextColor(m_textColor->color()); state->setTextEquivalent(m_textEquivalent->text()); state->setOnAllTextLines(m_onEveryLines->isChecked()); state->setAllowCrossReferences(m_allowCrossRefernce->isChecked()); if (m_font->currentIndex() == 0) state->setFontName(QString()); else state->setFontName(m_font->currentFont().family()); bool conversionOk; int fontSize = m_fontSize->currentText().toInt(&conversionOk); if (conversionOk) state->setFontSize(fontSize); else state->setFontSize(-1); } void TagsEditDialog::saveTagTo(Tag *tag) { tag->setName(m_tagName->text()); QKeySequence shortcut; if (m_shortcut->shortcut().count() > 0) shortcut = m_shortcut->shortcut()[0]; tag->setShortcut(shortcut); tag->setInheritedBySiblings(m_inherit->isChecked()); } void TagsEditDialog::slotCancel() { // All copies of tag have a shortcut, that is stored as a QAction. // So, shortcuts are duplicated, and if the user press one tag keyboard-shortcut in the main window, there is a conflict. // We then should delete every copies: for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { delete (*tagCopyIt)->newTag; } } void TagsEditDialog::slotOk() { Tag::all.clear(); for (TagCopy::List::iterator tagCopyIt = m_tagCopies.begin(); tagCopyIt != m_tagCopies.end(); ++tagCopyIt) { TagCopy *tagCopy = *tagCopyIt; // Copy changes to the tag and append in the list of tags:: if (tagCopy->oldTag) { tagCopy->newTag->copyTo(tagCopy->oldTag); delete tagCopy->newTag; } Tag *tag = (tagCopy->oldTag ? tagCopy->oldTag : tagCopy->newTag); Tag::all.append(tag); // And change all states: State::List &states = tag->states(); StateCopy::List &stateCopies = tagCopy->stateCopies; states.clear(); for (StateCopy::List::iterator stateCopyIt = stateCopies.begin(); stateCopyIt != stateCopies.end(); ++stateCopyIt) { StateCopy *stateCopy = *stateCopyIt; // Copy changes to the state and append in the list of tags: if (stateCopy->oldState) stateCopy->newState->copyTo(stateCopy->oldState); State *state = (stateCopy->oldState ? stateCopy->oldState : stateCopy->newState); states.append(state); state->setParentTag(tag); } } Tag::saveTags(); // Notify removed states and tags, and then remove them: if (!m_deletedStates.isEmpty()) Global::bnpView->removedStates(m_deletedStates); // Update every note (change colors, size because of font change or added/removed emblems...): Global::bnpView->relayoutAllBaskets(); Global::bnpView->recomputeAllStyles(); } void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { // TagListViewItem* thisItem = qvariant_cast(index.data()); // qDebug() << "Pointer is: " << thisItem << endl; QItemDelegate::paint(painter, option, index); } diff --git a/src/variouswidgets.cpp b/src/variouswidgets.cpp index f01f9b7..3a052f5 100644 --- a/src/variouswidgets.cpp +++ b/src/variouswidgets.cpp @@ -1,411 +1,411 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "variouswidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** class RunCommandRequester: */ RunCommandRequester::RunCommandRequester(const QString &runCommand, const QString &message, QWidget *parent) : QWidget(parent) { m_message = message; QHBoxLayout *layout = new QHBoxLayout(this); m_runCommand = new QLineEdit(runCommand, this); QPushButton *pb = new QPushButton(/*"C&hoose..."*/ i18n("..."), this); pb->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); layout->addWidget(m_runCommand); layout->addWidget(pb); - connect(pb, SIGNAL(clicked()), this, SLOT(slotSelCommand())); + connect(pb, &QPushButton::clicked, this, &RunCommandRequester::slotSelCommand); } RunCommandRequester::~RunCommandRequester() { } void RunCommandRequester::slotSelCommand() { QPointer dlg = new KOpenWithDialog(QList(), m_message, m_runCommand->text(), this); dlg->exec(); if (!dlg->text().isEmpty()) m_runCommand->setText(dlg->text()); } QString RunCommandRequester::runCommand() { return m_runCommand->text(); } void RunCommandRequester::setRunCommand(const QString &runCommand) { m_runCommand->setText(runCommand); } /** class IconSizeCombo: */ IconSizeCombo::IconSizeCombo(QWidget *parent) : KComboBox(parent) { addItem(i18n("%1 by %1 pixels", KIconLoader::SizeSmall)); addItem(i18n("%1 by %1 pixels", KIconLoader::SizeSmallMedium)); addItem(i18n("%1 by %1 pixels", KIconLoader::SizeMedium)); addItem(i18n("%1 by %1 pixels", KIconLoader::SizeLarge)); addItem(i18n("%1 by %1 pixels", KIconLoader::SizeHuge)); addItem(i18n("%1 by %1 pixels", KIconLoader::SizeEnormous)); setCurrentIndex(2); } IconSizeCombo::~IconSizeCombo() { } int IconSizeCombo::iconSize() { switch (currentIndex()) { default: case 0: return KIconLoader::SizeSmall; case 1: return KIconLoader::SizeSmallMedium; case 2: return KIconLoader::SizeMedium; case 3: return KIconLoader::SizeLarge; case 4: return KIconLoader::SizeHuge; case 5: return KIconLoader::SizeEnormous; } } void IconSizeCombo::setSize(int size) { switch (size) { default: case KIconLoader::SizeSmall: setCurrentIndex(0); break; case KIconLoader::SizeSmallMedium: setCurrentIndex(1); break; case KIconLoader::SizeMedium: setCurrentIndex(2); break; case KIconLoader::SizeLarge: setCurrentIndex(3); break; case KIconLoader::SizeHuge: setCurrentIndex(4); break; case KIconLoader::SizeEnormous: setCurrentIndex(5); break; } } /** class ViewSizeDialog: */ ViewSizeDialog::ViewSizeDialog(QWidget *parent, int w, int h) : QDialog(parent) { QLabel *label = new QLabel(i18n("Resize the window to select the image size\n" "and close it or press Escape to accept changes."), this); label->move(8, 8); label->setFixedSize(label->sizeHint()); // setSizeGripEnabled(true) doesn't work (the grip stay at the same place), so we emulate it: m_sizeGrip = new QSizeGrip(this); m_sizeGrip->setFixedSize(m_sizeGrip->sizeHint()); setGeometry(x(), y(), w, h); } ViewSizeDialog::~ViewSizeDialog() { } void ViewSizeDialog::resizeEvent(QResizeEvent *) { setWindowTitle(i18n("%1 by %2 pixels", QString::number(width()), QString::number(height()))); m_sizeGrip->move(width() - m_sizeGrip->width(), height() - m_sizeGrip->height()); } /** class HelpLabel: */ HelpLabel::HelpLabel(const QString &text, const QString &message, QWidget *parent) : KUrlLabel(parent) , m_message(message) { setText(text); setWhatsThis(m_message); connect(this, SIGNAL(leftClickedUrl()), this, SLOT(display())); } HelpLabel::~HelpLabel() { } void HelpLabel::display() { QWhatsThis::showText(mapToGlobal(QPoint(width() / 2, height())), m_message); } /** class IconSizeDialog: */ class UndraggableKIconView : public QListWidget { public: UndraggableKIconView(QWidget *parent = nullptr) : QListWidget(parent) { this->setViewMode(QListView::IconMode); this->setMovement(QListView::Static); this->setSelectionMode(QAbstractItemView::SingleSelection); this->setWrapping(false); } QDrag *dragObject() { return nullptr; } }; IconSizeDialog::IconSizeDialog(const QString &caption, const QString &message, const QString &icon, int iconSize, QWidget *parent) : QDialog(parent) { // QDialog options setWindowTitle(caption); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); setModal(true); QWidget *page = new QWidget(this); QVBoxLayout *topLayout = new QVBoxLayout(page); QLabel *label = new QLabel(message, page); topLayout->addWidget(label); QListWidget *iconView = new UndraggableKIconView(page); QIcon desktopIcon = QIcon::fromTheme(icon); m_size16 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeSmall), i18n("%1 by %1 pixels", KIconLoader::SizeSmall), iconView); m_size22 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeSmallMedium), i18n("%1 by %1 pixels", KIconLoader::SizeSmallMedium), iconView); m_size32 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeMedium), i18n("%1 by %1 pixels", KIconLoader::SizeMedium), iconView); m_size48 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeLarge), i18n("%1 by %1 pixels", KIconLoader::SizeLarge), iconView); m_size64 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeHuge), i18n("%1 by %1 pixels", KIconLoader::SizeHuge), iconView); m_size128 = new QListWidgetItem(desktopIcon.pixmap(KIconLoader::SizeEnormous), i18n("%1 by %1 pixels", KIconLoader::SizeEnormous), iconView); iconView->setIconSize(QSize(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous)); // 128x128 iconView->setMinimumSize(QSize(128 * 6 + (6 + 2) * iconView->spacing() + 20, m_size128->sizeHint().height() + 2 * iconView->spacing() + 20)); topLayout->addWidget(iconView); switch (iconSize) { case KIconLoader::SizeSmall: m_size16->setSelected(true); m_iconSize = KIconLoader::SizeSmall; break; case KIconLoader::SizeSmallMedium: m_size22->setSelected(true); m_iconSize = KIconLoader::SizeSmallMedium; break; default: case KIconLoader::SizeMedium: m_size32->setSelected(true); m_iconSize = KIconLoader::SizeMedium; break; case KIconLoader::SizeLarge: m_size48->setSelected(true); m_iconSize = KIconLoader::SizeLarge; break; case KIconLoader::SizeHuge: m_size64->setSelected(true); m_iconSize = KIconLoader::SizeHuge; break; case KIconLoader::SizeEnormous: m_size128->setSelected(true); m_iconSize = KIconLoader::SizeEnormous; break; } connect(iconView, SIGNAL(executed(QListWidgetItem *)), this, SLOT(choose(QListWidgetItem *))); - connect(iconView, SIGNAL(itemActivated(QListWidgetItem *)), this, SLOT(choose(QListWidgetItem *))); - connect(iconView, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); + connect(iconView, &QListWidget::itemActivated, this, &IconSizeDialog::choose); + connect(iconView, &QListWidget::itemSelectionChanged, this, &IconSizeDialog::slotSelectionChanged); mainLayout->addWidget(page); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &IconSizeDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &IconSizeDialog::reject); mainLayout->addWidget(buttonBox); - connect(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); + connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &IconSizeDialog::slotCancel); } IconSizeDialog::~IconSizeDialog() { } void IconSizeDialog::slotSelectionChanged() { // Change m_iconSize to the new selected one: if (m_size16->isSelected()) { m_iconSize = KIconLoader::SizeSmall; return; } if (m_size22->isSelected()) { m_iconSize = KIconLoader::SizeSmallMedium; return; } if (m_size32->isSelected()) { m_iconSize = KIconLoader::SizeMedium; return; } if (m_size48->isSelected()) { m_iconSize = KIconLoader::SizeLarge; return; } if (m_size64->isSelected()) { m_iconSize = KIconLoader::SizeHuge; return; } if (m_size128->isSelected()) { m_iconSize = KIconLoader::SizeEnormous; return; } // But if user unselected the item (by eg. right clicking a free space), reselect the last one: switch (m_iconSize) { case KIconLoader::SizeSmall: m_size16->setSelected(true); m_iconSize = KIconLoader::SizeSmall; break; case KIconLoader::SizeSmallMedium: m_size22->setSelected(true); m_iconSize = KIconLoader::SizeSmallMedium; break; default: case KIconLoader::SizeMedium: m_size32->setSelected(true); m_iconSize = KIconLoader::SizeMedium; break; case KIconLoader::SizeLarge: m_size48->setSelected(true); m_iconSize = KIconLoader::SizeLarge; break; case KIconLoader::SizeHuge: m_size64->setSelected(true); m_iconSize = KIconLoader::SizeHuge; break; case KIconLoader::SizeEnormous: m_size128->setSelected(true); m_iconSize = KIconLoader::SizeEnormous; break; } } void IconSizeDialog::choose(QListWidgetItem *) { okButton->animateClick(); } void IconSizeDialog::slotCancel() { m_iconSize = -1; } /** class FontSizeCombo: */ FontSizeCombo::FontSizeCombo(bool rw, bool withDefault, QWidget *parent) : KComboBox(rw, parent) , m_withDefault(withDefault) { if (m_withDefault) addItem(i18n("(Default)")); QFontDatabase fontDB; QList sizes = fontDB.standardSizes(); for (QList::Iterator it = sizes.begin(); it != sizes.end(); ++it) addItem(QString::number(*it)); - // connect( this, SIGNAL(activated(const QString&)), this, SLOT(textChangedInCombo(const QString&)) ); - connect(this, SIGNAL(editTextChanged(const QString &)), this, SLOT(textChangedInCombo(const QString &))); + //connect(this, &FontSizeCombo::activated, this, &FontSizeCombo::textChangedInCombo); + connect(this, &FontSizeCombo::editTextChanged, this, &FontSizeCombo::textChangedInCombo); // TODO: 01617 void KFontSizeAction::setFontSize( int size ) } FontSizeCombo::~FontSizeCombo() { } void FontSizeCombo::textChangedInCombo(const QString &text) { bool ok = false; int size = text.toInt(&ok); if (ok) emit sizeChanged(size); } void FontSizeCombo::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) emit escapePressed(); else if (event->key() == Qt::Key_Return) emit returnPressed2(); else KComboBox::keyPressEvent(event); } void FontSizeCombo::setFontSize(qreal size) { setItemText(currentIndex(), QString::number(size)); // TODO: SEE KFontSizeAction::setFontSize( int size ) !!! for a more complete method! } qreal FontSizeCombo::fontSize() { bool ok = false; int size = currentText().toInt(&ok); if (ok) return size; size = currentText().toInt(&ok); if (ok) return size; return font().pointSize(); }