1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6904 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-02-26 16:26:03 +0100 (Di, 26. Feb 2013) $
24 
25 ********************************************************************/
26 
27 
28 #include <QUndoStack>
29 #include <QHBoxLayout>
30 #include <QVBoxLayout>
31 #include <QFileDialog>
32 #include <QMessageBox>
33 #include <QSettings>
34 #include <QWidgetAction>
35 #include <QColorDialog>
36 #include <QBuffer>
37 #include <QSvgGenerator>
38 
39 #include "partsbinpalettewidget.h"
40 #include "partsbincommands.h"
41 #include "partsbiniconview.h"
42 #include "partsbinlistview.h"
43 #include "searchlineedit.h"
44 #include "../utils/misc.h"
45 #include "../debugdialog.h"
46 #include "../infoview/htmlinfoview.h"
47 #include "../utils/fileprogressdialog.h"
48 #include "../utils/folderutils.h"
49 #include "../utils/textutils.h"
50 #include "../mainwindow/mainwindow.h"          // TODO: PartsBinPaletteWidget should not call MainWindow functions
51 
52 
53 static QString CustomIconName = "Custom1.png";
54 static QString CustomIconTitle = "Fritzing Custom Icon";
55 
isCustomSvg(const QString & string)56 inline bool isCustomSvg(const QString & string) {
57 	return string.startsWith("<?xml") && string.contains(CustomIconTitle);
58 }
59 
60 static QHash<QString, PaletteModel *> PaletteBinModels;
61 
62 static QIcon EmptyIcon;
63 
64 //////////////////////////////////////////////
65 
PartsBinPaletteWidget(ReferenceModel * referenceModel,HtmlInfoView * infoView,WaitPushUndoStack * undoStack,BinManager * manager)66 PartsBinPaletteWidget::PartsBinPaletteWidget(ReferenceModel *referenceModel, HtmlInfoView *infoView, WaitPushUndoStack *undoStack, BinManager* manager) :
67 	QFrame(manager)
68 {
69     m_binLabel = NULL;
70 	m_monoIcon = m_icon = NULL;
71 	m_searchLineEdit = NULL;
72 	m_saveQuietly = false;
73 	m_fastLoaded = false;
74 	m_model = NULL;
75 
76 	m_loadingProgressDialog = NULL;
77 
78 	setAcceptDrops(true);
79 	setAllowsChanges(true);
80 
81 	m_manager = manager;
82 
83 	m_referenceModel = referenceModel;
84 	m_canDeleteModel = false;
85 	m_orderHasChanged = false;
86 
87 	Q_UNUSED(undoStack);
88 
89 	m_undoStack = new WaitPushUndoStack(this);
90 	connect(m_undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(undoStackCleanChanged(bool)) );
91 
92 	m_iconView = new PartsBinIconView(m_referenceModel, this);
93 	m_iconView->setInfoView(infoView);
94 
95 	m_listView = new PartsBinListView(m_referenceModel, this);
96 	m_listView->setInfoView(infoView);
97 
98 	m_stackedWidget = new QStackedWidget(this);
99 	m_stackedWidget->addWidget(m_iconView);
100 	m_stackedWidget->addWidget(m_listView);
101 
102 	QVBoxLayout * vbl = new QVBoxLayout(this);
103     vbl->setMargin(3);
104     vbl->setSpacing(0);
105 
106     m_header = NULL;
107     setupHeader();
108     if (m_header) {
109 	    vbl->addWidget(m_header);
110 
111 	    QFrame * separator = new QFrame();
112 	    separator->setMaximumHeight(1);
113 	    separator->setObjectName("partsBinHeaderSeparator");
114         separator->setFrameShape(QFrame::HLine);
115         separator->setFrameShadow(QFrame::Plain);
116 	    vbl->addWidget(separator);
117     }
118 
119 	vbl->addWidget(m_stackedWidget);
120 	this->setLayout(vbl);
121 
122 	setObjectName("partsBinContainer");
123 	toIconView();
124 
125 	m_defaultSaveFolder = FolderUtils::getUserDataStorePath("bins");
126 	m_untitledFileName = tr("Untitled Bin");
127 
128 	connect(m_listView, SIGNAL(currentRowChanged(int)), m_iconView, SLOT(setSelected(int)));
129 	connect(m_iconView, SIGNAL(selectionChanged(int)), m_listView, SLOT(setSelected(int)));
130 
131 	connect(m_listView, SIGNAL(currentRowChanged(int)), m_manager, SLOT(updateBinCombinedMenuCurrent()));
132 	connect(m_iconView, SIGNAL(selectionChanged(int)), m_manager, SLOT(updateBinCombinedMenuCurrent()));
133 
134 	connect(m_listView, SIGNAL(informItemMoved(int,int)), m_iconView, SLOT(itemMoved(int,int)));
135 	connect(m_iconView, SIGNAL(informItemMoved(int,int)), m_listView, SLOT(itemMoved(int,int)));
136 	connect(m_listView, SIGNAL(informItemMoved(int,int)), this, SLOT(itemMoved()));
137 	connect(m_iconView, SIGNAL(informItemMoved(int,int)), this, SLOT(itemMoved()));
138 
139 	if (m_binLabel) m_binLabel->setText(m_title);
140 
141 	m_addPartToMeAction = new QAction(m_title,this);
142 	connect(m_addPartToMeAction, SIGNAL(triggered()),this, SLOT(addSketchPartToMe()));
143 
144 	installEventFilter(this);
145 }
146 
~PartsBinPaletteWidget()147 PartsBinPaletteWidget::~PartsBinPaletteWidget() {
148 	if (m_canDeleteModel && m_model != NULL) {
149 		delete m_model;
150 		m_model = NULL;
151 	}
152 
153     if (m_icon) delete m_icon;
154     if (m_monoIcon) delete m_monoIcon;
155 }
156 
cleanup()157 void PartsBinPaletteWidget::cleanup() {
158     foreach (PaletteModel * paletteModel, PaletteBinModels) {
159         delete paletteModel;
160     }
161     PaletteBinModels.clear();
162 }
163 
164 
sizeHint() const165 QSize PartsBinPaletteWidget::sizeHint() const {
166 	return QSize(DockWidthDefault, PartsBinHeightDefault);
167 }
168 
title() const169 QString PartsBinPaletteWidget::title() const {
170 	return m_title;
171 }
172 
setTitle(const QString & title)173 void PartsBinPaletteWidget::setTitle(const QString &title) {
174 	if(m_title != title) {
175 		m_title = title;
176 		if (m_binLabel) m_binLabel->setText(title);
177 	}
178 }
179 
setupHeader()180 void PartsBinPaletteWidget::setupHeader()
181 {
182     QMenu * combinedMenu = m_manager->combinedMenu();
183     if (combinedMenu == NULL) return;
184 
185 	m_combinedBinMenuButton = newToolButton("partsBinCombinedMenuButton", ___emptyString___, ___emptyString___);
186 	m_combinedBinMenuButton->setMenu(combinedMenu);
187 
188 	m_binLabel = new QLabel(this);
189 	m_binLabel->setObjectName("partsBinLabel");
190 	m_binLabel->setWordWrap(false);
191 
192 	m_searchLineEdit = new SearchLineEdit(this);
193     m_searchLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
194 	connect(m_searchLineEdit, SIGNAL(returnPressed()), this, SLOT(search()));
195 
196 	m_searchStackedWidget = new QStackedWidget(this);
197 	m_searchStackedWidget->setObjectName("searchStackedWidget");
198 
199 	m_searchStackedWidget->addWidget(m_binLabel);
200 	m_searchStackedWidget->addWidget(m_searchLineEdit);
201 
202 	m_header = new QFrame(this);
203 	m_header->setObjectName("partsBinHeader");
204 	QHBoxLayout * hbl = new QHBoxLayout();
205     hbl->setSpacing(0);
206     hbl->setMargin(0);
207 
208 	hbl->addWidget(m_searchStackedWidget);
209 	hbl->addWidget(m_combinedBinMenuButton);
210 
211 	m_header->setLayout(hbl);
212 }
213 
setView(PartsBinView * view)214 void PartsBinPaletteWidget::setView(PartsBinView *view) {
215 	m_currentView = view;
216 	if(m_currentView == m_iconView) {
217 		m_stackedWidget->setCurrentIndex(0);
218 		m_manager->updateViewChecks(true);
219 	}
220 	else {
221 		m_stackedWidget->setCurrentIndex(1);
222 		m_manager->updateViewChecks(false);
223 	}
224 
225 }
226 
toIconView()227 void PartsBinPaletteWidget::toIconView() {
228 	setView(m_iconView);
229 }
230 
toListView()231 void PartsBinPaletteWidget::toListView() {
232 	disconnect(m_listView, SIGNAL(currentRowChanged(int)), m_iconView, SLOT(setSelected(int)));
233 	setView(m_listView);
234 	connect(m_listView, SIGNAL(currentRowChanged(int)), m_iconView, SLOT(setSelected(int)));
235 }
236 
saveAsAux(const QString & filename)237 bool PartsBinPaletteWidget::saveAsAux(const QString &filename) {
238     FileProgressDialog progress("Saving...", 0, this);
239 
240 	QString oldFilename = m_fileName;
241 	setFilename(filename);
242 	QString title = this->title();
243 	if(!title.isNull() && !title.isEmpty()) {
244 		ModelPartSharedRoot * root = m_model->rootModelPartShared();
245 		if (root) root->setTitle(title);
246 	}
247 
248 	if(m_orderHasChanged) {
249 		m_model->setOrdererChildren(m_iconView->orderedChildren());
250 	}
251 	m_model->save(filename, false);
252 	if(m_orderHasChanged) {
253 		m_model->setOrdererChildren(QList<QObject*>());
254 		m_orderHasChanged = false;
255 	}
256 
257 	m_undoStack->setClean();
258 
259 	m_location = BinLocation::findLocation(filename);
260 
261 	if(oldFilename != m_fileName) {
262 		emit fileNameUpdated(this,m_fileName,oldFilename);
263 	}
264 	emit saved(hasAlienParts());
265 
266     foreach (QString path, m_removed) {
267         bool result = QFile::remove(path);
268         if (!result) {
269             DebugDialog::debug("unable to delete '" + path + "' from bin");
270         }
271     }
272     m_removed.clear();
273 
274 	return true;
275 }
276 
loadFromModel(PaletteModel * model)277 void PartsBinPaletteWidget::loadFromModel(PaletteModel *model) {
278 	m_iconView->loadFromModel(model);
279 	m_listView->setPaletteModel(model);
280 	afterModelSetted(model);
281 	m_canDeleteModel = false;				// FApplication is holding this model, so don't delete it
282 	setFilename(model->loadedFrom());
283 }
284 
setPaletteModel(PaletteModel * model,bool clear)285 void PartsBinPaletteWidget::setPaletteModel(PaletteModel *model, bool clear) {
286 	m_iconView->setPaletteModel(model, clear);
287 	m_listView->setPaletteModel(model, clear);
288 	afterModelSetted(model);
289 }
290 
afterModelSetted(PaletteModel * model)291 void PartsBinPaletteWidget::afterModelSetted(PaletteModel *model) {
292 	grabTitle(model);
293 	m_model = model;
294 	m_undoStack->setClean();
295 	setFilename(model->loadedFrom());
296 }
297 
grabTitle(PaletteModel * model)298 void PartsBinPaletteWidget::grabTitle(PaletteModel *model) {
299 	ModelPartSharedRoot * root = model->rootModelPartShared();
300 	if (!root) return;
301 
302 	QString iconFilename = root->icon();
303 	grabTitle(root->title(), iconFilename);
304 	root->setIcon(iconFilename);
305 
306 	if (m_searchLineEdit) m_searchLineEdit->setText(root->searchTerm());
307 }
308 
grabTitle(const QString & title,QString & iconFilename)309 void PartsBinPaletteWidget::grabTitle(const QString & title, QString & iconFilename)
310 {
311 	m_title = title;
312 	m_addPartToMeAction->setText(m_title);
313 	if (m_binLabel) m_binLabel->setText(m_title);
314 
315 	QString temp = BinManager::StandardBinIcons.value(m_fileName, "");
316 	if (!temp.isEmpty()) {
317 		iconFilename = temp;
318 	}
319 	else if (iconFilename.isEmpty()) {
320 		iconFilename = CustomIconName;
321 	}
322 
323 	if (isCustomSvg(iconFilename)) {
324 		// convert to image
325 		int w = TextUtils::getViewBoxCoord(iconFilename, 2);
326 		int h = TextUtils::getViewBoxCoord(iconFilename, 3);
327 		QImage image(w, h, QImage::Format_ARGB32);
328 		image.fill(0);
329 		QRectF target(0, 0, w, h);
330 		QSvgRenderer renderer(iconFilename.toUtf8());
331 		QPainter painter;
332 		painter.begin(&image);
333 		renderer.render(&painter, target);
334 		painter.end();
335 		//image.save(FolderUtils::getUserDataStorePath("") + "/test icon.png");
336 		m_icon = new QIcon(QPixmap::fromImage(image));
337         m_monoIcon = new QIcon(":resources/bins/icons/Custom1-mono.png");
338 
339         // TODO: hack svg to make a mono icon
340 	}
341 	else {
342         QFileInfo info(m_fileName);
343         QDir dir = info.absoluteDir();
344         QString path = dir.absoluteFilePath(iconFilename);
345         QFile file1(path);
346         if (file1.exists()) {
347             m_icon = new QIcon(path);
348         }
349         else {
350             path = ":resources/bins/icons/" + iconFilename;
351 		    QFile file2(path);
352 		    if (file2.exists()) {
353 			    m_icon = new QIcon(path);
354 		    }
355         }
356 
357         if (m_icon) {
358             int ix = path.lastIndexOf(".");
359             path.insert(ix, "-mono");
360             QFile file3(path);
361             if (file3.exists()) {
362                 m_monoIcon = new QIcon(path);
363             }
364         }
365 	}
366 }
367 
addPart(ModelPart * modelPart,int position)368 void PartsBinPaletteWidget::addPart(ModelPart *modelPart, int position) {
369     if (m_model == NULL) {
370         return;
371     }
372 
373 	ModelPart *mp = m_model->addModelPart(m_model->root(),modelPart);
374 
375 	m_iconView->addPart(mp, position);
376 	m_listView->addPart(mp, position);
377 
378 	if(modelPart->isAlien()) {
379 		m_alienParts << mp->moduleID();
380 	}
381 }
382 
newToolButton(const QString & btnObjName,const QString & imgPath,const QString & text)383 QToolButton* PartsBinPaletteWidget::newToolButton(const QString& btnObjName, const QString& imgPath, const QString &text) {
384 	QToolButton *toolBtn = new QToolButton(this);
385 	toolBtn->setObjectName(btnObjName);
386 	toolBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
387 	toolBtn->setArrowType(Qt::NoArrow);
388 	if (!text.isEmpty()) {
389 		toolBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
390 		toolBtn->setText(text);
391 	}
392 	toolBtn->setPopupMode(QToolButton::InstantPopup);
393 	if (!imgPath.isEmpty()) {
394 		toolBtn->setIcon(QIcon(imgPath));
395 	}
396 	toolBtn->setArrowType(Qt::NoArrow);
397 	return toolBtn;
398 }
399 
save()400 bool PartsBinPaletteWidget::save() {
401 	bool result = true;
402 	if (FolderUtils::isEmptyFileName(m_fileName,m_untitledFileName) || currentBinIsCore()) {
403 		result = saveAs();
404 	} else {
405 		saveAsAux(m_fileName);
406 	}
407 	return result;
408 }
409 
saveAs()410 bool PartsBinPaletteWidget::saveAs() {
411 	QString fileExt;
412     QString fileName = QFileDialog::getSaveFileName(
413 		this,
414 		tr("Specify a file name"),
415 		(m_fileName.isNull() || m_fileName.isEmpty() || /* it's a resource */ m_fileName.startsWith(":"))?
416 				m_defaultSaveFolder+"/"+title()+FritzingBinExtension:
417 				m_fileName,
418 		tr("Fritzing Bin (*%1)").arg(FritzingBinExtension),
419 		&fileExt
420 	  );
421 
422     if (fileName.isEmpty()) return false; // Cancel pressed
423 
424     if(!FritzingWindow::alreadyHasExtension(fileName, FritzingBinExtension)) {
425 		fileName += FritzingBinExtension;
426 	}
427     saveAsAux(fileName);
428     return true;
429 }
430 
saveBundledBin()431 void PartsBinPaletteWidget::saveBundledBin() {
432 	bool wasModified = m_isDirty;
433 	m_manager->mainWindow()->saveBundledNonAtomicEntity(
434 		m_fileName, FritzingBundledBinExtension, this,
435 		m_model->root()->getAllNonCoreParts(), true, "", true, false
436 	);
437 	setDirty(wasModified);
438 }
439 
loadBundledAux(QDir & unzipDir,QList<ModelPart * > mps)440 bool PartsBinPaletteWidget::loadBundledAux(QDir &unzipDir, QList<ModelPart*> mps) {
441 	QStringList namefilters;
442 	namefilters << "*"+FritzingBinExtension;
443 
444 	this->load(unzipDir.entryInfoList(namefilters)[0].filePath(), this, false);
445 	foreach(ModelPart* mp, mps) {
446 		if(mp->isAlien()) { // double check
447 			m_alienParts << mp->moduleID();
448 		}
449 	}
450 	setFilename(___emptyString___);
451 
452 	return true;
453 }
454 
455 
open(QString fileName,QWidget * progressTarget,bool fastLoad)456 bool PartsBinPaletteWidget::open(QString fileName, QWidget * progressTarget, bool fastLoad) {
457 	QFile file(fileName);
458 	if (!file.exists()) {
459        QMessageBox::warning(NULL, tr("Fritzing"),
460                              tr("Cannot find file %1.")
461                              .arg(fileName));
462 		return false;
463 	}
464 
465     if (!file.open(QFile::ReadOnly | QFile::Text)) {
466         QMessageBox::warning(NULL, tr("Fritzing"),
467                              tr("Cannot read file %1:\n%2.")
468                              .arg(fileName)
469                              .arg(file.errorString()));
470         return false;
471     }
472 
473     file.close();
474 
475     if(fileName.endsWith(FritzingBinExtension)) {
476     	load(fileName, progressTarget, fastLoad);
477     	m_isDirty = false;
478     } else if(fileName.endsWith(FritzingBundledBinExtension)) {
479     	return m_manager->mainWindow()->loadBundledNonAtomicEntity(fileName,this,false, false);
480     }
481 
482     return true;
483 }
484 
load(const QString & filename,QWidget * progressTarget,bool fastLoad)485 void PartsBinPaletteWidget::load(const QString &filename, QWidget * progressTarget, bool fastLoad) {
486 	// TODO deleting this local palette reference model deletes modelPartShared held by the palette bin modelParts
487 	//PaletteModel * paletteReferenceModel = new PaletteModel(true, true);
488 
489 	m_location = BinLocation::findLocation(filename);
490 
491 	//DebugDialog::debug("loading bin");
492 
493 	if (fastLoad) {
494 		QString binName, iconName;
495 		if (BinManager::getBinTitle(filename, binName, iconName)) {
496 			m_fileName = filename;
497 			grabTitle(binName, iconName);
498 			m_fastLoaded = true;
499 		}
500 		return;
501 	}
502 
503 	m_fastLoaded = false;
504     PaletteModel * paletteBinModel = PaletteBinModels.value(filename);
505     if (paletteBinModel == NULL) {
506 	    paletteBinModel = new PaletteModel(true, false);
507 	    //DebugDialog::debug("after palette model");
508 
509 	    QString name = m_title;
510 	    if (name.isEmpty()) name = QFileInfo(filename).completeBaseName();
511 
512 	    bool deleteWhenDone = false;
513         if (progressTarget != NULL) {
514             //DebugDialog::debug("open progress " + filename);
515 		    deleteWhenDone = true;
516             progressTarget = m_loadingProgressDialog = new FileProgressDialog(tr("Loading..."), 200, progressTarget);
517 		    m_loadingProgressDialog->setBinLoadingChunk(200);
518 		    m_loadingProgressDialog->setBinLoadingCount(1);
519 		    m_loadingProgressDialog->setMessage(tr("loading bin '%1'").arg(name));
520 		    m_loadingProgressDialog->show();
521 	    }
522 
523 	    if (progressTarget) {
524 		    connect(paletteBinModel, SIGNAL(loadingInstances(ModelBase *, QDomElement &)), progressTarget, SLOT(loadingInstancesSlot(ModelBase *, QDomElement &)));
525 		    connect(paletteBinModel, SIGNAL(loadingInstance(ModelBase *, QDomElement &)), progressTarget, SLOT(loadingInstanceSlot(ModelBase *, QDomElement &)));
526 		    connect(m_iconView, SIGNAL(settingItem()), progressTarget, SLOT(settingItemSlot()));
527 		    connect(m_listView, SIGNAL(settingItem()), progressTarget, SLOT(settingItemSlot()));
528 	    }
529 	    DebugDialog::debug(QString("loading bin '%1'").arg(name));
530 	    bool result = paletteBinModel->loadFromFile(filename, m_referenceModel, false);
531 	    //DebugDialog::debug(QString("done loading bin '%1'").arg(name));
532 
533 	    if (!result) {
534 		    QMessageBox::warning(NULL, QObject::tr("Fritzing"), QObject::tr("Fritzing cannot load the parts bin"));
535 	    }
536 	    else {
537 		    m_fileName = filename;
538 		    setPaletteModel(paletteBinModel,true);
539             PaletteBinModels.insert(filename, paletteBinModel);
540 	    }
541 
542 	    if (progressTarget) {
543             //DebugDialog::debug("close progress " + filename);
544 		    disconnect(paletteBinModel, SIGNAL(loadingInstances(ModelBase *, QDomElement &)), progressTarget, SLOT(loadingInstancesSlot(ModelBase *, QDomElement &)));
545 		    disconnect(paletteBinModel, SIGNAL(loadingInstance(ModelBase *, QDomElement &)), progressTarget, SLOT(loadingInstanceSlot(ModelBase *, QDomElement &)));
546 		    disconnect(m_iconView, SIGNAL(settingItem()), progressTarget, SLOT(settingItemSlot()));
547 		    disconnect(m_listView, SIGNAL(settingItem()), progressTarget, SLOT(settingItemSlot()));
548 		    if (deleteWhenDone) {
549 			    m_loadingProgressDialog->close();
550 			    delete m_loadingProgressDialog;
551 		    }
552 		    m_loadingProgressDialog = NULL;
553 	    }
554     }
555     else {
556 		m_fileName = filename;
557 		setPaletteModel(paletteBinModel,true);
558     }
559 
560 
561 	//DebugDialog::debug("done loading bin");
562 	//delete paletteReferenceModel;
563 }
564 
undoStackCleanChanged(bool isClean)565 void PartsBinPaletteWidget::undoStackCleanChanged(bool isClean) {
566 	if(!isClean && currentBinIsCore()) {
567 		setFilename(QString::null);
568 	}
569 	setWindowModified(!isClean);
570 	m_manager->setDirtyTab(this,isClean);
571 }
572 
currentBinIsCore()573 bool PartsBinPaletteWidget::currentBinIsCore() {
574     return m_fileName == BinManager::CorePartsBinLocation;
575 }
576 
beforeClosing()577 bool PartsBinPaletteWidget::beforeClosing() {
578 	bool retval;
579 	if (this->isWindowModified()) {
580 		QMessageBox::StandardButton reply;
581 		if (m_saveQuietly) {
582 			reply = QMessageBox::Save;
583 		}
584 		else {
585             QMessageBox messageBox(this);
586             messageBox.setWindowTitle(tr("Save bin \"%1\"").arg(title()));
587             messageBox.setText(tr("Do you want to save the changes you made in the bin \"%1\"?").arg(title()));
588             messageBox.setInformativeText(tr("Your changes will be lost if you don't save them."));
589             messageBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
590             messageBox.setDefaultButton(QMessageBox::Save);
591             messageBox.setIcon(QMessageBox::Warning);
592             messageBox.setWindowModality(Qt::WindowModal);
593             messageBox.setButtonText(QMessageBox::Save, tr("Save"));
594             messageBox.setButtonText(QMessageBox::Discard, tr("Don't Save"));
595             messageBox.button(QMessageBox::Discard)->setShortcut(tr("Ctrl+D"));
596             messageBox.setButtonText(QMessageBox::Cancel, tr("Cancel"));
597 
598 			reply = (QMessageBox::StandardButton)messageBox.exec();
599 		}
600 
601      	if (reply == QMessageBox::Save) {
602      		retval = save();
603     	} else if (reply == QMessageBox::Discard) {
604     		retval = true;
605         } else {
606          	retval = false;
607         }
608 	} else {
609 		retval = true;
610 	}
611 	return retval;
612 }
613 
closeEvent(QCloseEvent * event)614 void PartsBinPaletteWidget::closeEvent(QCloseEvent* event) {
615 	QFrame::closeEvent(event);
616 }
617 
mousePressEvent(QMouseEvent * event)618 void PartsBinPaletteWidget::mousePressEvent(QMouseEvent* event) {
619 	emit focused(this);
620 	QFrame::mousePressEvent(event);
621 }
622 
selectedModelPart()623 ModelPart * PartsBinPaletteWidget::selectedModelPart() {
624 	return m_currentView->selectedModelPart();
625 }
626 
selectedItemBase()627 ItemBase * PartsBinPaletteWidget::selectedItemBase() {
628 	return m_currentView->selectedItemBase();
629 }
630 
contains(const QString & moduleID)631 bool PartsBinPaletteWidget::contains(const QString &moduleID) {
632 	return m_iconView->contains(moduleID);
633 }
634 
hasAlienParts()635 bool PartsBinPaletteWidget::hasAlienParts() {
636 	return m_alienParts.size() > 0;
637 }
638 
addPart(const QString & moduleID,int position)639 void PartsBinPaletteWidget::addPart(const QString& moduleID, int position) {
640 	ModelPart *modelPart = m_referenceModel->retrieveModelPart(moduleID);
641 	addPart(modelPart, position);
642 }
643 
removePart(const QString & moduleID,const QString & path)644 void PartsBinPaletteWidget::removePart(const QString & moduleID, const QString & path) {
645 	m_iconView->removePart(moduleID);
646 	m_listView->removePart(moduleID);
647 
648 	// remove the model part from the model last, as this deletes it,
649 	// and the removePart calls above still need the modelpart
650 	m_model->removePart(moduleID);
651     if (path.contains(FolderUtils::getUserDataStorePath())) {
652         m_removed << path;
653     }
654 }
655 
656 
removeParts()657 void PartsBinPaletteWidget::removeParts() {
658     m_iconView->removeParts();
659     m_listView->removeParts();
660 
661     // remove the model part from the model last, as this deletes it,
662     // and the removePart calls above still need the modelpart
663     m_model->removeParts();
664 }
665 
removeAlienParts()666 void PartsBinPaletteWidget::removeAlienParts() {
667 	foreach(QString moduleID, m_alienParts) {
668 		removePart(moduleID, "");
669 	}
670 	m_alienParts.clear();
671 }
672 
setInfoViewOnHover(bool infoViewOnHover)673 void PartsBinPaletteWidget::setInfoViewOnHover(bool infoViewOnHover) {
674 	if(m_iconView) m_iconView->setInfoViewOnHover(infoViewOnHover);
675 	if(m_listView) m_listView->setInfoViewOnHover(infoViewOnHover);
676 }
677 
addPartCommand(const QString & moduleID)678 void PartsBinPaletteWidget::addPartCommand(const QString& moduleID) {
679 	/*bool updating = alreadyIn(moduleID);
680 
681 	QString partTitle = m_referenceModel->partTitle(moduleID);
682 	if(partTitle.isEmpty()) partTitle = moduleID;
683 
684 	QString undoStackMsg;
685 
686 	if(!updating) {
687 		undoStackMsg = tr("\"%1\" added to bin").arg(partTitle);
688 	} else {
689 		undoStackMsg = tr("\"%1\" updated in bin").arg(partTitle);
690 	}
691 	QUndoCommand *parentCmd = new QUndoCommand(undoStackMsg);
692 
693 	int index = m_listView->position(moduleID);
694 	new PartsBinAddCommand(this, moduleID, index, parentCmd);
695 	m_undoStack->push(parentCmd);*/
696 
697 	QMessageBox::StandardButton answer = QMessageBox::question(
698 		this,
699 		tr("Add to bin"),
700 		tr("Do you really want to add the selected part to the bin?"),
701 		QMessageBox::Yes | QMessageBox::No,
702 		QMessageBox::Yes
703 	);
704 	// TODO: make button texts translatable
705 	if(answer == QMessageBox::Yes) {
706 		int index = m_listView->position(moduleID);
707 		m_undoStack->push(new QUndoCommand("Parts bin: part added"));
708 		addPart(moduleID, index);
709 	}
710 }
711 
itemMoved()712 void PartsBinPaletteWidget::itemMoved() {
713 	m_orderHasChanged = true;
714 	m_manager->setDirtyTab(this);
715 }
716 
setDirty(bool dirty)717 void PartsBinPaletteWidget::setDirty(bool dirty) {
718 	m_manager->setDirtyTab(this, dirty);
719 	m_isDirty = dirty;
720 }
721 
fileName()722 const QString &PartsBinPaletteWidget::fileName() {
723 	return m_fileName;
724 }
725 
eventFilter(QObject * obj,QEvent * event)726 bool PartsBinPaletteWidget::eventFilter(QObject *obj, QEvent *event) {
727 	if (obj == this) {
728 		if (event->type() == QEvent::MouseButtonPress ||
729 			event->type() == QEvent::GraphicsSceneDragMove ||
730 			event->type() == QEvent::GraphicsSceneDrop ||
731 			event->type() == QEvent::GraphicsSceneMousePress
732 		) {
733 			emit focused(this);
734 		}
735 	}
736 	return QFrame::eventFilter(obj, event);
737 }
738 
currentView()739 PartsBinView *PartsBinPaletteWidget::currentView() {
740 	return m_currentView;
741 }
742 
dragEnterEvent(QDragEnterEvent * event)743 void PartsBinPaletteWidget::dragEnterEvent(QDragEnterEvent *event) {
744 	QFrame::dragEnterEvent(event);
745 }
746 
dragLeaveEvent(QDragLeaveEvent * event)747 void PartsBinPaletteWidget::dragLeaveEvent(QDragLeaveEvent *event) {
748 	QFrame::dragLeaveEvent(event);
749 }
750 
dragMoveEvent(QDragMoveEvent * event)751 void PartsBinPaletteWidget::dragMoveEvent(QDragMoveEvent *event) {
752 	QFrame::dragMoveEvent(event);
753 }
754 
dropEvent(QDropEvent * event)755 void PartsBinPaletteWidget::dropEvent(QDropEvent *event) {
756 	QFrame::dropEvent(event);
757 }
758 
759 
addPartToMeAction()760 QAction *PartsBinPaletteWidget::addPartToMeAction() {
761 	return m_addPartToMeAction;
762 }
763 
addSketchPartToMe()764 void PartsBinPaletteWidget::addSketchPartToMe() {
765     m_manager->openBinIn(this->m_fileName, false);
766 	QString moduleID = m_manager->getSelectedModuleIDFromSketch();
767     if (moduleID.isEmpty()) return;
768 
769 	bool wasAlreadyIn = contains(moduleID);
770 	addPart(moduleID, -1);
771 	if(!wasAlreadyIn) {
772 		setDirty();
773 	}
774 }
775 
setFilename(const QString & filename)776 void PartsBinPaletteWidget::setFilename(const QString &filename) {
777 	m_fileName = filename;
778 	if (m_fileName.compare(BinManager::SearchBinLocation) == 0) {
779 		m_searchStackedWidget->setCurrentIndex(1);
780 	}
781 	bool acceptIt = !currentBinIsCore();
782 	setAcceptDrops(acceptIt);
783 	m_iconView->setAcceptDrops(acceptIt);
784 	m_listView->setAcceptDrops(acceptIt);
785 }
786 
search()787 void PartsBinPaletteWidget::search() {
788 	SearchLineEdit * edit = qobject_cast<SearchLineEdit *>(sender());
789 	if (edit == NULL) return;
790 
791 	QString searchText = edit->text();
792 	if (searchText.isEmpty()) return;
793 
794 	ModelPartSharedRoot * root = m_model->rootModelPartShared();
795 	if (root) {
796 		root->setSearchTerm(searchText);
797 	}
798 
799     m_manager->search(searchText);
800 }
801 
allowsChanges()802 bool PartsBinPaletteWidget::allowsChanges() {
803 	return m_allowsChanges;
804 }
805 
readOnly()806 bool PartsBinPaletteWidget::readOnly() {
807 	return !allowsChanges();
808 }
809 
setAllowsChanges(bool allowsChanges)810 void PartsBinPaletteWidget::setAllowsChanges(bool allowsChanges) {
811 	m_allowsChanges = allowsChanges;
812 }
813 
setReadOnly(bool readOnly)814 void PartsBinPaletteWidget::setReadOnly(bool readOnly) {
815 	setAllowsChanges(!readOnly);
816 }
817 
focusSearch()818 void PartsBinPaletteWidget::focusSearch() {
819 	if (m_searchLineEdit) {
820 		QTimer::singleShot(20, this, SLOT(focusSearchAfter()));
821 	}
822 }
823 
focusSearchAfter()824 void PartsBinPaletteWidget::focusSearchAfter() {
825 	//DebugDialog::debug("focus search after");
826 	if (m_searchLineEdit->decoy()) {
827 		m_searchLineEdit->setDecoy(false);
828 	}
829 	m_searchLineEdit->setFocus(Qt::OtherFocusReason);
830 }
831 
setSaveQuietly(bool saveQuietly)832 void PartsBinPaletteWidget::setSaveQuietly(bool saveQuietly) {
833 	m_saveQuietly = saveQuietly;
834 }
835 
currentViewIsIconView()836 bool PartsBinPaletteWidget::currentViewIsIconView() {
837 	if (m_currentView == NULL) return true;
838 
839 	return (m_currentView == m_iconView);
840 }
841 
icon()842 QIcon PartsBinPaletteWidget::icon() {
843 	if (m_icon) return *m_icon;
844 
845 	return EmptyIcon;
846 }
847 
hasMonoIcon()848 bool PartsBinPaletteWidget::hasMonoIcon() {
849     return m_monoIcon != NULL;
850 }
851 
monoIcon()852 QIcon PartsBinPaletteWidget::monoIcon() {
853 	if (m_monoIcon) return *m_monoIcon;
854 
855 	return EmptyIcon;
856 }
857 
combinedMenu()858 QMenu * PartsBinPaletteWidget::combinedMenu()
859 {
860 	return m_manager->combinedMenu(this);
861 }
862 
partContextMenu()863 QMenu * PartsBinPaletteWidget::partContextMenu()
864 {
865 	return m_manager->partContextMenu(this);
866 }
867 
binContextMenu()868 QMenu * PartsBinPaletteWidget::binContextMenu()
869 {
870 	QMenu * menu = m_manager->binContextMenu(this);
871 	if (menu == NULL) return NULL;
872 
873 	QMenu * newMenu = new QMenu();
874 	foreach (QAction * action, menu->actions()) {
875 		newMenu->addAction(action);
876 	}
877 
878 	// TODO: need to enable/disable actions based on this bin
879 
880 	ModelPartSharedRoot * root = (m_model == NULL) ? NULL : m_model->rootModelPartShared();
881 	if (root) {
882 		QString iconFilename = root->icon();
883 		if (iconFilename.compare(CustomIconName) == 0 || isCustomSvg(iconFilename)) {
884 			newMenu->addSeparator();
885 			QAction * action = new QAction(tr("Change icon color..."), newMenu);
886 			action->setToolTip(tr("Change the color of the icon for this bin."));
887 			connect(action, SIGNAL(triggered()), this, SLOT(changeIconColor()));
888 			newMenu->addAction(action);
889 		}
890 	}
891 	return newMenu;
892 }
893 
changeIconColor()894 void PartsBinPaletteWidget::changeIconColor() {
895 	QImage image(":resources/bins/icons/" + CustomIconName);
896 	QColor initial(image.pixel(image.width() / 2, image.height() / 2));
897 	QColor color = QColorDialog::getColor(initial, this, tr("Select a color for this icon"), 0 );
898 	if (!color.isValid()) return;
899 
900 	QRgb match = initial.rgba();
901 	for (int y = 0; y < image.height(); y++) {
902 		for (int x = 0; x < image.width(); x++) {
903 			QRgb rgb = image.pixel(x, y);
904 			if (qRed(rgb) == qRed(match) && qBlue(rgb) == qBlue(match) && qGreen(rgb) == qGreen(match)) {
905 				image.setPixel(x, y, (color.rgb() & 0xffffff) | (qAlpha(rgb) << 24));
906 			}
907 		}
908 	}
909 
910 #ifndef QT_NO_DEBUG
911     //QFileInfo info(m_fileName);
912     //image.save(FolderUtils::getUserDataStorePath("") + "/" + info.completeBaseName() + ".png");
913 #endif
914 
915 	delete m_icon;
916 	m_icon = new QIcon(QPixmap::fromImage(image));
917 
918 	m_manager->setTabIcon(this, m_icon);
919 	setDirty();
920 
921 	ModelPartSharedRoot * root = m_model->rootModelPartShared();
922 
923 	if (root) {
924 		QSvgGenerator svgGenerator;
925 		svgGenerator.setResolution(90);
926 		svgGenerator.setTitle(CustomIconTitle);
927 		QBuffer buffer;
928 		svgGenerator.setOutputDevice(&buffer);
929 		QSize sz = image.size();
930 		svgGenerator.setSize(sz);
931 		svgGenerator.setViewBox(QRect(0, 0, sz.width(), sz.height()));
932 		QPainter svgPainter(&svgGenerator);
933 		svgPainter.drawImage(QPoint(0,0), image);
934 		svgPainter.end();
935 		QString svg(buffer.buffer());
936 		root->setIcon(svg);
937 	}
938 }
939 
fastLoaded()940 bool PartsBinPaletteWidget::fastLoaded() {
941 	return m_fastLoaded;
942 }
943 
location()944 BinLocation::Location PartsBinPaletteWidget::location() {
945 	return m_location;
946 }
947 
canClose()948 bool PartsBinPaletteWidget::canClose() {
949 	switch (m_location) {
950 	case BinLocation::User:
951 		if (m_fileName.compare(BinManager::SearchBinLocation) == 0) return false;
952 		if (m_fileName.compare(BinManager::ContribPartsBinLocation) == 0) return false;
953 		if (m_fileName.compare(BinManager::MyPartsBinLocation) == 0) return false;
954 		if (m_manager->isTempPartsBin(this)) return false;
955 		return true;
956 	case BinLocation::More:
957 		return false;
958 	case BinLocation::App:
959 		return false;
960 	case BinLocation::Outside:
961 	default:
962 		return true;
963 	}
964 }
965 
copyFilesToContrib(ModelPart * mp,QWidget * originator)966 void PartsBinPaletteWidget::copyFilesToContrib(ModelPart * mp, QWidget * originator) {
967 	m_manager->copyFilesToContrib(mp, originator);
968 }
969 
root()970 ModelPart * PartsBinPaletteWidget::root() {
971     if (m_model == NULL) return NULL;
972 
973     return m_model->root();
974 }
975 
isTempPartsBin()976 bool PartsBinPaletteWidget::isTempPartsBin() {
977     return m_manager->isTempPartsBin(this);
978 }
979 
reloadPart(const QString & moduleID)980 void PartsBinPaletteWidget::reloadPart(const QString & moduleID) {
981 	m_iconView->reloadPart(moduleID);
982 	m_listView->reloadPart(moduleID);
983 }
984 
getAllParts()985 QList<ModelPart *> PartsBinPaletteWidget::getAllParts() {
986     QList<ModelPart*> empty;
987     if (m_model == NULL) return empty;
988 
989     if (m_model->root() == NULL) return empty;
990 
991     return m_model->root()->getAllParts();
992 }
993 
994 
995