File indexing completed on 2024-05-19 04:29:16

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2005 Peter Simonsson <psn@linux.se>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "KisOpenPane.h"
0008 
0009 #include <QLayout>
0010 #include <QLabel>
0011 #include <QImage>
0012 #include <QPainter>
0013 #include <QPen>
0014 #include <QPixmap>
0015 #include <QSize>
0016 #include <QString>
0017 #include <QTreeWidget>
0018 #include <QTreeWidgetItem>
0019 #include <QStyledItemDelegate>
0020 #include <QLinearGradient>
0021 #include <QStandardPaths>
0022 #include <QDragEnterEvent>
0023 #include <QDropEvent>
0024 #include <QMimeData>
0025 
0026 #include <klocalizedstring.h>
0027 #include <ksharedconfig.h>
0028 #include <kis_debug.h>
0029 #include <QUrl>
0030 
0031 
0032 #include <KisKineticScroller.h>
0033 #include <KoFileDialog.h>
0034 #include <KoIcon.h>
0035 #include "KisTemplateTree.h"
0036 #include "KisTemplateGroup.h"
0037 #include "KisTemplate.h"
0038 #include "KisDetailsPane.h"
0039 #include "KisTemplatesPane.h"
0040 #include "ui_KisOpenPaneBase.h"
0041 
0042 #include <limits.h>
0043 #include <kconfiggroup.h>
0044 
0045 #include <kis_icon.h>
0046 
0047 class KoSectionListItem : public QTreeWidgetItem
0048 {
0049 public:
0050     KoSectionListItem(QTreeWidget* treeWidget, const QString& name, QString untranslatedName, int sortWeight, int widgetIndex = -1)
0051         : QTreeWidgetItem(treeWidget, QStringList() << name)
0052         , m_sortWeight(sortWeight)
0053         , m_widgetIndex(widgetIndex)
0054         , m_untranslatedName(untranslatedName)
0055     {
0056         Qt::ItemFlags newFlags = Qt::NoItemFlags;
0057 
0058         if(m_widgetIndex >= 0)
0059             newFlags |= Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0060 
0061         setFlags(newFlags);
0062     }
0063 
0064     bool operator<(const QTreeWidgetItem & other) const override {
0065         const KoSectionListItem* item = dynamic_cast<const KoSectionListItem*>(&other);
0066 
0067         if (!item)
0068             return 0;
0069 
0070         return ((item->sortWeight() - sortWeight()) < 0);
0071     }
0072 
0073     int sortWeight() const {
0074         return m_sortWeight;
0075     }
0076 
0077     int widgetIndex() const {
0078         return m_widgetIndex;
0079     }
0080 
0081     QString untranslatedName() const {
0082         return m_untranslatedName;
0083     }
0084 
0085 private:
0086     int m_sortWeight {0};
0087     int m_widgetIndex {-1};
0088     QString m_untranslatedName;
0089 };
0090 
0091 class KisOpenPanePrivate : public Ui_KisOpenPaneBase
0092 {
0093 public:
0094     KisOpenPanePrivate() :
0095         Ui_KisOpenPaneBase()
0096     { }
0097 
0098     int m_freeCustomWidgetIndex {0};
0099     KoSectionListItem* m_templatesSeparator {nullptr};
0100 
0101 
0102 };
0103 
0104 KisOpenPane::KisOpenPane(QWidget *parent, const QStringList& mimeFilter, const QString& templatesResourcePath)
0105     : QDialog(parent)
0106     , d(new KisOpenPanePrivate)
0107 {
0108     d->setupUi(this);
0109 
0110     m_mimeFilter = mimeFilter;
0111 
0112     QStyledItemDelegate* delegate = new QStyledItemDelegate(d->m_sectionList);
0113     d->m_sectionList->setItemDelegate(delegate);
0114     QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(d->m_sectionList);
0115     if (scroller) {
0116         connect(scroller, &QScroller::stateChanged, this, [&](QScroller::State state) {
0117             KisKineticScroller::updateCursor(this, state);
0118         });
0119     }
0120 
0121     connect(d->m_sectionList, SIGNAL(itemSelectionChanged()),
0122             this, SLOT(updateSelectedWidget()));
0123     connect(d->m_sectionList, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
0124             this, SLOT(itemClicked(QTreeWidgetItem*)));
0125     connect(d->m_sectionList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
0126             this, SLOT(itemClicked(QTreeWidgetItem*)));
0127 
0128     initTemplates(templatesResourcePath);
0129 
0130     d->m_freeCustomWidgetIndex = 4;
0131 
0132     if (!d->m_sectionList->selectedItems().isEmpty())
0133     {
0134         KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
0135 
0136         if (selectedItem) {
0137             d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
0138         }
0139     }
0140 
0141     QList<int> sizes;
0142 
0143     // Set the sizes of the details pane splitters
0144     KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog"); sizes = cfgGrp.readEntry("DetailsPaneSplitterSizes", sizes);
0145 
0146     if (!sizes.isEmpty())
0147         emit splitterResized(0, sizes);
0148 
0149     connect(this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
0150             this, SLOT(saveSplitterSizes(KisDetailsPane*,QList<int>)));
0151 
0152     setAcceptDrops(true);
0153 }
0154 
0155 KisOpenPane::~KisOpenPane()
0156 {
0157     if (!d->m_sectionList->selectedItems().isEmpty()) {
0158         KoSectionListItem* item = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
0159 
0160         if (item) {
0161             if (!qobject_cast<KisDetailsPane*>(d->m_widgetStack->widget(item->widgetIndex()))) {
0162                 KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
0163                 cfgGrp.writeEntry("LastReturnType", item->untranslatedName());
0164             }
0165         }
0166     }
0167 
0168     delete d;
0169 }
0170 
0171 
0172 
0173 void KisOpenPane::openFileDialog()
0174 {
0175 
0176     KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocument");
0177     dialog.setCaption(i18n("Open Existing Document"));
0178     dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
0179     dialog.setMimeTypeFilters(m_mimeFilter);
0180     Q_FOREACH (const QString &filename, dialog.filenames()) {
0181         emit openExistingFile(QUrl::fromUserInput(filename));
0182     }
0183 }
0184 
0185 void KisOpenPane::slotOpenTemplate(const QUrl &url)
0186 {
0187     emit openTemplate(url);
0188     accept();
0189 }
0190 
0191 void KisOpenPane::initTemplates(const QString& templatesResourcePath)
0192 {
0193     QTreeWidgetItem* selectItem = 0;
0194     QTreeWidgetItem* firstItem = 0;
0195     const int templateOffset = 1000;
0196 
0197     if (!templatesResourcePath.isEmpty()) {
0198         KisTemplateTree templateTree(templatesResourcePath, true);
0199 
0200         Q_FOREACH (KisTemplateGroup *group, templateTree.groups()) {
0201             if (group->isHidden()) {
0202                 continue;
0203             }
0204 
0205             if (!d->m_templatesSeparator) {
0206                 d->m_templatesSeparator = new KoSectionListItem(d->m_sectionList, "", "", 999);
0207             }
0208 
0209             KisTemplatesPane* pane = new KisTemplatesPane(this, group->name(),
0210                                                           group, templateTree.defaultTemplate());
0211             connect(pane, SIGNAL(openUrl(QUrl)), this, SLOT(slotOpenTemplate(QUrl)));
0212             connect(pane, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)),
0213                     this, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)));
0214             connect(this, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)),
0215                     pane, SLOT(changeAlwaysUseTemplate(KisTemplatesPane*,QString)));
0216             connect(pane, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
0217                     this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)));
0218             connect(this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
0219                     pane, SLOT(resizeSplitter(KisDetailsPane*,QList<int>)));
0220             QTreeWidgetItem* item = addPane(group->name(), "Template", group->templates().first()->loadPicture(),
0221                                             pane, group->sortingWeight() + templateOffset);
0222 
0223 
0224 
0225             if (!firstItem) {
0226                 firstItem = item;
0227             }
0228 
0229             if (group == templateTree.defaultGroup()) {
0230                 firstItem = item;
0231             }
0232 
0233             if (pane->isSelected()) {
0234                 selectItem = item;
0235             }
0236         }
0237     } else {
0238         firstItem = d->m_sectionList->topLevelItem(0);
0239     }
0240 
0241     KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
0242 
0243     if (selectItem && (cfgGrp.readEntry("LastReturnType") == "Template")) {
0244         d->m_sectionList->setCurrentItem(selectItem, 0, QItemSelectionModel::ClearAndSelect);
0245     }
0246     else if (d->m_sectionList->selectedItems().isEmpty() && firstItem) {
0247         d->m_sectionList->setCurrentItem(firstItem, 0, QItemSelectionModel::ClearAndSelect);
0248     }
0249 }
0250 
0251 void KisOpenPane::dragEnterEvent(QDragEnterEvent *event)
0252 {
0253     if (event->mimeData()->hasUrls()) {
0254         event->accept();
0255     }
0256 }
0257 
0258 void KisOpenPane::dropEvent(QDropEvent *event)
0259 {
0260     if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
0261         // XXX: when the MVC refactoring is done, this can open a bunch of
0262         //      urls, but since the part/document combination is still 1:1
0263         //      that won't work for now.
0264         emit openExistingFile(event->mimeData()->urls().first());
0265 
0266     }
0267 }
0268 
0269 void KisOpenPane::addCustomDocumentWidget(QWidget *widget, const QString& title, const QString &untranslatedName, const QString& icon)
0270 {
0271     Q_ASSERT(widget);
0272 
0273     QTreeWidgetItem* item = addPane(title, untranslatedName, icon, widget, d->m_freeCustomWidgetIndex);
0274     ++d->m_freeCustomWidgetIndex;
0275     KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
0276 
0277     QString lastActiveItem = cfgGrp.readEntry("LastReturnType");
0278     bool showCustomItemByDefault = cfgGrp.readEntry("ShowCustomDocumentWidgetByDefault", false);
0279     if (lastActiveItem == untranslatedName || (lastActiveItem.isEmpty() && showCustomItemByDefault)) {
0280         d->m_sectionList->setCurrentItem(item, 0, QItemSelectionModel::ClearAndSelect);
0281         KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
0282         d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
0283     }
0284 }
0285 
0286 
0287 QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &untranslatedName, const QString &iconName, QWidget *widget, int sortWeight)
0288 {
0289     if (!widget) {
0290         return 0;
0291     }
0292 
0293     int id = d->m_widgetStack->addWidget(widget);
0294     KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, untranslatedName, sortWeight, id);
0295 
0296     // resizes icons so they are a bit smaller
0297     QIcon icon = KisIconUtils::loadIcon(iconName);
0298     QPixmap iconPixmap = icon.pixmap(32, 32);
0299 
0300     QIcon finalIcon(iconPixmap);
0301     listItem->setIcon(0, finalIcon);
0302 
0303     return listItem;
0304 }
0305 
0306 QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &untranslatedName, const QPixmap& icon, QWidget* widget, int sortWeight)
0307 {
0308     if (!widget) {
0309         return 0;
0310     }
0311 
0312     int id = d->m_widgetStack->addWidget(widget);
0313 
0314     int iconSize = 32;
0315 
0316     KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, untranslatedName, sortWeight, id);
0317 
0318     if (!icon.isNull()) {
0319         QImage image = icon.toImage();
0320 
0321         if (!image.isNull() && ((image.width() > iconSize) || (image.height() > iconSize))) {
0322             image = image.scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0323         }
0324 
0325         image.convertTo(QImage::Format_ARGB32);
0326         image = image.copy((image.width() - iconSize) / 2, (image.height() - iconSize) / 2, iconSize, iconSize);
0327         listItem->setIcon(0, QIcon(QPixmap::fromImage(image)));
0328     }
0329 
0330     return listItem;
0331 }
0332 
0333 void KisOpenPane::updateSelectedWidget()
0334 {
0335     if(!d->m_sectionList->selectedItems().isEmpty())
0336     {
0337         KoSectionListItem* section = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
0338 
0339         if (!section)
0340             return;
0341 
0342         d->m_widgetStack->setCurrentIndex(section->widgetIndex());
0343     }
0344 }
0345 
0346 void KisOpenPane::saveSplitterSizes(KisDetailsPane* sender, const QList<int>& sizes)
0347 {
0348     Q_UNUSED(sender);
0349     KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
0350     cfgGrp.writeEntry("DetailsPaneSplitterSizes", sizes);
0351 }
0352 
0353 void KisOpenPane::itemClicked(QTreeWidgetItem* item)
0354 {
0355     KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
0356 
0357     if (selectedItem && selectedItem->widgetIndex() >= 0) {
0358         d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
0359     }
0360 }