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 "packagecategoryeditorwidget.h"
24 
25 #include "../common/categorychooserdialog.h"
26 #include "../common/categorytreelabeltextbuilder.h"
27 #include "ui_packagecategoryeditorwidget.h"
28 
29 #include <librepcb/library/cat/cmd/cmdlibrarycategoryedit.h>
30 #include <librepcb/library/cat/packagecategory.h>
31 #include <librepcb/library/msg/msgmissingauthor.h>
32 #include <librepcb/library/msg/msgnamenottitlecase.h>
33 #include <librepcb/workspace/workspace.h>
34 
35 #include <QtCore>
36 #include <QtWidgets>
37 
38 /*******************************************************************************
39  *  Namespace
40  ******************************************************************************/
41 namespace librepcb {
42 namespace library {
43 namespace editor {
44 
45 /*******************************************************************************
46  *  Constructors / Destructor
47  ******************************************************************************/
48 
PackageCategoryEditorWidget(const Context & context,const FilePath & fp,QWidget * parent)49 PackageCategoryEditorWidget::PackageCategoryEditorWidget(const Context& context,
50                                                          const FilePath& fp,
51                                                          QWidget* parent)
52   : EditorWidgetBase(context, fp, parent),
53     mUi(new Ui::PackageCategoryEditorWidget) {
54   mUi->setupUi(this);
55   mUi->lstMessages->setHandler(this);
56   mUi->lstMessages->setProvideFixes(!mContext.readOnly);
57   mUi->edtName->setReadOnly(mContext.readOnly);
58   mUi->edtDescription->setReadOnly(mContext.readOnly);
59   mUi->edtKeywords->setReadOnly(mContext.readOnly);
60   mUi->edtAuthor->setReadOnly(mContext.readOnly);
61   mUi->edtVersion->setReadOnly(mContext.readOnly);
62   mUi->cbxDeprecated->setCheckable(!mContext.readOnly);
63   mUi->btnChooseParentCategory->setEnabled(!mContext.readOnly);
64   mUi->btnResetParentCategory->setEnabled(!mContext.readOnly);
65   setWindowIcon(QIcon(":/img/places/folder_green.png"));
66   connect(mUi->btnChooseParentCategory, &QToolButton::clicked, this,
67           &PackageCategoryEditorWidget::btnChooseParentCategoryClicked);
68   connect(mUi->btnResetParentCategory, &QToolButton::clicked, this,
69           &PackageCategoryEditorWidget::btnResetParentCategoryClicked);
70 
71   // Load element.
72   mCategory.reset(new PackageCategory(std::unique_ptr<TransactionalDirectory>(
73       new TransactionalDirectory(mFileSystem))));  // can throw
74   updateMetadata();
75 
76   // Reload metadata on undo stack state changes.
77   connect(mUndoStack.data(), &UndoStack::stateModified, this,
78           &PackageCategoryEditorWidget::updateMetadata);
79 
80   // Handle changes of metadata.
81   connect(mUi->edtName, &QLineEdit::editingFinished, this,
82           &PackageCategoryEditorWidget::commitMetadata);
83   connect(mUi->edtDescription, &PlainTextEdit::editingFinished, this,
84           &PackageCategoryEditorWidget::commitMetadata);
85   connect(mUi->edtKeywords, &QLineEdit::editingFinished, this,
86           &PackageCategoryEditorWidget::commitMetadata);
87   connect(mUi->edtAuthor, &QLineEdit::editingFinished, this,
88           &PackageCategoryEditorWidget::commitMetadata);
89   connect(mUi->edtVersion, &QLineEdit::editingFinished, this,
90           &PackageCategoryEditorWidget::commitMetadata);
91   connect(mUi->cbxDeprecated, &QCheckBox::clicked, this,
92           &PackageCategoryEditorWidget::commitMetadata);
93 }
94 
~PackageCategoryEditorWidget()95 PackageCategoryEditorWidget::~PackageCategoryEditorWidget() noexcept {
96 }
97 
98 /*******************************************************************************
99  *  Public Slots
100  ******************************************************************************/
101 
save()102 bool PackageCategoryEditorWidget::save() noexcept {
103   // Commit metadata.
104   QString errorMsg = commitMetadata();
105   if (!errorMsg.isEmpty()) {
106     QMessageBox::critical(this, tr("Invalid metadata"), errorMsg);
107     return false;
108   }
109 
110   // Save element.
111   try {
112     mCategory->save();  // can throw
113     mFileSystem->save();  // can throw
114     return EditorWidgetBase::save();
115   } catch (const Exception& e) {
116     QMessageBox::critical(this, tr("Save failed"), e.getMsg());
117     return false;
118   }
119 }
120 
121 /*******************************************************************************
122  *  Private Methods
123  ******************************************************************************/
124 
updateMetadata()125 void PackageCategoryEditorWidget::updateMetadata() noexcept {
126   setWindowTitle(*mCategory->getNames().getDefaultValue());
127   mUi->edtName->setText(*mCategory->getNames().getDefaultValue());
128   mUi->edtDescription->setPlainText(
129       mCategory->getDescriptions().getDefaultValue());
130   mUi->edtKeywords->setText(mCategory->getKeywords().getDefaultValue());
131   mUi->edtAuthor->setText(mCategory->getAuthor());
132   mUi->edtVersion->setText(mCategory->getVersion().toStr());
133   mUi->cbxDeprecated->setChecked(mCategory->isDeprecated());
134   mParentUuid = mCategory->getParentUuid();
135   updateCategoryLabel();
136 }
137 
commitMetadata()138 QString PackageCategoryEditorWidget::commitMetadata() noexcept {
139   try {
140     QScopedPointer<CmdLibraryCategoryEdit> cmd(
141         new CmdLibraryCategoryEdit(*mCategory));
142     try {
143       // throws on invalid name
144       cmd->setName("", ElementName(mUi->edtName->text().trimmed()));
145     } catch (const Exception& e) {
146     }
147     cmd->setDescription("", mUi->edtDescription->toPlainText().trimmed());
148     cmd->setKeywords("", mUi->edtKeywords->text().trimmed());
149     try {
150       // throws on invalid version
151       cmd->setVersion(Version::fromString(mUi->edtVersion->text().trimmed()));
152     } catch (const Exception& e) {
153     }
154     cmd->setAuthor(mUi->edtAuthor->text().trimmed());
155     cmd->setDeprecated(mUi->cbxDeprecated->isChecked());
156     cmd->setParentUuid(mParentUuid);
157 
158     // Commit all changes.
159     mUndoStack->execCmd(cmd.take());  // can throw
160 
161     // Reload metadata into widgets to discard invalid input.
162     updateMetadata();
163   } catch (const Exception& e) {
164     return e.getMsg();
165   }
166   return QString();
167 }
168 
runChecks(LibraryElementCheckMessageList & msgs) const169 bool PackageCategoryEditorWidget::runChecks(
170     LibraryElementCheckMessageList& msgs) const {
171   msgs = mCategory->runChecks();  // can throw
172   mUi->lstMessages->setMessages(msgs);
173   return true;
174 }
175 
176 template <>
fixMsg(const MsgNameNotTitleCase & msg)177 void PackageCategoryEditorWidget::fixMsg(const MsgNameNotTitleCase& msg) {
178   mUi->edtName->setText(*msg.getFixedName());
179   commitMetadata();
180 }
181 
182 template <>
fixMsg(const MsgMissingAuthor & msg)183 void PackageCategoryEditorWidget::fixMsg(const MsgMissingAuthor& msg) {
184   Q_UNUSED(msg);
185   mUi->edtAuthor->setText(getWorkspaceSettingsUserName());
186   commitMetadata();
187 }
188 
189 template <typename MessageType>
fixMsgHelper(std::shared_ptr<const LibraryElementCheckMessage> msg,bool applyFix)190 bool PackageCategoryEditorWidget::fixMsgHelper(
191     std::shared_ptr<const LibraryElementCheckMessage> msg, bool applyFix) {
192   if (msg) {
193     if (auto m = msg->as<MessageType>()) {
194       if (applyFix) fixMsg(*m);  // can throw
195       return true;
196     }
197   }
198   return false;
199 }
200 
processCheckMessage(std::shared_ptr<const LibraryElementCheckMessage> msg,bool applyFix)201 bool PackageCategoryEditorWidget::processCheckMessage(
202     std::shared_ptr<const LibraryElementCheckMessage> msg, bool applyFix) {
203   if (fixMsgHelper<MsgNameNotTitleCase>(msg, applyFix)) return true;
204   if (fixMsgHelper<MsgMissingAuthor>(msg, applyFix)) return true;
205   return false;
206 }
207 
btnChooseParentCategoryClicked()208 void PackageCategoryEditorWidget::btnChooseParentCategoryClicked() noexcept {
209   PackageCategoryChooserDialog dialog(mContext.workspace);
210   if (dialog.exec()) {
211     mParentUuid = dialog.getSelectedCategoryUuid();
212     commitMetadata();
213   }
214 }
215 
btnResetParentCategoryClicked()216 void PackageCategoryEditorWidget::btnResetParentCategoryClicked() noexcept {
217   mParentUuid = tl::nullopt;
218   commitMetadata();
219 }
220 
updateCategoryLabel()221 void PackageCategoryEditorWidget::updateCategoryLabel() noexcept {
222   const workspace::WorkspaceLibraryDb& db = mContext.workspace.getLibraryDb();
223   PackageCategoryTreeLabelTextBuilder textBuilder(db, getLibLocaleOrder(),
224                                                   *mUi->lblParentCategories);
225   textBuilder.setEndlessRecursionUuid(mCategory->getUuid());
226   textBuilder.setHighlightLastLine(true);
227   textBuilder.updateText(mParentUuid, mUi->edtName->text());
228 }
229 
230 /*******************************************************************************
231  *  End of File
232  ******************************************************************************/
233 
234 }  // namespace editor
235 }  // namespace library
236 }  // namespace librepcb
237