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 }