File indexing completed on 2024-05-12 16:34:42
0001 /* Part of the Calligra project 0002 * Copyright (C) 2010-2014 Yue Liu <yue.liu@mail.com> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Library General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2 of the License, or (at your option) any later version. 0008 * 0009 * This library is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 * Library General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU Library General Public License 0015 * along with this library; see the file COPYING.LIB. If not, write to 0016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "StencilBoxDocker.h" 0021 0022 #include "StencilBoxDocker_p.h" 0023 #include "StencilShapeFactory.h" 0024 #include "CollectionItemModel.h" 0025 #include "CollectionTreeWidget.h" 0026 #include "StencilBoxDebug.h" 0027 0028 #include <KoShapeFactoryBase.h> 0029 #include <KoShapeRegistry.h> 0030 #include <KoCanvasController.h> 0031 #include <KoCreateShapesTool.h> 0032 #include <KoShape.h> 0033 #include <KoShapeGroup.h> 0034 #include <KoZoomHandler.h> 0035 #include <KoShapePaintingContext.h> 0036 #include <KoProperties.h> 0037 #include <KoIcon.h> 0038 0039 #include <kiconeffect.h> 0040 #include <kcolorscheme.h> 0041 #include <klocalizedstring.h> 0042 #include <kdesktopfile.h> 0043 #include <kconfiggroup.h> 0044 #include <kmessagebox.h> 0045 #include <klineedit.h> 0046 0047 #ifdef GHNS 0048 #include <knewstuff3/downloaddialog.h> 0049 #endif 0050 0051 #include <QStandardPaths> 0052 #include <QVBoxLayout> 0053 #include <QListView> 0054 #include <QStandardItemModel> 0055 #include <QRegExp> 0056 #include <QSortFilterProxyModel> 0057 #include <QList> 0058 #include <QSize> 0059 #include <QToolButton> 0060 #include <QDir> 0061 #include <QFile> 0062 #include <QMenu> 0063 #include <QPainter> 0064 #include <QDesktopServices> 0065 #include <QPixmapCache> 0066 0067 #define StencilShapeId "StencilShape" 0068 0069 StencilBoxDocker::StencilBoxDocker(QWidget* parent) 0070 : QDockWidget(parent) 0071 { 0072 setWindowTitle(i18n("Stencil Box")); 0073 QWidget* mainWidget = new QWidget(this); 0074 mainWidget->setAcceptDrops(true); 0075 setWidget(mainWidget); 0076 0077 m_menu = new QMenu(); 0078 #ifdef GHNS 0079 QAction *ghnsAction = m_menu->addAction(koIcon("get-hot-new-stuff"), i18n("Stencils Online")); 0080 connect(ghnsAction, SIGNAL(triggered()), this, SLOT(getHotNewStuff())); 0081 #endif 0082 QAction *installAction = m_menu->addAction(koIcon("document-open-folder"), i18n("Add/Remove Stencil")); 0083 connect(installAction, SIGNAL(triggered()), this, SLOT(manageStencilsFolder())); 0084 0085 m_button = new QToolButton; 0086 /* 0087 m_button->setFixedHeight(qApp->fontMetrics().height()+3); 0088 m_button->setAutoFillBackground(true); 0089 m_button->setStyleSheet("\ 0090 QToolButton {\ 0091 border: 1px solid #a0a0a0;\ 0092 border-top: 0px;\ 0093 border-left: 0px;\ 0094 border-right: 0px;\ 0095 background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,\ 0096 stop:0 #ffffff, stop:0.5 #e0e0e0, stop:1 #ffffff);\ 0097 }");*/ 0098 m_button->setIcon(koIcon("list-add")); 0099 m_button->setToolTip(i18n("More shapes")); 0100 m_button->setMenu(m_menu); 0101 m_button->setPopupMode(QToolButton::InstantPopup); 0102 0103 m_filterLineEdit = new KLineEdit; 0104 m_filterLineEdit->setPlaceholderText(i18n("Filter")); 0105 m_filterLineEdit->setClearButtonShown(true); 0106 0107 m_treeWidget = new CollectionTreeWidget(mainWidget); 0108 m_treeWidget->setSelectionMode(QListView::SingleSelection); 0109 m_treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0110 0111 m_panelLayout = new QHBoxLayout(); 0112 m_panelLayout->addWidget(m_button); 0113 m_panelLayout->addWidget(m_filterLineEdit); 0114 0115 m_layout = new QVBoxLayout(mainWidget); 0116 m_layout->addLayout(m_panelLayout); 0117 m_layout->addWidget(m_treeWidget); 0118 0119 // Load the stencils 0120 m_loader = new StencilBoxDockerLoader(this); 0121 m_loader->moveToThread(&loaderThread); 0122 connect(&loaderThread, SIGNAL(started()), this, SLOT(threadStarted())); 0123 connect(this , SIGNAL(startLoading()), m_loader, SLOT(loadShapeCollections())); 0124 connect(&loaderThread, SIGNAL(finished()), m_loader, SLOT(deleteLater())); 0125 connect(m_loader, SIGNAL(resultReady()), this, SLOT(collectionsLoaded())); 0126 loaderThread.start(); 0127 } 0128 0129 StencilBoxDocker::~StencilBoxDocker() 0130 { 0131 loaderThread.quit(); 0132 loaderThread.wait(); 0133 qDeleteAll(m_modelMap); 0134 } 0135 0136 void StencilBoxDocker::threadStarted() 0137 { 0138 Q_EMIT startLoading(); 0139 } 0140 0141 void StencilBoxDocker::collectionsLoaded() 0142 { 0143 debugStencilBox; 0144 m_modelMap = m_loader->m_modelMap; 0145 m_treeWidget->setFamilyMap(m_modelMap); 0146 m_treeWidget->regenerateFilteredMap(); 0147 connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), 0148 this, SLOT(locationChanged(Qt::DockWidgetArea))); 0149 connect(m_filterLineEdit, SIGNAL(textEdited(QString)), this, SLOT(reapplyFilter())); 0150 0151 loaderThread.quit(); 0152 } 0153 0154 #ifdef GHNS 0155 void StencilBoxDocker::getHotNewStuff() 0156 { 0157 KNS3::DownloadDialog dialog("calligra_stencils.knsrc", this); 0158 dialog.exec(); 0159 if(!dialog.installedEntries().isEmpty()) { 0160 KMessageBox::information(0, i18n("Stencils successfully installed.")); 0161 } 0162 else if(!dialog.changedEntries().isEmpty()) { 0163 KMessageBox::information(0, i18n("Stencils successfully uninstalled.")); 0164 } 0165 } 0166 #endif 0167 0168 void StencilBoxDocker::manageStencilsFolder() 0169 { 0170 const QString destination = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/calligra/stencils"); 0171 QDir().mkpath(destination); 0172 QFile file(destination + "/readme.txt"); 0173 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { 0174 debugStencilBox << "could not open" << destination + "/readme.txt" << "for writing"; 0175 } else { 0176 QTextStream out(&file); 0177 out << i18n("\ 0178 This is the user stencils directory.\n\ 0179 From here you can add / remove stencils for use in the Stencil Box docker.\n\ 0180 \n\ 0181 Stencils are organized in collections, a collection is a folder containing a text file 'collection.desktop':\n\ 0182 \n\ 0183 collection.desktop\n\ 0184 \n\ 0185 [Desktop Entry]\n\ 0186 Name=Your Stencil Collection Name\n\ 0187 \n\ 0188 A stencil is an ODG/SVG file, a desktop file, an optional PNG icon file, all with the same name under its collection folder:\n\ 0189 \n\ 0190 foo.odg or foo.svgz or foo.svg\n\ 0191 \n\ 0192 ODF elements for stencil should be a <draw:g> element or <draw:custom-shape> element\n\ 0193 No special requirements to SVG file\n\ 0194 \n\ 0195 foo.desktop\n\ 0196 \n\ 0197 [Desktop Entry]\n\ 0198 Name=Foo\n\ 0199 CS-KeepAspectRatio=1\n\ 0200 \n\ 0201 If CS-KeepAspectRatio=1, the stencil added to canvas will have geometry aspect ratio locked, by default it's 0.\n\ 0202 \n\ 0203 foo.png\n\ 0204 \n\ 0205 Should have size 32x32 pixel, if the png file is not included, the ODG/SVG file will be rendered as the icon,\n\ 0206 but it won't look good under small pixels when the stencil stroke is complicated.\n"); 0207 file.close(); 0208 } 0209 QDesktopServices::openUrl(QUrl::fromLocalFile(destination)); 0210 } 0211 0212 void StencilBoxDocker::locationChanged(Qt::DockWidgetArea area) 0213 { 0214 switch(area) { 0215 case Qt::TopDockWidgetArea: 0216 case Qt::BottomDockWidgetArea: 0217 break; 0218 case Qt::LeftDockWidgetArea: 0219 case Qt::RightDockWidgetArea: 0220 break; 0221 default: 0222 break; 0223 } 0224 m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); 0225 m_layout->invalidate(); 0226 } 0227 0228 void StencilBoxDocker::reapplyFilter() 0229 { 0230 QRegExp regExp(m_filterLineEdit->originalText(), Qt::CaseInsensitive, QRegExp::RegExp2); 0231 m_treeWidget->setFilter(regExp); 0232 } 0233 0234 /// Load shape collections to m_modelMap and register in the KoShapeRegistry 0235 void StencilBoxDockerLoader::loadShapeCollections() 0236 { 0237 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("calligra/stencils"), QStandardPaths::LocateDirectory); 0238 foreach(const QString& path, dirs) 0239 { 0240 debugStencilBox << path; 0241 QDir dir(path); 0242 QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0243 foreach(const QString & collectionDirName, collectionDirs) { 0244 addCollection(path + QLatin1Char('/') + collectionDirName); 0245 debugStencilBox << path + collectionDirName; 0246 } 0247 } 0248 emit resultReady(); 0249 } 0250 0251 bool StencilBoxDockerLoader::addCollection(const QString& path) 0252 { 0253 QDir dir(path); 0254 0255 if(!dir.exists("collection.desktop")) 0256 return false; 0257 0258 KDesktopFile collection(dir.absoluteFilePath("collection.desktop")); 0259 KConfigGroup dg = collection.desktopGroup(); 0260 QString family = dg.readEntry("Name"); 0261 0262 if(!m_modelMap.contains(family)) { 0263 CollectionItemModel* model = new CollectionItemModel(); 0264 m_modelMap.insert(family, model); 0265 } 0266 0267 CollectionItemModel* model = m_modelMap[family]; 0268 QList<KoCollectionItem> templateList = model->shapeTemplateList(); 0269 QStringList stencils = dir.entryList(QStringList("*.desktop")); 0270 0271 KStatefulBrush brushForeground(KColorScheme::Window, KColorScheme::NormalText); 0272 KStatefulBrush brushBackground(KColorScheme::Window, KColorScheme::NormalBackground); 0273 const QColor blackColor = brushForeground.brush(q).color(); 0274 const QColor whiteColor = brushBackground.brush(q).color(); 0275 0276 foreach(const QString & stencil, stencils) { 0277 if(stencil == "collection.desktop") 0278 continue; 0279 0280 KDesktopFile entry(dir.absoluteFilePath(stencil)); 0281 KConfigGroup content = entry.desktopGroup(); 0282 QString name = content.readEntry("Name"); 0283 bool keepAspectRatio = content.readEntry("CS-KeepAspectRatio", false); 0284 KoProperties* props = new KoProperties(); 0285 props->setProperty("keepAspectRatio", keepAspectRatio); 0286 0287 // find data file path 0288 QString filename = dir.absoluteFilePath(stencil); 0289 filename.chop(7); // remove 'desktop' 0290 static const char * const suffix[3] = { "odg", "svgz", "svg"}; 0291 static const int suffixCount = sizeof(suffix)/sizeof(suffix[0]); 0292 0293 QString source; 0294 for (int i = 0; i < suffixCount; ++i) { 0295 source = filename + QLatin1String(suffix[i]); 0296 if (QFile::exists(source)) { 0297 break; 0298 } 0299 source.clear(); 0300 } 0301 if (source.isEmpty()) { 0302 debugStencilBox << filename << "not found"; 0303 continue; 0304 } 0305 0306 // register shape factory 0307 StencilShapeFactory* factory = new StencilShapeFactory(source, name, props); 0308 KoShapeRegistry::instance()->add(source, factory); 0309 0310 KoCollectionItem temp; 0311 temp.id = source; 0312 temp.name = name; 0313 temp.toolTip = name; 0314 0315 QImage img; 0316 const QString thumbnailFile = filename + QStringLiteral("png"); 0317 if (QFile::exists(thumbnailFile)) { 0318 img = QIcon(thumbnailFile).pixmap(QSize(22, 22)).toImage(); 0319 } else { 0320 // generate icon using factory 0321 QPixmap pix(22, 22); 0322 pix.fill(Qt::white); 0323 if (!QPixmapCache::find(source, &pix)) { 0324 KoShape* shape = factory->createDefaultShape(); 0325 if (shape) { 0326 KoZoomHandler converter; 0327 qreal diffx = 20 / converter.documentToViewX(shape->size().width()); 0328 qreal diffy = 20 / converter.documentToViewY(shape->size().height()); 0329 converter.setZoom(qMin(diffx, diffy)); 0330 QPainter painter(&pix); 0331 painter.setRenderHint(QPainter::Antialiasing, true); 0332 painter.translate(1, 1); 0333 KoShapePaintingContext paintContext; 0334 shape->paint(painter, converter, paintContext); 0335 painter.end(); 0336 QPixmapCache::insert(source, pix); 0337 delete shape; 0338 } 0339 } 0340 img = pix.toImage(); 0341 } 0342 KIconEffect::toMonochrome(img, blackColor, whiteColor, 1.0f); 0343 temp.icon = QIcon(QPixmap::fromImage(img)); 0344 templateList.append(temp); 0345 } 0346 model->setShapeTemplateList(templateList); 0347 return true; 0348 } 0349 0350 void StencilBoxDocker::removeCollection(const QString& family) 0351 { 0352 if(m_modelMap.contains(family)) 0353 { 0354 CollectionItemModel* model = m_modelMap[family]; 0355 QList<KoCollectionItem> list = model->shapeTemplateList(); 0356 foreach(const KoCollectionItem & temp, list) 0357 { 0358 KoShapeFactoryBase* factory = KoShapeRegistry::instance()->get(temp.id); 0359 KoShapeRegistry::instance()->remove(temp.id); 0360 delete factory; 0361 } 0362 0363 m_modelMap.remove(family); 0364 delete model; 0365 m_treeWidget->regenerateFilteredMap(); 0366 } 0367 }