diff --git a/src/application.h b/src/application.h index dc33a31..2b371a6 100644 --- a/src/application.h +++ b/src/application.h @@ -1,31 +1,31 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef APPLICATION_H #define APPLICATION_H #include #include /** * @class Application * @brief Base application * @author Sébastien Laoût */ class Application : public QApplication { public: Application(int &argc, char **argv); ~Application() override; void tryLoadFile(const QStringList &args, const QString &workingDir); //!< Open a file passed as command line argument -private slots: +private Q_SLOTS: /// Activate program window if duplicate instance is started, load file from args void onActivateRequested(const QStringList &args, const QString &workingDir); private: KDBusService m_service; }; #endif // APPLICATION_H diff --git a/src/backgroundmanager.h b/src/backgroundmanager.h index adf13e7..d669bcb 100644 --- a/src/backgroundmanager.h +++ b/src/backgroundmanager.h @@ -1,122 +1,122 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BACKGROUNDMANAGER_H #define BACKGROUNDMANAGER_H #include #include #include #include class QPixmap; class QString; /** A node in the list of background images of BackgroundManager. * It can only be used by BackgroundManager because it is an internal structure of this manager. * @author Sébastien Laoût */ class BackgroundEntry { friend class BackgroundManager; public: ~BackgroundEntry(); protected: BackgroundEntry(const QString &location); QString name; QString location; bool tiled; /// << Only valid after some object subscribed to this image! Because it's only read at this time. QPixmap *pixmap; /// << Only valid (non-null) after some object subscribed to this image! Because it's only read at this time. QPixmap *preview; /// << Only valid (non-null) after some object requested the preview. int customersCount; }; /** A node in the list of opaque background images (with a background color applied to an image) of BackgroundManager. * It can only be used by BackgroundManager because it is an internal structure of this manager. * @author Sébastien Laoût */ class OpaqueBackgroundEntry { friend class BackgroundManager; public: ~OpaqueBackgroundEntry(); protected: OpaqueBackgroundEntry(const QString &name, const QColor &color); QString name; QColor color; QPixmap *pixmap; int customersCount; }; /** Manage the list of background images. * BASIC FUNCTIONNING OF A BACKGROUND CHOOSER: * It get all image names with imageNames() to put them in eg. a QComboBox and then, * when it's time to get the preview of an image it call preview() with the image name to get it. * Preview are only computed on demand and then cached to fast the next demands (only the pointer will have to be returned). * Previews are scaled to fit in a rectangle of 100 by 75 pixels, and with a white background color. * They are also saved to files, so that the scaling/opaquification has not to be done later (they will be directly loaded from file). * Previews are saved in Global::backgroundsFolder()+"previews/", so that emptying the folder is sufficient to remove them. * BASIC FUNCTIONING OF AN IMAGE REQUESTER: * When eg. a basket is assigned an image name, it register it with subscribe(). * The full pixmap is then loaded from file and cached (if it was not already loaded) and the "tiled" property is read from the image configuration file. * If this object want to have the pixmap applied on a background color (for no transparency => really faster drawing), * it should register for the couple (imageName,color) with subscribe(): the pixmap will be created in the cache. * Then, the object can get the subscribed images with pixmap() or opaquePixmap() and know if it's tiled with tiled(). * When the user removed the object background image (or when the object/basket/... is removed), the object should call unsubscribe() for * EVERY subscribed image and image couples. Usage count is decreased for those images and a garbage collector will remove the cached images * if nothing is subscribed to them (to free memory). * @author Sébastien Laoût */ class BackgroundManager : private QObject { Q_OBJECT private: /// LIST OF IMAGES: typedef QList BackgroundsList; typedef QList OpaqueBackgroundsList; public: /// CONTRUCTOR AND DESTRUCTOR: BackgroundManager(); ~BackgroundManager() override; /// SUBSCRIPTION TO IMAGES: bool subscribe(const QString &image); /// << @Return true if the loading is a success. In the counter-case, calling methods below is unsafe with this @p image name. bool subscribe(const QString &image, const QColor &color); /// << Idem. void unsubscribe(const QString &image); void unsubscribe(const QString &image, const QColor &color); /// GETTING THE IMAGES AND PROPERTIES: QPixmap *pixmap(const QString &image); QPixmap *opaquePixmap(const QString &image, const QColor &color); bool tiled(const QString &image); /// LIST OF IMAGES AND PREVIEWS: bool exists(const QString &image); QStringList imageNames(); QPixmap *preview(const QString &image); /// USED FOR EXPORTATION: QString pathForImageName(const QString &image); /// << It is STRONGLY advised to not use those two methods unless it's to copy (export) the images or something like that... QString previewPathForImageName(const QString &image); /// USED FOR IMPORTATION: void addImage(const QString &fullPath); private: BackgroundEntry *backgroundEntryFor(const QString &image); OpaqueBackgroundEntry *opaqueBackgroundEntryFor(const QString &image, const QColor &color); private: BackgroundsList m_backgroundsList; OpaqueBackgroundsList m_opaqueBackgroundsList; QTimer m_garbageTimer; -private slots: +private Q_SLOTS: void requestDelayedGarbage(); void doGarbage(); }; #endif // BACKGROUNDMANAGER_H diff --git a/src/backup.h b/src/backup.h index 7cd46c8..3df0725 100644 --- a/src/backup.h +++ b/src/backup.h @@ -1,83 +1,83 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BACKUP_H #define BACKUP_H #include #include class QApplication; class QLabel; #include "basket_export.h" /** * @author Sébastien Laoût */ class BackupDialog : public QDialog { Q_OBJECT public: explicit BackupDialog(QWidget *parent = nullptr, const char *name = nullptr); ~BackupDialog() override; -private slots: +private Q_SLOTS: void moveToAnotherFolder(); void useAnotherExistingFolder(); void backup(); void restore(); void populateLastBackup(); private: QLabel *m_lastBackup; }; /** * @author Sébastien Laoût */ class BASKET_EXPORT Backup { public: static void figureOutBinaryPath(const char *argv0, QApplication &app); static void setFolderAndRestart(const QString &folder, const QString &message); static QString newSafetyFolder(); private: static QString binaryPath; }; class BackupThread : public QThread { public: BackupThread(const QString &tarFile, const QString &folderToBackup); protected: void run() override; private: QString m_tarFile; QString m_folderToBackup; }; class RestoreThread : public QThread { public: RestoreThread(const QString &tarFile, const QString &destFolder); inline bool success() { return m_success; } protected: void run() override; private: QString m_tarFile; QString m_destFolder; bool m_success; }; #endif // BACKUP_H diff --git a/src/basketlistview.cpp b/src/basketlistview.cpp index ccfa129..50d74fd 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, &QTimer::timeout, this, &BasketTreeListView::autoOpen); setItemDelegate(new FoundCountIcon(this)); } void BasketTreeListView::contextMenuEvent(QContextMenuEvent *e) { - emit contextMenuRequested(e->pos()); + Q_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/basketlistview.h b/src/basketlistview.h index 1ed51de..37d2f2d 100644 --- a/src/basketlistview.h +++ b/src/basketlistview.h @@ -1,130 +1,130 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BASKETLISTVIEW_H #define BASKETLISTVIEW_H #include #include #include class QPixmap; class QResizeEvent; class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; class QFocusEvent; class QDragLeaveEvent; class BasketScene; class BasketListViewItem : public QTreeWidgetItem { public: /// CONSTRUCTOR AND DESTRUCTOR: BasketListViewItem(QTreeWidget *parent, BasketScene *basket); BasketListViewItem(QTreeWidgetItem *parent, BasketScene *basket); BasketListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, BasketScene *basket); BasketListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, BasketScene *basket); ~BasketListViewItem() override; BasketScene *basket() { return m_basket; } void setup(); BasketListViewItem *lastChild(); QStringList childNamesTree(int deep = 0); void moveChildsBaskets(); void ensureVisible(); bool isShown(); bool isCurrentBasket(); bool isUnderDrag(); QString escapedName(const QString &string); bool haveChildsLoading(); bool haveHiddenChildsLoading(); bool haveChildsLocked(); bool haveHiddenChildsLocked(); int countChildsFound(); int countHiddenChildsFound(); void setUnderDrag(bool); bool isAbbreviated(); void setAbbreviated(bool b); private: BasketScene *m_basket; int m_width; bool m_isUnderDrag; bool m_isAbbreviated; }; Q_DECLARE_METATYPE(BasketListViewItem *); class BasketTreeListView : public QTreeWidget { Q_OBJECT public: explicit BasketTreeListView(QWidget *parent = nullptr); void dragEnterEvent(QDragEnterEvent *event) override; void removeExpands(); void dragLeaveEvent(QDragLeaveEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; void resizeEvent(QResizeEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; Qt::DropActions supportedDropActions() const override; /*! Retrieve a basket from the tree * @see BasketListViewItem::basket() */ BasketListViewItem *getBasketInTree(const QModelIndex &index) const; static QString TREE_ITEM_MIME_STRING; protected: QStringList mimeTypes() const override; QMimeData *mimeData(const QList items) const override; bool event(QEvent *e) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void focusInEvent(QFocusEvent *) override; private: QTimer m_autoOpenTimer; QTreeWidgetItem *m_autoOpenItem; -signals: +Q_SIGNALS: void contextMenuRequested(const QPoint &); -private slots: +private Q_SLOTS: void autoOpen(); private: void setItemUnderDrag(BasketListViewItem *item); BasketListViewItem *m_itemUnderDrag; QPoint m_dragStartPosition; }; /** @class FoundCountIcon * Custom-drawn "little numbers" shown on Filter all */ class FoundCountIcon : public QStyledItemDelegate { public: FoundCountIcon(BasketTreeListView *basketTree, QObject *parent = nullptr) : QStyledItemDelegate(parent) , m_basketTree(basketTree) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QPixmap circledTextPixmap(const QString &text, int height, const QFont &font, const QColor &color) const; //! @returns Rect-with-number, or null pixmap if nothing was found / basket is loading QPixmap foundCountPixmap(bool isLoading, int countFound, bool childrenAreLoading, int countChildsFound, const QFont &font, int height) const; BasketTreeListView *m_basketTree; }; #endif // BASKETLISTVIEW_H diff --git a/src/basketproperties.h b/src/basketproperties.h index 0023a62..a35820f 100644 --- a/src/basketproperties.h +++ b/src/basketproperties.h @@ -1,55 +1,55 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BASKETPROPERTIES_H #define BASKETPROPERTIES_H #include #include #include #include "ui_basketproperties.h" class KIconButton; class QLineEdit; class QGroupBox; class QVBoxLayout; class QRadioButton; class QString; class KComboBox; class KShortcutWidget; class QKeySequence; class KColorCombo2; class BasketScene; /** The dialog that hold basket settings. * @author Sébastien Laoût */ class BasketPropertiesDialog : public QDialog, private Ui::BasketPropertiesUi { Q_OBJECT public: explicit BasketPropertiesDialog(BasketScene *basket, QWidget *parent = nullptr); ~BasketPropertiesDialog() override; void ensurePolished(); -public slots: +public Q_SLOTS: void applyChanges(); -protected slots: +protected Q_SLOTS: void capturedShortcut(const QList &shortcut); void selectColumnsLayout(); private: BasketScene *m_basket; KColorCombo2 *m_backgroundColor; KColorCombo2 *m_textColor; QMap m_backgroundImagesMap; }; #endif // BASKETPROPERTIES_H diff --git a/src/basketscene.cpp b/src/basketscene.cpp index c328fc0..6ff1e17 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); + Q_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); + Q_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, &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, &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, &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, &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, &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, &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, &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, &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(); + Q_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(); + Q_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); + Q_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); + // Q_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()))); + Q_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(); + Q_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(); + Q_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); + Q_EMIT resetStatusBarText(); + Q_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, &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, &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(), &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(), &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. + Q_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, &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." + Q_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)); + Q_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)); + Q_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)); + Q_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."*/); + Q_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..." + Q_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()); + Q_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."*/); + Q_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..." + Q_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); + Q_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/basketscene.h b/src/basketscene.h index 4a154fc..e87ce76 100644 --- a/src/basketscene.h +++ b/src/basketscene.h @@ -1,725 +1,725 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BASKET_H #define BASKET_H #include #include #include #include #include #include #include #include "config.h" #include "note.h" // For Note::Zone class QFrame; class QPixmap; class QPushButton; class QDomDocument; class QDomElement; class QContextMenuEvent; class QDragLeaveEvent; class QDragEnterEvent; class QDragMoveEvent; class QDropEvent; class QEvent; class QFocusEvent; class QHelpEvent; class QKeyEvent; class QMouseEvent; class QResizeEvent; class QWheelEvent; class QAction; class KDirWatch; class QKeySequence; class QUrl; namespace KIO { class Job; } class DecoratedBasket; class Note; class NoteEditor; class Tag; class TransparentWidget; #ifdef HAVE_LIBGPGME class KGpgMe; #endif /** * @author Sébastien Laoût */ class BasketScene : public QGraphicsScene { Q_OBJECT public: enum EncryptionTypes { NoEncryption = 0, PasswordEncryption = 1, PrivateKeyEncryption = 2 }; public: /// CONSTRUCTOR AND DESTRUCTOR: BasketScene(QWidget *parent, const QString &folderName); ~BasketScene() override; /// USER INTERACTION: private: bool m_noActionOnMouseRelease; bool m_ignoreCloseEditorOnNextMouseRelease; QPointF m_pressPos; bool m_canDrag; public: void drawBackground(QPainter *painter, const QRectF &rect) override; void drawForeground(QPainter *painter, const QRectF &rect) override; void enterEvent(QEvent *); void leaveEvent(QEvent *); void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; void clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked = nullptr, int zone = 0); -private slots: +private Q_SLOTS: void setFocusIfNotInPopupMenu(); -signals: +Q_SIGNALS: void crossReference(QString link); /// LAYOUT: private: Note *m_firstNote; int m_columnsCount; bool m_mindMap; Note *m_resizingNote; int m_pickedResizer; Note *m_movingNote; QPoint m_pickedHandle; QSet m_notesToBeDeleted; public: qreal tmpWidth; qreal tmpHeight; public: void unsetNotesWidth(); void relayoutNotes(); Note *noteAt(QPointF pos); inline Note *firstNote() { return m_firstNote; } inline int columnsCount() { return m_columnsCount; } inline bool isColumnsLayout() { return m_columnsCount > 0; } inline bool isFreeLayout() { return m_columnsCount <= 0; } inline bool isMindMap() { return isFreeLayout() && m_mindMap; } Note *resizingNote() { return m_resizingNote; } void deleteNotes(); Note *lastNote(); void setDisposition(int disposition, int columnCount); void equalizeColumnSizes(); /// NOTES INSERTION AND REMOVAL: public: /// The following methods assume that the note(s) to insert already all have 'this' as the parent basket: void prependNoteIn(Note *note, Note *in); /// << Add @p note (and the next linked notes) as the first note(s) of the group @p in. void appendNoteIn(Note *note, Note *in); /// << Add @p note (and the next linked notes) as the last note(s) of the group @p in. void appendNoteAfter(Note *note, Note *after); /// << Add @p note (and the next linked notes) just after (just below) the note @p after. void appendNoteBefore(Note *note, Note *before); /// << Add @p note (and the next linked notes) just before (just above) the note @p before. void groupNoteAfter(Note *note, Note *with); /// << Add a group at @p with place, move @p with in it, and add @p note (and the next linked notes) just after the group. void groupNoteBefore(Note *note, Note *with); /// << Add a group at @p with place, move @p with in it, and add @p note (and the next linked notes) just before the group. void unplugNote(Note *note); /// << Unplug @p note (and its child notes) from the basket (and also decrease counts...). /// << After that, you should delete the notes yourself. Do not call prepend/append/group... functions two times: unplug and ok void ungroupNote(Note *group); /// << Unplug @p group but put child notes at its place. /// And this one do almost all the above methods depending on the context: void insertNote(Note *note, Note *clicked, int zone, const QPointF &pos = QPointF(), bool animateNewPosition = false); void insertCreatedNote(Note *note); /// And working with selections: void unplugSelection(NoteSelection *selection); void insertSelection(NoteSelection *selection, Note *after); void selectSelection(NoteSelection *selection); -protected slots: +protected Q_SLOTS: void doCleanUp(); private: void preparePlug(Note *note); private: Note *m_clickedToInsert; int m_zoneToInsert; QPointF m_posToInsert; Note *m_savedClickedToInsert; int m_savedZoneToInsert; QPointF m_savedPosToInsert; bool m_isInsertPopupMenu; QAction *m_insertMenuTitle; public: void saveInsertionData(); void restoreInsertionData(); void resetInsertionData(); -public slots: +public Q_SLOTS: void insertEmptyNote(int type); void insertWizard(int type); void insertColor(const QColor &color); void insertImage(const QPixmap &image); void pasteNote(QClipboard::Mode mode = QClipboard::Clipboard); void delayedCancelInsertPopupMenu(); void setInsertPopupMenu() { m_isInsertPopupMenu = true; } void cancelInsertPopupMenu() { m_isInsertPopupMenu = false; } -private slots: +private Q_SLOTS: void hideInsertPopupMenu(); void timeoutHideInsertPopupMenu(); /// TOOL TIPS: protected: void helpEvent(QGraphicsSceneHelpEvent *event) override; /// LOAD AND SAVE: private: bool m_loaded; bool m_loadingLaunched; bool m_locked; bool m_shouldConvertPlainTextNotes; QFrame *m_decryptBox; QPushButton *m_button; int m_encryptionType; QString m_encryptionKey; #ifdef HAVE_LIBGPGME KGpgMe *m_gpg; #endif QTimer m_inactivityAutoLockTimer; QTimer m_commitdelay; void enableActions(); -private slots: +private Q_SLOTS: void loadNotes(const QDomElement ¬es, Note *parent); void saveNotes(QXmlStreamWriter &stream, Note *parent); void unlock(); -protected slots: +protected Q_SLOTS: void inactivityAutoLockTimeout(); -public slots: +public Q_SLOTS: void load(); void loadProperties(const QDomElement &properties); void saveProperties(QXmlStreamWriter &stream); bool save(); void commitEdit(); void reload(); public: bool isEncrypted(); bool isFileEncrypted(); bool isLocked() { return m_locked; }; void lock(); bool isLoaded() { return m_loaded; }; bool loadingLaunched() { return m_loadingLaunched; }; bool loadFromFile(const QString &fullPath, QString *string); bool loadFromFile(const QString &fullPath, QByteArray *array); bool saveToFile(const QString &fullPath, const QString &string); bool saveToFile(const QString &fullPath, const QByteArray &array); //[Encrypt and] save binary content static bool safelySaveToFile(const QString &fullPath, const QByteArray &array, unsigned long length); static bool safelySaveToFile(const QString &fullPath, const QString &string); bool setProtection(int type, QString key); int encryptionType() { return m_encryptionType; }; QString encryptionKey() { return m_encryptionKey; }; bool saveAgain(); /// BACKGROUND: private: QColor m_backgroundColorSetting; QString m_backgroundImageName; QPixmap *m_backgroundPixmap; QPixmap *m_opaqueBackgroundPixmap; QPixmap *m_selectedBackgroundPixmap; bool m_backgroundTiled; QColor m_textColorSetting; public: inline bool hasBackgroundImage() { return m_backgroundPixmap != nullptr; } inline const QPixmap *backgroundPixmap() { return m_backgroundPixmap; } inline bool isTiledBackground() { return m_backgroundTiled; } inline QString backgroundImageName() { return m_backgroundImageName; } inline QColor backgroundColorSetting() { return m_backgroundColorSetting; } inline QColor textColorSetting() { return m_textColorSetting; } QColor backgroundColor() const; QColor textColor() const; void setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor); void blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter = -1, qreal yPainter = -1, bool opaque = false, QPixmap *bg = nullptr); void blendBackground(QPainter &painter, const QRectF &rect, bool opaque, QPixmap *bg); void unbufferizeAll(); void subscribeBackgroundImages(); void unsubscribeBackgroundImages(); /// KEYBOARD SHORTCUT: public: QAction *m_action; private: int m_shortcutAction; -private slots: +private Q_SLOTS: void activatedShortcut(); public: QKeySequence shortcut() { return m_action->shortcut(); } int shortcutAction() { return m_shortcutAction; } void setShortcut(QKeySequence shortcut, int action); /// USER INTERACTION: private: Note *m_hoveredNote; int m_hoveredZone; bool m_lockedHovering; bool m_underMouse; QRectF m_inserterRect; bool m_inserterShown; bool m_inserterSplit; bool m_inserterTop; bool m_inserterGroup; void placeInserter(Note *note, int zone); void removeInserter(); public: // bool inserterShown() { return m_inserterShown; } bool inserterSplit() { return m_inserterSplit; } bool inserterGroup() { return m_inserterGroup; } -public slots: +public Q_SLOTS: void doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos = QPointF(0, 0)); /// << @p pos is optional and only used to show the link target in the statusbar void doHoverEffects(const QPointF &pos); void doHoverEffects(); // The same, but using the current cursor position void mouseEnteredEditorWidget(); public: void popupTagsMenu(Note *note); void popupEmblemMenu(Note *note, int emblemNumber); void addTagToSelectedNotes(Tag *tag); void removeTagFromSelectedNotes(Tag *tag); void removeAllTagsFromSelectedNotes(); void addStateToSelectedNotes(State *state); void changeStateOfSelectedNotes(State *state); bool selectedNotesHaveTags(); const QRectF &inserterRect() { return m_inserterRect; } bool inserterShown() { return m_inserterShown; } void drawInserter(QPainter &painter, qreal xPainter, qreal yPainter); DecoratedBasket *decoration(); State *stateForTagFromSelectedNotes(Tag *tag); -public slots: +public Q_SLOTS: void activatedTagShortcut(Tag *tag); void recomputeAllStyles(); void removedStates(const QList &deletedStates); -private slots: +private Q_SLOTS: void toggledTagInMenu(QAction *act); void toggledStateInMenu(QAction *act); void unlockHovering(); void disableNextClick(); public: Note *m_tagPopupNote; private: Tag *m_tagPopup; QTime m_lastDisableClick; /// SELECTION: private: bool m_isSelecting; bool m_selectionStarted; bool m_selectionInvert; QPointF m_selectionBeginPoint; QPointF m_selectionEndPoint; QRectF m_selectionRect; QTimer m_autoScrollSelectionTimer; void stopAutoScrollSelection(); -private slots: +private Q_SLOTS: void doAutoScrollSelection(); public: inline bool isSelecting() { return m_isSelecting; } inline const QRectF &selectionRect() { return m_selectionRect; } void selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers = true); void resetWasInLastSelectionRect(); void selectAll(); void unselectAll(); void invertSelection(); void unselectAllBut(Note *toSelect); void invertSelectionOf(Note *toSelect); QColor selectionRectInsideColor(); Note *theSelectedNote(); NoteSelection *selectedNotes(); /// BLANK SPACES DRAWING: private: QList m_blankAreas; void recomputeBlankRects(); QWidget *m_cornerWidget; /// COMMUNICATION WITH ITS CONTAINER: -signals: +Q_SIGNALS: void postMessage(const QString &message); /// << Post a temporary message in the statusBar. void setStatusBarText(const QString &message); /// << Set the permanent statusBar text or reset it if message isEmpty(). void resetStatusBarText(); /// << Equivalent to setStatusBarText(QString()). void propertiesChanged(BasketScene *basket); void countsChanged(BasketScene *basket); -public slots: +public Q_SLOTS: void linkLookChanged(); void signalCountsChanged(); private: QTimer m_timerCountsChanged; -private slots: +private Q_SLOTS: void countsChangedTimeOut(); /// NOTES COUNTING: public: void addSelectedNote() { ++m_countSelecteds; signalCountsChanged(); } void removeSelectedNote() { --m_countSelecteds; signalCountsChanged(); } void resetSelectedNote() { m_countSelecteds = 0; signalCountsChanged(); } // FIXME: Useful ??? int count() { return m_count; } int countFounds() { return m_countFounds; } int countSelecteds() { return m_countSelecteds; } private: int m_count; int m_countFounds; int m_countSelecteds; /// PROPERTIES: public: QString basketName() { return m_basketName; } QString icon() { return m_icon; } QString folderName() { return m_folderName; } QString fullPath(); QString fullPathForFileName(const QString &fileName); // Full path of an [existing or not] note in this basket static QString fullPathForFolderName(const QString &folderName); private: QString m_basketName; QString m_icon; QString m_folderName; /// ACTIONS ON SELECTED NOTES FROM THE INTERFACE: -public slots: +public Q_SLOTS: void noteEdit(Note *note = nullptr, bool justAdded = false, const QPointF &clickedPoint = QPointF()); void showEditedNoteWhileFiltering(); void noteDelete(); void noteDeleteWithoutConfirmation(bool deleteFilesToo = true); void noteCopy(); void noteCut(); void noteOpen(Note *note = nullptr); void noteOpenWith(Note *note = nullptr); void noteSaveAs(); void noteGroup(); void noteUngroup(); void noteMoveOnTop(); void noteMoveOnBottom(); void noteMoveNoteUp(); void noteMoveNoteDown(); void moveSelectionTo(Note *here, bool below); public: enum CopyMode { CopyToClipboard, CopyToSelection, CutToClipboard }; void doCopy(CopyMode copyMode); bool selectionIsOneGroup(); Note *selectedGroup(); Note *firstSelected(); Note *lastSelected(); /// NOTES EDITION: private: NoteEditor *m_editor; // QWidget *m_rightEditorBorder; TransparentWidget *m_leftEditorBorder; TransparentWidget *m_rightEditorBorder; bool m_redirectEditActions; bool m_editorTrackMouseEvent; qreal m_editorWidth; qreal m_editorHeight; QTimer m_inactivityAutoSaveTimer; bool m_doNotCloseEditor; QTextCursor m_textCursor; public: bool isDuringEdit() { return m_editor; } bool redirectEditActions() { return m_redirectEditActions; } bool hasTextInEditor(); bool hasSelectedTextInEditor(); bool selectedAllTextInEditor(); Note *editedNote(); -protected slots: +protected Q_SLOTS: void selectionChangedInEditor(); void contentChangedInEditor(); void inactivityAutoSaveTimeout(); -public slots: +public Q_SLOTS: void editorCursorPositionChanged(); private: qreal m_editorX; qreal m_editorY; -public slots: +public Q_SLOTS: void placeEditor(bool andEnsureVisible = false); void placeEditorAndEnsureVisible(); bool closeEditor(bool deleteEmptyNote = true); void closeEditorDelayed(); void updateEditorAppearance(); void editorPropertiesChanged(); void openBasket(); void closeBasket(); /// FILTERING: -public slots: +public Q_SLOTS: void newFilter(const FilterData &data, bool andEnsureVisible = true); void filterAgain(bool andEnsureVisible = true); void filterAgainDelayed(); bool isFiltering(); /// DRAG AND DROP: private: bool m_isDuringDrag; QList m_draggedNotes; public: static void acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond = true); void dropEvent(QGraphicsSceneDragDropEvent *event) override; void blindDrop(QGraphicsSceneDragDropEvent *event); void blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source); bool isDuringDrag() { return m_isDuringDrag; } QList draggedNotes() { return m_draggedNotes; } protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *) override; void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override; void dragLeaveEvent(QGraphicsSceneDragDropEvent *) override; -public slots: +public Q_SLOTS: void slotCopyingDone2(KIO::Job *job, const QUrl &from, const QUrl &to); public: Note *noteForFullPath(const QString &path); /// EXPORTATION: public: QList usedStates(); public: void listUsedTags(QList &list); /// MANAGE FOCUS: private: Note *m_focusedNote; public: void setFocusedNote(Note *note); void focusANote(); void focusANonSelectedNoteAbove(bool inSameColumn); void focusANonSelectedNoteBelow(bool inSameColumn); void focusANonSelectedNoteBelowOrThenAbove(); void focusANonSelectedNoteAboveOrThenBelow(); Note *focusedNote() { return m_focusedNote; } Note *firstNoteInStack(); Note *lastNoteInStack(); Note *firstNoteShownInStack(); Note *lastNoteShownInStack(); void selectRange(Note *start, Note *end, bool unselectOthers = true); /// FIXME: Not really a focus related method! void ensureNoteVisible(Note *note); void keyPressEvent(QKeyEvent *event) override; void focusInEvent(QFocusEvent *) override; void focusOutEvent(QFocusEvent *) override; QRectF noteVisibleRect(Note *note); // clipped global (desktop as origin) rectangle Note *firstNoteInGroup(); Note *noteOnHome(); Note *noteOnEnd(); enum NoteOn { LEFT_SIDE = 1, RIGHT_SIDE, TOP_SIDE, BOTTOM_SIDE }; Note *noteOn(NoteOn side); /// REIMPLEMENTED: public: void deleteFiles(); bool convertTexts(); public: void wheelEvent(QGraphicsSceneWheelEvent *event) override; public: Note *m_startOfShiftSelectionNote; /// THE NEW FILE WATCHER: private: KDirWatch *m_watcher; QTimer m_watcherTimer; QList m_modifiedFiles; public: void addWatchedFile(const QString &fullPath); void removeWatchedFile(const QString &fullPath); -private slots: +private Q_SLOTS: void watchedFileModified(const QString &fullPath); void watchedFileDeleted(const QString &fullPath); void updateModifiedNotes(); /// FROM OLD ARCHITECTURE ********************** -public slots: +public Q_SLOTS: void showFrameInsertTo() { } void resetInsertTo() { } void computeInsertPlace(const QPointF & /*cursorPosition*/) { } public: friend class SystemTray; /// SPEED OPTIMIZATION private: bool m_finishLoadOnFirstShow; bool m_relayoutOnNextShow; public: void aboutToBeActivated(); QGraphicsView *graphicsView() { return m_view; } private: QGraphicsView *m_view; }; #endif // BASKET_H diff --git a/src/basketstatusbar.h b/src/basketstatusbar.h index 123a079..18b8d0c 100644 --- a/src/basketstatusbar.h +++ b/src/basketstatusbar.h @@ -1,59 +1,59 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BASKETSTATUSBAR_H #define BASKETSTATUSBAR_H #include #include #include "basket_export.h" class QStatusBar; namespace KParts { class StatusBarExtension; } class QWidget; class QLabel; /** @author Sébastien Laoût */ class BASKET_EXPORT BasketStatusBar : public QObject { Q_OBJECT public: explicit BasketStatusBar(QStatusBar *bar); BasketStatusBar(KParts::StatusBarExtension *extension); ~BasketStatusBar() override; -public slots: +public Q_SLOTS: /** GUI Main Window actions **/ void setStatusBarHint(const QString &hint); /// << Set a specific message or update if hint is empty void updateStatusBarHint(); /// << Display the current state message (dragging, editing) or reset the startsbar message void postStatusbarMessage(const QString &text); void setSelectionStatus(const QString &s); void setLockStatus(bool isLocked); void setupStatusBar(); void setUnsavedStatus(bool isUnsaved); protected: QStatusBar *statusBar() const; void addWidget(QWidget *widget, int stretch = 0, bool permanent = false); void setStatusText(const QString &txt); bool eventFilter(QObject *obj, QEvent *event) override; private: QStatusBar *m_bar; KParts::StatusBarExtension *m_extension; QLabel *m_selectionStatus; QLabel *m_lockStatus; QLabel *m_basketStatus; QLabel *m_savedStatus; QPixmap m_savedStatusPixmap; }; #endif diff --git a/src/bnpview.cpp b/src/bnpview.cpp index f870657..62e5096 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, &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, &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, &BasketTreeListView::itemActivated, this, &BNPView::slotPressed); connect(m_tree, &BasketTreeListView::itemPressed, this, &BNPView::slotPressed); connect(m_tree, &BasketTreeListView::itemClicked, this, &BNPView::slotPressed); 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, &BasketTreeListView::itemExpanded, this, &BNPView::basketChanged); connect(m_tree, &BasketTreeListView::itemCollapsed, this, &BNPView::basketChanged); connect(this, &BNPView::basketChanged, this, &BNPView::slotBasketChanged); 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())); // 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, &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, &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, &BasketScene::countsChanged, this, &BNPView::countsChanged); // Important: Create listViewItem and connect signal BEFORE loadProperties(), so we get the listViewItem updated without extra work: connect(basket, &BasketScene::propertiesChanged, this, &BNPView::updateBasketListViewItem); 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(); + Q_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()) { + Q_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()) { + Q_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) { + Q_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) { + Q_FOREACH (subDirEntry, fileList) { fileInfo.setFile(Global::basketsFolder() + '/' + subDirEntry); if (!fileInfo.isFile()) { fileList.removeAll(subDirEntry); DEBUG_WIN << "\t\t" + subDirEntry + " already removed!"; } } } - foreach (subDirEntry, fileList) { + Q_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, &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); + Q_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(); + Q_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, &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"), &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/bnpview.h b/src/bnpview.h index 09c5a03..cea8704 100644 --- a/src/bnpview.h +++ b/src/bnpview.h @@ -1,372 +1,372 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BNPVIEW_H #define BNPVIEW_H #include #include #include #include #include #include "basket_export.h" #include "global.h" #include class QDomElement; class QStackedWidget; class QPixmap; class QTimer; class QTreeWidget; class QTreeWidgetItem; class QUndoStack; class QEvent; class QShowEvent; class QAction; class KToggleAction; class QMenu; class KTar; class DesktopColorPicker; class RegionGrabber; class BasketScene; class DecoratedBasket; class BasketListViewItem; class BasketTreeListView; class NoteSelection; class BasketStatusBar; class Tag; class State; class Note; class KMainWindow; class BASKET_EXPORT BNPView : public QSplitter { Q_OBJECT Q_CLASSINFO("D Bus Interface", "org.kde.basket.dbus"); public: /// CONSTRUCTOR AND DESTRUCTOR: BNPView(QWidget *parent, const char *name, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, BasketStatusBar *bar); ~BNPView() override; /// MANAGE CONFIGURATION EVENTS!: void setTreePlacement(bool onLeft); void relayoutAllBaskets(); void recomputeAllStyles(); void removedStates(const QList &deletedStates); void linkLookChanged(); void filterPlacementChanged(bool onTop); /// MANAGE BASKETS: BasketListViewItem *listViewItemForBasket(BasketScene *basket); BasketScene *currentBasket(); BasketScene *parentBasketOf(BasketScene *basket); void setCurrentBasket(BasketScene *basket); void setCurrentBasketInHistory(BasketScene *basket); void removeBasket(BasketScene *basket); /// For NewBasketDialog (and later some other classes): int topLevelItemCount(); /// BasketListViewItem *topLevelItem(int i); int basketCount(QTreeWidgetItem *parent = nullptr); bool canFold(); bool canExpand(); void enableActions(); private: //! Create element with void writeBasketElement(QTreeWidgetItem *item, QXmlStreamWriter &steam); -public slots: +public Q_SLOTS: void countsChanged(BasketScene *basket); void notesStateChanged(); bool convertTexts(); void updateBasketListViewItem(BasketScene *basket); void save(); void save(QTreeWidget *listView, QTreeWidgetItem *firstItem, QXmlStreamWriter &stream); void saveSubHierarchy(QTreeWidgetItem *item, QXmlStreamWriter &stream, bool recursive); void load(); void load(QTreeWidgetItem *item, const QDomElement &baskets); void loadNewBasket(const QString &folderName, const QDomElement &properties, BasketScene *parent); void goToPreviousBasket(); void goToNextBasket(); void foldBasket(); void expandBasket(); void closeAllEditors(); /// void toggleFilterAllBaskets(bool doFilter); void newFilter(); void newFilterFromFilterBar(); bool isFilteringAllBaskets(); // From main window void importTextFile(); void backupRestore(); void checkCleanup(); /** Note */ void activatedTagShortcut(); void exportToHTML(); void editNote(); void cutNote(); void copyNote(); void delNote(); void openNote(); void openNoteWith(); void saveNoteAs(); void noteGroup(); void noteUngroup(); void moveOnTop(); void moveOnBottom(); void moveNoteUp(); void moveNoteDown(); void slotSelectAll(); void slotUnselectAll(); void slotInvertSelection(); void slotResetFilter(); void slotColorFromScreen(bool global = false); void slotColorFromScreenGlobal(); void colorPicked(const QColor &color); void slotConvertTexts(); /** Global shortcuts */ void addNoteText(); void addNoteHtml(); void addNoteImage(); void addNoteLink(); void addNoteCrossReference(); void addNoteColor(); /** Passive Popups for Global Actions */ void showPassiveDropped(const QString &title); void showPassiveDroppedDelayed(); // Do showPassiveDropped(), but delayed void showPassiveContent(bool forceShow = false); void showPassiveContentForced(); void showPassiveImpossible(const QString &message); void showPassiveLoading(BasketScene *basket); // For GUI : void setFiltering(bool filtering); /** Edit */ void undo(); void redo(); void globalPasteInCurrentBasket(); void pasteInCurrentBasket(); void pasteSelInCurrentBasket(); void pasteToBasket(int index, QClipboard::Mode mode = QClipboard::Clipboard); void showHideFilterBar(bool show, bool switchFocus = true); /** Insert **/ void insertEmpty(int type); void insertWizard(int type); void grabScreenshot(bool global = false); void grabScreenshotGlobal(); void screenshotGrabbed(const QPixmap &pixmap); /** BasketScene */ void askNewBasket(); void askNewBasket(BasketScene *parent, BasketScene *pickProperties); void askNewSubBasket(); void askNewSiblingBasket(); void aboutToHideNewBasketPopup(); void setNewBasketPopup(); void cancelNewBasketPopup(); void propBasket(); void delBasket(); void doBasketDeletion(BasketScene *basket); void password(); void saveAsArchive(); void openArchive(); void delayedOpenArchive(); void delayedOpenBasket(); void lockBasket(); void hideOnEscape(); void changedSelectedNotes(); void timeoutTryHide(); void timeoutHide(); void loadCrossReference(QString link); QString folderFromBasketNameLink(QStringList pages, QTreeWidgetItem *parent = nullptr); void sortChildrenAsc(); void sortChildrenDesc(); void sortSiblingsAsc(); void sortSiblingsDesc(); public: static QString s_fileToOpen; static QString s_basketToOpen; -public slots: +public Q_SLOTS: void addWelcomeBaskets(); -private slots: +private Q_SLOTS: void updateNotesActions(); void slotBasketChanged(); void canUndoRedoChanged(); void currentBasketChanged(); void isLockedChanged(); void lateInit(); void onFirstShow(); public: QAction *m_actEditNote; QAction *m_actOpenNote; QAction *m_actPaste; QAction *m_actGrabScreenshot; QAction *m_actColorPicker; QAction *m_actLockBasket; QAction *m_actPassBasket; QAction *actNewBasket; QAction *actNewSubBasket; QAction *actNewSiblingBasket; QAction *m_actHideWindow; QAction *m_actExportToHtml; QAction *m_actPropBasket; QAction *m_actSortChildrenAsc; QAction *m_actSortChildrenDesc; QAction *m_actSortSiblingsAsc; QAction *m_actSortSiblingsDesc; QAction *m_actDelBasket; KToggleAction *m_actFilterAllBaskets; private: // Basket actions: QAction *m_actSaveAsArchive; QAction *m_actOpenArchive; // Notes actions : QAction *m_actOpenNoteWith; QAction *m_actSaveNoteAs; QAction *m_actGroup; QAction *m_actUngroup; QAction *m_actMoveOnTop; QAction *m_actMoveNoteUp; QAction *m_actMoveNoteDown; QAction *m_actMoveOnBottom; // Edit actions : QAction *m_actUndo; QAction *m_actRedo; QAction *m_actCutNote; QAction *m_actCopyNote; QAction *m_actDelNote; QAction *m_actSelectAll; QAction *m_actUnselectAll; QAction *m_actInvertSelection; // Insert actions : // QAction *m_actInsertText; QAction *m_actInsertHtml; QAction *m_actInsertLink; QAction *m_actInsertCrossReference; QAction *m_actInsertImage; QAction *m_actInsertColor; QAction *m_actImportKMenu; QAction *m_actInsertLauncher; QAction *m_actImportIcon; QAction *m_actLoadFile; QList m_insertActions; // Basket actions : KToggleAction *m_actShowFilter; QAction *m_actResetFilter; // Go actions : QAction *m_actPreviousBasket; QAction *m_actNextBasket; QAction *m_actFoldBasket; QAction *m_actExpandBasket; // QAction *m_convertTexts; // FOR_BETA_PURPOSE void setupActions(); void setupGlobalShortcuts(); DecoratedBasket *currentDecoratedBasket(); public: BasketScene *loadBasket(const QString &folderName); // Public only for class Archive BasketListViewItem *appendBasket(BasketScene *basket, QTreeWidgetItem *parentItem); // Public only for class Archive BasketScene *basketForFolderName(const QString &folderName); Note *noteForFileName(const QString &fileName, BasketScene &basket, Note *note = nullptr); QMenu *popupMenu(const QString &menuName); bool isMainWindowActive(); void showMainWindow(); // TODO: dcop calls -- dbus these public Q_SLOTS: Q_SCRIPTABLE void newBasket(); Q_SCRIPTABLE void handleCommandLine(); Q_SCRIPTABLE void reloadBasket(const QString &folderName); Q_SCRIPTABLE bool createNoteHtml(const QString content, const QString basket); Q_SCRIPTABLE QStringList listBaskets(); Q_SCRIPTABLE bool createNoteFromFile(const QString url, const QString basket); Q_SCRIPTABLE bool changeNoteHtml(const QString content, const QString basket, const QString noteName); -public slots: +public Q_SLOTS: void setWindowTitle(QString s); void updateStatusBarHint(); void setSelectionStatus(QString s); void setLockStatus(bool isLocked); void postStatusbarMessage(const QString &); void setStatusBarHint(const QString &); void setUnsavedStatus(bool isUnsaved); void setActive(bool active = true); KActionCollection *actionCollection() { return m_actionCollection; }; void populateTagsMenu(); void populateTagsMenu(QMenu &menu, Note *referenceNote); void connectTagsMenu(); void disconnectTagsMenu(); void disconnectTagsMenuDelayed(); protected: void showEvent(QShowEvent *) override; private: QMenu *m_lastOpenedTagsMenu; -private slots: +private Q_SLOTS: void slotPressed(QTreeWidgetItem *item, int column); void needSave(QTreeWidgetItem *); void slotContextMenu(const QPoint &pos); void slotShowProperties(QTreeWidgetItem *item); void initialize(); -signals: +Q_SIGNALS: void basketChanged(); void setWindowCaption(const QString &s); void showPart(); protected: void enterEvent(QEvent *) override; void leaveEvent(QEvent *) override; protected: void hideMainWindow(); private: BasketTreeListView *m_tree; QStackedWidget *m_stack; bool m_loading; bool m_newBasketPopup; bool m_firstShow; std::unique_ptr m_colorPicker; bool m_colorPickWasShown; bool m_colorPickWasGlobal; RegionGrabber *m_regionGrabber; QString m_passiveDroppedTitle; NoteSelection *m_passiveDroppedSelection; static const int c_delayTooltipTime; KActionCollection *m_actionCollection; KXMLGUIClient *m_guiClient; BasketStatusBar *m_statusbar; QTimer *m_tryHideTimer; QTimer *m_hideTimer; QUndoStack *m_history; KMainWindow *m_HiddenMainWindow; }; #endif // BNPVIEW_H diff --git a/src/colorpicker.cpp b/src/colorpicker.cpp index 02e91df..0343dd7 100644 --- a/src/colorpicker.cpp +++ b/src/colorpicker.cpp @@ -1,88 +1,88 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #include "colorpicker.h" #include #include #include #include /// /// /** DektopColorPicker */ /* From Qt documentation: * " Note that only visible widgets can grab mouse input. * If isVisible() returns FALSE for a widget, that widget cannot call grabMouse(). " * So, we should use an always visible widget to be able to pick a color from screen, * even by first the main window (user rarely want to grab a color from BasKet!) * or use a global shortcut (main window can be hidden when hitting that shortcut). */ DesktopColorPicker::DesktopColorPicker() : QDesktopWidget() { setObjectName("DesktopColorPicker"); m_gettingColorFromScreen = false; } void DesktopColorPicker::pickColor() { m_gettingColorFromScreen = true; // Global::mainContainer->setActive(false); QTimer::singleShot(50, this, &DesktopColorPicker::slotDelayedPick); } /* When firered from basket context menu, and not from menu, grabMouse doesn't work! * It's perhaps because context menu call slotColorFromScreen() and then * ungrab the mouse (since menus grab the mouse). * But why isn't there such bug with normal menus?... * By calling this method with a QTimer::singleShot, we are sure context menu code is * finished and we can grab the mouse without loosing the grab: */ void DesktopColorPicker::slotDelayedPick() { grabKeyboard(); grabMouse(Qt::CrossCursor); setMouseTracking(true); } /* Validate the color */ void DesktopColorPicker::mouseReleaseEvent(QMouseEvent *event) { if (m_gettingColorFromScreen) { m_gettingColorFromScreen = false; releaseMouse(); releaseKeyboard(); setMouseTracking(false); // copied logic from https://code.woboq.org/qt5/qtbase/src/widgets/dialogs/qcolordialog.cpp.html const QPoint globalPos = QCursor::pos(); const QDesktopWidget *desktop = QApplication::desktop(); const QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(desktop->winId(), globalPos.x(), globalPos.y(), 1, 1); QImage i = pixmap.toImage(); Q_EMIT pickedColor(i.pixel(0, 0)); } else { QDesktopWidget::mouseReleaseEvent(event); } } /* Cancel the mode */ void DesktopColorPicker::keyPressEvent(QKeyEvent *event) { if (m_gettingColorFromScreen) { if (event->key() == Qt::Key_Escape) { m_gettingColorFromScreen = false; releaseMouse(); releaseKeyboard(); setMouseTracking(false); - emit canceledPick(); + Q_EMIT canceledPick(); } } QDesktopWidget::keyPressEvent(event); } diff --git a/src/colorpicker.h b/src/colorpicker.h index 9172783..4d14f51 100644 --- a/src/colorpicker.h +++ b/src/colorpicker.h @@ -1,46 +1,46 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef COLORPICKER_H #define COLORPICKER_H #include class QKeyEvent; class QMouseEvent; /** Class to pick a color on the screen * @author Sébastien Laoût */ class DesktopColorPicker : public QDesktopWidget { Q_OBJECT public: /** Constructor, initializer and destructor */ DesktopColorPicker(); ~DesktopColorPicker() override = default; -public slots: +public Q_SLOTS: /** Begin color picking. * This function returns immediately, and pickedColor() is emitted if user has * chosen a color, and not canceled the process (by pressing Escape). */ void pickColor(); -signals: +Q_SIGNALS: /** When user picked a color, this signal is emitted. */ void pickedColor(const QColor &color); /** When user cancel a picking (by pressing Escape), this signal is emitted. */ void canceledPick(); -protected slots: +protected Q_SLOTS: void slotDelayedPick(); protected: void mouseReleaseEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; bool m_gettingColorFromScreen; }; #endif // COLORPICKER_H diff --git a/src/crashhandler.cpp b/src/crashhandler.cpp index 3d027a1..a9fcee9 100644 --- a/src/crashhandler.cpp +++ b/src/crashhandler.cpp @@ -1,238 +1,238 @@ /** * SPDX-FileCopyrightText: (C) 2005 Max Howell * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "crashhandler.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif #include //popen, fread #include //pid_t #include //waitpid #include //write, getppid static QString runCommand(const QByteArray &command) { static const uint SIZE = 40960; // 40 KiB static char stdoutBuf[SIZE]; // debug() << "Running: " << command << endl; FILE *process = ::popen(command, "r"); stdoutBuf[std::fread(static_cast(stdoutBuf), sizeof(char), SIZE - 1, process)] = '\0'; ::pclose(process); return QString::fromLocal8Bit(stdoutBuf); } void Crash::crashHandler(int /*signal*/) { #ifndef Q_OS_WIN // we need to fork to be able to get a // semi-decent bt - I dunno why const pid_t pid = ::fork(); if (pid <= 0) { // we are the child process (the result of the fork) // debug() << "amaroK is crashing...\n"; QString subject = "[basket-crash] " VERSION " "; QString body = i18n( "%1 has crashed! We're sorry about this.\n" "\n" "But, all is not lost! You could potentially help us fix the crash. " "Information describing the crash is below, so just click send, " "or if you have time, write a brief description of how the crash happened first.\n\n" "Many thanks.", QGuiApplication::applicationDisplayName()) + "\n\n"; body += "\n\n\n\n\n\n" + i18n("The information below is to help the developers identify the problem, " "please do not modify it.") + "\n\n\n\n"; body += "======== DEBUG INFORMATION =======\n" "Version: " VERSION "\n" "CC version: " __VERSION__ "\n" // assuming we're using GCC "Frameworks: " KCOREADDONS_VERSION_STRING "\n"; // "TagLib: %2.%3.%4\n"; /* body = body .arg( TAGLIB_MAJOR_VERSION ) .arg( TAGLIB_MINOR_VERSION ) .arg( TAGLIB_PATCH_VERSION );*/ #ifdef NDEBUG body += "NDEBUG: true"; #endif body += '\n'; body += "OS:\n" + Crash::getOSVersionInfo() + '\n'; /// obtain the backtrace with gdb QTemporaryFile temp; temp.open(); temp.setAutoRemove(true); const int handle = temp.handle(); // QCString gdb_command_string = // "file amaroqApp\n" // "attach " + QCString().setNum( ::getppid() ) + "\n" // "bt\n" "echo \\n\n" // "thread apply all bt\n"; const QByteArray gdb_batch = "bt\n" "echo \\n\\n\n" "bt full\n" "echo \\n\\n\n" "echo ==== (gdb) thread apply all bt ====\\n\n" "thread apply all bt\n"; ::write(handle, gdb_batch, gdb_batch.length()); ::fsync(handle); // so we can read stderr too ::dup2(fileno(stdout), fileno(stderr)); QByteArray gdb; gdb = "gdb --nw -n --batch -x "; gdb += temp.fileName().toLatin1(); gdb += ' '; gdb += QGuiApplication::applicationFilePath(); gdb += ' '; gdb += QByteArray().setNum(::getppid()); QString bt = runCommand(gdb); /// clean up bt.remove("(no debugging symbols found)..."); bt.remove("(no debugging symbols found)\n"); bt.replace(QRegExp("\n{2,}"), "\n"); // clean up multiple \n characters bt = bt.trimmed(); /// analyze usefulness bool useful = true; const QString fileCommandOutput = runCommand("file `which basket`"); if (fileCommandOutput.indexOf("not stripped") == -1) subject += "[___stripped]"; // same length as below else subject += "[NOTstripped]"; if (!bt.isEmpty()) { const int invalidFrames = bt.count(QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?")); const int validFrames = bt.count(QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]")); const int totalFrames = invalidFrames + validFrames; if (totalFrames > 0) { const double validity = double(validFrames) / totalFrames; subject += QString("[validity: %1]").arg(validity, 0, 'f', 2); if (validity <= 0.5) useful = false; } subject += QString("[frames: %1]").arg(totalFrames, 3 /*padding*/); if (bt.indexOf(QRegExp(" at \\w*\\.cpp:\\d+\n")) != -1) subject += "[line numbers]"; } else useful = false; // subject += QString("[%1]").arg( AmarokConfig::soundSystem().remove( QRegExp("-?engine") ) ); // debug() << subject << endl; // TODO -fomit-frame-pointer buggers up the backtrace, so detect it // TODO -O optimization can rearrange execution and stuff so show a warning for the developer // TODO pass the CXXFLAGS used with the email if (useful) { body += "==== file `which basket` ==========\n"; body += fileCommandOutput + '\n'; body += "==== (gdb) bt =====================\n"; body += bt; //+ "\n\n"; // body += "==== kBacktrace() ================\n"; // body += kBacktrace(); // TODO startup notification KToolInvocation::invokeMailer(QStringLiteral("kde.basket@gmx.com"), /*to*/ QString(), /*cc*/ QString(), /*bcc*/ subject, /*subject*/ body, /*body*/ QString(), /*messageFile*/ QStringList(), /*attachURLs*/ QByteArray()); /*startup_id*/ } else { qDebug() << '\n' + i18n("%1 has crashed! We're sorry about this.\n\n" "But, all is not lost! Perhaps an upgrade is already available " "which fixes the problem. Please check your distribution's software repository.", QGuiApplication::applicationDisplayName()); } //_exit() exits immediately, otherwise this // function is called repeatedly ad finitum ::_exit(255); } else { // we are the process that crashed ::alarm(0); // wait for child to exit ::waitpid(pid, nullptr, 0); ::_exit(253); } #endif //#ifndef Q_OS_WIN } QString Crash::getOSVersionInfo() { QString result; #ifdef Q_OS_UNIX QProcess process; process.start("lsb_release", QStringList("-a")); if (process.waitForFinished()) { result += "lsb_release -a:\n"; result += QString(process.readAll()); } // Read version files in /etc QStringList versionFileMasks = QStringList() << "*release" << "*version"; QFileInfoList versionFiles = QDir("/etc").entryInfoList(versionFileMasks, QDir::Files); - foreach (const QFileInfo &versionFile, versionFiles) { + Q_FOREACH (const QFileInfo &versionFile, versionFiles) { result += versionFile.absoluteFilePath() + ":\n"; QFile file(versionFile.absoluteFilePath()); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); result += stream.readAll(); } } #else result = QString("Windows WinVersion=0x%1").arg((int)QSysInfo::windowsVersion(), 0, 16); #endif return result; } diff --git a/src/filter.cpp b/src/filter.cpp index 9f0f109..e9219b8 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, &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())); m_inAllBasketsButton->setDefaultAction(Global::bnpView->m_actFilterAllBaskets); FocusWidgetFilter *lineEditF = new FocusWidgetFilter(m_lineEdit); m_tagsBox->installEventFilter(lineEditF); 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); + Q_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); + Q_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); + Q_EMIT newFilter(m_data); } diff --git a/src/filter.h b/src/filter.h index 0096266..d1f0072 100644 --- a/src/filter.h +++ b/src/filter.h @@ -1,88 +1,88 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef FILTER_H #define FILTER_H #include #include class QToolButton; class QLineEdit; class KComboBox; class Tag; class State; /** The structure that contain all filter terms * @author Sébastien Laoût */ struct FilterData { public: // Useful Enum for tagFilterType: enum TagFilterType { DontCareTagsFilter = 0, NotTaggedFilter, TaggedFilter, TagFilter, StateFilter }; // Constructor and Destructor: FilterData() { isFiltering = false; tagFilterType = DontCareTagsFilter; tag = nullptr; state = nullptr; } ~FilterData() { } // Filter data: QString string; int tagFilterType; Tag *tag; State *state; bool isFiltering; }; /** A QWidget that allow user to enter terms to filter in a Basket. * @author Sébastien Laoût */ class FilterBar : public QWidget { Q_OBJECT public: explicit FilterBar(QWidget *parent = nullptr); ~FilterBar() override; const FilterData &filterData(); -signals: +Q_SIGNALS: void newFilter(const FilterData &data); -public slots: +public Q_SLOTS: void repopulateTagsCombo(); void reset(); void inAllBaskets(); void setEditFocus(); void filterTag(Tag *tag); void filterState(State *state); void setFilterData(const FilterData &data); public: bool hasEditFocus(); QLineEdit *lineEdit() { return m_lineEdit; } -private slots: +private Q_SLOTS: void changeFilter(); void tagChanged(int index); private: FilterData m_data; QLineEdit *m_lineEdit; QToolButton *m_resetButton; KComboBox *m_tagsBox; QToolButton *m_inAllBasketsButton; QMap m_tagsMap; QMap m_statesMap; }; #endif // FILTER_H diff --git a/src/focusedwidgets.cpp b/src/focusedwidgets.cpp index 501f9e5..9bf00bf 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, &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(); + Q_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(); + Q_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(); + Q_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(); + Q_EMIT returnPressed(); return true; case Qt::Key_Escape: - emit escapePressed(); + Q_EMIT escapePressed(); return true; default: return false; }; } case QEvent::Enter: - emit mouseEntered(); + Q_EMIT mouseEntered(); // pass through default: return false; }; } diff --git a/src/focusedwidgets.h b/src/focusedwidgets.h index 49519bf..fe7a65d 100644 --- a/src/focusedwidgets.h +++ b/src/focusedwidgets.h @@ -1,74 +1,74 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef FOCUSEDWIDGETS_H #define FOCUSEDWIDGETS_H #include #include class QEvent; class QKeyEvent; class QWheelEvent; class QMenu; class FocusedTextEdit : public KTextEdit { Q_OBJECT public: explicit FocusedTextEdit(bool disableUpdatesOnKeyPress, QWidget *parent = nullptr); ~FocusedTextEdit() override; void paste(QClipboard::Mode mode); -public slots: +public Q_SLOTS: void onSelectionChanged(); //!< Put selected text into the global mouse selection protected: void keyPressEvent(QKeyEvent *event) override; void wheelEvent(QWheelEvent *event) override; void enterEvent(QEvent *event) override; void insertFromMimeData(const QMimeData *source) override; -signals: +Q_SIGNALS: void escapePressed(); void mouseEntered(); private: bool m_disableUpdatesOnKeyPress; }; /** class FocusWidgetFilter * @author Kelvie Wong * * A very simple event filter that returns when escape and return are pressed, * and as well, to emit a signal for the mouse event. * * This allows us to create our own focus model with widgets inside baskets * (although I'm not sure how useful this will all be after we port Basket to be * use QGraphicsView). * * Keypresses are filtered (i.e. the widget will not get the key press events), * but the enterEvent is not (for backwards compatibility). */ class FocusWidgetFilter : public QObject { Q_OBJECT public: /** Constructor * @param watched The widget to install the event filter on; also becomes * the parent of this object. */ explicit FocusWidgetFilter(QWidget *watched = nullptr); ~FocusWidgetFilter() override { } protected: bool eventFilter(QObject *object, QEvent *event) override; -signals: +Q_SIGNALS: void escapePressed(); void returnPressed(); void mouseEntered(); }; #endif // FOCUSEDWIDGETS_H diff --git a/src/formatimporter.h b/src/formatimporter.h index 358ca86..1edb04b 100644 --- a/src/formatimporter.h +++ b/src/formatimporter.h @@ -1,38 +1,38 @@ /** * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef FORMATIMPORTER_H #define FORMATIMPORTER_H #include class QDomElement; namespace KIO { class Job; } /** * @author Sébastien Laoût */ class FormatImporter : public QObject { Q_OBJECT public: static bool shouldImportBaskets(); static void importBaskets(); static QDomElement importBasket(const QString &folderName); void copyFolder(const QString &folder, const QString &newFolder); void moveFolder(const QString &folder, const QString &newFolder); -private slots: +private Q_SLOTS: void slotCopyingDone(KIO::Job *); private: bool copyFinished; }; #endif // FORMATIMPORTER_H diff --git a/src/kcolorcombo2.cpp b/src/kcolorcombo2.cpp index 1f64918..990f6f6 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(); + Q_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(); + Q_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, &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); + Q_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/kcolorcombo2.h b/src/kcolorcombo2.h index 3d83855..c485489 100644 --- a/src/kcolorcombo2.h +++ b/src/kcolorcombo2.h @@ -1,333 +1,333 @@ /** * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KCOLORCOMBO2_H #define KCOLORCOMBO2_H #include class QColor; class QPixmap; class QDragEnterEvent; class QDropEvent; class QMouseEvent; class QKeyEvent; class QPaintEvent; class KColorPopup; /** * @short A combobox to display or allow user selection of a color in a user-friendly way. * * A combobox widget that popup an array of colors for the user to easily pick a common color.\n * He/she can use the popup to quickly pick a reasonable color or open a color chooser dialog for a more precise choice.\n * The user can also choose a default color (the standard background color, text color, etc... it's to the programmer to make sense of this property).\n * \n * The user is also offered some facilities: like KColorButton he/she can copy a color or paste it * (with standard keyboard shortcuts, usually Ctrl+C and Ctrl+V), and he/she can drag or drop colors. * * @par Quick usage: * Just create a new KColorCombo2() with the initial color and eventually an allowed default color * (eg. palette().color(QPalette::Base) for a background color, palette().color(QPalette::Text)...).\n * You will be noticed of the color the user selects with the signal changed(), or you can use color() to get the color at any moment.\n * Note that they can return an invalid color (see QColor::isValid()) if the user chosen the default color (if he can choose that).\n * It's then easy to save in settings, but if you want the real color (even for the default), you can get it with effectiveColor(). * * @par Notes about default color: * If you set a default color using Qt or KDE standard colors, the user can change them in the KDE Control Center, * but this widget willn't be update and will still show the old one.\n * To be noticed of such color change and then update the widget with the new standard color, you can use one of those two methods: * @code * void QWidgetDerivate::paletteChange(const QPalette &oldPalette) { // QWidgetDerivate is a parent or near custom widget * theComboBox->setDefaultColor(theNewDefaultColor); * QWidget::paletteChange(oldPalette); * } * @endcode * or connect the signal QApplication::kdisplayPaletteChanged() to a slot that will set the default color of this widget. * * @par Advanced usage: * By default, the combobox show a well balanced rainbow, OK for most usages, and you don't need to do anything for it to work.\n * You however can set your own color array by calling newColorArray() with the number of columns and rows. * Then, setColorAt() several times to fill the array.\n * This allow the most flexibility. But if you just want a rainbow with more or less colors, setRainbowPreset() is what you want.\n * If you worry about performance issues of creating a combobox with the default color array and then allocating another color array by yourself, * note that the default color array is not allocated in the constructor, but as soon as it is demanded (on first popup if no array has been * set before, or on first call of any accessors: colorAt(), columnCount(), setColorAt()...). * Finally, colorRectPixmap() and drawColorRect() allow to draw the color rounded-rectangle in other places for a consistent look. * * @see KGlobalSettings Use one of the static functions to get KDE standard colors for default values. * @see KColorButton The same, but without the rainbow popup or the choice of a default color. * @see QColorDialog The dialog that is shown when the user click the "Other..." entry. * @author Sébastien Laoût * * @image html commoncolorselector.png "Common Color Selector ComboBox" */ class KColorCombo2 : public KComboBox { Q_OBJECT Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(QColor defaultColor READ defaultColor WRITE setDefaultColor) -public slots: +public Q_SLOTS: /** * Change the selected color.\n * If the popup is open, it will not reflect the change. FIXME: Should it? * @param color The new selected color. Can be invalid to select the default one.\n * If @p color is invalid and no default color is allowed, the function will keep the old one. */ void setColor(const QColor &color); /** * Change the default color. * @param color The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one. * @see defaultColor() to get it. */ void setDefaultColor(const QColor &color); -private slots: +private Q_SLOTS: void popupClosed(); -signals: +Q_SIGNALS: /** * Emitted when the color of the widget is changed, either with setColor() or via user selection. * @see color() to know the content of @p newColor. */ void changed(const QColor &newColor); public: /** * Constructs a color combobox with parent @p parent. * @param color The initial selected color. If it is not valid, the default one will then be selected.\n * But if @p color is invalid and there is no default color, the result is undefined. * @param defaultColor The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one. */ KColorCombo2(const QColor &color, const QColor &defaultColor, QWidget *parent = nullptr); /** * Constructs a color combobox with parent @p parent.\n * The user is not allowed to choose a default color, unless you call setDefaultColor() later. * @param color The initial selected color. If it is invalid, the result is undefined. */ explicit KColorCombo2(const QColor &color, QWidget *parent = nullptr); /** * Destroys the combobox. */ ~KColorCombo2() override; /** * Get the color chosen by the user.\n * Can be invalid, if the user chosen the default one.\n * Ideal to store it in settings for later recall. * @see effectiveColor() if you want the color to be always valid. */ QColor color() const; /** * Return the color chosen by the user.\n * If the user chosen the default color, the default one is then returned, so the returned color is always valid.\n * Ideal to directly use to draw. * @see color() if you want to be notified of a default color choice. */ QColor effectiveColor() const; /** * Returns the default color or an invalid color if no default color is set (if the user isn't allowed to choose a default color). * @see setDefaultColor() to change it. */ QColor defaultColor() const; /** * Allocate a new color array of the specified dimension.\n * The new array will have invalid colors: you should then assign them one by one.\n * If one or both of the dimensions are negative or null, this function do nothing (both dimensions are always ensured to be at least equal to 1). * @param columnCount The number of columns of the array. * @param rowCount The number of rows of the array. * @see setColorAt() to set all colors once the array have been created. */ void newColorArray(int columnCount, int rowCount); /** * Get the number of columns in the array that the user can see to choose. * @see rowCount() for the number of rows, and colorAt() to get a color from the array. */ int columnCount() const; /** * Get the number of rows in the array that the user can see to choose. * @see columnCount() for the number of columns, and colorAt() to get a color from the array. */ int rowCount() const; /** * Set a color in the array at position (column,row).\n * If one or both of the indexes are out of range, this function do nothing.\n * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1. * * @param column The x coordinate of the color to set or change. * @param row The y coordinate of the color to set or change. * @param color The color to assign at this position. */ void setColorAt(int column, int row, const QColor &color); /** * Get a color in the array that the user can see to choose.\n * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1. * * @return The asked color, or an invalid color if the index is out of limit of the array. * @see columnCount() and rowCount() to get the array dimensions. */ QColor colorAt(int column, int row) /* const*/; /** * Fill the array of colors (that will be shown to the user in the popup that appears when he/she click the arrow) with a rainbow of different luminosity.\n * This rainbow representation have the advantage of being natural and well structured for a human to be able to select reasonable colors.\n * This function will allocate a color array by itself depending on the parameters (no need to call newColorArray()). * @param colorColumnCount The number of columns. The 360 possible colors of the rainbow will be split to take the wanted number of colors, equally separated. * @param lightRowCount There is always at least 1 row of colors: the "pure" colors: pure red, pure blue, pure green, pure fuchsia...\n * Additionally, you can add row on top: they will contain the same colors, but lighter.\n * The parameter @p lightRowCount specify how many different lighting grades should be shown (from near to white, but not white, to "pure"). * @param darkRowCount Finally, on bottom of the row of "pure colors", you can put any variety of dark colors (from "pure", to near to black, but not black).\n * So, the number of rows is equal to @p lightRowCount + 1 + @p darkRowCount. On top are light colors, gradually going to dark ones on bottom. * @param withGray If true, another column (so there will be @p colorColumnCount+1 columns) is added on the very-right of the popup * to show different gray values, matching the brightness of the sibling colors. * * The most acceptable parameters: * @li The default values are good to have the 7 colors of the rainbow + colors between them, and light/dark colors are well distinct. * @li If the color is a background color, you can set @p darkRowCount to 0, so only light colors are shown. * @li The inverse is true for text color choice: you can set @p lightRowCount to 0. * @li But be careful: some advanced users prefer white text on dark background, so you eg. can set @p lightRowCount to a big value and * @p darkRowCount to a small one for a fewer choice of dark colors, but at least some ones. */ void setRainbowPreset(int colorColumnCount = 12, int lightRowCount = 4, int darkRowCount = 4, bool withGray = true); // void setHsvPreset(QColor hue[], QColor saturation[], QColor value[], bool withGray = true); /** * Returns a pixmap of a colored rounded-rectangle. The four corners are transparent.\n * Useful if you want to set such a rectangle as an icon for a menu entry, or for drag and drop operation... * @param color The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup). * @param isDefault True if @p color is the default one and should then be draw with a diagonal line. * @param width The width of the rectangle pixmap to return. * @param height The height of the rectangle pixmap to return. * * @see drawColorRect() if you need to draw it directly: it's faster. */ QPixmap colorRectPixmap(const QColor &color, bool isDefault, int width, int height); /** * Draw an image of a colored rounded-rectangle.\n * This is like colorRectPixmap() but significantly faster because there is nothing to copy, and no transparency mask to create and apply. * @param painter The painter where to draw the image. * @param x The x coordinate on the @p painter where to draw the rectangle. * @param y The y coordinate on the @p painter where to draw the rectangle. * @param color The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup). * @param isDefault True if @p color is the default one and should then be draw with a diagonal line. * @param width The width of the rectangle pixmap to return. * @param height The height of the rectangle pixmap to return. * * @see colorRectPixmap() to get a transparent pixmap of the rectangle. */ void drawColorRect(QPainter &painter, int x, int y, const QColor &color, bool isDefault, int width, int height); /** * Get the height of a color rectangle for this combobox.\n * This is equal to the text height, regarding to the current font of this combobox. */ int colorRectHeight() const; /** * Get the width of a color rectangle, depending of the @p height of it.\n * It typically return 1.4 * @p height for decent rectangle proportions. */ int colorRectWidthForHeight(int height) const; protected: void showPopup() override; void mouseMoveEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void keyPressEvent(QKeyEvent *event) override; virtual void fontChange(const QFont &oldFont); private: /** * Initialization routine common to every constructors.\n * Constructors just have to initialize the KComboBox, m_color and m_defaultColor * and this function do the rest to complete the creation of this widget. */ void init(); /** * Free up all memory allocated for the color array.\n * But only if an array have previously been allocated, of course. */ void deleteColorArray(); /** * Update the only item of the combobox to mirror the new selected color.\n * Mainly called on init() and setColor(). */ void updateComboBox(); KColorPopup *m_popup; QColor m_color; QColor m_defaultColor; QColor **m_colorArray; int m_columnCount; int m_rowCount; QPoint m_dragStartPos; protected: /** * Keep place for future improvements without having to break binary compatibility.\n * Does nothing for the moment. */ void virtual_hook(int id, void *data) override; private: /** * Keep place for future improvements without having to break binary compatibility. */ class KColorCombo2Private; KColorCombo2Private *d; }; // TODO: setColorArray(QColor **, int, int) and use signals/slots ?? class KColorPopup : public QWidget { Q_OBJECT public: explicit KColorPopup(KColorCombo2 *parent); ~KColorPopup() override; void relayout(); // updateGeometry() ?? -signals: +Q_SIGNALS: void closed(); protected: void paintEvent(QPaintEvent * /*event*/) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void doSelection(); void validate(); void updateCell(int column, int row); friend class KColorCombo2; private: KColorCombo2 *m_selector; QPixmap *m_pixmap; int m_selectedRow; int m_selectedColumn; int m_columnOther; QColor m_otherColor; static const int MARGIN; static const int FRAME_WIDTH; }; #endif // KCOLORCOMBO2_H diff --git a/src/linklabel.h b/src/linklabel.h index e9a7fd5..9210c9e 100644 --- a/src/linklabel.h +++ b/src/linklabel.h @@ -1,248 +1,248 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef LINKLABEL_H #define LINKLABEL_H #include class QPixmap; class QString; class QUrl; class QColor; class QLabel; class QBoxLayout; class QSpacerItem; class QPushButton; class QCheckBox; class QEvent; class KComboBox; class KColorCombo2; class IconSizeCombo; class HTMLExporter; class HelpLabel; class KCModule; /** Store the style of links * @author Sébastien Laoût */ class LinkLook { public: enum Underlining { Always = 0, Never, OnMouseHover, OnMouseOutside }; enum Preview { None = 0, IconSize, TwiceIconSize, ThreeIconSize }; explicit LinkLook(bool useLinkColor = true, bool canPreview = true); LinkLook(const LinkLook &other); void setLook(bool italic, bool bold, int underlining, QColor color, QColor hoverColor, int iconSize, int preview /*= None*/); inline bool italic() const { return m_italic; } inline bool bold() const { return m_bold; } inline int underlining() const { return m_underlining; } inline QColor color() const { return m_color; } inline QColor hoverColor() const { return m_hoverColor; } inline int iconSize() const { return m_iconSize; } inline int preview() const { return m_preview; } inline bool useLinkColor() const { return m_useLinkColor; } inline bool canPreview() const { return m_canPreview; } /* Helping Functions */ bool underlineOutside() const { return underlining() == Always || underlining() == OnMouseOutside; } bool underlineInside() const { return underlining() == Always || underlining() == OnMouseHover; } bool previewEnabled() const { return canPreview() && preview() > None; } int previewSize() const; QColor effectiveColor() const; QColor effectiveHoverColor() const; QColor defaultColor() const; QColor defaultHoverColor() const; QString toCSS(const QString &cssClass, const QColor &defaultTextColor) const; private: bool m_italic; bool m_bold; int m_underlining; QColor m_color; QColor m_hoverColor; int m_iconSize; int m_preview; bool m_useLinkColor; bool m_canPreview; public: /* Global Looks */ static LinkLook *soundLook; static LinkLook *fileLook; static LinkLook *localLinkLook; static LinkLook *networkLinkLook; static LinkLook *launcherLook; static LinkLook *crossReferenceLook; /* Static method to get a LinkLook from an URL */ static LinkLook *lookForURL(const QUrl &url); }; /** Used to represent links with icon and specific look * Note : This label will appear blank while LinkLook willn't be set * @author Sébastien Laoût */ class LinkLabel : public QFrame { Q_OBJECT public: LinkLabel(int hAlign, int vAlign, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); LinkLabel(const QString &title, const QString &icon, LinkLook *look, int hAlign, int vAlign, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); ~LinkLabel() override; public: void setLink(const QString &title, const QString &icon, LinkLook *look = nullptr); void setLook(LinkLook *look); void setAlign(int hAlign, int vAlign); void setSelected(bool selected); void setPaletteBackgroundColor(const QColor &color); int heightForWidth(int w = -1) const override; protected: void initLabel(int hAlign, int vAlign); void enterEvent(QEvent *) override; void leaveEvent(QEvent *) override; private: QBoxLayout *m_layout; QLabel *m_icon; QLabel *m_title; QSpacerItem *m_spacer1; QSpacerItem *m_spacer2; bool m_isSelected; bool m_isHovered; LinkLook *m_look; int m_hAlign; int m_vAlign; }; /** THE NEW CLASS TO DISPLAY Links FOR THE NEW BASKET ENGINE. * We should get ride of class LinkLabel soon. * And LinkLabel will be entirely rewritten to use this LinkDisplay as the drawing primitives. * @author Sébastien Laoût */ class LinkDisplay { public: LinkDisplay(); /// << Create a new empty unselected LinkDisplay. Please then call setLink() to make sense. // Configure the link displayer: void setLink(const QString &title, const QString &icon, LinkLook *look, const QFont &font); /// << Change the content and disposition. minWidth(), width() & height() can have changed. Keep the old preview (if any) void setLink(const QString &title, const QString &icon, const QPixmap &preview, LinkLook *look, const QFont &font); /// << Idem but change the preview too (or remove it if it is invalid) void setWidth(qreal width); /// << Set a new width. @see height() that will be computed. // Get its properties: qreal minWidth() const { return m_minWidth; } /// << @return the minimum width to display this link. qreal maxWidth() const { return m_maxWidth; } /// << @return the maximum width to display this link. qreal width() const { return m_width; } /// << @return the width of the link. It is never less than minWidth()! qreal height() const { return m_height; } /// << @return the height if the link after having set it a width. // And finally, use it: void paint(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QPalette &palette, bool isDefaultColor, bool isSelected, bool isHovered, bool isIconButtonHovered) const; /// << Draw the link on a painter. Set textColor to be !isValid() to use the LinkLook color. Otherwise it will use this color! QPixmap feedbackPixmap(qreal width, qreal height, const QPalette &palette, bool isDefaultColor); /// << @return the pixmap to put under the cursor while dragging this object. // Eventually get some information about the link display: bool iconButtonAt(const QPointF &pos) const; /// << @return true if the icon button is under point @p pos. QRectF iconButtonRect() const; /// << @return the rectangle of the icon button. // Utility function: QFont labelFont(QFont font, bool isIconButtonHovered) const; /// << @return the font for this link, according to parent font AND LinkLook! qreal heightForWidth(qreal width) const; /// << @return the needed height to display the link in function of a width. QString toHtml(const QString &imageName) const; /// << Convert the link to HTML code, using the LinkLook to style it. QString toHtml(HTMLExporter *exporter, const QUrl &url, const QString &title = QString()); private: QString m_title; QString m_icon; QPixmap m_preview; LinkLook *m_look; QFont m_font; qreal m_minWidth; qreal m_maxWidth; qreal m_width; qreal m_height; }; /** A widget to edit a LinkLook, showing a live example to the user. * @author Sébastien Laoût */ class LinkLookEditWidget : public QWidget { Q_OBJECT public: LinkLookEditWidget(KCModule *module, const QString exTitle, const QString exIcon, QWidget *parent = nullptr, Qt::WindowFlags fl = nullptr); ~LinkLookEditWidget() override; void saveChanges(); void saveToLook(LinkLook *look); void set(LinkLook *look); -private slots: +private Q_SLOTS: void slotChangeLook(); protected: LinkLook *m_look; QCheckBox *m_italic; QCheckBox *m_bold; KComboBox *m_underlining; KColorCombo2 *m_color; KColorCombo2 *m_hoverColor; IconSizeCombo *m_iconSize; KComboBox *m_preview; LinkLook *m_exLook; LinkLabel *m_example; QString m_exTitle; QString m_exIcon; HelpLabel *m_hLabel; QLabel *m_label; }; #endif // LINKLABEL_H diff --git a/src/mainwindow.h b/src/mainwindow.h index a89b0d8..0b4a907 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,76 +1,76 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef CONTAINER_H #define CONTAINER_H #include class QResizeEvent; class QVBoxLayout; class QMoveEvent; class QWidget; class QAction; class KToggleAction; class BNPView; namespace KSettings { class Dialog; }; /** The window that contain baskets, organized by tabs. * @author Sébastien Laoût */ class MainWindow : public KXmlGuiWindow { Q_OBJECT public: /** Constructor, initializer and destructor */ explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; private: void setupActions(); -public slots: +public Q_SLOTS: bool askForQuit(); /** Settings **/ // void toggleToolBar(); void toggleStatusBar(); void showShortcutsSettingsDialog(); void configureToolbars() override; void configureNotifications(); void showSettingsDialog(); void minimizeRestore(); void quit(); void slotNewToolbarConfig(); protected: bool queryExit(); bool queryClose() override; void resizeEvent(QResizeEvent *) override; void moveEvent(QMoveEvent *) override; public: void ensurePolished(); private: // Settings actions : // KToggleAction *m_actShowToolbar; KToggleAction *m_actShowStatusbar; QAction *actQuit; QAction *actAppConfig; QList actBasketsList; private: QVBoxLayout *m_layout; BNPView *m_baskets; bool m_startDocked; KSettings::Dialog *m_settings; bool m_quit; }; #endif // CONTAINER_H diff --git a/src/newbasketdialog.h b/src/newbasketdialog.h index a5bae99..59a470a 100644 --- a/src/newbasketdialog.h +++ b/src/newbasketdialog.h @@ -1,92 +1,92 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef NEWBASKETDIALOG_H #define NEWBASKETDIALOG_H #include #include #include class KIconButton; class QLineEdit; class QMimeData; class KComboBox; class QTreeWidgetItem; class BasketScene; class KColorCombo2; /** The class QListWidget allow to drag items. We don't want to, so we disable it. * This class also unselect the selected item when the user right click an empty space. We don't want to, so we reselect it if that happens. * @author Sébastien Laoût */ class SingleSelectionKIconView : public QListWidget { Q_OBJECT public: explicit SingleSelectionKIconView(QWidget *parent = nullptr); QMimeData *dragObject(); QListWidgetItem *selectedItem() { return m_lastSelected; } -private slots: +private Q_SLOTS: void slotSelectionChanged(QListWidgetItem *cur); private: QListWidgetItem *m_lastSelected; }; /** Struct to store default properties of a new basket. * When the dialog shows up, the @p icon is used, as well as the @p backgroundColor. * A template is chosen depending on @p freeLayout and @p columnLayout. * If @p columnLayout is too high, the template with the more columns will be chosen instead. * If the user change the background color in the dialog, then @p backgroundImage and @p textColor will not be used! * @author Sébastien Laoût */ struct NewBasketDefaultProperties { QString icon; QString backgroundImage; QColor backgroundColor; QColor textColor; bool freeLayout; int columnCount; NewBasketDefaultProperties(); }; /** The dialog to create a new basket from a template. * @author Sébastien Laoût */ class NewBasketDialog : public QDialog { Q_OBJECT public: NewBasketDialog(BasketScene *parentBasket, const NewBasketDefaultProperties &defaultProperties, QWidget *parent = nullptr); ~NewBasketDialog() override; void ensurePolished(); -protected slots: +protected Q_SLOTS: void slotOk(); void returnPressed(); void manageTemplates(); void nameChanged(const QString &newName); private: int populateBasketsList(QTreeWidgetItem *item, int indent, int index); NewBasketDefaultProperties m_defaultProperties; KIconButton *m_icon; QLineEdit *m_name; KColorCombo2 *m_backgroundColor; QListWidget *m_templates; KComboBox *m_createIn; QMap m_basketsMap; QPushButton *okButton; }; #endif // NEWBASKETDIALOG_H diff --git a/src/notecontent.h b/src/notecontent.h index 256586b..be2923f 100644 --- a/src/notecontent.h +++ b/src/notecontent.h @@ -1,948 +1,948 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef NOTECONTENT_H #define NOTECONTENT_H #include #include #include #include #include #include #include "basket_export.h" #include "linklabel.h" class QDomElement; class QBuffer; class QColor; class QMimeData; class QMovie; class QPainter; class QPixmap; class QPoint; class QRect; class QString; class QStringList; class QTextDocument; class QWidget; class KFileItem; class QUrl; namespace KIO { class PreviewJob; } namespace Phonon { class MediaObject; } class BasketScene; class FilterData; class Note; /** * LinkDisplayItem is a QGraphicsItem using a LinkDisplay */ class LinkDisplayItem : public QGraphicsItem { public: explicit LinkDisplayItem(Note *parent) : m_note(parent) { } ~LinkDisplayItem() override { } QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; LinkDisplay &linkDisplay() { return m_linkDisplay; } private: LinkDisplay m_linkDisplay; Note *m_note; }; /** A list of numeric identifier for each note type. * Declare a variable with the type NoteType::Id and assign a value like NoteType::Text... * @author Sébastien Laoût */ namespace NoteType { enum Id { Group = 255, Text = 1, Html, Image, Animation, Sound, File, Link, CrossReference, Launcher, Color, Unknown }; // Always positive } /** Abstract base class for every content type of basket note. * It's a base class to represent those types: Text, Html, Image, Animation, Sound, File, Link, Launcher, Color, Unknown. * @author Sébastien Laoût */ class BASKET_EXPORT NoteContent { public: // Constructor and destructor: explicit NoteContent(Note *parent, const QString &fileName = QString()); /// << Constructor. Inherited notes should call it to initialize the parent note. virtual ~NoteContent() { } /// << Virtual destructor. Reimplement it if you should destroy some data your custom types. // Simple Abstract Generic Methods: virtual NoteType::Id type() const = 0; /// << @return the internal number that identify that note type. virtual QString typeName() const = 0; /// << @return the translated type name to display in the user interface. virtual QString lowerTypeName() const = 0; /// << @return the type name in lowercase without space, for eg. saving. virtual QString toText(const QString &cuttedFullPath); /// << @return a plain text equivalent of the content. virtual QString toHtml(const QString &imageName, const QString &cuttedFullPath) = 0; /// << @return an HTML text equivalent of the content. @param imageName Save image in this Qt resource. virtual QPixmap toPixmap() { return QPixmap(); } /// << @return an image equivalent of the content. virtual void toLink(QUrl *url, QString *title, const QString &cuttedFullPath); /// << Set the link to the content. By default, it set them to fullPath() if useFile(). virtual bool useFile() const = 0; /// << @return true if it use a file to store the content. virtual bool canBeSavedAs() const = 0; /// << @return true if the content can be saved as a file by the user. virtual QString saveAsFilters() const = 0; /// << @return the filters for the user to choose a file destination to save the note as. virtual bool match(const FilterData &data) = 0; /// << @return true if the content match the filter criteria. // Complex Abstract Generic Methods: virtual void exportToHTML(HTMLExporter *exporter, int indent) = 0; /// << Export the note in an HTML file. virtual QString cssClass() const = 0; /// << @return the CSS class of the note when exported to HTML virtual qreal setWidthAndGetHeight(qreal width) = 0; /// << Relayout content with @p width (never less than minWidth()). @return its new height. virtual bool loadFromFile(bool /*lazyLoad*/) { return false; } /// << Load the content from the file. The default implementation does nothing. @see fileName(). virtual bool finishLazyLoad() { return false; } /// << Load what was not loaded by loadFromFile() if it was lazy-loaded virtual bool saveToFile() { return false; } /// << Save the content to the file. The default implementation does nothing. @see fileName(). virtual QString linkAt(const QPointF & /*pos*/) { return QString(); } /// << @return the link anchor at position @p pos or QString() if there is no link. virtual void saveToNode(QXmlStreamWriter &stream); /// << Save the note in the basket XML file. By default it store the filename if a file is used. virtual void fontChanged() = 0; /// << If your content display textual data, called when the font have changed (from tags or basket font) virtual void linkLookChanged() { } /// << If your content use LinkDisplay with preview enabled, reload the preview (can have changed size) virtual QString editToolTipText() const = 0; /// << @return "Edit this [text|image|...]" to put in the tooltip for the note's content zone. virtual void toolTipInfos(QStringList * /*keys*/, QStringList * /*values*/) { } /// << Get "key: value" couples to put in the tooltip for the note's content zone. // Custom Zones: /// Implement this if you want to store custom data. virtual int zoneAt(const QPointF & /*pos*/) { return 0; } /// << If your note-type have custom zones, @return the zone at @p pos or 0 if it's not a custom zone! virtual QRectF zoneRect(int /*zone*/, const QPointF & /*pos*/); /// << Idem, @return the rect of the custom zone virtual QString zoneTip(int /*zone*/) { return QString(); } /// << Idem, @return the toolTip of the custom zone virtual Qt::CursorShape cursorFromZone(int /*zone*/) const { return Qt::ArrowCursor; } /// << Idem, @return the mouse cursor when it is over zone @p zone! virtual void setHoveredZone(int /*oldZone*/, int /*newZone*/) { } /// << If your note type need some feedback, you get notified of hovering changes here. virtual QString statusBarMessage(int /*zone*/) { return QString(); } /// << @return the statusBar message to show for zone @p zone, or QString() if nothing special have to be said. // Drag and Drop Content: virtual void serialize(QDataStream & /*stream*/) { } /// << Serialize the content in a QDragObject. If it consists of a file, it can be serialized for you. virtual bool shouldSerializeFile() { return useFile(); } /// << @return true if the dragging process should serialize the filename (and move the file if cutting). virtual void addAlternateDragObjects(QMimeData * /*dragObj*/) { } /// << If you offer more than toText/Html/Image/Link(), this will be called if this is the only selected. virtual QPixmap feedbackPixmap(qreal width, qreal height) = 0; /// << @return the pixmap to put under the cursor while dragging this object. virtual bool needSpaceForFeedbackPixmap() { return false; } /// << @return true if a space must be inserted before and after the DND feedback pixmap. // Content Edition: virtual int xEditorIndent() { return 0; } /// << If the editor should be indented (eg. to not cover an icon), return the number of pixels. // Open Content or File: virtual QUrl urlToOpen(bool /*with*/); /// << @return the URL to open the note, or an invalid QUrl if it's not openable. If @p with if false, it's a normal "Open". If it's true, it's for an "Open with..." action. The default /// implementation return the fullPath() if the note useFile() and nothing if not. enum OpenMessage { OpenOne, /// << Message to send to the statusbar when opening this note. OpenSeveral, /// << Message to send to the statusbar when opening several notes of this type. OpenOneWith, /// << Message to send to the statusbar when doing "Open With..." on this note. OpenSeveralWith, /// << Message to send to the statusbar when doing "Open With..." several notes of this type. OpenOneWithDialog, /// << Prompt-message of the "Open With..." dialog for this note. OpenSeveralWithDialog /// << Prompt-message of the "Open With..." dialog for several notes of this type. }; virtual QString messageWhenOpening(OpenMessage /*where*/) { return QString(); } /// << @return the message to display according to @p where or nothing if it can't be done. @see OpenMessage describing the nature of the message that should be returned... The default implementation return an empty string. NOTE: If /// urlToOpen() is invalid and messageWhenOpening() is not empty, then the user will be prompted to edit the note (with the message returned by messageWhenOpening()) for eg. being able to edit URL of a link if it's empty when opening /// it... virtual QString customOpenCommand() { return QString(); } /// << Reimplement this if your urlToOpen() should be opened with another application instead of the default KDE one. This choice should be left to the users in the setting (choice to use a custom app or not, and which app). // Common File Management: /// (and do save changes) and optionally hide the toolbar. virtual void setFileName(const QString &fileName); /// << Set the filename. Reimplement it if you eg. want to update the view when the filename is changed. bool trySetFileName(const QString &fileName); /// << Set the new filename and return true. Can fail and return false if a file with this fileName already exists. QString fullPath(); /// << Get the absolute path of the file where this content is stored on disk. QString fileName() const { return m_fileName; } /// << Get the file name where this content is stored (relative to the basket folder). @see fullPath(). qreal minWidth() const { return m_minWidth; } /// << Get the minimum width for this content. Note *note() { return m_note; } /// << Get the note managing this content. BasketScene *basket(); /// << Get the basket containing the note managing this content. virtual QGraphicsItem *graphicsItem() = 0; public: void setEdited(); /// << Mark the note as edited NOW: change the "last modification time and time" AND save the basket to XML file. protected: void contentChanged(qreal newMinWidth); /// << When the content has changed, inherited classes should call this to specify its new minimum size and trigger a basket relayout. private: Note *m_note; QString m_fileName; qreal m_minWidth; public: static const int FEEDBACK_DARKING; }; /** Real implementation of plain text notes: * @author Sébastien Laoût */ class BASKET_EXPORT TextContent : public NoteContent { public: // Constructor and destructor: TextContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~TextContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; QString linkAt(const QPointF &pos) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; // QString customOpenCommand(); // Content-Specific Methods: void setText(const QString &text, bool lazyLoad = false); /// << Change the text note-content and relayout the note. QString text() { return m_graphicsTextItem.text(); } /// << @return the text note-content. QGraphicsItem *graphicsItem() override { return &m_graphicsTextItem; } protected: // QString m_text; // QTextDocument *m_simpleRichText; QGraphicsSimpleTextItem m_graphicsTextItem; }; #include /** Real implementation of rich text (HTML) notes: * @author Sébastien Laoût */ class BASKET_EXPORT HtmlContent : public NoteContent { public: // Constructor and destructor: HtmlContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~HtmlContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; QString linkAt(const QPointF &pos) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: void setHtml(const QString &html, bool lazyLoad = false); /// << Change the HTML note-content and relayout the note. QString html() { return m_html; } /// << @return the HTML note-content. QGraphicsItem *graphicsItem() override { return &m_graphicsTextItem; } protected: QString m_html; QString m_textEquivalent; // OPTIM_FILTER QTextDocument *m_simpleRichText; QGraphicsTextItem m_graphicsTextItem; }; /** Real implementation of image notes: * @author Sébastien Laoût */ class BASKET_EXPORT ImageContent : public NoteContent { public: // Constructor and destructor: ImageContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~ImageContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; QPixmap toPixmap() override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: void setPixmap(const QPixmap &pixmap); /// << Change the pixmap note-content and relayout the note. QPixmap pixmap() { return m_pixmapItem.pixmap(); } /// << @return the pixmap note-content. QByteArray data(); QGraphicsItem *graphicsItem() override { return &m_pixmapItem; } protected: QGraphicsPixmapItem m_pixmapItem; QByteArray m_format; }; /** Real implementation of animated image (GIF, MNG) notes: * @author Sébastien Laoût */ class BASKET_EXPORT AnimationContent : public QObject, public NoteContent // QObject to be able to receive QMovie signals { Q_OBJECT public: // Constructor and destructor: AnimationContent(Note *parent, const QString &fileName, bool lazyLoad = false); ~AnimationContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; QPixmap toPixmap() override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool lazyLoad) override; bool finishLazyLoad() override; bool saveToFile() override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; QGraphicsItem *graphicsItem() override { return &m_graphicsPixmap; } // Content-Specific Methods: bool startMovie(); -protected slots: +protected Q_SLOTS: void movieUpdated(); void movieResized(); void movieFrameChanged(); protected: QBuffer *m_buffer; QMovie *m_movie; qreal m_currentWidth; QGraphicsPixmapItem m_graphicsPixmap; }; /** Real implementation of file notes: * @author Sébastien Laoût */ class BASKET_EXPORT FileContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: FileContent(Note *parent, const QString &fileName); ~FileContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; // Content Edition: int xEditorIndent() override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setFileName(const QString &fileName) override; /// << Reimplemented to be able to relayout the note. virtual LinkLook *linkLook() { return LinkLook::fileLook; } QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: LinkDisplayItem m_linkDisplayItem; // File Preview Management: -protected slots: +protected Q_SLOTS: void newPreview(const KFileItem &, const QPixmap &preview); void removePreview(const KFileItem &); void startFetchingUrlPreview(); protected: KIO::PreviewJob *m_previewJob; }; /** Real implementation of sound notes: * @author Sébastien Laoût */ class BASKET_EXPORT SoundContent : public FileContent // A sound is a file with just a bit different user interaction { Q_OBJECT public: // Constructor and destructor: SoundContent(Note *parent, const QString &fileName); // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; QString editToolTipText() const override; // Complex Generic Methods: QString cssClass() const override; // Custom Zones: QString zoneTip(int zone) override; void setHoveredZone(int oldZone, int newZone) override; // Open Content or File: QString messageWhenOpening(OpenMessage where) override; QString customOpenCommand() override; // Content-Specific Methods: LinkLook *linkLook() override { return LinkLook::soundLook; } Phonon::MediaObject *music; -private slots: +private Q_SLOTS: void stateChanged(Phonon::State, Phonon::State); }; /** Real implementation of link notes: * @author Sébastien Laoût */ class BASKET_EXPORT LinkContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: LinkContent(Note *parent, const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon); ~LinkContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; QString statusBarMessage(int zone) override; // Open Content or File: QUrl urlToOpen(bool /*with*/) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLink(const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon); /// << Change the link and relayout the note. QUrl url() { return m_url; } /// << @return the URL of the link note-content. QString title() { return m_title; } /// << @return the displayed title of the link note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the link note-content. bool autoTitle() { return m_autoTitle; } /// << @return if the title is auto-computed from the URL. bool autoIcon() { return m_autoIcon; } /// << @return if the icon is auto-computed from the URL. void startFetchingLinkTitle(); QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QUrl m_url; QString m_title; QString m_icon; bool m_autoTitle; bool m_autoIcon; LinkDisplayItem m_linkDisplayItem; KIO::Integration::AccessManager *m_access_manager; QNetworkReply *m_reply; QByteArray m_httpBuff; ///< Accumulator for downloaded HTTP data with yet unknown encoding bool m_acceptingData; ///< When false, don't accept any HTTP data // File Preview Management: -protected slots: +protected Q_SLOTS: void httpReadyRead(); void httpDone(QNetworkReply *reply); void newPreview(const KFileItem &, const QPixmap &preview); void removePreview(const KFileItem &); void startFetchingUrlPreview(); protected: KIO::PreviewJob *m_previewJob; private: void decodeHtmlTitle(); ///< Detect encoding of \p m_httpBuff and extract the title from HTML void endFetchingLinkTitle(); ///< Extract title and clear http buffer }; /** Real implementation of cross reference notes: * Copied and modified from LinkContent. * @author Brian C. Milco */ class BASKET_EXPORT CrossReferenceContent : public QObject, public NoteContent { Q_OBJECT public: // Constructor and destructor: CrossReferenceContent(Note *parent, const QUrl &url, const QString &title, const QString &icon); ~CrossReferenceContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; void linkLookChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; QString statusBarMessage(int zone) override; // Open Content or File: QUrl urlToOpen(bool /*with*/) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLink(const QUrl &url, const QString &title, const QString &icon); /// << Change the link and relayout the note. void setCrossReference(const QUrl &url, const QString &title, const QString &icon); QUrl url() { return m_url; } /// << @return the URL of the link note-content. QString title() { return m_title; } /// << @return the displayed title of the link note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the link note-content. QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QUrl m_url; QString m_title; QString m_icon; LinkDisplayItem m_linkDisplayItem; }; /** Real implementation of launcher notes: * @author Sébastien Laoût */ class BASKET_EXPORT LauncherContent : public NoteContent { public: // Constructor and destructor: LauncherContent(Note *parent, const QString &fileName); ~LauncherContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: QPixmap feedbackPixmap(qreal width, qreal height) override; // Custom Zones: int zoneAt(const QPointF &pos) override; QRectF zoneRect(int zone, const QPointF & /*pos*/) override; QString zoneTip(int zone) override; Qt::CursorShape cursorFromZone(int zone) const override; // Open Content or File: QUrl urlToOpen(bool with) override; QString messageWhenOpening(OpenMessage where) override; // Content-Specific Methods: void setLauncher(const QString &name, const QString &icon, const QString &exec); /// << Change the launcher note-content and relayout the note. Normally called by loadFromFile (no save done). QString name() { return m_name; } /// << @return the URL of the launcher note-content. QString icon() { return m_icon; } /// << @return the displayed icon of the launcher note-content. QString exec() { return m_exec; } /// << @return the execute command line of the launcher note-content. // TODO: KService *service() ??? And store everything in thta service ? QGraphicsItem *graphicsItem() override { return &m_linkDisplayItem; } protected: QString m_name; // TODO: Store them in linkDisplay to gain place (idem for Link notes) QString m_icon; QString m_exec; LinkDisplayItem m_linkDisplayItem; }; /** * */ class BASKET_EXPORT ColorItem : public QGraphicsItem { public: ColorItem(Note *parent, const QColor &color); // virtual ~ColorItem(); virtual QColor color() { return m_color; } virtual void setColor(const QColor &color); QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; private: Note *m_note; QColor m_color; QRectF m_textRect; static const int RECT_MARGIN; }; /** Real implementation of color notes: * @author Sébastien Laoût */ class BASKET_EXPORT ColorContent : public NoteContent { public: // Constructor and destructor: ColorContent(Note *parent, const QColor &color); ~ColorContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; void saveToNode(QXmlStreamWriter &stream) override; void fontChanged() override; QString editToolTipText() const override; void toolTipInfos(QStringList *keys, QStringList *values) override; // Drag and Drop Content: void serialize(QDataStream &stream) override; QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } void addAlternateDragObjects(QMimeData *dragObject) override; // Content-Specific Methods: void setColor(const QColor &color); /// << Change the color note-content and relayout the note. QColor color() { return m_colorItem.color(); } /// << @return the color note-content. QGraphicsItem *graphicsItem() override { return &m_colorItem; } protected: ColorItem m_colorItem; }; /** * */ class BASKET_EXPORT UnknownItem : public QGraphicsItem { public: UnknownItem(Note *parent); QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; virtual QString mimeTypes() { return m_mimeTypes; } virtual void setMimeTypes(QString mimeTypes); virtual void setWidth(qreal width); private: Note *m_note; QString m_mimeTypes; QRectF m_textRect; static const qreal DECORATION_MARGIN; }; /** Real implementation of unknown MIME-types dropped notes: * @author Sébastien Laoût */ class BASKET_EXPORT UnknownContent : public NoteContent { public: // Constructor and destructor: UnknownContent(Note *parent, const QString &fileName); ~UnknownContent() override; // Simple Generic Methods: NoteType::Id type() const override; QString typeName() const override; QString lowerTypeName() const override; QString toText(const QString & /*cuttedFullPath*/) override; QString toHtml(const QString &imageName, const QString &cuttedFullPath) override; void toLink(QUrl *url, QString *title, const QString &cuttedFullPath) override; bool useFile() const override; bool canBeSavedAs() const override; QString saveAsFilters() const override; bool match(const FilterData &data) override; // Complex Generic Methods: void exportToHTML(HTMLExporter *exporter, int indent) override; QString cssClass() const override; qreal setWidthAndGetHeight(qreal width) override; bool loadFromFile(bool /*lazyLoad*/) override; void fontChanged() override; QString editToolTipText() const override; // Drag and Drop Content: bool shouldSerializeFile() override { return false; } void addAlternateDragObjects(QMimeData *dragObject) override; QPixmap feedbackPixmap(qreal width, qreal height) override; bool needSpaceForFeedbackPixmap() override { return true; } // Open Content or File: QUrl urlToOpen(bool /*with*/) override { return QUrl(); } QGraphicsItem *graphicsItem() override { return &m_unknownItem; } // Content-Specific Methods: QString mimeTypes() { return m_unknownItem.mimeTypes(); } /// << @return the list of MIME types this note-content contains. private: UnknownItem m_unknownItem; }; #endif // NOTECONTENT_H diff --git a/src/noteedit.h b/src/noteedit.h index 6a7febd..8fbdfea 100644 --- a/src/noteedit.h +++ b/src/noteedit.h @@ -1,342 +1,342 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef NOTEEDIT_H #define NOTEEDIT_H #include #include #include #include "notecontent.h" class QGraphicsProxyWidget; class QWidget; class QPushButton; class QKeyEvent; class QFontComboBox; class QTextCharFormat; class QAction; class KIconButton; class KUrlRequester; class KTextEdit; class KToggleAction; class KToolBar; class KActionCollection; class KComboBox; class KColorCombo; class FontSizeCombo; class Note; class RunCommandRequester; class FocusWidgetFilter; class BasketListViewItem; /** The base class for every note editor. * Scenario: * The Basket class calls NoteEditor::editNoteContent() with the NoteContent to edit. * This method create the good child NoteEditor depending * on the note content type and return it to the Basket. * This custom NoteEditor have two choices regarding what to do in its constructor: * - Display a dialog and then call cancel() if the user canceled the dialog; * - Create an inline editor and call setInlineEditor() with that editor as parameter. * When the user exit the edition, validate() is called by the Basket. * You should then call setEmpty() is the user cleared the content. * The custom editor SHOULD call the NoteEditor constructor. * If the user cleared the content OR if the user canceled the dialog whereas he/she * JUST ADDED the note, then the note will be deleted by the Basket. */ class NoteEditor : public QObject { Q_OBJECT public: explicit NoteEditor(NoteContent *noteContent); ~NoteEditor() override; bool isEmpty() { return m_isEmpty; } bool canceled() { return m_canceled; } bool isInline() { return m_widget != nullptr; } QGraphicsProxyWidget *graphicsWidget() { return m_widget; } KTextEdit *textEdit() { return m_textEdit; } QLineEdit *lineEdit() { return m_lineEdit; } void setCursorTo(const QPointF &pos); void connectActions(BasketScene *scene); void startSelection(const QPointF &pos); void updateSelection(const QPointF &pos); void endSelection(const QPointF &pos); void paste(const QPointF &pos, QClipboard::Mode mode); private: bool m_isEmpty; bool m_canceled; QGraphicsProxyWidget *m_widget; KTextEdit *m_textEdit; QLineEdit *m_lineEdit; NoteContent *m_noteContent; public: NoteContent *noteContent() { return m_noteContent; } Note *note(); protected: void setEmpty() { m_isEmpty = true; } void cancel() { m_canceled = true; } void setInlineEditor(QWidget *inlineEditor); public: virtual void validate() { } virtual void autoSave(bool /*toFileToo*/) { } // Same as validate(), but does not precede editor close and is triggered either while the editor widget changed size or after 3 seconds of inactivity. -signals: +Q_SIGNALS: void askValidation(); void mouseEnteredEditorWidget(); public: static NoteEditor *editNoteContent(NoteContent *noteContent, QWidget *parent); }; class TextEditor : public NoteEditor { Q_OBJECT public: TextEditor(TextContent *textContent, QWidget *parent); ~TextEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: TextContent *m_textContent; }; class HtmlEditor : public NoteEditor { Q_OBJECT public: HtmlEditor(HtmlContent *htmlContent, QWidget *parent); ~HtmlEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: HtmlContent *m_htmlContent; -public slots: +public Q_SLOTS: void cursorPositionChanged(); void editTextChanged(); void charFormatChanged(const QTextCharFormat &format); -protected slots: +protected Q_SLOTS: void setBold(bool isChecked); void setLeft(); void setCentered(); void setRight(); void setBlock(); void onFontSelectionChanged(const QFont &font); //!< When a font is selected from combo box }; class ImageEditor : public NoteEditor { Q_OBJECT public: ImageEditor(ImageContent *imageContent, QWidget *parent); }; class AnimationEditor : public NoteEditor { Q_OBJECT public: AnimationEditor(AnimationContent *animationContent, QWidget *parent); }; class FileEditor : public NoteEditor { Q_OBJECT public: FileEditor(FileContent *fileContent, QWidget *parent); ~FileEditor() override; void validate() override; void autoSave(bool toFileToo) override; protected: FileContent *m_fileContent; }; class LinkEditor : public NoteEditor { Q_OBJECT public: LinkEditor(LinkContent *linkContent, QWidget *parent); }; class CrossReferenceEditor : public NoteEditor { Q_OBJECT public: CrossReferenceEditor(CrossReferenceContent *crossReferenceContent, QWidget *parent); }; class LauncherEditor : public NoteEditor { Q_OBJECT public: LauncherEditor(LauncherContent *launcherContent, QWidget *parent); }; class ColorEditor : public NoteEditor { Q_OBJECT public: ColorEditor(ColorContent *colorContent, QWidget *parent); }; class UnknownEditor : public NoteEditor { Q_OBJECT public: UnknownEditor(UnknownContent *unknownContent, QWidget *parent); }; /** The dialog to edit Link Note content. * @author Sébastien Laoût */ class LinkEditDialog : public QDialog { Q_OBJECT public: explicit LinkEditDialog(LinkContent *contentNote, QWidget *parent = nullptr); ~LinkEditDialog() override; void ensurePolished(); -protected slots: +protected Q_SLOTS: void slotOk(); void urlChanged(const QString &); void doNotAutoTitle(const QString &); void doNotAutoIcon(QString); void guessTitle(); void guessIcon(); private: LinkContent *m_noteContent; bool m_isAutoModified; KUrlRequester *m_url; QLineEdit *m_title; KIconButton *m_icon; QPushButton *m_autoTitle; QPushButton *m_autoIcon; }; /** The dialog to edit cross reference content. * @author Brian C. Milco */ class CrossReferenceEditDialog : public QDialog { Q_OBJECT public: explicit CrossReferenceEditDialog(CrossReferenceContent *contentNote, QWidget *parent = nullptr); ~CrossReferenceEditDialog() override; -protected slots: +protected Q_SLOTS: void slotOk(); void urlChanged(const int index); protected: void generateBasketList(KComboBox *targetList, BasketListViewItem *item = nullptr, int indent = 0); private: CrossReferenceContent *m_noteContent; KComboBox *m_targetBasket; }; /** The dialog to edit Launcher Note content. * @author Sébastien Laoût */ class LauncherEditDialog : public QDialog { Q_OBJECT public: explicit LauncherEditDialog(LauncherContent *contentNote, QWidget *parent = nullptr); ~LauncherEditDialog() override; void ensurePolished(); -protected slots: +protected Q_SLOTS: void slotOk(); void guessIcon(); private: LauncherContent *m_noteContent; RunCommandRequester *m_command; QLineEdit *m_name; KIconButton *m_icon; }; /** This class manage toolbars for the inline editors. * The toolbars should be created once at the application startup, * then KXMLGUI can manage them and save their state and position... * @author Sébastien Laoût */ class InlineEditors : public QObject { Q_OBJECT public: InlineEditors(); ~InlineEditors() override; void initToolBars(KActionCollection *ac); static InlineEditors *instance(); public: // Rich Text ToolBar: KToolBar *richTextToolBar(); void enableRichTextToolBar(); void disableRichTextToolBar(); QPalette palette() const; QFontComboBox *richTextFont; FontSizeCombo *richTextFontSize; KColorCombo *richTextColor; KToggleAction *richTextBold; KToggleAction *richTextItalic; KToggleAction *richTextUnderline; // KToggleAction *richTextSuper; // KToggleAction *richTextSub; KToggleAction *richTextLeft; KToggleAction *richTextCenter; KToggleAction *richTextRight; KToggleAction *richTextJustified; QAction *richTextUndo; QAction *richTextRedo; FocusWidgetFilter *focusWidgetFilter; }; #endif // NOTEEDIT_H diff --git a/src/notefactory.cpp b/src/notefactory.cpp index 1557d94..d3db6d9 100644 --- a/src/notefactory.cpp +++ b/src/notefactory.cpp @@ -1,1074 +1,1074 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "notefactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For createHeuristicMask #include #include #include #include #include #include //For Qt::mightBeRichText(...) #include //For KGlobal::mainComponent().aboutData(...) #include #include #include #include #include #include #include #include #include "basketlistview.h" #include "basketscene.h" #include "file_mimetypes.h" #include "global.h" #include "note.h" #include "notedrag.h" #include "settings.h" #include "tools.h" #include "variouswidgets.h" //For IconSizeDialog #include "xmlwork.h" #include "debugwindow.h" /** Create notes from scratch (just a content) */ Note *NoteFactory::createNoteText(const QString &text, BasketScene *parent, bool reallyPlainText /* = false*/) { Note *note = new Note(parent); if (reallyPlainText) { TextContent *content = new TextContent(note, createFileForNewNote(parent, "txt")); content->setText(text); content->saveToFile(); } else { HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html")); QString html = "" + Tools::textToHTMLWithoutP(text) + ""; content->setHtml(html); content->saveToFile(); } return note; } Note *NoteFactory::createNoteHtml(const QString &html, BasketScene *parent) { Note *note = new Note(parent); HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html")); content->setHtml(html); content->saveToFile(); return note; } Note *NoteFactory::createNoteLink(const QUrl &url, BasketScene *parent) { Note *note = new Note(parent); new LinkContent(note, url, titleForURL(url), iconForURL(url), /*autoTitle=*/true, /*autoIcon=*/true); return note; } Note *NoteFactory::createNoteLink(const QUrl &url, const QString &title, BasketScene *parent) { Note *note = new Note(parent); new LinkContent(note, url, title, iconForURL(url), /*autoTitle=*/false, /*autoIcon=*/true); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, titleForURL(url), iconForURL(url)); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, title, iconForURL(url)); return note; } Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, const QString &icon, BasketScene *parent) { Note *note = new Note(parent); new CrossReferenceContent(note, url, title, icon); return note; } Note *NoteFactory::createNoteImage(const QPixmap &image, BasketScene *parent) { Note *note = new Note(parent); ImageContent *content = new ImageContent(note, createFileForNewNote(parent, "png")); content->setPixmap(image); content->saveToFile(); return note; } Note *NoteFactory::createNoteColor(const QColor &color, BasketScene *parent) { Note *note = new Note(parent); new ColorContent(note, color); return note; } /** Return a string list containing {url1, title1, url2, title2, url3, title3...} */ QStringList NoteFactory::textToURLList(const QString &text) { // List to return: QStringList list; // Split lines: QStringList texts = text.split('\n'); // For each lines: QStringList::iterator it; for (it = texts.begin(); it != texts.end(); ++it) { // Strip white spaces: (*it) = (*it).trimmed(); // Don't care of empty entries: if ((*it).isEmpty()) continue; // Compute lower case equivalent: QString ltext = (*it).toLower(); /* Search for mail address ("*@*.*" ; "*" can contain '_', '-', or '.') and add protocol to it */ QString mailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+"; QRegExp mailExp("^" + mailExpString + '$'); if (mailExp.exactMatch(ltext)) { ltext.insert(0, "mailto:"); (*it).insert(0, "mailto:"); } // TODO: Recognize "" (link between '<' and '>') // TODO: Replace " at " by "@" and " dot " by "." to look for e-mail addresses /* Search for mail address like "Name " */ QRegExp namedMailExp("^([\\w\\s]+)\\s<(" + mailExpString + ")>$"); // namedMailExp.setCaseSensitive(true); // For the name to be keeped with uppercases // DOESN'T WORK ! if (namedMailExp.exactMatch(ltext)) { QString name = namedMailExp.cap(1); QString address = "mailto:" + namedMailExp.cap(2); // Threat it NOW, as it's an exception (it have a title): list.append(address); list.append(name); continue; } /* Search for an url and create an URL note */ if ((ltext.startsWith('/') && ltext[1] != '/' && ltext[1] != '*') || // Take files but not C/C++/... comments ! ltext.startsWith(QLatin1String("file:")) || ltext.startsWith(QLatin1String("http://")) || ltext.startsWith(QLatin1String("https://")) || ltext.startsWith(QLatin1String("www.")) || ltext.startsWith(QLatin1String("ftp.")) || ltext.startsWith(QLatin1String("ftp://")) || ltext.startsWith(QLatin1String("mailto:"))) { // First, correct the text to use the good format for the url if (ltext.startsWith('/')) (*it).insert(0, "file:"); if (ltext.startsWith(QLatin1String("www."))) (*it).insert(0, "http://"); if (ltext.startsWith(QLatin1String("ftp."))) (*it).insert(0, "ftp://"); // And create the Url note (or launcher if URL point a .desktop file) list.append(*it); list.append(QString()); // We don't have any title } else return QStringList(); // FAILED: treat the text as a text, and not as a URL list! } return list; } Note *NoteFactory::createNoteFromText(const QString &text, BasketScene *parent) { /* Search for a color (#RGB , #RRGGBB , #RRRGGGBBB , #RRRRGGGGBBBB) and create a color note */ QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); if (exp.exactMatch(text)) return createNoteColor(QColor(text), parent); /* Try to convert the text as a URL or a list of URLs */ QStringList uriList = textToURLList(text); if (!uriList.isEmpty()) { // TODO: This code is almost duplicated from fropURLs()! Note *note; Note *firstNote = nullptr; Note *lastInserted = nullptr; QStringList::iterator it; for (it = uriList.begin(); it != uriList.end(); ++it) { QString url = (*it); ++it; QString title = (*it); if (title.isEmpty()) note = createNoteLinkOrLauncher(QUrl::fromUserInput(url), parent); else note = createNoteLink(QUrl::fromUserInput(url), title, parent); // If we got a new note, insert it in a linked list (we will return the first note of that list): if (note) { // qDebug() << "Drop URL: " << (*it).toDisplayString(); if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } return firstNote; // It don't return ALL inserted notes ! } // QString newText = text.trimmed(); // The text for a new note, without useless spaces /* Else, it's a text or an HTML note, so, create it */ if (Qt::mightBeRichText(/*newT*/ text)) return createNoteHtml(/*newT*/ text, parent); else return createNoteText(/*newT*/ text, parent); } Note *NoteFactory::createNoteLauncher(const QUrl &url, BasketScene *parent) { if (url.isEmpty()) return createNoteLauncher(QString(), QString(), QString(), parent); else return copyFileAndLoad(url, parent); } Note *NoteFactory::createNoteLauncher(const QString &command, const QString &name, const QString &icon, BasketScene *parent) { QString fileName = createNoteLauncherFile(command, name, icon, parent); if (fileName.isEmpty()) return nullptr; else return loadFile(fileName, parent); } QString NoteFactory::createNoteLauncherFile(const QString &command, const QString &name, const QString &icon, BasketScene *parent) { QString content = QString( "[Desktop Entry]\n" "Exec=%1\n" "Name=%2\n" "Icon=%3\n" "Encoding=UTF-8\n" "Type=Application\n") .arg(command, name, icon.isEmpty() ? QString("exec") : icon); QString fileName = fileNameForNewNote(parent, "launcher.desktop"); QString fullPath = parent->fullPathForFileName(fileName); // parent->dontCareOfCreation(fullPath); QFile file(fullPath); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream.setCodec("UTF-8"); stream << content; file.close(); return fileName; } else return QString(); } Note *NoteFactory::createNoteLinkOrLauncher(const QUrl &url, BasketScene *parent) { // IMPORTANT: we create the service ONLY if the extension is ".desktop". // Otherwise, KService take a long time to analyze all the file // and output such things to stdout: // "Invalid entry (missing '=') at /my/file.ogg:11984" // "Invalid entry (missing ']') at /my/file.ogg:11984"... KService::Ptr service; if (url.fileName().endsWith(QLatin1String(".desktop"))) service = new KService(url.path()); // If link point to a .desktop file then add a launcher, otherwise it's a link if (service && service->isValid()) return createNoteLauncher(url, parent); else return createNoteLink(url, parent); } bool NoteFactory::movingNotesInTheSameBasket(const QMimeData *source, BasketScene *parent, Qt::DropAction action) { if (NoteDrag::canDecode(source)) return action == Qt::MoveAction && NoteDrag::basketOf(source) == parent; else return false; } Note *NoteFactory::dropNote(const QMimeData *source, BasketScene *parent, bool fromDrop, Qt::DropAction action, Note * /*noteSource*/) { if (source == nullptr) { return nullptr; } Note *note = nullptr; QStringList formats = source->formats(); /* No data */ if (formats.size() == 0) { // TODO: add a parameter to say if it's from a clipboard paste, a selection paste, or a drop // To be able to say "The clipboard/selection/drop is empty". // KMessageBox::error(parent, i18n("There is no data to insert."), i18n("No Data")); return nullptr; } /* Debug */ if (Global::debugWindow) { *Global::debugWindow << "Drop :"; for (int i = 0; i < formats.size(); ++i) *Global::debugWindow << "\t[" + QString::number(i) + "] " + formats[i]; switch (action) { // The source want that we: case Qt::CopyAction: *Global::debugWindow << ">> Drop action: Copy"; break; case Qt::MoveAction: *Global::debugWindow << ">> Drop action: Move"; break; case Qt::LinkAction: *Global::debugWindow << ">> Drop action: Link"; break; default: *Global::debugWindow << ">> Drop action: Unknown"; // supported by Qt! } } /* Copy or move a Note */ if (NoteDrag::canDecode(source)) { bool moveFiles = fromDrop && action == Qt::MoveAction; bool moveNotes = moveFiles; return NoteDrag::decode(source, parent, moveFiles, moveNotes); // Filename will be kept } /* Else : Drop object to note */ QImage image = qvariant_cast(source->imageData()); if (!image.isNull()) return createNoteImage(QPixmap::fromImage(image), parent); if (source->hasColor()) { return createNoteColor(qvariant_cast(source->colorData()), parent); } // And then the hack (if provide color MIME type or a text that contains color), using createNote Color RegExp: QString hack; QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); hack = source->text(); if (source->hasFormat("application/x-color") || (!hack.isNull() && exp.exactMatch(hack))) { QColor color = qvariant_cast(source->colorData()); if (color.isValid()) return createNoteColor(color, parent); // if ( (note = createNoteColor(color, parent)) ) // return note; // // Theoretically it should be returned. If not, continue by dropping other things } QList urls = source->urls(); if (!urls.isEmpty()) { // If it's a Paste, we should know if files should be copied (copy&paste) or moved (cut&paste): if (!fromDrop && Tools::isAFileCut(source)) action = Qt::MoveAction; return dropURLs(urls, parent, action, fromDrop); } // FIXME: use dropURLs() also from Mozilla? /* * Mozilla's stuff sometimes uses utf-16-le - little-endian UTF-16. * * This has the property that for the ASCII subset case (And indeed, the * ISO-8859-1 subset, I think), if you treat it as a C-style string, * it'll come out to one character long in most cases, since it looks * like: * * "<\0H\0T\0M\0L\0>\0" * * A strlen() call on that will give you 1, which simply isn't correct. * That might, I suppose, be the answer, or something close. * * Also, Mozilla's drag/drop code predates the use of MIME types in XDnD * - hence it'll throw about STRING and UTF8_STRING quite happily, hence * the odd named types. * * Thanks to Dave Cridland for having said me that. */ if (source->hasFormat("text/x-moz-url")) { // FOR MOZILLA // Get the array and create a QChar array of 1/2 of the size QByteArray mozilla = source->data("text/x-moz-url"); QVector chars(mozilla.count() / 2); // A small debug work to know the value of each bytes if (Global::debugWindow) for (int i = 0; i < mozilla.count(); i++) *Global::debugWindow << QString("'") + QChar(mozilla[i]) + "' " + QString::number(int(mozilla[i])); // text/x-moz-url give the URL followed by the link title and separated by OxOA (10 decimal: new line?) uint size = 0; QChar *name = nullptr; // For each little endian mozilla chars, copy it to the array of QChars for (int i = 0; i < mozilla.count(); i += 2) { chars[i / 2] = QChar(mozilla[i], mozilla[i + 1]); if (mozilla.at(i) == 0x0A) { size = i / 2; name = &(chars[i / 2 + 1]); } } // Create a QString that take the address of the first QChar and a length if (name == nullptr) { // We haven't found name (FIXME: Is it possible ?) QString normalHtml(&(chars[0]), chars.size()); return createNoteLink(normalHtml, parent); } else { QString normalHtml(&(chars[0]), size); QString normalTitle(name, chars.size() - size - 1); return createNoteLink(normalHtml, normalTitle, parent); } } if (source->hasFormat("text/html")) { QString html; QString subtype("html"); // If the text/html comes from Mozilla or GNOME it can be UTF-16 encoded: we need ExtendedTextDrag to check that ExtendedTextDrag::decode(source, html, subtype); return createNoteHtml(html, parent); } QString text; // If the text/plain comes from GEdit or GNOME it can be empty: we need ExtendedTextDrag to check other MIME types if (ExtendedTextDrag::decode(source, text)) return createNoteFromText(text, parent); /* Create a cross reference note */ if (source->hasFormat(BasketTreeListView::TREE_ITEM_MIME_STRING)) { QByteArray data = source->data(BasketTreeListView::TREE_ITEM_MIME_STRING); QDataStream stream(&data, QIODevice::ReadOnly); QString basketName, folderName, icon; while (!stream.atEnd()) stream >> basketName >> folderName >> icon; return createNoteCrossReference(QUrl("basket://" + folderName), basketName, icon, parent); } /* Unsuccessful drop */ note = createNoteUnknown(source, parent); QString message = i18n( "

