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 "packagepadlistmodel.h"
24 
25 #include <librepcb/common/toolbox.h>
26 #include <librepcb/common/undocommandgroup.h>
27 #include <librepcb/common/undostack.h>
28 #include <librepcb/library/pkg/cmd/cmdpackagepadedit.h>
29 
30 #include <QtCore>
31 
32 /*******************************************************************************
33  *  Namespace
34  ******************************************************************************/
35 namespace librepcb {
36 namespace library {
37 namespace editor {
38 
39 /*******************************************************************************
40  *  Constructors / Destructor
41  ******************************************************************************/
42 
PackagePadListModel(QObject * parent)43 PackagePadListModel::PackagePadListModel(QObject* parent) noexcept
44   : QAbstractTableModel(parent),
45     mPadList(nullptr),
46     mUndoStack(nullptr),
47     mNewName(),
48     mOnEditedSlot(*this, &PackagePadListModel::padListEdited) {
49 }
50 
~PackagePadListModel()51 PackagePadListModel::~PackagePadListModel() noexcept {
52 }
53 
54 /*******************************************************************************
55  *  Setters
56  ******************************************************************************/
57 
setPadList(PackagePadList * list)58 void PackagePadListModel::setPadList(PackagePadList* list) noexcept {
59   emit beginResetModel();
60 
61   if (mPadList) {
62     mPadList->onEdited.detach(mOnEditedSlot);
63   }
64 
65   mPadList = list;
66 
67   if (mPadList) {
68     mPadList->onEdited.attach(mOnEditedSlot);
69   }
70 
71   emit endResetModel();
72 }
73 
setUndoStack(UndoStack * stack)74 void PackagePadListModel::setUndoStack(UndoStack* stack) noexcept {
75   mUndoStack = stack;
76 }
77 
78 /*******************************************************************************
79  *  Slots
80  ******************************************************************************/
81 
addPad(const QVariant & editData)82 void PackagePadListModel::addPad(const QVariant& editData) noexcept {
83   Q_UNUSED(editData);
84   if (!mPadList) {
85     return;
86   }
87 
88   try {
89     QScopedPointer<UndoCommandGroup> cmd(
90         new UndoCommandGroup(tr("Add package pad(s)")));
91     // if no name is set we search for the next free numerical pad name
92     if (mNewName.isEmpty()) {
93       mNewName = getNextPadNameProposal();
94     }
95     foreach (const QString& name, Toolbox::expandRangesInString(mNewName)) {
96       std::shared_ptr<PackagePad> pad = std::make_shared<PackagePad>(
97           Uuid::createRandom(), validateNameOrThrow(name));
98       cmd->appendChild(new CmdPackagePadInsert(*mPadList, pad));
99     }
100     execCmd(cmd.take());
101     mNewName = QString();
102   } catch (const Exception& e) {
103     QMessageBox::critical(0, tr("Error"), e.getMsg());
104   }
105 }
106 
removePad(const QVariant & editData)107 void PackagePadListModel::removePad(const QVariant& editData) noexcept {
108   if (!mPadList) {
109     return;
110   }
111 
112   try {
113     Uuid uuid = Uuid::fromString(editData.toString());
114     std::shared_ptr<PackagePad> pad = mPadList->get(uuid);
115     execCmd(new CmdPackagePadRemove(*mPadList, pad.get()));
116   } catch (const Exception& e) {
117     QMessageBox::critical(0, tr("Error"), e.getMsg());
118   }
119 }
120 
121 /*******************************************************************************
122  *  Inherited from QAbstractItemModel
123  ******************************************************************************/
124 
rowCount(const QModelIndex & parent) const125 int PackagePadListModel::rowCount(const QModelIndex& parent) const {
126   if (!parent.isValid() && mPadList) {
127     return mPadList->count() + 1;
128   }
129   return 0;
130 }
131 
columnCount(const QModelIndex & parent) const132 int PackagePadListModel::columnCount(const QModelIndex& parent) const {
133   if (!parent.isValid()) {
134     return _COLUMN_COUNT;
135   }
136   return 0;
137 }
138 
data(const QModelIndex & index,int role) const139 QVariant PackagePadListModel::data(const QModelIndex& index, int role) const {
140   if (!index.isValid() || !mPadList) {
141     return QVariant();
142   }
143 
144   std::shared_ptr<PackagePad> item = mPadList->value(index.row());
145   switch (index.column()) {
146     case COLUMN_NAME: {
147       QString name = item ? *item->getName() : mNewName;
148       bool showHint = (!item) && mNewName.isEmpty();
149       QString hint =
150           tr("Pad name (may contain ranges like \"%1\")").arg("1..5");
151       switch (role) {
152         case Qt::DisplayRole:
153           return showHint ? hint : name;
154         case Qt::ToolTipRole:
155           return showHint ? hint : QVariant();
156         case Qt::EditRole:
157           return name;
158         case Qt::ForegroundRole:
159           if (showHint) {
160             QColor color = qApp->palette().text().color();
161             color.setAlpha(128);
162             return QBrush(color);
163           } else {
164             return QVariant();
165           }
166         default:
167           return QVariant();
168       }
169     }
170     case COLUMN_ACTIONS: {
171       switch (role) {
172         case Qt::EditRole:
173           return item ? item->getUuid().toStr() : QVariant();
174         default:
175           return QVariant();
176       }
177     }
178     default:
179       return QVariant();
180   }
181 
182   return QVariant();
183 }
184 
headerData(int section,Qt::Orientation orientation,int role) const185 QVariant PackagePadListModel::headerData(int section,
186                                          Qt::Orientation orientation,
187                                          int role) const {
188   if (orientation == Qt::Horizontal) {
189     if (role == Qt::DisplayRole) {
190       switch (section) {
191         case COLUMN_NAME:
192           return tr("Name");
193         default:
194           return QVariant();
195       }
196     }
197   } else if (orientation == Qt::Vertical) {
198     if (mPadList && (role == Qt::DisplayRole)) {
199       std::shared_ptr<PackagePad> item = mPadList->value(section);
200       return item ? item->getUuid().toStr().left(8) : tr("New:");
201     } else if (mPadList && (role == Qt::ToolTipRole)) {
202       std::shared_ptr<PackagePad> item = mPadList->value(section);
203       return item ? item->getUuid().toStr() : tr("Add a new pad");
204     } else if (role == Qt::TextAlignmentRole) {
205       return QVariant(Qt::AlignRight | Qt::AlignVCenter);
206     } else if (role == Qt::FontRole) {
207       QFont f = QAbstractTableModel::headerData(section, orientation, role)
208                     .value<QFont>();
209       f.setStyleHint(QFont::Monospace);  // ensure fixed column width
210       f.setFamily("Monospace");
211       return f;
212     }
213   }
214   return QVariant();
215 }
216 
flags(const QModelIndex & index) const217 Qt::ItemFlags PackagePadListModel::flags(const QModelIndex& index) const {
218   Qt::ItemFlags f = QAbstractTableModel::flags(index);
219   if (index.isValid()) {
220     f |= Qt::ItemIsEditable;
221   }
222   return f;
223 }
224 
setData(const QModelIndex & index,const QVariant & value,int role)225 bool PackagePadListModel::setData(const QModelIndex& index,
226                                   const QVariant& value, int role) {
227   if (!mPadList) {
228     return false;
229   }
230 
231   try {
232     std::shared_ptr<PackagePad> item = mPadList->value(index.row());
233     QScopedPointer<CmdPackagePadEdit> cmd;
234     if (item) {
235       cmd.reset(new CmdPackagePadEdit(*item));
236     }
237     if ((index.column() == COLUMN_NAME) && role == Qt::EditRole) {
238       QString name = value.toString().trimmed();
239       QString cleanedName = cleanCircuitIdentifier(name);
240       if (cmd) {
241         if (cleanedName != item->getName()) {
242           cmd->setName(validateNameOrThrow(cleanedName));
243         }
244       } else {
245         QStringList names = Toolbox::expandRangesInString(name);
246         if (names.count() == 1 && (names.first() == name)) {
247           mNewName = cleanedName;  // no ranges -> clean name
248         } else {
249           mNewName = name;  // contains ranges -> keep them!
250         }
251       }
252     } else {
253       return false;  // do not execute command!
254     }
255     if (cmd) {
256       execCmd(cmd.take());
257     } else if (!item) {
258       emit dataChanged(index, index);
259     }
260     return true;
261   } catch (const Exception& e) {
262     QMessageBox::critical(0, tr("Error"), e.getMsg());
263   }
264   return false;
265 }
266 
267 /*******************************************************************************
268  *  Private Methods
269  ******************************************************************************/
270 
padListEdited(const PackagePadList & list,int index,const std::shared_ptr<const PackagePad> & pad,PackagePadList::Event event)271 void PackagePadListModel::padListEdited(
272     const PackagePadList& list, int index,
273     const std::shared_ptr<const PackagePad>& pad,
274     PackagePadList::Event event) noexcept {
275   Q_UNUSED(list);
276   Q_UNUSED(pad);
277   switch (event) {
278     case PackagePadList::Event::ElementAdded:
279       beginInsertRows(QModelIndex(), index, index);
280       endInsertRows();
281       break;
282     case PackagePadList::Event::ElementRemoved:
283       beginRemoveRows(QModelIndex(), index, index);
284       endRemoveRows();
285       break;
286     case PackagePadList::Event::ElementEdited:
287       dataChanged(this->index(index, 0), this->index(index, _COLUMN_COUNT - 1));
288       break;
289     default:
290       qWarning() << "Unhandled switch-case in PackagePadList::padListEdited()";
291       break;
292   }
293 }
294 
execCmd(UndoCommand * cmd)295 void PackagePadListModel::execCmd(UndoCommand* cmd) {
296   if (mUndoStack) {
297     mUndoStack->execCmd(cmd);
298   } else {
299     QScopedPointer<UndoCommand> cmdGuard(cmd);
300     cmdGuard->execute();
301   }
302 }
303 
validateNameOrThrow(const QString & name) const304 CircuitIdentifier PackagePadListModel::validateNameOrThrow(
305     const QString& name) const {
306   if (mPadList && mPadList->contains(name)) {
307     throw RuntimeError(
308         __FILE__, __LINE__,
309         tr("There is already a pad with the name \"%1\".").arg(name));
310   }
311   return CircuitIdentifier(name);  // can throw
312 }
313 
getNextPadNameProposal() const314 QString PackagePadListModel::getNextPadNameProposal() const noexcept {
315   int i = 1;
316   while (mPadList->contains(QString::number(i))) {
317     ++i;
318   }
319   return QString::number(i);
320 }
321 
322 /*******************************************************************************
323  *  End of File
324  ******************************************************************************/
325 
326 }  // namespace editor
327 }  // namespace library
328 }  // namespace librepcb
329