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