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 "componentsymbolvariantlistmodel.h"
24 
25 #include <librepcb/common/undostack.h>
26 #include <librepcb/library/cmp/cmd/cmdcomponentsymbolvariantedit.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 
ComponentSymbolVariantListModel(QObject * parent)41 ComponentSymbolVariantListModel::ComponentSymbolVariantListModel(
42     QObject* parent) noexcept
43   : QAbstractTableModel(parent),
44     mSymbolVariantList(nullptr),
45     mUndoStack(nullptr),
46     mNewName(),
47     mNewDescription(),
48     mNewNorm(),
49     mOnEditedSlot(*this,
50                   &ComponentSymbolVariantListModel::symbolVariantListEdited) {
51 }
52 
~ComponentSymbolVariantListModel()53 ComponentSymbolVariantListModel::~ComponentSymbolVariantListModel() noexcept {
54 }
55 
56 /*******************************************************************************
57  *  Setters
58  ******************************************************************************/
59 
setSymbolVariantList(ComponentSymbolVariantList * list)60 void ComponentSymbolVariantListModel::setSymbolVariantList(
61     ComponentSymbolVariantList* list) noexcept {
62   emit beginResetModel();
63 
64   if (mSymbolVariantList) {
65     mSymbolVariantList->onEdited.detach(mOnEditedSlot);
66   }
67 
68   mSymbolVariantList = list;
69 
70   if (mSymbolVariantList) {
71     mSymbolVariantList->onEdited.attach(mOnEditedSlot);
72   }
73 
74   emit endResetModel();
75 }
76 
setUndoStack(UndoStack * stack)77 void ComponentSymbolVariantListModel::setUndoStack(UndoStack* stack) noexcept {
78   mUndoStack = stack;
79 }
80 
81 /*******************************************************************************
82  *  Slots
83  ******************************************************************************/
84 
addSymbolVariant(const QVariant & editData)85 void ComponentSymbolVariantListModel::addSymbolVariant(
86     const QVariant& editData) noexcept {
87   Q_UNUSED(editData);
88   if (!mSymbolVariantList) {
89     return;
90   }
91 
92   try {
93     std::shared_ptr<ComponentSymbolVariant> sv =
94         std::make_shared<ComponentSymbolVariant>(Uuid::createRandom(), mNewNorm,
95                                                  validateNameOrThrow(mNewName),
96                                                  mNewDescription);
97     execCmd(new CmdComponentSymbolVariantInsert(*mSymbolVariantList, sv));
98     mNewName = QString();
99     mNewDescription = QString();
100     mNewNorm = QString();
101   } catch (const Exception& e) {
102     QMessageBox::critical(0, tr("Error"), e.getMsg());
103   }
104 }
105 
removeSymbolVariant(const QVariant & editData)106 void ComponentSymbolVariantListModel::removeSymbolVariant(
107     const QVariant& editData) noexcept {
108   if (!mSymbolVariantList) {
109     return;
110   }
111 
112   try {
113     Uuid uuid = Uuid::fromString(editData.toString());
114     std::shared_ptr<ComponentSymbolVariant> sv = mSymbolVariantList->get(uuid);
115     execCmd(new CmdComponentSymbolVariantRemove(*mSymbolVariantList, sv.get()));
116   } catch (const Exception& e) {
117     QMessageBox::critical(0, tr("Error"), e.getMsg());
118   }
119 }
120 
moveSymbolVariantUp(const QVariant & editData)121 void ComponentSymbolVariantListModel::moveSymbolVariantUp(
122     const QVariant& editData) noexcept {
123   if (!mSymbolVariantList) {
124     return;
125   }
126 
127   try {
128     Uuid uuid = Uuid::fromString(editData.toString());
129     int index = mSymbolVariantList->indexOf(uuid);
130     if ((index >= 1) && (index < mSymbolVariantList->count())) {
131       execCmd(new CmdComponentSymbolVariantsSwap(*mSymbolVariantList, index,
132                                                  index - 1));
133     }
134   } catch (const Exception& e) {
135     QMessageBox::critical(0, tr("Error"), e.getMsg());
136   }
137 }
138 
moveSymbolVariantDown(const QVariant & editData)139 void ComponentSymbolVariantListModel::moveSymbolVariantDown(
140     const QVariant& editData) noexcept {
141   if (!mSymbolVariantList) {
142     return;
143   }
144 
145   try {
146     Uuid uuid = Uuid::fromString(editData.toString());
147     int index = mSymbolVariantList->indexOf(uuid);
148     if ((index >= 0) && (index < mSymbolVariantList->count() - 1)) {
149       execCmd(new CmdComponentSymbolVariantsSwap(*mSymbolVariantList, index,
150                                                  index + 1));
151     }
152   } catch (const Exception& e) {
153     QMessageBox::critical(0, tr("Error"), e.getMsg());
154   }
155 }
156 
157 /*******************************************************************************
158  *  Inherited from QAbstractItemModel
159  ******************************************************************************/
160 
rowCount(const QModelIndex & parent) const161 int ComponentSymbolVariantListModel::rowCount(const QModelIndex& parent) const {
162   if (!parent.isValid() && mSymbolVariantList) {
163     return mSymbolVariantList->count() + 1;
164   }
165   return 0;
166 }
167 
columnCount(const QModelIndex & parent) const168 int ComponentSymbolVariantListModel::columnCount(
169     const QModelIndex& parent) const {
170   if (!parent.isValid()) {
171     return _COLUMN_COUNT;
172   }
173   return 0;
174 }
175 
data(const QModelIndex & index,int role) const176 QVariant ComponentSymbolVariantListModel::data(const QModelIndex& index,
177                                                int role) const {
178   if (!index.isValid() || !mSymbolVariantList) {
179     return QVariant();
180   }
181 
182   std::shared_ptr<ComponentSymbolVariant> item =
183       mSymbolVariantList->value(index.row());
184   switch (index.column()) {
185     case COLUMN_NAME: {
186       QString name = item ? *item->getNames().getDefaultValue() : mNewName;
187       bool showHint = (!item) && mNewName.isEmpty();
188       QString hint = tr("Symbol variant name");
189       switch (role) {
190         case Qt::DisplayRole:
191           if (item && (index.row() == 0) && (mSymbolVariantList->count() > 1)) {
192             return name + " [" + tr("default") + "]";
193           } else {
194             return showHint ? hint : name;
195           }
196         case Qt::ToolTipRole:
197           return showHint ? hint : QVariant();
198         case Qt::EditRole:
199           return name;
200         case Qt::ForegroundRole:
201           if (showHint) {
202             QColor color = qApp->palette().text().color();
203             color.setAlpha(128);
204             return QBrush(color);
205           } else {
206             return QVariant();
207           }
208         default:
209           return QVariant();
210       }
211     }
212     case COLUMN_DESCRIPTION: {
213       switch (role) {
214         case Qt::DisplayRole:
215         case Qt::EditRole:
216           return item ? item->getDescriptions().getDefaultValue()
217                       : mNewDescription;
218         default:
219           return QVariant();
220       }
221     }
222     case COLUMN_NORM: {
223       switch (role) {
224         case Qt::DisplayRole:
225         case Qt::EditRole:
226           return item ? item->getNorm() : mNewNorm;
227         default:
228           return QVariant();
229       }
230     }
231     case COLUMN_SYMBOLCOUNT: {
232       switch (role) {
233         case Qt::DisplayRole:
234           return item ? item->getSymbolItems().count() : QVariant();
235         case Qt::TextAlignmentRole:
236           return Qt::AlignCenter;
237         default:
238           return QVariant();
239       }
240     }
241     case COLUMN_ACTIONS: {
242       switch (role) {
243         case Qt::EditRole:
244           return item ? item->getUuid().toStr() : QVariant();
245         default:
246           return QVariant();
247       }
248     }
249     default:
250       return QVariant();
251   }
252 
253   return QVariant();
254 }
255 
headerData(int section,Qt::Orientation orientation,int role) const256 QVariant ComponentSymbolVariantListModel::headerData(
257     int section, Qt::Orientation orientation, int role) const {
258   if (orientation == Qt::Horizontal) {
259     if (role == Qt::DisplayRole) {
260       switch (section) {
261         case COLUMN_NAME:
262           return tr("Name");
263         case COLUMN_DESCRIPTION:
264           return tr("Description");
265         case COLUMN_NORM:
266           return tr("Norm");
267         case COLUMN_SYMBOLCOUNT:
268           return tr("Symbols");
269         default:
270           return QVariant();
271       }
272     }
273   } else if (orientation == Qt::Vertical) {
274     if (mSymbolVariantList && (role == Qt::DisplayRole)) {
275       std::shared_ptr<ComponentSymbolVariant> item =
276           mSymbolVariantList->value(section);
277       return item ? item->getUuid().toStr().left(8) : tr("New:");
278     } else if (mSymbolVariantList && (role == Qt::ToolTipRole)) {
279       std::shared_ptr<ComponentSymbolVariant> item =
280           mSymbolVariantList->value(section);
281       return item ? item->getUuid().toStr() : tr("Add a new symbol variant");
282     } else if (role == Qt::TextAlignmentRole) {
283       return QVariant(Qt::AlignRight | Qt::AlignVCenter);
284     } else if (role == Qt::FontRole) {
285       QFont f = QAbstractTableModel::headerData(section, orientation, role)
286                     .value<QFont>();
287       f.setStyleHint(QFont::Monospace);  // ensure fixed column width
288       f.setFamily("Monospace");
289       return f;
290     }
291   }
292   return QVariant();
293 }
294 
flags(const QModelIndex & index) const295 Qt::ItemFlags ComponentSymbolVariantListModel::flags(
296     const QModelIndex& index) const {
297   Qt::ItemFlags f = QAbstractTableModel::flags(index);
298   if (index.isValid() && (index.column() != COLUMN_SYMBOLCOUNT) &&
299       (index.column() != COLUMN_ACTIONS)) {
300     f |= Qt::ItemIsEditable;
301   }
302   return f;
303 }
304 
setData(const QModelIndex & index,const QVariant & value,int role)305 bool ComponentSymbolVariantListModel::setData(const QModelIndex& index,
306                                               const QVariant& value, int role) {
307   if (!mSymbolVariantList) {
308     return false;
309   }
310 
311   try {
312     std::shared_ptr<ComponentSymbolVariant> item =
313         mSymbolVariantList->value(index.row());
314     QScopedPointer<CmdComponentSymbolVariantEdit> cmd;
315     if (item) {
316       cmd.reset(new CmdComponentSymbolVariantEdit(*item));
317     }
318     if ((index.column() == COLUMN_NAME) && role == Qt::EditRole) {
319       QString name = value.toString().trimmed();
320       QString cleanedName = cleanElementName(name);
321       if (cmd) {
322         LocalizedNameMap names = item->getNames();
323         if (cleanedName != names.getDefaultValue()) {
324           names.setDefaultValue(validateNameOrThrow(cleanedName));
325           cmd->setNames(names);
326         }
327       } else {
328         mNewName = cleanedName;
329       }
330     } else if ((index.column() == COLUMN_DESCRIPTION) && role == Qt::EditRole) {
331       QString description = value.toString().trimmed();
332       if (cmd) {
333         LocalizedDescriptionMap descriptions = item->getDescriptions();
334         descriptions.setDefaultValue(description);
335         cmd->setDescriptions(descriptions);
336       } else {
337         mNewDescription = description;
338       }
339     } else if ((index.column() == COLUMN_NORM) && role == Qt::EditRole) {
340       QString norm = value.toString().trimmed();
341       if (cmd) {
342         cmd->setNorm(norm);
343       } else {
344         mNewNorm = norm;
345       }
346     } else {
347       return false;  // do not execute command!
348     }
349     if (cmd) {
350       execCmd(cmd.take());
351     } else if (!item) {
352       emit dataChanged(index, index);
353     }
354     return true;
355   } catch (const Exception& e) {
356     QMessageBox::critical(0, tr("Error"), e.getMsg());
357   }
358   return false;
359 }
360 
361 /*******************************************************************************
362  *  Private Methods
363  ******************************************************************************/
364 
symbolVariantListEdited(const ComponentSymbolVariantList & list,int index,const std::shared_ptr<const ComponentSymbolVariant> & variant,ComponentSymbolVariantList::Event event)365 void ComponentSymbolVariantListModel::symbolVariantListEdited(
366     const ComponentSymbolVariantList& list, int index,
367     const std::shared_ptr<const ComponentSymbolVariant>& variant,
368     ComponentSymbolVariantList::Event event) noexcept {
369   Q_UNUSED(list);
370   Q_UNUSED(variant);
371   switch (event) {
372     case ComponentSymbolVariantList::Event::ElementAdded:
373       beginInsertRows(QModelIndex(), index, index);
374       endInsertRows();
375       break;
376     case ComponentSymbolVariantList::Event::ElementRemoved:
377       beginRemoveRows(QModelIndex(), index, index);
378       endRemoveRows();
379       break;
380     case ComponentSymbolVariantList::Event::ElementEdited:
381       dataChanged(this->index(index, 0), this->index(index, _COLUMN_COUNT - 1));
382       break;
383     default:
384       qWarning()
385           << "Unhandled switch-case in "
386              "ComponentSymbolVariantListModel::symbolVariantListEdited()";
387       break;
388   }
389 }
390 
execCmd(UndoCommand * cmd)391 void ComponentSymbolVariantListModel::execCmd(UndoCommand* cmd) {
392   if (mUndoStack) {
393     mUndoStack->execCmd(cmd);
394   } else {
395     QScopedPointer<UndoCommand> cmdGuard(cmd);
396     cmdGuard->execute();
397   }
398 }
399 
validateNameOrThrow(const QString & name) const400 ElementName ComponentSymbolVariantListModel::validateNameOrThrow(
401     const QString& name) const {
402   if (mSymbolVariantList && mSymbolVariantList->contains(name)) {
403     throw RuntimeError(
404         __FILE__, __LINE__,
405         tr("There is already a symbol variant with the name \"%1\".")
406             .arg(name));
407   }
408   return ElementName(name);  // can throw
409 }
410 
411 /*******************************************************************************
412  *  End of File
413  ******************************************************************************/
414 
415 }  // namespace editor
416 }  // namespace library
417 }  // namespace librepcb
418