%1 doesn't support the data you've dropped.
" "It however created a generic note, allowing you to drag or copy it to an application that understand it.

" "

If you want the support of these data, please contact developer.

", QGuiApplication::applicationDisplayName()); KMessageBox::information(parent->graphicsView()->viewport(), message, i18n("Unsupported MIME Type(s)"), "unsupportedDropInfo", KMessageBox::AllowLink); return note; } Note *NoteFactory::createNoteUnknown(const QMimeData *source, BasketScene *parent /*, const QString &annotations*/) { // Save the MimeSource in a file: create and open the file: QString fileName = createFileForNewNote(parent, "unknown"); QFile file(parent->fullPath() + fileName); if (!file.open(QIODevice::WriteOnly)) return nullptr; QDataStream stream(&file); // Echo MIME types: QStringList formats = source->formats(); for (int i = 0; formats.size(); ++i) stream << QString(formats[i]); // Output the '\0'-terminated format name string // Echo end of MIME types list delimiter: stream << QString(); // Echo the length (in bytes) and then the data, and then same for next MIME type: for (int i = 0; formats.size(); ++i) { QByteArray data = source->data(formats[i]); stream << (quint32)data.count(); stream.writeRawData(data.data(), data.count()); } file.close(); Note *note = new Note(parent); new UnknownContent(note, fileName); return note; } Note *NoteFactory::dropURLs(QList urls, BasketScene *parent, Qt::DropAction action, bool fromDrop) { KModifierKeyInfo keyinfo; int shouldAsk = 0; // shouldAsk==0: don't ask ; shouldAsk==1: ask for "file" ; shouldAsk>=2: ask for "files" bool shiftPressed = keyinfo.isKeyPressed(Qt::Key_Shift); bool ctrlPressed = keyinfo.isKeyPressed(Qt::Key_Control); bool modified = fromDrop && (shiftPressed || ctrlPressed); if (modified) // Then no menu + modified action ; // action is already set: no work to do else if (fromDrop) { // Compute if user should be asked or not for (QList::iterator it = urls.begin(); it != urls.end(); ++it) if ((*it).scheme() != "mailto") { // Do not ask when dropping mail address :-) shouldAsk++; if (shouldAsk == 1 /*2*/) // Sufficient break; } if (shouldAsk) { QMenu menu(parent->graphicsView()); QList actList; actList << new QAction(QIcon::fromTheme("go-jump"), i18n("&Move Here\tShift"), &menu) << new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy Here\tCtrl"), &menu) << new QAction(QIcon::fromTheme("insert-link"), i18n("&Link Here\tCtrl+Shift"), &menu); - foreach (QAction *a, actList) + Q_FOREACH (QAction *a, actList) menu.addAction(a); menu.addSeparator(); menu.addAction(QIcon::fromTheme("dialog-cancel"), i18n("C&ancel\tEscape")); int id = actList.indexOf(menu.exec(QCursor::pos())); switch (id) { case 0: action = Qt::MoveAction; break; case 1: action = Qt::CopyAction; break; case 2: action = Qt::LinkAction; break; default: return nullptr; } modified = true; } } else { // fromPaste ; } /* Policy of drops of URL: * Email: [Modifier keys: Useless] + - Link mail address * Remote URL: [Modifier keys: {Copy,Link}] + - Download as Image, Animation and Launcher + - Link other URLs * Local URL: [Modifier keys: {Copy,Move,Link}] * - Copy as Image, Animation and Launcher [Modifier keys: {Copy,Move,Link}] * - Link folder [Modifier keys: Useless] * - Make Launcher of executable [Modifier keys: {Copy_exec,Move_exec,Link_Launcher}] * - Ask for file (if use want to copy and it is a sound: make Sound) * Policy of pastes of URL: [NO modifier keys] * - Same as drops * - But copy when ask should be done * - Unless cut-selection is true: move files instead * Policy of file created in the basket dir: [NO modifier keys] * - View as Image, Animation, Sound, Launcher * - View as File */ Note *note; Note *firstNote = nullptr; Note *lastInserted = nullptr; for (QList::iterator it = urls.begin(); it != urls.end(); ++it) { if (((*it).scheme() == "mailto") || (action == Qt::LinkAction)) note = createNoteLinkOrLauncher(*it, parent); // else if (!(*it).isLocalFile()) { // if (action != Qt::LinkAction && (maybeImageOrAnimation(*it)/* || maybeSound(*it)*/)) // note = copyFileAndLoad(*it, parent); // else // note = createNoteLinkOrLauncher(*it, parent); // } else { if (action == Qt::CopyAction) note = copyFileAndLoad(*it, parent); else if (action == Qt::MoveAction) note = moveFileAndLoad(*it, parent); else note = createNoteLinkOrLauncher(*it, parent); } // If we got a new note, insert it in a linked list (we will return the first note of that list): if (note) { DEBUG_WIN << "Drop URL: " + (*it).toDisplayString(); if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } return firstNote; } void NoteFactory::consumeContent(QDataStream &stream, NoteType::Id type) { if (type == NoteType::Link) { QUrl url; QString title, icon; quint64 autoTitle64, autoIcon64; stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; } else if (type == NoteType::CrossReference) { QUrl url; QString title, icon; stream >> url >> title >> icon; } else if (type == NoteType::Color) { QColor color; stream >> color; } } Note *NoteFactory::decodeContent(QDataStream &stream, NoteType::Id type, BasketScene *parent) { /* if (type == NoteType::Text) { QString text; stream >> text; return NoteFactory::createNoteText(text, parent); } else if (type == NoteType::Html) { QString html; stream >> html; return NoteFactory::createNoteHtml(html, parent); } else if (type == NoteType::Image) { QPixmap pixmap; stream >> pixmap; return NoteFactory::createNoteImage(pixmap, parent); } else */ if (type == NoteType::Link) { QUrl url; QString title, icon; quint64 autoTitle64, autoIcon64; bool autoTitle, autoIcon; stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; autoTitle = (bool)autoTitle64; autoIcon = (bool)autoIcon64; Note *note = NoteFactory::createNoteLink(url, parent); ((LinkContent *)(note->content()))->setLink(url, title, icon, autoTitle, autoIcon); return note; } else if (type == NoteType::CrossReference) { QUrl url; QString title, icon; stream >> url >> title >> icon; Note *note = NoteFactory::createNoteCrossReference(url, parent); ((CrossReferenceContent *)(note->content()))->setCrossReference(url, title, icon); return note; } else if (type == NoteType::Color) { QColor color; stream >> color; return NoteFactory::createNoteColor(color, parent); } else return nullptr; // NoteFactory::loadFile() is sufficient } bool NoteFactory::maybeText(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::TEXT); } bool NoteFactory::maybeHtml(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::HTML); } bool NoteFactory::maybeImage(const QMimeType &mimeType) { return mimeType.name().startsWith(MimeTypes::IMAGE); } bool NoteFactory::maybeAnimation(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::ANIMATION) || mimeType.name() == MimeTypes::ANIMATION_MNG; } bool NoteFactory::maybeSound(const QMimeType &mimeType) { return mimeType.name().startsWith(MimeTypes::AUDIO); } bool NoteFactory::maybeLauncher(const QMimeType &mimeType) { return mimeType.inherits(MimeTypes::LAUNCHER); } ////////////// NEW: Note *NoteFactory::copyFileAndLoad(const QUrl &url, BasketScene *parent) { QString fileName = fileNameForNewNote(parent, url.fileName()); QString fullPath = parent->fullPathForFileName(fileName); if (Global::debugWindow) *Global::debugWindow << "copyFileAndLoad: " + url.toDisplayString() + " to " + fullPath; // QString annotations = i18n("Original file: %1", url.toDisplayString()); // parent->dontCareOfCreation(fullPath); KIO::CopyJob *copyJob = KIO::copy(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet return loadFile(fileName, type, parent); } Note *NoteFactory::moveFileAndLoad(const QUrl &url, BasketScene *parent) { // Globally the same as copyFileAndLoad() but move instead of copy (KIO::move()) QString fileName = fileNameForNewNote(parent, url.fileName()); QString fullPath = parent->fullPathForFileName(fileName); if (Global::debugWindow) *Global::debugWindow << "moveFileAndLoad: " + url.toDisplayString() + " to " + fullPath; // QString annotations = i18n("Original file: %1", url.toDisplayString()); // parent->dontCareOfCreation(fullPath); KIO::CopyJob *copyJob = KIO::move(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet return loadFile(fileName, type, parent); } Note *NoteFactory::loadFile(const QString &fileName, BasketScene *parent) { // The file MUST exists QFileInfo file(QUrl::fromLocalFile(parent->fullPathForFileName(fileName)).path()); if (!file.exists()) return nullptr; NoteType::Id type = typeForURL(parent->fullPathForFileName(fileName), parent); Note *note = loadFile(fileName, type, parent); return note; } Note *NoteFactory::loadFile(const QString &fileName, NoteType::Id type, BasketScene *parent) { Note *note = new Note(parent); switch (type) { case NoteType::Text: new TextContent(note, fileName); break; case NoteType::Html: new HtmlContent(note, fileName); break; case NoteType::Image: new ImageContent(note, fileName); break; case NoteType::Animation: new AnimationContent(note, fileName); break; case NoteType::Sound: new SoundContent(note, fileName); break; case NoteType::File: new FileContent(note, fileName); break; case NoteType::Launcher: new LauncherContent(note, fileName); break; case NoteType::Unknown: new UnknownContent(note, fileName); break; default: case NoteType::Link: case NoteType::CrossReference: case NoteType::Color: return nullptr; } return note; } NoteType::Id NoteFactory::typeForURL(const QUrl &url, BasketScene * /*parent*/) { bool viewText = Settings::viewTextFileContent(); bool viewHTML = Settings::viewHtmlFileContent(); bool viewImage = Settings::viewImageFileContent(); bool viewSound = Settings::viewSoundFileContent(); QMimeDatabase db; QMimeType mimeType = db.mimeTypeForUrl(url); if (Global::debugWindow) { if (mimeType.isValid()) *Global::debugWindow << "typeForURL: " + url.toDisplayString() + " ; MIME type = " + mimeType.name(); else *Global::debugWindow << "typeForURL: mimeType is empty for " + url.toDisplayString(); } // Go from specific to more generic if (maybeLauncher(mimeType)) return NoteType::Launcher; else if (viewHTML && (maybeHtml(mimeType))) return NoteType::Html; else if (viewText && maybeText(mimeType)) return NoteType::Text; else if (viewImage && maybeAnimation(mimeType)) return NoteType::Animation; // See Note::movieStatus(int) else if (viewImage && maybeImage(mimeType)) return NoteType::Image; // for more explanations else if (viewSound && maybeSound(mimeType)) return NoteType::Sound; else return NoteType::File; } QString NoteFactory::fileNameForNewNote(BasketScene *parent, const QString &wantedName) { return Tools::fileNameForNewFile(wantedName, parent->fullPath()); } // Create a file to store a new note in Basket parent and with extension extension. // If wantedName is provided, the function will first try to use this file name, or derive it if it's impossible // (extension willn't be used for that case) QString NoteFactory::createFileForNewNote(BasketScene *parent, const QString &extension, const QString &wantedName) { QString fileName; QString fullName; if (wantedName.isEmpty()) { // TODO: fileNameForNewNote(parent, "note1."+extension); QDir dir; int nb = parent->count() + 1; QString time = QTime::currentTime().toString("hhmmss"); for (;; ++nb) { fileName = QString("note%1-%2.%3").arg(nb).arg(time).arg(extension); fullName = parent->fullPath() + fileName; dir = QDir(fullName); if (!dir.exists(fullName)) break; } } else { fileName = fileNameForNewNote(parent, wantedName); fullName = parent->fullPath() + fileName; } // Create the file // parent->dontCareOfCreation(fullName); QFile file(fullName); file.open(QIODevice::WriteOnly); file.close(); return fileName; } QUrl NoteFactory::filteredURL(const QUrl &url) { // KURIFilter::filteredURI() is slow if the URL contains only letters, digits and '-' or '+'. // So, we don't use that function is that case: bool isSlow = true; for (int i = 0; i < url.url().length(); ++i) { QChar c = url.url()[i]; if (!c.isLetterOrNumber() && c != '-' && c != '+') { isSlow = false; break; } } if (isSlow) return url; else { QStringList list; list << QLatin1String("kshorturifilter") << QLatin1String("kuriikwsfilter") << QLatin1String("kurisearchfilter") << QLatin1String("localdomainfilter") << QLatin1String("fixuphosturifilter"); return KUriFilter::self()->filteredUri(url, list); } } QString NoteFactory::titleForURL(const QUrl &url) { QString title = url.toDisplayString(); QString home = "file:" + QDir::homePath() + '/'; if (title.startsWith(QLatin1String("mailto:"))) return title.remove(0, 7); if (title.startsWith(home)) title = "~/" + title.remove(0, home.length()); if (title.startsWith(QLatin1String("file://"))) title = title.remove(0, 7); // 7 == QString("file://").length() - 1 else if (title.startsWith(QLatin1String("file:"))) title = title.remove(0, 5); // 5 == QString("file:").length() - 1 else if (title.startsWith(QLatin1String("http://www."))) title = title.remove(0, 11); // 11 == QString("http://www.").length() - 1 else if (title.startsWith(QLatin1String("https://www."))) title = title.remove(0, 12); // 12 == QString("https://www.").length() - 1 else if (title.startsWith(QLatin1String("http://"))) title = title.remove(0, 7); // 7 == QString("http://").length() - 1 else if (title.startsWith(QLatin1String("https://"))) title = title.remove(0, 8); // 8 == QString("https://").length() - 1 if (!url.isLocalFile()) { if (title.endsWith(QLatin1String("/index.html")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.html").length() else if (title.endsWith(QLatin1String("/index.htm")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.htm").length() else if (title.endsWith(QLatin1String("/index.xhtml")) && title.length() > 12) title.truncate(title.length() - 12); // 12 == QString("/index.xhtml").length() else if (title.endsWith(QLatin1String("/index.php")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.php").length() else if (title.endsWith(QLatin1String("/index.asp")) && title.length() > 10) title.truncate(title.length() - 10); // 10 == QString("/index.asp").length() else if (title.endsWith(QLatin1String("/index.php3")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php3").length() else if (title.endsWith(QLatin1String("/index.php4")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php4").length() else if (title.endsWith(QLatin1String("/index.php5")) && title.length() > 11) title.truncate(title.length() - 11); // 11 == QString("/index.php5").length() } if (title.length() > 2 && title.endsWith('/')) // length > 2 because "/" and "~/" shouldn't be transformed to QString() and "~" title.truncate(title.length() - 1); // eg. transform "www.kde.org/" to "www.kde.org" return title; } QString NoteFactory::iconForURL(const QUrl &url) { if (url.scheme() == "mailto") { return QStringLiteral("message"); } // return KMimeType::iconNameForUrl(url.url()); return QString(); } // TODO: Can I add "autoTitle" and "autoIcon" entries to .desktop files? or just store them in basket, as now... /* Try our better to find an icon suited to the command line * eg. "/usr/bin/kwrite-3.2 ~/myfile.txt /home/other/file.xml" * will give the "kwrite" icon! */ QString NoteFactory::iconForCommand(const QString &command) { QString icon; // 1. Use first word as icon (typically the program without argument) icon = command.split(' ').first(); // 2. If the command is a full path, take only the program file name icon = icon.mid(icon.lastIndexOf('/') + 1); // strip path if given [But it doesn't care of such // "myprogram /my/path/argument" -> return "argument". Would // must first strip first word and then strip path... Useful ?? // 3. Use characters before any '-' (e.g. use "gimp" icon if run command is "gimp-1.3") if (!isIconExist(icon)) icon = icon.split('-').first(); // 4. If the icon still not findable, use a generic icon if (!isIconExist(icon)) icon = "exec"; return icon; } bool NoteFactory::isIconExist(const QString &icon) { return !KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, true).isNull(); } Note *NoteFactory::createEmptyNote(NoteType::Id type, BasketScene *parent) { QPixmap *pixmap; switch (type) { case NoteType::Text: return NoteFactory::createNoteText(QString(), parent, /*reallyPlainText=*/true); case NoteType::Html: return NoteFactory::createNoteHtml(QString(), parent); case NoteType::Image: pixmap = new QPixmap(QSize(Settings::defImageX(), Settings::defImageY())); pixmap->fill(); pixmap->setMask(pixmap->createHeuristicMask()); return NoteFactory::createNoteImage(*pixmap, parent); case NoteType::Link: return NoteFactory::createNoteLink(QUrl(), parent); case NoteType::CrossReference: return NoteFactory::createNoteCrossReference(QUrl(), parent); case NoteType::Launcher: return NoteFactory::createNoteLauncher(QUrl(), parent); case NoteType::Color: return NoteFactory::createNoteColor(Qt::black, parent); default: case NoteType::Animation: case NoteType::Sound: case NoteType::File: case NoteType::Unknown: return nullptr; // Not possible! } } Note *NoteFactory::importKMenuLauncher(BasketScene *parent) { QPointer dialog = new KOpenWithDialog(parent->graphicsView()->viewport()); dialog->setSaveNewApplications(true); // To create temp file, needed by createNoteLauncher() dialog->exec(); if (dialog->service()) { // * locateLocal() return a local file even if it is a system wide one (local one doesn't exists) // * desktopEntryPath() returns the full path for system wide resources, but relative path if in home QString serviceFilePath = dialog->service()->entryPath(); if (!serviceFilePath.startsWith('/')) serviceFilePath = dialog->service()->locateLocal(); return createNoteLauncher(QUrl::fromUserInput(serviceFilePath), parent); } return nullptr; } Note *NoteFactory::importIcon(BasketScene *parent) { QString iconName = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, Settings::defIconSize()); if (!iconName.isEmpty()) { QPointer dialog = new IconSizeDialog(i18n("Import Icon as Image"), i18n("Choose the size of the icon to import as an image:"), iconName, Settings::defIconSize(), nullptr); dialog->exec(); if (dialog->iconSize() > 0) { Settings::setDefIconSize(dialog->iconSize()); Settings::saveConfig(); return createNoteImage(QIcon::fromTheme(iconName).pixmap(dialog->iconSize()), parent); // TODO: wantedName = iconName ! } } return nullptr; } Note *NoteFactory::importFileContent(BasketScene *parent) { QUrl url = QFileDialog::getOpenFileUrl(parent->graphicsView(), i18n("Load File Content into a Note"), QUrl(), QString()); if (!url.isEmpty()) return copyFileAndLoad(url, parent); return nullptr; } void NoteFactory::loadNode(const QDomElement &content, const QString &lowerTypeName, Note *parent, bool lazyLoad) { if (lowerTypeName == "text") { new TextContent(parent, content.text(), lazyLoad); } else if (lowerTypeName == "html") { new HtmlContent(parent, content.text(), lazyLoad); } else if (lowerTypeName == "image") { new ImageContent(parent, content.text(), lazyLoad); } else if (lowerTypeName == "animation") { new AnimationContent(parent, content.text(), lazyLoad); } else if (lowerTypeName == "sound") { new SoundContent(parent, content.text()); } else if (lowerTypeName == "file") { new FileContent(parent, content.text()); } else if (lowerTypeName == "link") { bool autoTitle = content.attribute("title") == content.text(); bool autoIcon = content.attribute("icon") == NoteFactory::iconForURL(QUrl::fromUserInput(content.text())); autoTitle = XMLWork::trueOrFalse(content.attribute("autoTitle"), autoTitle); autoIcon = XMLWork::trueOrFalse(content.attribute("autoIcon"), autoIcon); new LinkContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon"), autoTitle, autoIcon); } else if (lowerTypeName == "cross_reference") { new CrossReferenceContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon")); } else if (lowerTypeName == "launcher") { new LauncherContent(parent, content.text()); } else if (lowerTypeName == "color") { new ColorContent(parent, QColor(content.text())); } else if (lowerTypeName == "unknown") { new UnknownContent(parent, content.text()); } } diff --git a/src/regiongrabber.cpp b/src/regiongrabber.cpp index eb16592..f3a0dda 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, &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) { + Q_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()); + Q_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)); + Q_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) + Q_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/regiongrabber.h b/src/regiongrabber.h index 5f51331..95a1abf 100644 --- a/src/regiongrabber.h +++ b/src/regiongrabber.h @@ -1,70 +1,70 @@ /** * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann * * SPDX-License-Identifier: LGPL-2.0-only */ #ifndef REGIONGRABBER_H #define REGIONGRABBER_H #include #include #include class QPoint; class QRect; class QRegion; class QPaintEvent; class QResizeEvent; class QMouseEvent; class RegionGrabber : public QWidget { Q_OBJECT public: RegionGrabber(); ~RegionGrabber() override; -protected slots: +protected Q_SLOTS: void init(); void displayHelp(); -signals: +Q_SIGNALS: void regionGrabbed(const QPixmap &); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *) override; void keyPressEvent(QKeyEvent *e) override; void updateHandles(); QRegion handleMask() const; QPoint limitPointToRect(const QPoint &p, const QRect &r) const; void grabRect(); QRect selection; bool mouseDown; bool newSelection; const int handleSize; QRect *mouseOverHandle; QPoint dragStartPoint; QRect selectionBeforeDrag; QTimer idleTimer; bool showHelp; bool grabbing; // naming convention for handles // T top, B bottom, R Right, L left // 2 letters: a corner // 1 letter: the handle on the middle of the corresponding side QRect TLHandle, TRHandle, BLHandle, BRHandle; QRect LHandle, THandle, RHandle, BHandle; QVector handles; QPixmap pixmap; }; #endif diff --git a/src/settings.h b/src/settings.h index 3dc9986..ad0ca65 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,648 +1,648 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SETTINGS_H #define SETTINGS_H #include #include //For Global::mainWindow() #include //For UseSysTray #include #include "basket_export.h" #include "bnpview.h" #include "systemtray.h" class KComboBox; class QString; class QCheckBox; class QPushButton; class QPoint; class QSize; class QSpinBox; class LinkLook; class LinkLookEditWidget; class RunCommandRequester; class BASKET_EXPORT GeneralPage : public KCModule { Q_OBJECT public: explicit GeneralPage(QWidget *parent = nullptr, const char *name = nullptr); ~GeneralPage() override { } void load() override; void save() override; void defaults() override; private: // General KComboBox *m_treeOnLeft; KComboBox *m_filterOnTop; QCheckBox *m_usePassivePopup; // System Tray Icon QCheckBox *m_useSystray; QWidget *m_systray; QCheckBox *m_showIconInSystray; QCheckBox *m_hideOnMouseOut; QSpinBox *m_timeToHideOnMouseOut; QCheckBox *m_showOnMouseIn; QSpinBox *m_timeToShowOnMouseIn; }; class BASKET_EXPORT BasketsPage : public KCModule { Q_OBJECT public: explicit BasketsPage(QWidget *parent = nullptr, const char *name = nullptr); void load() override; void save() override; void defaults() override; private: // Appearance QCheckBox *m_playAnimations; QCheckBox *m_showNotesToolTip; QCheckBox *m_bigNotes; // Behavior QCheckBox *m_autoBullet; QCheckBox *m_confirmNoteDeletion; QCheckBox *m_exportTextTags; QWidget *m_groupOnInsertionLineWidget; QCheckBox *m_groupOnInsertionLine; KComboBox *m_middleAction; QCheckBox *m_pasteAsPlainText; // Protection QCheckBox *m_useGnuPGAgent; QCheckBox *m_enableReLockTimeoutMinutes; QSpinBox *m_reLockTimeoutMinutes; }; class BASKET_EXPORT NewNotesPage : public KCModule { Q_OBJECT public: explicit NewNotesPage(QWidget *parent = nullptr, const char *name = nullptr); void load() override; void save() override; void defaults() override; -private slots: +private Q_SLOTS: void visualize(); private: // Notes Image Size QSpinBox *m_imgSizeX; QSpinBox *m_imgSizeY; QPushButton *m_pushVisualize; // Note Addition KComboBox *m_newNotesPlace; QCheckBox *m_viewTextFileContent; QCheckBox *m_viewHtmlFileContent; QCheckBox *m_viewImageFileContent; QCheckBox *m_viewSoundFileContent; }; class BASKET_EXPORT NotesAppearancePage : public KCModule { Q_OBJECT public: explicit NotesAppearancePage(QWidget *parent = nullptr, const char *name = nullptr); void load() override; void save() override; void defaults() override; private: // Link Looks LinkLookEditWidget *m_soundLook; LinkLookEditWidget *m_fileLook; LinkLookEditWidget *m_localLinkLook; LinkLookEditWidget *m_networkLinkLook; LinkLookEditWidget *m_launcherLook; LinkLookEditWidget *m_crossReferenceLook; }; class BASKET_EXPORT ApplicationsPage : public KCModule { Q_OBJECT public: explicit ApplicationsPage(QWidget *parent = nullptr, const char *name = nullptr); void load() override; void save() override; void defaults() override; private: // Applications QCheckBox *m_htmlUseProg; QCheckBox *m_imageUseProg; QCheckBox *m_animationUseProg; QCheckBox *m_soundUseProg; RunCommandRequester *m_htmlProg; RunCommandRequester *m_imageProg; RunCommandRequester *m_animationProg; RunCommandRequester *m_soundProg; }; /** Handle all global variables (to avoid lot of extern declarations) * @author Sébastien Laoût */ class BASKET_EXPORT Settings // FIXME: Dispatch new config events ? { protected: /** Main window */ static bool s_treeOnLeft; static bool s_filterOnTop; static bool s_playAnimations; static bool s_showNotesToolTip; static bool s_confirmNoteDeletion; static bool s_bigNotes; static bool s_autoBullet; static bool s_pasteAsPlainText; static bool s_exportTextTags; static bool s_useGnuPGAgent; static bool s_usePassivePopup; static int s_middleAction; // O:Nothing ; 1:Paste ; 2:Text ; 3:Html ; 4:Image ; 5:Link ; 6:Launcher ; 7:Color static bool s_groupOnInsertionLine; static bool s_spellCheckTextNotes; static int s_basketTreeWidth; static bool s_welcomeBasketsAdded; static QString s_dataFolder; static QDate s_lastBackup; static QPoint s_mainWindowPosition; static QSize s_mainWindowSize; static bool s_showEmptyBasketInfo; static bool s_blinkedFilter; static bool s_enableReLockTimeout; static int s_reLockTimeoutMinutes; /** Note Addition */ static int s_newNotesPlace; // 0:OnTop ; 1:OnBottom ; 2:AtCurrentNote static int s_viewTextFileContent; static int s_viewHtmlFileContent; static int s_viewImageFileContent; static int s_viewSoundFileContent; /** System tray Icon */ static bool s_useSystray; static bool s_showIconInSystray; static bool s_startDocked; static bool s_hideOnMouseOut; static int s_timeToHideOnMouseOut; static bool s_showOnMouseIn; static int s_timeToShowOnMouseIn; /** Programs */ static bool s_htmlUseProg; static bool s_imageUseProg; static bool s_animationUseProg; static bool s_soundUseProg; static QString s_htmlProg; static QString s_imageProg; static QString s_animationProg; static QString s_soundProg; /** Insert Note Default Values */ static int s_defImageX; static int s_defImageY; static int s_defIconSize; /** Version Sync */ static bool s_versionSyncEnabled; public: /* And the following methods are just getter / setters */ /** App settings GET */ static inline bool treeOnLeft() { return s_treeOnLeft; } static inline bool filterOnTop() { return s_filterOnTop; } static inline bool playAnimations() { return s_playAnimations; } static inline bool showNotesToolTip() { return s_showNotesToolTip; } static inline bool confirmNoteDeletion() { return s_confirmNoteDeletion; } static inline bool bigNotes() { return s_bigNotes; } static inline bool autoBullet() { return s_autoBullet; } static inline bool pasteAsPlainText() { return s_pasteAsPlainText; } static inline bool exportTextTags() { return s_exportTextTags; } static inline bool useGnuPGAgent() { return s_useGnuPGAgent; } static inline bool blinkedFilter() { return s_blinkedFilter; } static inline bool enableReLockTimeout() { return s_enableReLockTimeout; } static inline int reLockTimeoutMinutes() { return s_reLockTimeoutMinutes; } static inline bool useSystray() { return s_useSystray; } static inline bool showIconInSystray() { return s_showIconInSystray; } static inline bool startDocked() { return s_startDocked; } static inline int middleAction() { return s_middleAction; } static inline bool groupOnInsertionLine() { return s_groupOnInsertionLine; } static inline bool spellCheckTextNotes() { return s_spellCheckTextNotes; } static inline bool hideOnMouseOut() { return s_hideOnMouseOut; } static inline int timeToHideOnMouseOut() { return s_timeToHideOnMouseOut; } static inline bool showOnMouseIn() { return s_showOnMouseIn; } static inline int timeToShowOnMouseIn() { return s_timeToShowOnMouseIn; } static inline int basketTreeWidth() { return s_basketTreeWidth; } static inline int dropTimeToShow() { return 7; } // TODO: 700 ; TODO: There is certainly a KGlobalConfig ??? static inline bool usePassivePopup() { return s_usePassivePopup; } static inline bool welcomeBasketsAdded() { return s_welcomeBasketsAdded; } static inline QString dataFolder() { return s_dataFolder; } static inline QDate lastBackup() { return s_lastBackup; } static inline QPoint mainWindowPosition() { return s_mainWindowPosition; } static inline QSize mainWindowSize() { return s_mainWindowSize; } static inline bool showEmptyBasketInfo() { return s_showEmptyBasketInfo; } /** Programs */ static inline bool isHtmlUseProg() { return s_htmlUseProg; } static inline bool isImageUseProg() { return s_imageUseProg; } static inline bool isAnimationUseProg() { return s_animationUseProg; } static inline bool isSoundUseProg() { return s_soundUseProg; } static inline QString htmlProg() { return s_htmlProg; } static inline QString imageProg() { return s_imageProg; } static inline QString animationProg() { return s_animationProg; } static inline QString soundProg() { return s_soundProg; } /** Insert Note Default Values */ static inline int defImageX() { return s_defImageX; } static inline int defImageY() { return s_defImageY; } static inline int defIconSize() { return s_defIconSize; } /** Note Addition */ static inline int newNotesPlace() { return s_newNotesPlace; } static inline int viewTextFileContent() { return s_viewTextFileContent; } static inline int viewHtmlFileContent() { return s_viewHtmlFileContent; } static inline int viewImageFileContent() { return s_viewImageFileContent; } static inline int viewSoundFileContent() { return s_viewSoundFileContent; } /** Version Sync */ static inline bool versionSyncEnabled() { return s_versionSyncEnabled; } /** App settings SET */ static void setTreeOnLeft(bool onLeft) { s_treeOnLeft = onLeft; if (Global::bnpView) Global::bnpView->setTreePlacement(onLeft); } static void setFilterOnTop(bool onTop) { if (s_filterOnTop != onTop) { s_filterOnTop = onTop; if (Global::bnpView) Global::bnpView->filterPlacementChanged(onTop); } } static void setShowNotesToolTip(bool show) { s_showNotesToolTip = show; } static void setUseSystray(bool useSystray) { if (s_useSystray != useSystray) { s_useSystray = useSystray; if (Global::systemTray != nullptr) { if (Settings::useSystray()) Global::systemTray->setStatus(KStatusNotifierItem::Active); else { Global::systemTray->setStatus(KStatusNotifierItem::Passive); if (Global::activeMainWindow()) Global::activeMainWindow()->show(); } } if (Global::bnpView) Global::bnpView->m_actHideWindow->setEnabled(useSystray); } } static void setShowIconInSystray(bool show) { if (s_showIconInSystray != show) s_showIconInSystray = show; } static inline void setConfirmNoteDeletion(bool confirm) { s_confirmNoteDeletion = confirm; } static inline void setPasteAsPlainText(bool yes) { s_pasteAsPlainText = yes; } static void setBigNotes(bool big); static void setAutoBullet(bool yes); static inline void setExportTextTags(bool yes) { s_exportTextTags = yes; } static inline void setUseGnuPGAgent(bool yes) { s_useGnuPGAgent = yes; } static inline void setPlayAnimations(bool play) { s_playAnimations = play; } static inline void setBlinkedFilter(bool blinked) { s_blinkedFilter = blinked; } static inline void setEnableReLockTimeout(bool yes) { s_enableReLockTimeout = yes; } static inline void setReLockTimeoutMinutes(int minutes) { s_reLockTimeoutMinutes = minutes; } static inline void setStartDocked(bool docked) { s_startDocked = docked; } static inline void setMiddleAction(int action) { s_middleAction = action; } static inline void setGroupOnInsertionLine(bool yes) { s_groupOnInsertionLine = yes; } static inline void setSpellCheckTextNotes(bool yes) { s_spellCheckTextNotes = yes; } static inline void setHideOnMouseOut(bool hide) { s_hideOnMouseOut = hide; } static inline void setTimeToHideOnMouseOut(int time) { s_timeToHideOnMouseOut = time; } static inline void setShowOnMouseIn(bool show) { s_showOnMouseIn = show; } static inline void setTimeToShowOnMouseIn(int time) { s_timeToShowOnMouseIn = time; } static inline void setBasketTreeWidth(int width) { s_basketTreeWidth = width; } static inline void setUsePassivePopup(bool enable) { s_usePassivePopup = enable; } static inline void setWelcomeBasketsAdded(bool added) { s_welcomeBasketsAdded = added; } static inline void setDataFolder(const QString &folder) { s_dataFolder = folder; } static inline void setLastBackup(const QDate &date) { s_lastBackup = date; } static inline void setMainWindowPosition(const QPoint &pos) { s_mainWindowPosition = pos; } static inline void setMainWindowSize(const QSize &size) { s_mainWindowSize = size; } static inline void setShowEmptyBasketInfo(bool show) { s_showEmptyBasketInfo = show; } // Programs : static inline void setIsHtmlUseProg(bool useProg) { s_htmlUseProg = useProg; } static inline void setIsImageUseProg(bool useProg) { s_imageUseProg = useProg; } static inline void setIsAnimationUseProg(bool useProg) { s_animationUseProg = useProg; } static inline void setIsSoundUseProg(bool useProg) { s_soundUseProg = useProg; } static inline void setHtmlProg(const QString &prog) { s_htmlProg = prog; } static inline void setImageProg(const QString &prog) { s_imageProg = prog; } static inline void setAnimationProg(const QString &prog) { s_animationProg = prog; } static inline void setSoundProg(const QString &prog) { s_soundProg = prog; } // Insert Note Default Values : static inline void setDefImageX(int val) { s_defImageX = val; } static inline void setDefImageY(int val) { s_defImageY = val; } static inline void setDefIconSize(int val) { s_defIconSize = val; } // Note Addition static inline void setNewNotesPlace(int val) { s_newNotesPlace = val; } static inline void setViewTextFileContent(bool view) { s_viewTextFileContent = view; } static inline void setViewHtmlFileContent(bool view) { s_viewHtmlFileContent = view; } static inline void setViewImageFileContent(bool view) { s_viewImageFileContent = view; } static inline void setViewSoundFileContent(bool view) { s_viewSoundFileContent = view; } // Version Sync static inline void setVersionSyncEnabled(bool enable) { s_versionSyncEnabled = enable; } public: /* Save and load config */ static void loadConfig(); static void saveConfig(); protected: static void loadLinkLook(LinkLook *look, const QString &name, const LinkLook &defaultLook); static void saveLinkLook(LinkLook *look, const QString &name); }; #endif // SETTINGS_H diff --git a/src/settings_versionsync.h b/src/settings_versionsync.h index f1b072c..fa7202c 100644 --- a/src/settings_versionsync.h +++ b/src/settings_versionsync.h @@ -1,41 +1,41 @@ /** * SPDX-FileCopyrightText: (C) 2016 Gleb Baryshev * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SETTINGS_VERSIONSYNC_H #define SETTINGS_VERSIONSYNC_H #include "basket_export.h" #include namespace Ui { class VersionSyncPage; } class BASKET_EXPORT VersionSyncPage : public KCModule { Q_OBJECT public: explicit VersionSyncPage(QWidget *parent = nullptr, const char *name = nullptr); ~VersionSyncPage() override; void load() override; void save() override; void defaults() override; -public slots: +public Q_SLOTS: void setHistorySize(qint64 size_bytes); -private slots: +private Q_SLOTS: void on_checkBoxEnable_clicked(); void on_buttonClearHistory_clicked(); private: Ui::VersionSyncPage *ui; }; #endif // SETTINGS_VERSIONSYNC_H diff --git a/src/softwareimporters.h b/src/softwareimporters.h index 8b25d0c..037b31f 100644 --- a/src/softwareimporters.h +++ b/src/softwareimporters.h @@ -1,77 +1,77 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SOFTWAREIMPORTERS_H #define SOFTWAREIMPORTERS_H #include class QString; class QGroupBox; class QDomElement; class QRadioButton; class KTextEdit; class QVBoxLayout; class BasketScene; class Note; /** The dialog to ask how to import hierarchical data. * @author Sébastien Laoût */ class TreeImportDialog : public QDialog { Q_OBJECT public: explicit TreeImportDialog(QWidget *parent = nullptr); ~TreeImportDialog() override; int choice(); private: QGroupBox *m_choices; QVBoxLayout *m_choiceLayout; QRadioButton *m_hierarchy_choice; QRadioButton *m_separate_baskets_choice; QRadioButton *m_one_basket_choice; }; /** The dialog to ask how to import text files. * @author Sébastien Laoût */ class TextFileImportDialog : public QDialog { Q_OBJECT public: explicit TextFileImportDialog(QWidget *parent = nullptr); ~TextFileImportDialog() override; QString separator(); -protected slots: +protected Q_SLOTS: void customSeparatorChanged(); private: QGroupBox *m_choices; QVBoxLayout *m_choiceLayout; QRadioButton *m_emptyline_choice; QRadioButton *m_newline_choice; QRadioButton *m_dash_choice; QRadioButton *m_star_choice; QRadioButton *m_all_in_one_choice; QRadioButton *m_anotherSeparator; KTextEdit *m_customSeparator; }; /** Functions that import data from other software. * @author Sébastien Laoût */ namespace SoftwareImporters { void finishImport(BasketScene *basket); // The importers in themselves: void importTextFile(); } #endif // SOFTWAREIMPORTERS_H diff --git a/src/systemtray.cpp b/src/systemtray.cpp index 9792d42..b0ed1fa 100644 --- a/src/systemtray.cpp +++ b/src/systemtray.cpp @@ -1,344 +1,344 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ /** SystemTray */ #include "systemtray.h" // Qt #include #include #include #include // Frameworks #include //Port to Blitz/Quasar? #include // Local #include "basketscene.h" #include "global.h" #include "icon_names.h" #include "settings.h" #include "tools.h" /* This function comes directly from JuK: */ /* * This function copies the entirety of src into dest, starting in * dest at x and y. This function exists because I was unable to find * a function like it in either QImage or kdefx */ static bool copyImage(QImage &dest, QImage &src, int x, int y) { if (dest.depth() != src.depth()) return false; if ((x + src.width()) >= dest.width()) return false; if ((y + src.height()) >= dest.height()) return false; // We want to use KIconEffect::overlay to do this, since it handles // alpha, but the images need to be the same size. We can handle that. QImage large_src(dest); // It would perhaps be better to create large_src based on a size, but // this is the easiest way to make a new image with the same depth, size, // etc. large_src.detach(); // However, we do have to specifically ensure that setAlphaBuffer is set // to false // large_src.setAlphaBuffer(false); large_src.fill(0); // All transparent pixels // large_src.setAlphaBuffer(true); int w = src.width(); int h = src.height(); for (int dx = 0; dx < w; dx++) for (int dy = 0; dy < h; dy++) large_src.setPixel(dx + x, dy + y, src.pixel(dx, dy)); // Apply effect to image KIconEffect::overlay(dest, large_src); return true; } /** Constructor */ SystemTray::SystemTray(QWidget *parent) : KStatusNotifierItem(parent) { updateDisplay(); } /** Destructor */ SystemTray::~SystemTray() { // pass } /** Updates the icon and tooltip in the system tray */ void SystemTray::updateDisplay() { BasketScene *basket = Global::bnpView->currentBasket(); if (!basket) return; // Update the icon if (basket->icon().isEmpty() || basket->icon() == "basket" || !Settings::showIconInSystray()) setIconByName("basket"); else { // What pixmap size to use? For example, see how overlay icon is applied: // https://api.kde.org/frameworks-api/frameworks5-apidocs/knotifications/html/kstatusnotifieritem_8cpp_source.html // Code that comes from JuK: QPixmap bgPix = QIcon::fromTheme("basket").pixmap(22); QPixmap fgPix = QIcon::fromTheme(basket->icon()).pixmap(16); QImage bgImage = bgPix.toImage(); // Probably 22x22 QImage fgImage = fgPix.toImage(); // Should be 16x16 KIconEffect::semiTransparent(bgImage); copyImage(bgImage, fgImage, (bgImage.width() - fgImage.width()) / 2, (bgImage.height() - fgImage.height()) / 2); setIconByPixmap(QPixmap::fromImage(bgImage)); } setOverlayIconByName(basket->isLocked() ? IconNames::LOCKED : QString()); // update the tooltip QString tip = "

"; QString basketName = "%1"; if (basket->isLocked()) basketName += i18n(" (Locked)"); tip += Tools::makeStandardCaption(basketName); tip = tip.arg(Tools::textToHTMLWithoutP(basket->basketName())); setToolTipTitle(tip); } #ifdef USE_OLD_SYSTRAY #define QT3_SUPPORT // No need to port that old stuff SystemTray::SystemTray(QWidget *parent, const char *name) : KSystemTray2(parent, name != 0 ? name : "SystemTray") , m_showTimer(0) , m_autoShowTimer(0) { setAcceptDrops(true); m_showTimer = new QTimer(this); connect(m_showTimer, SIGNAL(timeout()), Global::bnpView, SLOT(setActive())); m_autoShowTimer = new QTimer(this); connect(m_autoShowTimer, SIGNAL(timeout()), Global::bnpView, SLOT(setActive())); // Create pixmaps for the icon: m_iconPixmap = loadIcon("basket"); // FIXME: When main window is shown at start, the icon is loaded 1 pixel too high // and then reloaded instantly after at the right position. // setPixmap(m_iconPixmap); // Load it the sooner as possible to avoid flicker QImage lockedIconImage = m_iconPixmap.convertToImage(); QPixmap lockOverlayPixmap = loadIcon("object-locked"); QImage lockOverlayImage = lockOverlayPixmap.convertToImage(); KIconEffect::overlay(lockedIconImage, lockOverlayImage); m_lockedIconPixmap.convertFromImage(lockedIconImage); updateToolTip(); // Set toolTip AND icon } SystemTray::~SystemTray() { } void SystemTray::mousePressEvent(QMouseEvent *event) { if (event->button() & Qt::LeftButton) { // Prepare drag m_pressPos = event->globalPos(); m_canDrag = true; event->accept(); } else if (event->button() & Qt::MidButton) { // Paste Global::bnpView->currentBasket()->setInsertPopupMenu(); Global::bnpView->currentBasket()->pasteNote(QClipboard::Selection); Global::bnpView->currentBasket()->cancelInsertPopupMenu(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Pasted selection to basket %1")); event->accept(); } else if (event->button() & Qt::RightButton) { // Popup menu QMenu menu(this); menu.addSection(SmallIcon("basket"), QGuiApplication::applicationDisplayName()); Global::bnpView->actNewBasket->plug(&menu); Global::bnpView->actNewSubBasket->plug(&menu); Global::bnpView->actNewSiblingBasket->plug(&menu); menu.insertSeparator(); Global::bnpView->m_actPaste->plug(&menu); Global::bnpView->m_actGrabScreenshot->plug(&menu); Global::bnpView->m_actColorPicker->plug(&menu); if (!Global::bnpView->isPart()) { QAction *action; menu.insertSeparator(); action = Global::bnpView->actionCollection()->action("options_configure_global_keybinding"); if (action) action->plug(&menu); action = Global::bnpView->actionCollection()->action("options_configure"); if (action) action->plug(&menu); menu.insertSeparator(); // Minimize / restore : since we manage the popup menu by ourself, we should do that work : action = Global::bnpView->actionCollection()->action("minimizeRestore"); if (action) { if (Global::mainWindow()->isVisible()) action->setText(i18n("&Minimize")); else action->setText(i18n("&Restore")); action->plug(&menu); } action = Global::bnpView->actionCollection()->action("file_quit"); if (action) action->plug(&menu); } Global::bnpView->currentBasket()->setInsertPopupMenu(); connect(&menu, SIGNAL(aboutToHide()), Global::bnpView->currentBasket(), SLOT(delayedCancelInsertPopupMenu())); menu.exec(event->globalPos()); event->accept(); } else event->ignore(); } void SystemTray::mouseMoveEvent(QMouseEvent *event) { event->ignore(); } void SystemTray::mouseReleaseEvent(QMouseEvent *event) { m_canDrag = false; if (event->button() == Qt::LeftButton) // Show / hide main window if (rect().contains(event->pos())) { // Accept only if released in systemTray toggleActive(); - emit showPart(); + Q_EMIT showPart(); event->accept(); } else event->ignore(); } void SystemTray::dragEnterEvent(QDragEnterEvent *event) { m_showTimer->start(Settings::dropTimeToShow() * 100, true); Global::bnpView->currentBasket()->showFrameInsertTo(); /// m_parentContainer->setStatusBarDrag(); // FIXME: move this line in BasketScene::showFrameInsertTo() ? BasketScene::acceptDropEvent(event); } void SystemTray::dragMoveEvent(QDragMoveEvent *event) { BasketScene::acceptDropEvent(event); } void SystemTray::dragLeaveEvent(QDragLeaveEvent *) { m_showTimer->stop(); m_canDrag = false; Global::bnpView->currentBasket()->resetInsertTo(); Global::bnpView->updateStatusBarHint(); } #include void SystemTray::dropEvent(QDropEvent *event) { m_showTimer->stop(); Global::bnpView->currentBasket()->blindDrop(event); /* BasketScene *basket = Global::bnpView->currentBasket(); if (!basket->isLoaded()) { Global::bnpView->showPassiveLoading(basket); basket->load(); } basket->contentsDropEvent(event); qDebug() << (long) basket->selectedNotes(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1", m_basketName));*/ } void SystemTray::updateToolTip() { // return; ///////////////////////////////////////////////////// BasketScene *basket = Global::bnpView->currentBasket(); if (!basket) return; if (basket->icon().isEmpty() || basket->icon() == "basket" || !Settings::showIconInSystray()) setPixmap(basket->isLocked() ? m_lockedIconPixmap : m_iconPixmap); else { // Code that comes from JuK: QPixmap bgPix = loadIcon("basket"); QPixmap fgPix = SmallIcon(basket->icon()); QImage bgImage = bgPix.convertToImage(); // Probably 22x22 QImage fgImage = fgPix.convertToImage(); // Should be 16x16 QImage lockOverlayImage = loadIcon("object-locked").convertToImage(); KIconEffect::semiTransparent(bgImage); copyImage(bgImage, fgImage, (bgImage.width() - fgImage.width()) / 2, (bgImage.height() - fgImage.height()) / 2); if (basket->isLocked()) KIconEffect::overlay(bgImage, lockOverlayImage); bgPix.convertFromImage(bgImage); setPixmap(bgPix); } // QTimer::singleShot( Container::c_delayTooltipTime, this, SLOT(updateToolTipDelayed()) ); // No need to delay: it's be called when notes are changed: updateToolTipDelayed(); } void SystemTray::updateToolTipDelayed() { BasketScene *basket = Global::bnpView->currentBasket(); QString tip = "

" + (basket->isLocked() ? Tools::makeStandardCaption(i18n("%1 (Locked)")) : Tools::makeStandardCaption("%1")).arg(Tools::textToHTMLWithoutP(basket->basketName())); QToolTip::add(this, tip); } void SystemTray::wheelEvent(QWheelEvent *event) { if (event->delta() > 0) Global::bnpView->goToPreviousBasket(); else Global::bnpView->goToNextBasket(); if (Settings::usePassivePopup()) Global::bnpView->showPassiveContent(); } void SystemTray::enterEvent(QEvent *) { if (Settings::showOnMouseIn()) m_autoShowTimer->start(Settings::timeToShowOnMouseIn() * 100, true); } void SystemTray::leaveEvent(QEvent *) { m_autoShowTimer->stop(); } #undef QT3_SUPPORT #endif // USE_OLD_SYSTRAY diff --git a/src/systemtray.h b/src/systemtray.h index 2b84df8..42bd531 100644 --- a/src/systemtray.h +++ b/src/systemtray.h @@ -1,66 +1,66 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SYSTEMTRAY_H #define SYSTEMTRAY_H #include #include #include /** A thin wrapper around KSystemTrayIcon until the old SystemTray is ported. * As things are ported, items should * @author Kelvie Wong */ class SystemTray : public KStatusNotifierItem { Q_OBJECT Q_DISABLE_COPY(SystemTray); public: explicit SystemTray(QWidget *parent = nullptr); ~SystemTray() override; -public slots: +public Q_SLOTS: void updateDisplay(); -signals: +Q_SIGNALS: void showPart(); }; #ifdef USE_OLD_SYSTRAY /** This class provide a personalized system tray icon. * @author Sébastien Laoût */ class SystemTray2 : public SystemTray { Q_OBJECT public: explicit SystemTray2(QWidget *parent = nullptr, const char *name = nullptr); ~SystemTray2(); protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); virtual void dragEnterEvent(QDragEnterEvent *event); virtual void dragMoveEvent(QDragMoveEvent *event); virtual void dragLeaveEvent(QDragLeaveEvent *); virtual void dropEvent(QDropEvent *event); void wheelEvent(QWheelEvent *event); void enterEvent(QEvent *); void leaveEvent(QEvent *); private: QTimer *m_showTimer; QTimer *m_autoShowTimer; bool m_canDrag; QPoint m_pressPos; }; #endif // USE_OLD_SYSTRAY #endif // SYSTEMTRAY_H diff --git a/src/tagsedit.cpp b/src/tagsedit.cpp index ecc6f2f..85cabfb 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(); + Q_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(); + Q_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, &QDialogButtonBox::accepted, this, &TagsEditDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &TagsEditDialog::reject); mainLayout->addWidget(buttonBox); okButton->setDefault(true); setObjectName("CustomizeTags"); setModal(true); 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, &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, &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, &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, &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, &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, &QAction::triggered, this, &TagsEditDialog::selectUp); QAction *selectBelow = new QAction(this); selectBelow->setShortcut(Qt::CTRL + Qt::Key_Down); connect(selectBelow, &QAction::triggered, this, &TagsEditDialog::selectDown); QAction *selectLeft = new QAction(this); selectLeft->setShortcut(Qt::CTRL + Qt::Key_Left); connect(selectLeft, &QAction::triggered, this, &TagsEditDialog::selectLeft); QAction *selectRight = new QAction(this); selectRight->setShortcut(Qt::CTRL + Qt::Key_Right); connect(selectRight, &QAction::triggered, this, &TagsEditDialog::selectRight); QAction *moveAbove = new QAction(this); moveAbove->setShortcut(Qt::CTRL + Qt::Key_Up); connect(moveAbove, &QAction::triggered, this, &TagsEditDialog::moveUp); QAction *moveBelow = new QAction(this); moveBelow->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Down); connect(moveBelow, &QAction::triggered, this, &TagsEditDialog::moveDown); QAction *rename = new QAction(this); rename->setShortcut(Qt::Key_F2); 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/tagsedit.h b/src/tagsedit.h index 1ff831c..ff0fa88 100644 --- a/src/tagsedit.h +++ b/src/tagsedit.h @@ -1,208 +1,208 @@ /** * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef TAGEDIT_H #define TAGEDIT_H #include #include #include #include #include "tag.h" class QCheckBox; class QFontComboBox; class QGroupBox; class QHBoxLayout; class QLabel; class QLineEdit; class QTreeWidget; class QKeyEvent; class QMouseEvent; class KIconButton; class QPushButton; class QKeySequence; class KShortcutWidget; class FontSizeCombo; class KColorCombo2; class StateCopy { public: typedef QList List; explicit StateCopy(State *old = nullptr); ~StateCopy(); State *oldState; State *newState; void copyBack(); }; class TagCopy { public: typedef QList List; explicit TagCopy(Tag *old = nullptr); ~TagCopy(); Tag *oldTag; Tag *newTag; StateCopy::List stateCopies; void copyBack(); bool isMultiState(); }; class TagListViewItem : public QTreeWidgetItem { friend class TagListDelegate; public: TagListViewItem(QTreeWidget *parent, TagCopy *tagCopy); TagListViewItem(QTreeWidgetItem *parent, TagCopy *tagCopy); TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, TagCopy *tagCopy); TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, TagCopy *tagCopy); TagListViewItem(QTreeWidget *parent, StateCopy *stateCopy); TagListViewItem(QTreeWidgetItem *parent, StateCopy *stateCopy); TagListViewItem(QTreeWidget *parent, QTreeWidgetItem *after, StateCopy *stateCopy); TagListViewItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, StateCopy *stateCopy); ~TagListViewItem() override; TagCopy *tagCopy() { return m_tagCopy; } StateCopy *stateCopy() { return m_stateCopy; } bool isEmblemObligatory(); TagListViewItem *lastChild(); TagListViewItem *prevSibling(); TagListViewItem *nextSibling(); TagListViewItem *parent() const; // Reimplemented to cast the return value int width(const QFontMetrics &fontMetrics, const QTreeWidget *listView, int column) const; void setup(); private: TagCopy *m_tagCopy; StateCopy *m_stateCopy; }; Q_DECLARE_METATYPE(TagListViewItem *); class TagListView : public QTreeWidget { Q_OBJECT public: explicit TagListView(QWidget *parent = nullptr); ~TagListView() override; void keyPressEvent(QKeyEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; TagListViewItem *currentItem() const; // Reimplemented to cast the return value TagListViewItem *firstChild() const; // Reimplemented to cast the return value TagListViewItem *lastItem() const; // Reimplemented to cast the return value -signals: +Q_SIGNALS: void deletePressed(); void doubleClickedItem(); }; /** * @author Sébastien Laoût */ class TagsEditDialog : public QDialog { Q_OBJECT public: explicit TagsEditDialog(QWidget *parent = nullptr, State *stateToEdit = nullptr, bool addNewTag = false); ~TagsEditDialog() override; State::List deletedStates() { return m_deletedStates; } State::List addedStates() { return m_addedStates; } TagListViewItem *itemForState(State *state); -private slots: +private Q_SLOTS: void newTag(); void newState(); void moveUp(); void moveDown(); void deleteTag(); void renameIt(); void capturedShortcut(const QKeySequence &shortcut); void removeShortcut(); void removeEmblem(); void modified(); void currentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *next = nullptr); void slotCancel(); void slotOk(); void selectUp(); void selectDown(); void selectLeft(); void selectRight(); void resetTreeSizeHint(); private: void loadBlankState(); void loadStateFrom(State *state); void loadTagFrom(Tag *tag); void saveStateTo(State *state); void saveTagTo(Tag *tag); void ensureCurrentItemVisible(); TagListView *m_tags; QPushButton *m_moveUp; QPushButton *m_moveDown; QPushButton *m_deleteTag; QLineEdit *m_tagName; KShortcutWidget *m_shortcut; QPushButton *m_removeShortcut; QCheckBox *m_inherit; QGroupBox *m_tagBox; QHBoxLayout *m_tagBoxLayout; QGroupBox *m_stateBox; QHBoxLayout *m_stateBoxLayout; QLabel *m_stateNameLabel; QLineEdit *m_stateName; KIconButton *m_emblem; QPushButton *m_removeEmblem; QPushButton *m_bold; QPushButton *m_underline; QPushButton *m_italic; QPushButton *m_strike; KColorCombo2 *m_textColor; QFontComboBox *m_font; FontSizeCombo *m_fontSize; KColorCombo2 *m_backgroundColor; QLineEdit *m_textEquivalent; QCheckBox *m_onEveryLines; QCheckBox *m_allowCrossRefernce; TagCopy::List m_tagCopies; State::List m_deletedStates; State::List m_addedStates; bool m_loading; }; class TagListDelegate : public QItemDelegate { Q_OBJECT public: explicit TagListDelegate(QWidget *parent = nullptr) : QItemDelegate(parent) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; #endif // TAGEDIT_H diff --git a/src/tools.cpp b/src/tools.cpp index 7300a93..0e25d06 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,922 +1,922 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "tools.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //For KIO::trash #include #include "config.h" #include "debugwindow.h" // cross reference #include "bnpview.h" #include "global.h" #include "htmlexporter.h" #include "linklabel.h" #include QVector StopWatch::starts; QVector StopWatch::totals; QVector StopWatch::counts; void StopWatch::start(int id) { if (id >= starts.size()) { totals.resize(id + 1); counts.resize(id + 1); for (int i = starts.size(); i <= id; i++) { totals[i] = 0; counts[i] = 0; } starts.resize(id + 1); } starts[id] = QTime::currentTime(); } void StopWatch::check(int id) { if (id >= starts.size()) return; double time = starts[id].msecsTo(QTime::currentTime()) / 1000.0; totals[id] += time; counts[id]++; qDebug() << Q_FUNC_INFO << "Timer_" << id << ": " << time << " s [" << counts[id] << " times, total: " << totals[id] << " s, average: " << totals[id] / counts[id] << " s]" << endl; } /** @namespace HTM * @brief HTML tags constants */ namespace HTM { static const char *BR = "
"; static const char *PAR = "

"; static const char *_PAR = "

"; // Styles static const char *FONT_FAMILY = "font-family: %1; "; static const char *FONT_STYLE = "font-style: %1; "; static const char *TEXT_DECORATION = "text-decoration: %1; "; static const char *ITALIC = "italic"; static const char *UNDERLINE = "underline"; static const char *LINE_THROUGH = "line-through"; // static const char* FONT_WEIGHT = "font-weight: %1; "; static const char *FONT_SIZE = "font-size: %1pt; "; static const char *COLOR = "color: %1; "; } QString Tools::textToHTML(const QString &text) { if (text.isEmpty()) return "

"; if (/*text.isEmpty() ||*/ text == " " || text == " ") return "

 

"; // convertFromPlainText() replace "\n\n" by "

\n

": we don't want that QString htmlString = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal); return htmlString.replace("

\n", "
\n
\n").replace("\n

", "\n"); // Don't replace first and last tags } QString Tools::textToHTMLWithoutP(const QString &text) { // textToHTML(text) return "

HTMLizedText

". We remove the strating "

" and ending

" QString HTMLizedText = textToHTML(text); return HTMLizedText.mid(3, HTMLizedText.length() - 3 - 4); } QString Tools::htmlToParagraph(const QString &html) { QString result = html; bool startedBySpan = false; // Remove the start tag, all the and the start // Because can contain style="..." parameter, we transform it to int pos = result.indexOf("\n", each tag can be separated by space characters (%s) // "

" can be omitted (eg. if the HTML doesn't contain paragraph but tables), as well as "" (optional) pos = result.indexOf(QRegExp("(?:(?:

[\\s\\n\\r\\t]*)*[\\s\\n\\r\\t]*)*", Qt::CaseInsensitive)); if (pos != -1) result = result.left(pos); if (startedBySpan) result += "
"; return result; } // The following is adapted from KStringHanlder::tagURLs // The adaptation lies in the change to urlEx // Thanks to Richard Heck QString Tools::tagURLs(const QString &text) { QRegExp urlEx(""); QString richText(text); int urlPos = 0; int urlLen; if ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) urlPos += urlEx.matchedLength(); else urlPos = 0; urlEx.setPattern("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$]+[\\d\\w/]"); while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) { urlLen = urlEx.matchedLength(); // if this match is already a link don't convert it. if (richText.mid(urlPos - 6, 6) == "href=\"") { urlPos += urlLen; continue; } QString href = richText.mid(urlPos, urlLen); // we handle basket links separately... if (href.contains("basket://")) { urlPos += urlLen; continue; } // Qt doesn't support (?<=pattern) so we do it here if ((urlPos > 0) && richText[urlPos - 1].isLetterOrNumber()) { urlPos++; continue; } // Don't use QString::arg since %01, %20, etc could be in the string QString anchor = "" + href + ""; richText.replace(urlPos, urlLen, anchor); urlPos += anchor.length(); } return richText; } QString Tools::tagCrossReferences(const QString &text, bool userLink, HTMLExporter *exporter) { QString richText(text); int urlPos = 0; int urlLen; QRegExp urlEx("\\[\\[(.+)\\]\\]"); urlEx.setMinimal(true); while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) { urlLen = urlEx.matchedLength(); QString href = urlEx.cap(1); QStringList hrefParts = href.split('|'); QString anchor; if (exporter) // if we're exporting this basket to html. anchor = crossReferenceForHtml(hrefParts, exporter); else if (userLink) // the link is manually created (ie [[/top level/sub]] ) anchor = crossReferenceForConversion(hrefParts); else // otherwise it's a standard link (ie. [[basket://basket107]] ) anchor = crossReferenceForBasket(hrefParts); richText.replace(urlPos, urlLen, anchor); urlPos += anchor.length(); } return richText; } QString Tools::crossReferenceForBasket(QStringList linkParts) { QString basketLink = linkParts.first(); QString title; bool linkIsEmpty = false; if (basketLink == "basket://" || basketLink.isEmpty()) linkIsEmpty = true; title = linkParts.last().trimmed(); QString css = LinkLook::crossReferenceLook->toCSS("cross_reference", QColor()); QString classes = "cross_reference"; classes += (linkIsEmpty ? " xref_empty" : QString()); css += (linkIsEmpty ? " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }" " a:hover.xref_empty { color: #A55858; }" : QString()); QString anchor = "" + QUrl::fromPercentEncoding(title.toUtf8()) + ""; return anchor; } QString Tools::crossReferenceForHtml(QStringList linkParts, HTMLExporter *exporter) { QString basketLink = linkParts.first(); QString title; bool linkIsEmpty = false; if (basketLink == "basket://" || basketLink.isEmpty()) linkIsEmpty = true; title = linkParts.last().trimmed(); QString url; if (basketLink.startsWith(QLatin1String("basket://"))) url = basketLink.mid(9, basketLink.length() - 9); BasketScene *basket = Global::bnpView->basketForFolderName(url); // remove the trailing slash. url = url.left(url.length() - 1); // if the basket we're trying to link to is the basket that was exported then // we have to use a special way to refer to it for the links. if (basket == exporter->exportedBasket) url = "../../" + exporter->fileName; else { // if we're in the exported basket then the links have to include // the sub directories. if (exporter->currentBasket == exporter->exportedBasket) url.prepend(exporter->basketsFolderName); if (!url.isEmpty()) url.append(".html"); } QString classes = "cross_reference"; classes += (linkIsEmpty ? " xref_empty" : QString()); QString css = (linkIsEmpty ? " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }" " a:hover.xref_empty { color: #A55858; }" : QString()); QString anchor = "" + QUrl::fromPercentEncoding(title.toUtf8()) + ""; return anchor; } QString Tools::crossReferenceForConversion(QStringList linkParts) { QString basketLink = linkParts.first(); QString title; if (basketLink.startsWith(QLatin1String("basket://"))) return QString("[[%1|%2]]").arg(basketLink, linkParts.last()); if (basketLink.endsWith('/')) basketLink = basketLink.left(basketLink.length() - 1); QStringList pages = basketLink.split('/'); if (linkParts.count() <= 1) title = pages.last(); else title = linkParts.last().trimmed(); QString url = Global::bnpView->folderFromBasketNameLink(pages); url.prepend("basket://"); // if we don't change the link return it back exactly // as it came in because it may not be a link. if (url == "basket://" || url.isEmpty()) { return linkParts.join(QString()).prepend("[[").append("]]"); } else { return QString("[[%1|%2]]").arg(url, title); } } QString Tools::htmlToText(const QString &html) { QString text = htmlToParagraph(html); text.remove('\n'); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("
", " "); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", "\n"); text.replace("", " "); text.replace("", " "); text.replace("
", "\n"); text.replace("
", "\n"); text.replace("

", "\n"); // FIXME: Format tags better, if possible // TODO: Replace é and co. by their equivalent! // To manage tags: int pos = 0; int pos2; QString tag, tag3; // To manage lists: int deep = 0; // The deep of the current line in imbriqued lists QList ul; // true if current list is a
    one, false if it's an
      one QList lines; // The line number if it is an
        list // We're removing every other tags, or replace them in the case of li: while ((pos = text.indexOf("<"), pos) != -1) { // What is the current tag? tag = text.mid(pos + 1, 2); tag3 = text.mid(pos + 1, 3); // Lists work: if (tag == "ul") { deep++; ul.push_back(true); lines.push_back(-1); } else if (tag == "ol") { deep++; ul.push_back(false); lines.push_back(0); } else if (tag3 == "/ul" || tag3 == "/ol") { deep--; ul.pop_back(); lines.pop_back(); } // Where the tag closes? pos2 = text.indexOf(">"); if (pos2 != -1) { // Remove the tag: text.remove(pos, pos2 - pos + 1); // And replace li with "* ", "x. "... without forbidding to indent that: if (tag == "li") { // How many spaces before the line (indentation): QString spaces; for (int i = 1; i < deep; i++) spaces += QStringLiteral(" "); // The bullet or number of the line: QString bullet = "* "; if (ul.back() == false) { lines.push_back(lines.back() + 1); lines.pop_back(); bullet = QString::number(lines.back()) + ". "; } // Insertion: text.insert(pos, spaces + bullet); } if ((tag3 == "/ul" || tag3 == "/ol") && deep == 0) text.insert(pos, "\n"); // Empty line before and after a set of lists } ++pos; } text.replace(">", ">"); text.replace("<", "<"); text.replace(""", "\""); text.replace(" ", " "); text.replace("&", "&"); // CONVERT IN LAST!! // HtmlContent produces "\n" for empty note if (text == "\n") text = QString(); return text; } QString Tools::textDocumentToMinimalHTML(QTextDocument *document) { QString result = "\n" "\n"; QFont defaultFont; int fragCount, blockCount = 0; bool leadingBrNeeded = false; for (QTextBlock blockIt = document->begin(); blockIt != document->end(); blockIt = blockIt.next(), ++blockCount) { result += HTM::PAR; // Prepare to detect empty blocks fragCount = 0; for (QTextBlock::iterator subIt = blockIt.begin(); !(subIt.atEnd()); ++subIt, ++fragCount) { QTextFragment currentFragment = subIt.fragment(); if (currentFragment.isValid()) { // Dealing with need to add leading linebreak (see later for // further notes) if (leadingBrNeeded) { result += HTM::BR; leadingBrNeeded = false; } QTextCharFormat charFmt = currentFragment.charFormat(); const QColor &textColor = charFmt.foreground().color(); bool isTextBlack = (textColor == QColor() || textColor == QColor(Qt::black)); if (charFmt.font() == defaultFont && isTextBlack) { result += currentFragment.text().toHtmlEscaped(); continue; } // If we use charFmt.fontWeight, setting a tag overrides it and all characters become non-bold. // So we use instead bool bold = (charFmt.fontWeight() >= QFont::Bold); if (bold) result += ""; // Compose style string (font and color) result += "= QFont::Bold) ? QFont::Bold : QFont::Normal; result += QString(HTM::FONT_WEIGHT).arg(weight); }*/ if (charFmt.fontPointSize() != defaultFont.pointSize() && charFmt.fontPointSize() != 0) result += QString(HTM::FONT_SIZE).arg(charFmt.fontPointSize()); if (!isTextBlack) result += QString(HTM::COLOR).arg(textColor.name()); result += "\">" + currentFragment.text().toHtmlEscaped() + ""; if (bold) result += ""; } } // Detecting empty blocks (Qt4 fails to generate a fragment from an empty line) // Inserting a linebreak directly here seems to cause the renderer to render // two breaks, so have to append it to the contents of the previous paragraph... if (!fragCount) { // If the first fragment is an empty fragment, the linebreak must be // added to the next fragment otherwise you get the above double breaks if (!blockCount) leadingBrNeeded = true; // Deal with the problem only when the last block is not affected, // otherwise you get double breaks again... Blocks counted from 0 else if (blockCount != (document->blockCount() - 1)) { result.chop(7); result = result + HTM::BR + HTM::_PAR + HTM::PAR; } } result += HTM::_PAR; } result += ""; return result; } QString Tools::cssFontDefinition(const QFont &font, bool onlyFontFamily) { // The font definition: QString definition = font.key() + (font.italic() ? QStringLiteral("italic ") : QString()) + (font.bold() ? QStringLiteral("bold ") : QString()) + QString::number(QFontInfo(font).pixelSize()) + QStringLiteral("px "); // Then, try to match the font name with a standard CSS font family: QString genericFont; if (definition.contains("serif", Qt::CaseInsensitive) || definition.contains("roman", Qt::CaseInsensitive)) { genericFont = QStringLiteral("serif"); } // No "else if" because "sans serif" must be counted as "sans". So, the order between "serif" and "sans" is important if (definition.contains("sans", Qt::CaseInsensitive) || definition.contains("arial", Qt::CaseInsensitive) || definition.contains("helvetica", Qt::CaseInsensitive)) { genericFont = QStringLiteral("sans-serif"); } if (definition.contains("mono", Qt::CaseInsensitive) || definition.contains("courier", Qt::CaseInsensitive) || definition.contains("typewriter", Qt::CaseInsensitive) || definition.contains("console", Qt::CaseInsensitive) || definition.contains("terminal", Qt::CaseInsensitive) || definition.contains("news", Qt::CaseInsensitive)) { genericFont = QStringLiteral("monospace"); } // Eventually add the generic font family to the definition: QString fontDefinition = QStringLiteral("\"%1\"").arg(font.family()); if (!genericFont.isEmpty()) fontDefinition += ", " + genericFont; if (onlyFontFamily) return fontDefinition; return definition + fontDefinition; } QString Tools::stripEndWhiteSpaces(const QString &string) { uint length = string.length(); uint i; for (i = length; i > 0; --i) if (!string[i - 1].isSpace()) break; if (i == 0) return QString(); else return string.left(i); } QString Tools::cssColorName(const QString& colorHex) { static const QMap cssColors = { {"#00ffff", "aqua" }, {"#000000", "black" }, {"#0000ff", "blue" }, {"#ff00ff", "fuchsia" }, {"#808080", "gray" }, {"#008000", "green" }, {"#00ff00", "lime" }, {"#800000", "maroon" }, {"#000080", "navy" }, {"#808000", "olive" }, {"#800080", "purple" }, {"#ff0000", "red" }, {"#c0c0c0", "silver" }, {"#008080", "teal" }, {"#ffffff", "white" }, {"#ffff00", "yellow" }, // CSS extended colors {"#f0f8ff", "aliceblue" }, {"#faebd7", "antiquewhite" }, {"#7fffd4", "aquamarine" }, {"#f0ffff", "azure" }, {"#f5f5dc", "beige" }, {"#ffe4c4", "bisque" }, {"#ffebcd", "blanchedalmond" }, {"#8a2be2", "blueviolet" }, {"#a52a2a", "brown" }, {"#deb887", "burlywood" }, {"#5f9ea0", "cadetblue" }, {"#7fff00", "chartreuse" }, {"#d2691e", "chocolate" }, {"#ff7f50", "coral" }, {"#6495ed", "cornflowerblue" }, {"#fff8dc", "cornsilk" }, {"#dc1436", "crimson" }, {"#00ffff", "cyan" }, {"#00008b", "darkblue" }, {"#008b8b", "darkcyan" }, {"#b8860b", "darkgoldenrod" }, {"#a9a9a9", "darkgray" }, {"#006400", "darkgreen" }, {"#bdb76b", "darkkhaki" }, {"#8b008b", "darkmagenta" }, {"#556b2f", "darkolivegreen" }, {"#ff8c00", "darkorange" }, {"#9932cc", "darkorchid" }, {"#8b0000", "darkred" }, {"#e9967a", "darksalmon" }, {"#8fbc8f", "darkseagreen" }, {"#483d8b", "darkslateblue" }, {"#2f4f4f", "darkslategray" }, {"#00ced1", "darkturquoise" }, {"#9400d3", "darkviolet" }, {"#ff1493", "deeppink" }, {"#00bfff", "deepskyblue" }, {"#696969", "dimgray" }, {"#1e90ff", "dodgerblue" }, {"#b22222", "firebrick" }, {"#fffaf0", "floralwhite" }, {"#228b22", "forestgreen" }, {"#dcdcdc", "gainsboro" }, {"#f8f8ff", "ghostwhite" }, {"#ffd700", "gold" }, {"#daa520", "goldenrod" }, {"#adff2f", "greenyellow" }, {"#f0fff0", "honeydew" }, {"#ff69b4", "hotpink" }, {"#cd5c5c", "indianred" }, {"#4b0082", "indigo" }, {"#fffff0", "ivory" }, {"#f0e68c", "khaki" }, {"#e6e6fa", "lavender" }, {"#fff0f5", "lavenderblush" }, {"#7cfc00", "lawngreen" }, {"#fffacd", "lemonchiffon" }, {"#add8e6", "lightblue" }, {"#f08080", "lightcoral" }, {"#e0ffff", "lightcyan" }, {"#fafad2", "lightgoldenrodyellow" }, {"#90ee90", "lightgreen" }, {"#d3d3d3", "lightgrey" }, {"#ffb6c1", "lightpink" }, {"#ffa07a", "lightsalmon" }, {"#20b2aa", "lightseagreen" }, {"#87cefa", "lightskyblue" }, {"#778899", "lightslategray" }, {"#b0c4de", "lightsteelblue" }, {"#ffffe0", "lightyellow" }, {"#32cd32", "limegreen" }, {"#faf0e6", "linen" }, {"#ff00ff", "magenta" }, {"#66cdaa", "mediumaquamarine" }, {"#0000cd", "mediumblue" }, {"#ba55d3", "mediumorchid" }, {"#9370db", "mediumpurple" }, {"#3cb371", "mediumseagreen" }, {"#7b68ee", "mediumslateblue" }, {"#00fa9a", "mediumspringgreen" }, {"#48d1cc", "mediumturquoise" }, {"#c71585", "mediumvioletred" }, {"#191970", "midnightblue" }, {"#f5fffa", "mintcream" }, {"#ffe4e1", "mistyrose" }, {"#ffe4b5", "moccasin" }, {"#ffdead", "navajowhite" }, {"#fdf5e6", "oldlace" }, {"#6b8e23", "olivedrab" }, {"#ffa500", "orange" }, {"#ff4500", "orangered" }, {"#da70d6", "orchid" }, {"#eee8aa", "palegoldenrod" }, {"#98fb98", "palegreen" }, {"#afeeee", "paleturquoise" }, {"#db7093", "palevioletred" }, {"#ffefd5", "papayawhip" }, {"#ffdab9", "peachpuff" }, {"#cd853f", "peru" }, {"#ffc0cb", "pink" }, {"#dda0dd", "plum" }, {"#b0e0e6", "powderblue" }, {"#bc8f8f", "rosybrown" }, {"#4169e1", "royalblue" }, {"#8b4513", "saddlebrown" }, {"#fa8072", "salmon" }, {"#f4a460", "sandybrown" }, {"#2e8b57", "seagreen" }, {"#fff5ee", "seashell" }, {"#a0522d", "sienna" }, {"#87ceeb", "skyblue" }, {"#6a5acd", "slateblue" }, {"#708090", "slategray" }, {"#fffafa", "snow" }, {"#00ff7f", "springgreen" }, {"#4682b4", "steelblue" }, {"#d2b48c", "tan" }, {"#d8bfd8", "thistle" }, {"#ff6347", "tomato" }, {"#40e0d0", "turquoise" }, {"#ee82ee", "violet" }, {"#f5deb3", "wheat" }, {"#f5f5f5", "whitesmoke" }, {"#9acd32", "yellowgreen" } }; return cssColors.value(colorHex, QString()); } bool Tools::isWebColor(const QColor &color) { int r = color.red(); // The 216 web colors are those colors whose RGB (Red, Green, Blue) int g = color.green(); // values are all in the set (0, 51, 102, 153, 204, 255). int b = color.blue(); return ((r == 0 || r == 51 || r == 102 || r == 153 || r == 204 || r == 255) && (g == 0 || g == 51 || g == 102 || g == 153 || g == 204 || g == 255) && (b == 0 || b == 51 || b == 102 || b == 153 || b == 204 || b == 255)); } QColor Tools::mixColor(const QColor &color1, const QColor &color2, const float ratio) { QColor mixedColor; mixedColor.setRgb((color1.red() * ratio + color2.red()) / (1 + ratio), (color1.green() * ratio + color2.green()) / (1 + ratio), (color1.blue() * ratio + color2.blue()) / (1 + ratio)); return mixedColor; } bool Tools::tooDark(const QColor &color) { return color.value() < 175; } // TODO: Use it for all indentPixmap() QPixmap Tools::normalizePixmap(const QPixmap &pixmap, int width, int height) { if (height <= 0) height = width; if (pixmap.isNull() || (pixmap.width() == width && pixmap.height() == height)) return pixmap; return pixmap; } QPixmap Tools::indentPixmap(const QPixmap &source, int depth, int deltaX) { // Verify if it is possible: if (depth <= 0 || source.isNull()) return source; // Compute the number of pixels to indent: if (deltaX <= 0) deltaX = 2 * source.width() / 3; int indent = depth * deltaX; // Create the images: QImage resultImage(indent + source.width(), source.height(), QImage::Format_ARGB32); resultImage.setColorCount(32); QImage sourceImage = source.toImage(); // Clear the indent part (the left part) by making it fully transparent: uint *p; for (int row = 0; row < resultImage.height(); ++row) { for (int column = 0; column < resultImage.width(); ++column) { p = (uint *)resultImage.scanLine(row) + column; *p = 0; // qRgba(0, 0, 0, 0) } } // Copy the source image byte per byte to the right part: uint *q; for (int row = 0; row < sourceImage.height(); ++row) { for (int column = 0; column < sourceImage.width(); ++column) { p = (uint *)resultImage.scanLine(row) + indent + column; q = (uint *)sourceImage.scanLine(row) + column; *p = *q; } } // And return the result: QPixmap result = QPixmap::fromImage(resultImage); return result; } void Tools::deleteRecursively(const QString &folderOrFile) { if (folderOrFile.isEmpty()) return; QFileInfo fileInfo(folderOrFile); if (fileInfo.isDir()) { // Delete the child files: QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) if (*it != "." && *it != "..") deleteRecursively(folderOrFile + '/' + *it); // And then delete the folder: dir.rmdir(folderOrFile); } else // Delete the file: QFile::remove(folderOrFile); } void Tools::deleteMetadataRecursively(const QString &folderOrFile) { QFileInfo fileInfo(folderOrFile); if (fileInfo.isDir()) { // Delete Metadata of the child files: QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden); QStringList list = dir.entryList(); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) if (*it != "." && *it != "..") deleteMetadataRecursively(folderOrFile + '/' + *it); } } void Tools::trashRecursively(const QString &folderOrFile) { if (folderOrFile.isEmpty()) return; KIO::trash(QUrl::fromLocalFile(folderOrFile), KIO::HideProgressInfo); } QString Tools::fileNameForNewFile(const QString &wantedName, const QString &destFolder) { QString fileName = wantedName; QString fullName = destFolder + fileName; QString extension = QString(); int number = 2; QDir dir; // First check if the file do not exists yet (simpler and more often case) dir = QDir(fullName); if (!dir.exists(fullName)) return fileName; // Find the file extension, if it exists : Split fileName in fileName and extension // Example : fileName == "note5-3.txt" => fileName = "note5-3" and extension = ".txt" int extIndex = fileName.lastIndexOf('.'); if (extIndex != -1 && extIndex != int(fileName.length() - 1)) { // Extension found and fileName do not ends with '.' ! extension = fileName.mid(extIndex); fileName.truncate(extIndex); } // else fileName = fileName and extension = QString() // Find the file number, if it exists : Split fileName in fileName and number // Example : fileName == "note5-3" => fileName = "note5" and number = 3 int extNumber = fileName.lastIndexOf('-'); if (extNumber != -1 && extNumber != int(fileName.length() - 1)) { // Number found and fileName do not ends with '-' ! bool isANumber; int theNumber = fileName.mid(extNumber + 1).toInt(&isANumber); if (isANumber) { number = theNumber; fileName.truncate(extNumber); } // else : } // else fileName = fileName and number = 2 (because if the file already exists, the generated name is at last the 2nd) QString finalName; for (/*int number = 2*/;; ++number) { // TODO: FIXME: If overflow ??? finalName = fileName + '-' + QString::number(number) + extension; fullName = destFolder + finalName; dir = QDir(fullName); if (!dir.exists(fullName)) break; } return finalName; } qint64 Tools::computeSizeRecursively(const QString &path) { qint64 result = 0; QFileInfo file(path); result += file.size(); if (file.isDir()) { QFileInfoList children = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden); - foreach (const QFileInfo &child, children) + Q_FOREACH (const QFileInfo &child, children) result += computeSizeRecursively(child.absoluteFilePath()); } return result; } // TODO: Move it from NoteFactory /*QString NoteFactory::iconForURL(const QUrl &url) { QString icon = KMimeType::iconNameForUrl(url.url()); if ( url.scheme() == "mailto" ) icon = "message"; return icon; }*/ bool Tools::isAFileCut(const QMimeData *source) { if (source->hasFormat("application/x-kde-cutselection")) { QByteArray array = source->data("application/x-kde-cutselection"); return !array.isEmpty() && QByteArray(array.data(), array.size() + 1).at(0) == '1'; } else return false; } void Tools::printChildren(QObject *parent) { const QObjectList objs = parent->children(); QObject *obj; for (int i = 0; i < objs.size(); i++) { obj = objs.at(i); qDebug() << Q_FUNC_INFO << obj->metaObject()->className() << ": " << obj->objectName() << endl; } } QString Tools::makeStandardCaption(const QString &userCaption) { QString caption = QGuiApplication::applicationDisplayName(); if (!userCaption.isEmpty()) return userCaption + i18nc("Document/application separator in titlebar", " – ") + caption; else return caption; } QByteArray Tools::systemCodeset() { QByteArray codeset; #if HAVE_LANGINFO_H // Qt since 4.2 always returns 'System' as codecForLocale and libraries like for example // KEncodingFileDialog expects real encoding name. So on systems that have langinfo.h use // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own // workaround codeset = nl_langinfo(CODESET); if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) { // means ascii, "C"; QTextCodec doesn't know, so avoid warning codeset = "ISO-8859-1"; } #endif return codeset; } diff --git a/src/variouswidgets.cpp b/src/variouswidgets.cpp index 3a052f5..93fa747 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, &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, &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, &QDialogButtonBox::accepted, this, &IconSizeDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &IconSizeDialog::reject); mainLayout->addWidget(buttonBox); 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, &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); + Q_EMIT sizeChanged(size); } void FontSizeCombo::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) - emit escapePressed(); + Q_EMIT escapePressed(); else if (event->key() == Qt::Key_Return) - emit returnPressed2(); + Q_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(); } diff --git a/src/variouswidgets.h b/src/variouswidgets.h index abda3cd..81e2e76 100644 --- a/src/variouswidgets.h +++ b/src/variouswidgets.h @@ -1,153 +1,153 @@ /** * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef VARIOUSWIDGETS_H #define VARIOUSWIDGETS_H #include #include #include #include class QLineEdit; class QListWidgetItem; class QResizeEvent; class QString; class QKeyEvent; /** A widget to select a command to run, * with a QLineEdit and a QPushButton. * @author Sébastien Laoût */ class RunCommandRequester : public QWidget { Q_OBJECT public: RunCommandRequester(const QString &runCommand, const QString &message, QWidget *parent = nullptr); ~RunCommandRequester() override; QString runCommand(); void setRunCommand(const QString &runCommand); QLineEdit *lineEdit() { return m_runCommand; } -private slots: +private Q_SLOTS: void slotSelCommand(); private: QLineEdit *m_runCommand; QString m_message; }; /** KComboBox to ask icon size * @author Sébastien Laoût */ class IconSizeCombo : public KComboBox { Q_OBJECT public: explicit IconSizeCombo(QWidget *parent = nullptr); ~IconSizeCombo() override; int iconSize(); void setSize(int size); }; /** A window that the user resize to graphically choose a new image size * TODO: Create a SizePushButton or even SizeWidget * @author Sébastien Laoût */ class ViewSizeDialog : public QDialog { Q_OBJECT public: ViewSizeDialog(QWidget *parent, int w, int h); ~ViewSizeDialog() override; private: void resizeEvent(QResizeEvent *) override; QWidget *m_sizeGrip; }; /** A label displaying a link that, once clicked, offer a What's This messageBox to help users. * @author Sébastien Laoût */ class HelpLabel : public KUrlLabel { Q_OBJECT public: HelpLabel(const QString &text, const QString &message, QWidget *parent); ~HelpLabel() override; QString message() { return m_message; } -public slots: +public Q_SLOTS: void setMessage(const QString &message) { m_message = message; } void display(); private: QString m_message; }; /** A dialog to choose the size of an icon. * @author Sébastien Laoût */ class IconSizeDialog : public QDialog { Q_OBJECT public: IconSizeDialog(const QString &caption, const QString &message, const QString &icon, int iconSize, QWidget *parent); ~IconSizeDialog() override; int iconSize() { return m_iconSize; } /// << @return the chosen icon size (16, 32, ...) or -1 if canceled! -protected slots: +protected Q_SLOTS: void slotCancel(); void slotSelectionChanged(); void choose(QListWidgetItem *); private: QListWidgetItem *m_size16; QListWidgetItem *m_size22; QListWidgetItem *m_size32; QListWidgetItem *m_size48; QListWidgetItem *m_size64; QListWidgetItem *m_size128; int m_iconSize; QPushButton *okButton; }; /** * A missing class from Frameworks (and Qt): a combobox to select a font size! */ class FontSizeCombo : public KComboBox { Q_OBJECT public: FontSizeCombo(bool rw, bool withDefault, QWidget *parent = nullptr); ~FontSizeCombo() override; void setFontSize(qreal size); qreal fontSize(); protected: void keyPressEvent(QKeyEvent *event) override; -signals: +Q_SIGNALS: void sizeChanged(qreal size); void escapePressed(); void returnPressed2(); -private slots: +private Q_SLOTS: void textChangedInCombo(const QString &text); private: bool m_withDefault; }; #endif // VARIOUSWIDGETS_H