1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program 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  * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "footprintlistmodel.h"
24 
25 #include <librepcb/common/undostack.h>
26 #include <librepcb/library/pkg/cmd/cmdfootprintedit.h>
27 
28 #include <QtCore>
29 
30 /*******************************************************************************
31  *  Namespace
32  ******************************************************************************/
33 namespace librepcb {
34 namespace library {
35 namespace editor {
36 
37 /*******************************************************************************
38  *  Constructors / Destructor
39  ******************************************************************************/
40 
FootprintListModel(QObject * parent)41 FootprintListModel::FootprintListModel(QObject* parent) noexcept
42   : QAbstractTableModel(parent),
43     mFootprintList(nullptr),
44     mUndoStack(nullptr),
45     mNewName(),
46     mOnEditedSlot(*this, &FootprintListModel::footprintListEdited) {
47 }
48 
~FootprintListModel()49 FootprintListModel::~FootprintListModel() noexcept {
50 }
51 
52 /*******************************************************************************
53  *  Setters
54  ******************************************************************************/
55 
setFootprintList(FootprintList * list)56 void FootprintListModel::setFootprintList(FootprintList* list) noexcept {
57   emit beginResetModel();
58 
59   if (mFootprintList) {
60     mFootprintList->onEdited.detach(mOnEditedSlot);
61   }
62 
63   mFootprintList = list;
64 
65   if (mFootprintList) {
66     mFootprintList->onEdited.attach(mOnEditedSlot);
67   }
68 
69   emit endResetModel();
70 }
71 
setUndoStack(UndoStack * stack)72 void FootprintListModel::setUndoStack(UndoStack* stack) noexcept {
73   mUndoStack = stack;
74 }
75 
76 /*******************************************************************************
77  *  Slots
78  ******************************************************************************/
79 
addFootprint(const QVariant & editData)80 void FootprintListModel::addFootprint(const QVariant& editData) noexcept {
81   Q_UNUSED(editData);
82   if (!mFootprintList) {
83     return;
84   }
85 
86   try {
87     std::shared_ptr<Footprint> fpt = std::make_shared<Footprint>(
88         Uuid::createRandom(), validateNameOrThrow(mNewName), "");
89     execCmd(new CmdFootprintInsert(*mFootprintList, fpt));
90     mNewName = QString();
91   } catch (const Exception& e) {
92     QMessageBox::critical(0, tr("Error"), e.getMsg());
93   }
94 }
95 
copyFootprint(const QVariant & editData)96 void FootprintListModel::copyFootprint(const QVariant& editData) noexcept {
97   if (!mFootprintList) {
98     return;
99   }
100 
101   try {
102     Uuid uuid = Uuid::fromString(editData.toString());
103     std::shared_ptr<const Footprint> original = mFootprintList->get(uuid);
104     ElementName newName("Copy of " % original->getNames().getDefaultValue());
105     std::shared_ptr<Footprint> copy(
106         new Footprint(Uuid::createRandom(), newName, ""));  // can throw
107     copy->getDescriptions() = original->getDescriptions();
108     copy->getPads() = original->getPads();
109     copy->getPolygons() = original->getPolygons();
110     copy->getCircles() = original->getCircles();
111     copy->getStrokeTexts() = original->getStrokeTexts();
112     copy->getHoles() = original->getHoles();
113     execCmd(new CmdFootprintInsert(*mFootprintList, copy));
114     mNewName = QString();
115   } catch (const Exception& e) {
116     QMessageBox::critical(0, tr("Error"), e.getMsg());
117   }
118 }
119 
removeFootprint(const QVariant & editData)120 void FootprintListModel::removeFootprint(const QVariant& editData) noexcept {
121   if (!mFootprintList) {
122     return;
123   }
124 
125   try {
126     Uuid uuid = Uuid::fromString(editData.toString());
127     std::shared_ptr<Footprint> fpt = mFootprintList->get(uuid);
128     execCmd(new CmdFootprintRemove(*mFootprintList, fpt.get()));
129   } catch (const Exception& e) {
130     QMessageBox::critical(0, tr("Error"), e.getMsg());
131   }
132 }
133 
moveFootprintUp(const QVariant & editData)134 void FootprintListModel::moveFootprintUp(const QVariant& editData) noexcept {
135   if (!mFootprintList) {
136     return;
137   }
138 
139   try {
140     Uuid uuid = Uuid::fromString(editData.toString());
141     int index = mFootprintList->indexOf(uuid);
142     if ((index >= 1) && (index < mFootprintList->count())) {
143       execCmd(new CmdFootprintsSwap(*mFootprintList, index, index - 1));
144     }
145   } catch (const Exception& e) {
146     QMessageBox::critical(0, tr("Error"), e.getMsg());
147   }
148 }
149 
moveFootprintDown(const QVariant & editData)150 void FootprintListModel::moveFootprintDown(const QVariant& editData) noexcept {
151   if (!mFootprintList) {
152     return;
153   }
154 
155   try {
156     Uuid uuid = Uuid::fromString(editData.toString());
157     int index = mFootprintList->indexOf(uuid);
158     if ((index >= 0) && (index < mFootprintList->count() - 1)) {
159       execCmd(new CmdFootprintsSwap(*mFootprintList, index, index + 1));
160     }
161   } catch (const Exception& e) {
162     QMessageBox::critical(0, tr("Error"), e.getMsg());
163   }
164 }
165 
166 /*******************************************************************************
167  *  Inherited from QAbstractItemModel
168  ******************************************************************************/
169 
rowCount(const QModelIndex & parent) const170 int FootprintListModel::rowCount(const QModelIndex& parent) const {
171   if (!parent.isValid() && mFootprintList) {
172     return mFootprintList->count() + 1;
173   }
174   return 0;
175 }
176 
columnCount(const QModelIndex & parent) const177 int FootprintListModel::columnCount(const QModelIndex& parent) const {
178   if (!parent.isValid()) {
179     return _COLUMN_COUNT;
180   }
181   return 0;
182 }
183 
data(const QModelIndex & index,int role) const184 QVariant FootprintListModel::data(const QModelIndex& index, int role) const {
185   if (!index.isValid() || !mFootprintList) {
186     return QVariant();
187   }
188 
189   std::shared_ptr<Footprint> item = mFootprintList->value(index.row());
190   switch (index.column()) {
191     case COLUMN_NAME: {
192       QString name = item ? *item->getNames().getDefaultValue() : mNewName;
193       bool showHint = (!item) && mNewName.isEmpty();
194       QString hint = tr("Footprint name");
195       switch (role) {
196         case Qt::DisplayRole:
197           return showHint ? hint : name;
198         case Qt::ToolTipRole:
199           return showHint ? hint : QVariant();
200         case Qt::EditRole:
201           return name;
202         case Qt::ForegroundRole:
203           if (showHint) {
204             QColor color = qApp->palette().text().color();
205             color.setAlpha(128);
206             return QBrush(color);
207           } else {
208             return QVariant();
209           }
210         default:
211           return QVariant();
212       }
213     }
214     case COLUMN_ACTIONS: {
215       switch (role) {
216         case Qt::EditRole:
217           return item ? item->getUuid().toStr() : QVariant();
218         default:
219           return QVariant();
220       }
221     }
222     default:
223       return QVariant();
224   }
225 
226   return QVariant();
227 }
228 
headerData(int section,Qt::Orientation orientation,int role) const229 QVariant FootprintListModel::headerData(int section,
230                                         Qt::Orientation orientation,
231                                         int role) const {
232   if (orientation == Qt::Horizontal) {
233     if (role == Qt::DisplayRole) {
234       switch (section) {
235         case COLUMN_NAME:
236           return tr("Name");
237         default:
238           return QVariant();
239       }
240     }
241   } else if (orientation == Qt::Vertical) {
242     if (mFootprintList && (role == Qt::DisplayRole)) {
243       std::shared_ptr<Footprint> item = mFootprintList->value(section);
244       return item ? item->getUuid().toStr().left(8) : tr("New:");
245     } else if (mFootprintList && (role == Qt::ToolTipRole)) {
246       std::shared_ptr<Footprint> item = mFootprintList->value(section);
247       return item ? item->getUuid().toStr() : tr("Add a new footprint");
248     } else if (role == Qt::TextAlignmentRole) {
249       return QVariant(Qt::AlignRight | Qt::AlignVCenter);
250     } else if (role == Qt::FontRole) {
251       QFont f = QAbstractTableModel::headerData(section, orientation, role)
252                     .value<QFont>();
253       f.setStyleHint(QFont::Monospace);  // ensure fixed column width
254       f.setFamily("Monospace");
255       return f;
256     }
257   }
258   return QVariant();
259 }
260 
flags(const QModelIndex & index) const261 Qt::ItemFlags FootprintListModel::flags(const QModelIndex& index) const {
262   Qt::ItemFlags f = QAbstractTableModel::flags(index);
263   if (index.isValid() && (index.column() != COLUMN_ACTIONS)) {
264     f |= Qt::ItemIsEditable;
265   }
266   return f;
267 }
268 
setData(const QModelIndex & index,const QVariant & value,int role)269 bool FootprintListModel::setData(const QModelIndex& index,
270                                  const QVariant& value, int role) {
271   if (!mFootprintList) {
272     return false;
273   }
274 
275   try {
276     std::shared_ptr<Footprint> item = mFootprintList->value(index.row());
277     QScopedPointer<CmdFootprintEdit> cmd;
278     if (item) {
279       cmd.reset(new CmdFootprintEdit(*item));
280     }
281     if ((index.column() == COLUMN_NAME) && role == Qt::EditRole) {
282       QString name = value.toString().trimmed();
283       QString cleanedName = cleanElementName(name);
284       if (cmd) {
285         if (cleanedName != item->getNames().getDefaultValue()) {
286           cmd->setName(validateNameOrThrow(cleanedName));
287         }
288       } else {
289         mNewName = cleanedName;
290       }
291     } else {
292       return false;  // do not execute command!
293     }
294     if (cmd) {
295       execCmd(cmd.take());
296     } else if (!item) {
297       emit dataChanged(index, index);
298     }
299     return true;
300   } catch (const Exception& e) {
301     QMessageBox::critical(0, tr("Error"), e.getMsg());
302   }
303   return false;
304 }
305 
306 /*******************************************************************************
307  *  Private Methods
308  ******************************************************************************/
309 
footprintListEdited(const FootprintList & list,int index,const std::shared_ptr<const Footprint> & footprint,FootprintList::Event event)310 void FootprintListModel::footprintListEdited(
311     const FootprintList& list, int index,
312     const std::shared_ptr<const Footprint>& footprint,
313     FootprintList::Event event) noexcept {
314   Q_UNUSED(list);
315   Q_UNUSED(footprint);
316   switch (event) {
317     case FootprintList::Event::ElementAdded:
318       beginInsertRows(QModelIndex(), index, index);
319       endInsertRows();
320       break;
321     case FootprintList::Event::ElementRemoved:
322       beginRemoveRows(QModelIndex(), index, index);
323       endRemoveRows();
324       break;
325     case FootprintList::Event::ElementEdited:
326       dataChanged(this->index(index, 0), this->index(index, _COLUMN_COUNT - 1));
327       break;
328     default:
329       qWarning() << "Unhandled switch-case in "
330                     "FootprintListModel::footprintListEdited()";
331       break;
332   }
333 }
334 
execCmd(UndoCommand * cmd)335 void FootprintListModel::execCmd(UndoCommand* cmd) {
336   if (mUndoStack) {
337     mUndoStack->execCmd(cmd);
338   } else {
339     QScopedPointer<UndoCommand> cmdGuard(cmd);
340     cmdGuard->execute();
341   }
342 }
343 
validateNameOrThrow(const QString & name) const344 ElementName FootprintListModel::validateNameOrThrow(const QString& name) const {
345   if (mFootprintList) {
346     for (const Footprint& footprint : *mFootprintList) {
347       if (footprint.getNames().getDefaultValue() == name) {
348         throw RuntimeError(
349             __FILE__, __LINE__,
350             tr("There is already a footprint with the name \"%1\".").arg(name));
351       }
352     }
353   }
354   return ElementName(name);  // can throw
355 }
356 
357 /*******************************************************************************
358  *  End of File
359  ******************************************************************************/
360 
361 }  // namespace editor
362 }  // namespace library
363 }  // namespace librepcb
364