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