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 }