File indexing completed on 2024-05-12 05:53:56

0001 /*
0002  * Copyright (c) 2018 Sune Vuorela <sune@vuorela.dk>
0003  *
0004  * Permission is hereby granted, free of charge, to any person
0005  * obtaining a copy of this software and associated documentation
0006  * files (the "Software"), to deal in the Software without
0007  * restriction, including without limitation the rights to use,
0008  * copy, modify, merge, publish, distribute, sublicense, and/or sell
0009  * copies of the Software, and to permit persons to whom the
0010  * Software is furnished to do so, subject to the following
0011  * conditions:
0012  *
0013  * The above copyright notice and this permission notice shall be
0014  * included in all copies or substantial portions of the Software.
0015  *
0016  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
0017  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
0018  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
0019  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
0020  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
0021  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
0022  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
0023  * OTHER DEALINGS IN THE SOFTWARE.
0024  */
0025 
0026 #include "mainwindow.h"
0027 #include <memory>
0028 #include <QApplication>
0029 #include <QHBoxLayout>
0030 #include <QDockWidget>
0031 #include <QToolBar>
0032 #include <QDesktopServices>
0033 #include "fspane.h"
0034 #include "mainpane.h"
0035 #include "metadatapane.h"
0036 #include "rawviewpane.h"
0037 #include "activedocument.h"
0038 #include "scanner.h"
0039 #include <QFileDialog>
0040 #include <QProcess>
0041 #include "treepane.h"
0042 #include "listpane.h"
0043 #include "ingredientsparserpane.h"
0044 #include <QStatusBar>
0045 #include <QSettings>
0046 #include <QAbstractItemModel>
0047 #include <QRandomGenerator>
0048 #include <QStandardPaths>
0049 
0050 auto mkdock(const QString& title) {
0051     auto dock = std::make_unique<QDockWidget>(title);
0052     dock->setObjectName(title);
0053     return dock;
0054 }
0055 
0056 // QDesktopServices::openUrl just gives whatever is available to open it
0057 // in the markdown case, it is quite likely to be a viewer, so just try to
0058 // find some random editors, and if none of them are found
0059 // go for QDesktopServices::openUrl and hope the best
0060 static void openFile(const QString& file)
0061 {
0062     QStringList exes = {"kwrite", "kate", "gedit", "notepad++", "atom", "gvim"};
0063     QString foundExe;
0064     for(const auto& exe : qAsConst(exes)) {
0065         foundExe = QStandardPaths::findExecutable(exe);
0066         if (!foundExe.isEmpty()) {
0067             break;
0068         }
0069     }
0070     if (!foundExe.isEmpty()) {
0071         QProcess::startDetached(foundExe, QStringList() << file);
0072     } else {
0073         QDesktopServices::openUrl(QUrl::fromLocalFile(file));
0074     }
0075 }
0076 
0077 MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
0078 {
0079     m_activeDocument = std::make_unique<ActiveDocument>();
0080     m_scanner = std::make_unique<Scanner>();
0081     QDockWidget* fsdockptr = nullptr; // we need a handle to be able to tabify with it later.
0082     QVector<QDockWidget*> developerDocks; // want to hide them at the end.
0083     {
0084         auto mainpane = std::make_unique<MainPane>();
0085         m_activeDocument->registerListener(mainpane.get());
0086         m_mainPane = mainpane.get();
0087         setCentralWidget(mainpane.release());
0088         connect(m_mainPane, &MainPane::notifySimple, this, &MainWindow::notifyStatusBar);
0089     }
0090     {
0091         auto rawviewpane = std::make_unique<RawViewPane>();
0092         m_activeDocument->registerListener(rawviewpane.get());
0093         auto rawviewdock = mkdock("Raw view");
0094         developerDocks << rawviewdock.get();
0095         rawviewdock->setWidget(rawviewpane.release());
0096         addDockWidget(Qt::RightDockWidgetArea, rawviewdock.release());
0097     }
0098     {
0099         auto fspane = std::make_unique<FsPane>();
0100         connect(fspane.get(), &FsPane::fileSelected, m_activeDocument.get(), [this](const QString& path) {
0101             m_activeDocument->openPath(path);
0102         });
0103         auto fsdock = mkdock("Recipes");
0104         m_fsPane = fspane.get();
0105         fsdock->setWidget(fspane.release());
0106         fsdockptr = fsdock.get();
0107         addDockWidget(Qt::LeftDockWidgetArea, fsdock.release());
0108 
0109     }
0110     {
0111         auto metadatapane = std::make_unique<MetaDataPane>();
0112         m_activeDocument->registerListener(metadatapane.get());
0113         auto metadatadock = mkdock("Metadata");
0114         metadatadock->setWidget(metadatapane.release());
0115         developerDocks << metadatadock.get();
0116         addDockWidget(Qt::LeftDockWidgetArea, metadatadock.release());
0117     }
0118     {
0119         auto ingredientsparserpane = std::make_unique<IngredientsParserPane>();
0120         auto ingredientsparserdock = mkdock("Ingredients line");
0121         ingredientsparserdock->setWidget(ingredientsparserpane.release());
0122         developerDocks << ingredientsparserdock.get();
0123         addDockWidget(Qt::RightDockWidgetArea, ingredientsparserdock.release());
0124     }
0125     {
0126         auto ingredientspane = std::make_unique<TreePane>();
0127         connect(ingredientspane.get(), &TreePane::fileSelected, m_activeDocument.get(), [this](const QString& path) {
0128             m_activeDocument->openPath(path);
0129         });
0130         auto tagspane = std::make_unique<TreePane>();
0131         connect(tagspane.get(), &TreePane::fileSelected, m_activeDocument.get(), [this](const QString& path) {
0132             m_activeDocument->openPath(path);
0133         });
0134         auto titlelist = std::make_unique<ListPane>();
0135         connect(titlelist.get(), &ListPane::fileSelected, m_activeDocument.get(), [this](const QString& path) {
0136             m_activeDocument->openPath(path);
0137         });
0138 
0139         connect(m_scanner.get(), &Scanner::dataUpdated, this, [rawingredientspane = ingredientspane.get(),rawtagspane = tagspane.get(),rawtitlelist = titlelist.get(),this]() {
0140             rawingredientspane->setModel(m_scanner->parsedIngredients());
0141             rawtagspane->setModel(m_scanner->parsedTags());
0142             auto titlelist = m_scanner->parsedTitleList();
0143             rawtitlelist->setModel(titlelist);
0144             notifyStatusBar(QString("Found %1 recipes").arg(titlelist->rowCount()));
0145             m_fsPane->setFileNameTitleMap(m_scanner->parsedFileNameTitleMap());
0146         });
0147         connect(this, &MainWindow::clear, this, [rawingredientspane = ingredientspane.get(), rawtagspane = tagspane.get(), rawtitlelist = titlelist.get(),this]() {
0148             rawingredientspane->setModel(nullptr);
0149             rawtagspane->setModel(nullptr);
0150             rawtitlelist->setModel(nullptr);
0151             m_fsPane->setFileNameTitleMap({});
0152         });
0153 
0154         {
0155             auto titlesdock = mkdock("Title");
0156             titlesdock->setWidget(titlelist.release());
0157             auto titlesdockptr = titlesdock.get();
0158             addDockWidget(Qt::LeftDockWidgetArea,titlesdock.release());
0159             tabifyDockWidget(titlesdockptr, fsdockptr);
0160         }
0161         {
0162             auto tagsdock = mkdock("Tags");
0163             tagsdock->setWidget(tagspane.release());
0164             auto tagsdockptr = tagsdock.get();
0165             addDockWidget(Qt::LeftDockWidgetArea,tagsdock.release());
0166             auto ingredientsdock = mkdock("Ingredients");
0167             auto ingredientsdockptr = ingredientsdock.get();
0168             ingredientsdock->setWidget(ingredientspane.release());
0169             addDockWidget(Qt::LeftDockWidgetArea,ingredientsdock.release());
0170             tabifyDockWidget(ingredientsdockptr,tagsdockptr);
0171         }
0172 
0173     }
0174     for(auto dock : qAsConst(developerDocks)) {
0175         dock->hide();
0176     }
0177     auto toolbar = addToolBar("Main Toolbar");
0178     toolbar->setObjectName("Main Toolbar");
0179     {
0180         auto action = toolbar->addAction(QIcon::fromTheme("document-open-folder"),"Open collection",this, &MainWindow::openFolder);
0181         action->setShortcut(QKeySequence(QKeySequence::Open));
0182     }
0183     {
0184         auto action = toolbar->addAction(QIcon::fromTheme("view-refresh"),"Reload collection", m_scanner.get(), &Scanner::doUpdate);
0185         action->setShortcut(QKeySequence(QKeySequence::Refresh));
0186     }
0187     toolbar->addSeparator();
0188     {
0189         auto action = toolbar->addAction(QIcon::fromTheme("document-new"), "New recipe", this, &MainWindow::newRecipe);
0190         action->setShortcut(QKeySequence(QKeySequence::New));
0191     }
0192     {
0193         auto action = toolbar->addAction(QIcon::fromTheme("edit-entry"),"Edit current recipe", this, &MainWindow::editActiveRecipe);
0194         action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
0195     }
0196     {
0197         auto action = toolbar->addAction(QIcon::fromTheme("document-print"),"Print current recipe", m_mainPane, &MainPane::print);
0198         action->setShortcut(QKeySequence(QKeySequence::Print));
0199     }
0200     {
0201         auto action = new QAction("Quit", this);
0202         action->setShortcut(QKeySequence::Quit);
0203         addAction(action);
0204         connect(action, &QAction::triggered, qApp, &QApplication::quit);
0205     }
0206 
0207     toolbar->addAction(QIcon::fromTheme("randomize"), "Display random recipe", this, &MainWindow::showRandomRecipe);
0208     toolbar->addAction(QIcon::fromTheme("document-print-preview"),"Print preview current recipe", m_mainPane, &MainPane::printPreview);
0209     toolbar->toggleViewAction()->setEnabled(false);
0210     addToolBar(Qt::TopToolBarArea, toolbar);
0211     QSettings s;
0212     s.beginGroup("General");
0213     setCurrentFolder(s.value("location", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/recipes/").toString());
0214     restoreState(s.value("windowState", QByteArray()).toByteArray());
0215     statusBar()->show();
0216     toolbar->show();
0217 }
0218 
0219 MainWindow::~MainWindow()
0220 {
0221     QSettings s;
0222     s.beginGroup("General");
0223     if (QFile::exists(m_currentFolder)) {
0224         s.setValue("location", m_currentFolder);
0225     }
0226     s.setValue("windowState", saveState());
0227     s.sync();
0228 }
0229 
0230 void MainWindow::editActiveRecipe()
0231 {
0232     auto path = m_activeDocument->currentPath();
0233     if (QFile::exists(path)) {
0234         openFile(path);
0235     }
0236 }
0237 
0238 void MainWindow::openFolder()
0239 {
0240     QString folder = QFileDialog::getExistingDirectory(this,"Open Folder",QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
0241     if (folder.isEmpty()) {
0242         notifyStatusBar("Cancelled");
0243         return;
0244     }
0245     setCurrentFolder(folder);
0246 
0247 }
0248 
0249 void MainWindow::newRecipe()
0250 {
0251     QString file = QFileDialog::getSaveFileName(this, "Create Recipe", m_currentFolder, "Recipes (*.recipe.md)");
0252     if (!file.endsWith(".recipe.md")) {
0253         notifyStatusBar("Cancelled");
0254         return;
0255     }
0256     if (!QFile::exists(file)) {
0257         QFile::copy(":/docs/template.md", file);
0258         QFile::setPermissions(file,QFileDevice::WriteOwner | QFile::permissions(file));
0259     }
0260     m_activeDocument->openPath(file);
0261     openFile(file);
0262 }
0263 
0264 void MainWindow::showRandomRecipe()
0265 {
0266     QHash<QString, QString> map = m_scanner->parsedFileNameTitleMap();
0267     if (map.isEmpty()) {
0268         notifyStatusBar("No recipe!");
0269         return;
0270     }
0271     auto iterator = map.keyBegin();
0272     std::advance(iterator, QRandomGenerator::global()->bounded(map.size()));
0273     m_activeDocument->openPath(*iterator);
0274 }
0275 
0276 void MainWindow::notifyStatusBar(const QString& message)
0277 {
0278     statusBar()->showMessage(message, 5000);
0279 }
0280 
0281 
0282 void MainWindow::setCurrentFolder(const QString& folder)
0283 {
0284     m_currentFolder = folder;
0285 
0286     Q_EMIT clear();
0287     m_activeDocument->openPath(QString());
0288     m_fsPane->setRootPath(folder);
0289     notifyStatusBar(QString("Parsing %1").arg(folder));
0290     m_scanner->setRootPath(folder);
0291 
0292 }