File indexing completed on 2024-05-19 05:42:28

0001 // ct_lvtqtw_tabwidget.cpp                                           -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtprj_projectfile.h>
0021 
0022 #include <ct_lvtqtc_graphicsscene.h>
0023 #include <ct_lvtqtc_graphicsview.h>
0024 #include <ct_lvtqtc_undo_manager.h>
0025 
0026 #include <ct_lvtqtw_graphtabelement.h>
0027 #include <ct_lvtqtw_tabwidget.h>
0028 
0029 #include <ct_lvtldr_nodestorage.h>
0030 
0031 #include <ct_lvtclr_colormanagement.h>
0032 #include <ct_lvtmdl_modelhelpers.h>
0033 
0034 #include <ct_lvtqtc_inputdialog.h>
0035 
0036 #include <QDialog>
0037 #include <QJsonDocument>
0038 #include <QJsonObject>
0039 #include <QMenu>
0040 #include <QString>
0041 #include <QTabBar>
0042 #include <QToolButton>
0043 
0044 #include <unordered_map>
0045 #include <utility>
0046 
0047 using namespace Codethink::lvtldr;
0048 using namespace Codethink::lvtqtc;
0049 using namespace Codethink::lvtmdl;
0050 using namespace Codethink::lvtshr;
0051 using namespace Codethink::lvtprj;
0052 
0053 namespace Codethink::lvtqtw {
0054 
0055 struct TabWidget::Private {
0056     QToolButton *addGraphBtn = nullptr;
0057     std::shared_ptr<lvtclr::ColorManagement> colorManagement;
0058     QString cdbPath;
0059     NodeStorage& nodeStorage;
0060     lvtprj::ProjectFile& projectFile;
0061     UndoManager *undoManager = nullptr;
0062     std::unordered_map<const QUndoCommand *, GraphTabElement *> commandToTab;
0063     lvtplg::PluginManager *pluginManager = nullptr;
0064 
0065     explicit Private(lvtldr::NodeStorage& nodeStorage,
0066                      lvtprj::ProjectFile& projectFile,
0067                      std::shared_ptr<lvtclr::ColorManagement> colorManagement,
0068                      lvtplg::PluginManager *pluginManager = nullptr):
0069         colorManagement(std::move(colorManagement)),
0070         nodeStorage(nodeStorage),
0071         projectFile(projectFile),
0072         pluginManager(pluginManager)
0073     {
0074     }
0075 };
0076 
0077 // --------------------------------------------
0078 // class TabWidget
0079 // --------------------------------------------
0080 
0081 TabWidget::TabWidget(NodeStorage& nodeStorage,
0082                      lvtprj::ProjectFile& projectFile,
0083                      std::shared_ptr<lvtclr::ColorManagement> colorManagement,
0084                      lvtplg::PluginManager *pluginManager,
0085                      QWidget *parent):
0086     QTabWidget(parent),
0087     d(std::make_unique<TabWidget::Private>(nodeStorage, projectFile, std::move(colorManagement), pluginManager))
0088 {
0089     d->addGraphBtn = new QToolButton();
0090     d->addGraphBtn->setText("+");
0091 
0092     // TODO: Icons
0093     setCornerWidget(d->addGraphBtn, Qt::TopLeftCorner);
0094     connect(d->addGraphBtn, &QToolButton::clicked, this, [this]() {
0095         this->openNewGraphTab();
0096     });
0097     connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::closeTab);
0098     setTabsClosable(true);
0099 
0100     setDocumentMode(true);
0101     openNewGraphTab();
0102 
0103     tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
0104     connect(tabBar(), &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
0105         const auto idx = tabBar()->tabAt(pos);
0106         if (idx == -1) {
0107             return;
0108         }
0109 
0110         QMenu menu;
0111         if (tabIcon(idx).isNull()) {
0112             auto *action = menu.addAction(tr("Bookmark this tab"));
0113             connect(action, &QAction::triggered, this, [this, idx] {
0114                 saveBookmarkByTabIndex(idx);
0115             });
0116         } else {
0117             auto *action = menu.addAction(tr("Remove bookmark"));
0118             connect(action, &QAction::triggered, this, [this, idx] {
0119                 setTabIcon(idx, QIcon());
0120 
0121                 // QWidgets sometimes adds a `&` to denote mnemonic,
0122                 // remove it.
0123                 auto text = tabText(idx);
0124                 text.remove(QLatin1Char('&'));
0125                 d->projectFile.removeBookmark(text);
0126             });
0127         }
0128         menu.exec(tabBar()->mapToGlobal(pos));
0129     });
0130 }
0131 
0132 TabWidget::~TabWidget() noexcept = default;
0133 
0134 void TabWidget::saveBookmarkByTabIndex(int tabIdx)
0135 {
0136     InputDialog dlg;
0137     dlg.addTextField("name", tr("Bookmark Name:"));
0138     dlg.finish();
0139     auto res = dlg.exec();
0140     if (res == QDialog::DialogCode::Rejected) {
0141         return;
0142     }
0143 
0144     const auto text = std::any_cast<QString>(dlg.fieldValue("name"));
0145     saveBookmark(text, tabIdx, Codethink::lvtprj::ProjectFile::Bookmark);
0146 }
0147 
0148 void TabWidget::closeTab(int idx)
0149 {
0150     QWidget *widgetAt = widget(idx);
0151     removeTab(idx);
0152 
0153     widgetAt->deleteLater();
0154 
0155     if (count() == 0) {
0156         openNewGraphTab();
0157     }
0158 }
0159 
0160 void TabWidget::setCurrentTabText(const QString& fullyQualifiedName)
0161 {
0162     setTabText(currentIndex(), fullyQualifiedName);
0163     Q_EMIT currentTabTextChanged(fullyQualifiedName);
0164 }
0165 
0166 GraphTabElement *TabWidget::createTabElement()
0167 {
0168     auto *tabElement = new GraphTabElement(d->nodeStorage, d->projectFile, this);
0169     if (d->pluginManager) {
0170         tabElement->setPluginManager(*d->pluginManager);
0171     }
0172     if (d->undoManager) {
0173         auto *gv = tabElement->graphicsView();
0174         gv->setUndoManager(d->undoManager);
0175         connect(gv,
0176                 &GraphicsView::onUndoCommandReceived,
0177                 this,
0178                 [this, tabElement](GraphicsView *view, QUndoCommand *command) {
0179                     d->commandToTab[command] = tabElement;
0180                 });
0181     }
0182     auto *view = tabElement->graphicsView();
0183     view->setColorManagement(d->colorManagement);
0184 
0185     connect(tabElement, &GraphTabElement::historyUpdate, this, [this](const QString& bookmark) {
0186         loadBookmark(d->projectFile.getBookmark(bookmark), HistoryType::NoHistory);
0187     });
0188     return tabElement;
0189 }
0190 
0191 void TabWidget::replaceGraphAt(int idx, const QString& qualifiedName)
0192 {
0193     auto *tabElement = qobject_cast<Codethink::lvtqtw::GraphTabElement *>(widget(idx));
0194     auto *scene = qobject_cast<Codethink::lvtqtc::GraphicsScene *>(tabElement->graphicsView()->scene());
0195     scene->clearGraph();
0196     scene->loadEntityByQualifiedName(qualifiedName, QPoint{});
0197 }
0198 
0199 void TabWidget::openNewGraphTab(std::optional<QSet<QString>> qualifiedNames)
0200 {
0201     auto *tabElement = createTabElement();
0202     int tabIdx = addTab(tabElement, tr("Unnamed %1").arg(count()));
0203     setCurrentIndex(tabIdx);
0204 
0205     if (!qualifiedNames || qualifiedNames.value().empty()) {
0206         return;
0207     }
0208 
0209     auto *scene = qobject_cast<Codethink::lvtqtc::GraphicsScene *>(tabElement->graphicsView()->scene());
0210     for (const auto& qualifiedName : qualifiedNames.value()) {
0211         scene->loadEntityByQualifiedName(qualifiedName, QPoint{});
0212     }
0213     scene->reLayout();
0214 }
0215 
0216 lvtqtc::GraphicsView *TabWidget::graphicsView()
0217 {
0218     auto *tabElement = qobject_cast<lvtqtw::GraphTabElement *>(currentWidget());
0219     if (!tabElement) {
0220         return nullptr; // RETURN
0221     }
0222     return tabElement->graphicsView();
0223 }
0224 
0225 void TabWidget::setUndoManager(lvtqtc::UndoManager *undoManager)
0226 {
0227     d->undoManager = undoManager;
0228     auto onBeforeUndoRedo = [this](const QUndoCommand *command) {
0229         try {
0230             auto *tab = d->commandToTab.at(command);
0231             setCurrentWidget(tab);
0232         } catch (std::out_of_range const&) {
0233             // Ignore tab change if not found
0234             return;
0235         }
0236     };
0237     connect(d->undoManager, &UndoManager::onBeforeUndo, this, onBeforeUndoRedo);
0238     connect(d->undoManager, &UndoManager::onBeforeRedo, this, onBeforeUndoRedo);
0239     for (int index = 0; index < count(); ++index) {
0240         auto *tab = qobject_cast<GraphTabElement *>(widget(index));
0241         auto *gv = tab->graphicsView();
0242         gv->setUndoManager(undoManager);
0243         connect(gv, &GraphicsView::onUndoCommandReceived, this, [this, tab](GraphicsView *view, QUndoCommand *command) {
0244             d->commandToTab[command] = tab;
0245         });
0246     }
0247 }
0248 
0249 void TabWidget::saveTabsOnProject(ProjectFile::BookmarkType type)
0250 {
0251     for (int i = 0; i < count(); i++) {
0252         saveBookmark(tabText(i), i, type);
0253     }
0254 }
0255 
0256 void TabWidget::saveBookmark(const QString& title, int idx, ProjectFile::BookmarkType type)
0257 {
0258     auto *tabElement = qobject_cast<Codethink::lvtqtw::GraphTabElement *>(widget(idx));
0259     tabElement->saveBookmark(title, type);
0260 
0261     setTabText(idx, title);
0262 }
0263 
0264 void TabWidget::loadBookmark(const QJsonDocument& doc, lvtshr::HistoryType historyType)
0265 {
0266     QJsonObject obj = doc.object();
0267     const auto idx = currentIndex();
0268     auto *tabElement = qobject_cast<Codethink::lvtqtw::GraphTabElement *>(widget(idx));
0269 
0270     setTabText(idx, obj["tabname"].toString());
0271     setTabIcon(idx, QIcon(":/icons/build"));
0272 
0273     tabElement->loadBookmark(doc, historyType);
0274 }
0275 
0276 } // end namespace Codethink::lvtqtw