diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0d7b947..6511221 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,371 +1,372 @@ /* Atelier KDE Printer Host for 3D Printing Copyright (C) <2016> Author: Lays Rodrigues - lays.rodrigues@kde.org Chris Rizzitello - rizzitello@kde.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "dialogs/choosefiledialog.h" #include "dialogs/profilesdialog.h" #include "mainwindow.h" #include "widgets/3dview/viewer3d.h" #include "widgets/atcoreinstancewidget.h" #include "widgets/videomonitorwidget.h" #include "widgets/welcomewidget.h" MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) , m_currEditorView(nullptr) , m_theme(getTheme()) , m_instances(new QTabWidget(this)) { initWidgets(); setupActions(); setAcceptDrops(true); connect(m_instances, &QTabWidget::tabCloseRequested, this, [this](int index) { auto tempWidget = qobject_cast(m_instances->widget(index)); if (tempWidget->isPrinting()) { if (askToClose()) { delete tempWidget; } else { return; } } else { delete tempWidget; } if (m_instances->count() == 1) { m_instances->setTabsClosable(false); m_instances->setMovable(false); } }); } void MainWindow::closeEvent(QCloseEvent *event) { bool closePrompt = false; for (int i = 0; i < m_instances->count(); i++) { AtCoreInstanceWidget *instance = qobject_cast(m_instances->widget(i)); if (instance->isPrinting()) { closePrompt = true; break; } } if (closePrompt) { if (askToClose()) { event->accept(); } else { event->ignore(); } } } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { event->accept(); } void MainWindow::dropEvent(QDropEvent *event) { const QMimeData *mimeData = event->mimeData(); if (mimeData->hasUrls()) { processDropEvent(mimeData->urls()); } } void MainWindow::processDropEvent(const QList &fileList) { for (const auto &url : fileList) { //Loop thru the urls and only load ones ending our "supported" formats QString ext = url.toLocalFile().split('.').last(); if (ext.contains("gcode", Qt::CaseInsensitive) || ext.contains("gco", Qt::CaseInsensitive)) { loadFile(url); } } } void MainWindow::initWidgets() { setupLateralArea(); newAtCoreInstance(); // View: // Sidebar, Sidebar Controls, Printer Tabs. // Sidebar Controls and Printer Tabs can be resized, Sidebar can't. auto splitter = new QSplitter(); splitter->addWidget(m_lateral.m_stack); splitter->addWidget(m_instances); auto addTabBtn = new QToolButton(); addTabBtn->setIconSize(QSize(fontMetrics().lineSpacing(), fontMetrics().lineSpacing())); addTabBtn->setIcon(QIcon::fromTheme("list-add", QIcon(QString(":/%1/addTab").arg(m_theme)))); addTabBtn->setToolTip(i18n("Create new instance")); addTabBtn->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); connect(addTabBtn, &QToolButton::clicked, this, &MainWindow::newAtCoreInstance); m_instances->setCornerWidget(addTabBtn, Qt::TopLeftCorner); auto *centralLayout = new QHBoxLayout(); centralLayout->addWidget(m_lateral.m_toolBar); centralLayout->addWidget(splitter); auto *centralWidget = new QWidget(); centralWidget->setLayout(centralLayout); setCentralWidget(centralWidget); } void MainWindow::newAtCoreInstance() { auto newInstanceWidget = new AtCoreInstanceWidget(); QString name = QString::number(m_instances->addTab(newInstanceWidget, i18n("Connect a printer"))); newInstanceWidget->setObjectName(name); newInstanceWidget->setFileCount(m_openFiles.size()); connect(this, &MainWindow::profilesChanged, newInstanceWidget, &AtCoreInstanceWidget::updateProfileData); connect(newInstanceWidget, &AtCoreInstanceWidget::requestProfileDialog, this, [this] { std::unique_ptr pd(new ProfilesDialog); pd->exec(); emit(profilesChanged()); }); connect(newInstanceWidget, &AtCoreInstanceWidget::requestFileChooser, this, [newInstanceWidget, this] { switch (m_openFiles.size()) { case 0: QMessageBox::warning(this, i18n("Error"), i18n("There's no GCode File open. \n Please select a file and try again."), QMessageBox::Ok); break; case 1: newInstanceWidget->printFile(m_openFiles.at(0)); break; default: ChooseFileDialog dialog(this, m_openFiles); if (dialog.exec() == QDialog::Accepted) { newInstanceWidget->printFile(dialog.choosenFile()); } } }); connect(newInstanceWidget, &AtCoreInstanceWidget::connectionChanged, this, &MainWindow::atCoreInstanceNameChange); if (m_instances->count() > 1) { m_instances->setTabsClosable(true); m_instances->setMovable(true); m_instances->setCurrentIndex(m_instances->count() - 1); } } // Move to LateralArea. void MainWindow::setupLateralArea() { m_lateral.m_toolBar = new QWidget(); m_lateral.m_stack = new QStackedWidget(); auto buttonLayout = new QVBoxLayout(); auto setupButton = [this, buttonLayout](const QString & key, const QString & text, const QIcon & icon, QWidget * w) { auto *btn = new QPushButton(m_lateral.m_toolBar); btn->setToolTip(text); btn->setAutoExclusive(true); btn->setCheckable(true); //3d view is on top set it checked so users see its selected. btn->setChecked(key == QStringLiteral("welcome")); btn->setIcon(icon); //Set an iconSize based on the DPI. //96 was considered to be the "standard" DPI for years. //Hi-dpi monitors have a higher DPI //Tiny or old screen could have a lower DPI. //Start our iconSize at 16 so with a DPI less then 96 we get a sane iconsize. int iconSize = 16 + ((logicalDpiX() / 96) * 16); btn->setIconSize(QSize(iconSize, iconSize)); btn->setFixedSize(btn->iconSize()); btn->setFlat(true); m_lateral.m_stack->addWidget(w); m_lateral.m_map[key] = {btn, w}; buttonLayout->addWidget(btn); connect(btn, &QPushButton::clicked, this, [this, w, btn] { if (m_lateral.m_stack->currentWidget() == w) { m_lateral.m_stack->setHidden(m_lateral.m_stack->isVisible()); if (m_lateral.m_stack->isHidden()) { btn->setCheckable(false); btn->setCheckable(true); } } else { m_lateral.m_stack->setHidden(false); m_lateral.m_stack->setCurrentWidget(w); } toggleGCodeActions(); }); }; m_gcodeEditor = new GCodeEditorWidget(this); connect(m_gcodeEditor, &GCodeEditorWidget::updateClientFactory, this, &MainWindow::updateClientFactory); + connect(m_gcodeEditor, &GCodeEditorWidget::droppedUrls, this, &MainWindow::processDropEvent); connect(m_gcodeEditor, &GCodeEditorWidget::fileClosed, this, [this](const QUrl & file) { m_openFiles.removeAll(file); }); auto *viewer3D = new Viewer3D(this); connect(viewer3D, &Viewer3D::droppedUrls, this, &MainWindow::processDropEvent); connect(m_gcodeEditor, &GCodeEditorWidget::currentFileChanged, this, [this, viewer3D](const QUrl & url) { viewer3D->drawModel(url.toString()); }); setupButton("welcome", i18n("&Welcome"), QIcon::fromTheme("go-home", QIcon(QString(":/%1/home").arg(m_theme))), new WelcomeWidget(this)); setupButton("3d", i18n("&3D"), QIcon::fromTheme("draw-cuboid", QIcon(QString(":/%1/3d").arg(m_theme))), viewer3D); setupButton("gcode", i18n("&GCode"), QIcon::fromTheme("accessories-text-editor", QIcon(":/icon/edit")), m_gcodeEditor); setupButton("video", i18n("&Video"), QIcon::fromTheme("camera-web", QIcon(":/icon/video")), new VideoMonitorWidget(this)); buttonLayout->addStretch(); m_lateral.m_toolBar->setLayout(buttonLayout); } void MainWindow::setupActions() { // Actions for the Toolbar QAction *action; action = actionCollection()->addAction(QStringLiteral("open")); action->setIcon(QIcon::fromTheme("document-open", QIcon(QString(":/%1/open").arg(m_theme)))); action->setText(i18n("&Open")); actionCollection()->setDefaultShortcut(action, QKeySequence::Open); connect(action, &QAction::triggered, this, &MainWindow::openActionTriggered); action = actionCollection()->addAction(QStringLiteral("new_instance")); action->setIcon(QIcon::fromTheme("list-add", QIcon(QString(":/%1/addTab").arg(m_theme)))); action->setText(i18n("&New Connection")); actionCollection()->setDefaultShortcut(action, QKeySequence::AddTab); connect(action, &QAction::triggered, this, &MainWindow::newAtCoreInstance); action = actionCollection()->addAction(QStringLiteral("profiles")); action->setIcon(QIcon::fromTheme("document-properties", QIcon(QString(":/%1/configure").arg(m_theme)))); action->setText(i18n("&Profiles")); connect(action, &QAction::triggered, this, [this] { std::unique_ptr pd(new ProfilesDialog); pd->exec(); emit(profilesChanged()); }); action = actionCollection()->addAction(QStringLiteral("quit")); action->setIcon(QIcon::fromTheme("application-exit", QIcon(":/icon/exit"))); action->setText(i18n("&Quit")); actionCollection()->setDefaultShortcut(action, QKeySequence::Quit); connect(action, &QAction::triggered, this, &MainWindow::close); setupGUI(Default, "atelierui"); } void MainWindow::openActionTriggered() { QList fileList = QFileDialog::getOpenFileUrls( this , i18n("Open GCode") , QUrl::fromLocalFile(QDir::homePath()) , i18n("GCode(*.gco *.gcode);;All Files(*.*)") ); for (const auto &url : fileList) { loadFile(url); } } void MainWindow::loadFile(const QUrl &fileName) { if (!fileName.isEmpty()) { m_lateral.get("gcode")->loadFile(fileName); m_lateral.get("3d")->drawModel(fileName.toString()); // Make 3dview focused when opening a file if (m_openFiles.isEmpty() && m_lateral.m_stack->currentWidget() == m_lateral.get("welcome")) { m_lateral.getButton("3d")->setChecked(true); m_lateral.m_stack->setCurrentWidget(m_lateral.get("3d")); } const int tabs = m_instances->count(); if (!m_openFiles.contains(fileName)) { m_openFiles.append(fileName); } for (int i = 0; i < tabs; ++i) { auto instance = qobject_cast(m_instances->widget(i)); instance->setFileCount(m_openFiles.size()); } } } void MainWindow::atCoreInstanceNameChange(const QString &name) { m_instances->setTabText(sender()->objectName().toInt(), name); } QString MainWindow::getTheme() { return palette().text().color().value() >= QColor(Qt::lightGray).value() ? \ QString("dark") : QString("light"); } bool MainWindow::askToClose() { bool rtn = false; int result = QMessageBox::question( this , i18n("Printing") , i18n("Currently printing! \nAre you sure you want to close?") , QMessageBox::Close , QMessageBox::Cancel ); switch (result) { case QMessageBox::Close: rtn = true; break; default: break; } return rtn; } void MainWindow::toggleGCodeActions() { if (m_lateral.m_stack->currentWidget() == m_lateral.m_map["gcode"].second && m_lateral.m_stack->isVisible()) { if (m_currEditorView) { guiFactory()->addClient(m_currEditorView); } } else { guiFactory()->removeClient(m_currEditorView); } } void MainWindow::updateClientFactory(KTextEditor::View *view) { if (m_lateral.m_stack->currentWidget() == m_lateral.m_map["gcode"].second) { if (m_currEditorView) { guiFactory()->removeClient(m_currEditorView); } if (view) { guiFactory()->addClient(view); } } m_currEditorView = view; } diff --git a/src/widgets/gcodeeditorwidget.cpp b/src/widgets/gcodeeditorwidget.cpp index 72544b4..0d76abb 100644 --- a/src/widgets/gcodeeditorwidget.cpp +++ b/src/widgets/gcodeeditorwidget.cpp @@ -1,103 +1,118 @@ /* Atelier KDE Printer Host for 3D Printing Copyright (C) <2016> Author: Lays Rodrigues - lays.rodrigues@kde.org Chris Rizzitello - rizzitello@kde.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include +#include #include +#include #include #include "gcodeeditorwidget.h" GCodeEditorWidget::GCodeEditorWidget(QWidget *parent) : QWidget(parent) , m_tabwidget(new QTabWidget()) { + setAcceptDrops(true); m_editor = KTextEditor::Editor::instance(); setupTabWidget(); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(m_tabwidget); setLayout(layout); } void GCodeEditorWidget::setupTabWidget() { connect(m_tabwidget, &QTabWidget::tabCloseRequested, this, &GCodeEditorWidget::closeTab); connect(m_tabwidget, &QTabWidget::currentChanged, this, &GCodeEditorWidget::currentIndexChanged); m_tabwidget->setTabsClosable(true); } void GCodeEditorWidget::loadFile(const QUrl &file) { //if the file is loaded then reload the document. if (urlDoc.contains(file)) { urlDoc[file]->documentReload(); m_tabwidget->setCurrentIndex(m_tabwidget->indexOf(urlTab[file])); return; } auto doc = newDoc(file); int t = m_tabwidget->addTab(newView(doc), file.fileName()); urlDoc[doc->url()] = doc; urlTab[doc->url()] = m_tabwidget->widget(t); //connect our new document's modified state changed signal. connect(doc, &KTextEditor::Document::modifiedChanged, this, [this, t](const KTextEditor::Document * document) { QString filename = document->url().fileName(QUrl::FullyDecoded); if (document->isModified()) { filename.append(" *"); } m_tabwidget->setTabText(t, filename); }); m_tabwidget->setCurrentIndex(t); } void GCodeEditorWidget::setupInterface(const KTextEditor::View *view) { m_interface = qobject_cast(view); m_interface->setConfigValue("line-numbers", true); } KTextEditor::Document *GCodeEditorWidget::newDoc(const QUrl &file) { KTextEditor::Document *doc = m_editor->createDocument(this); doc->setMode("G-Code"); doc->openUrl(file); doc->setHighlightingMode(QString("G-Code")); return doc; } KTextEditor::View *GCodeEditorWidget::newView(KTextEditor::Document *doc) { auto view = doc->createView(this); + // Connection is a hack using undocumented parts of KTextEditor::View. + // One day this may break, KTextEditor::View needs this added correcty as a real slot to the API. + // Hopefully we can get that added and use it in the future. + // This must be the older style connect string or it will not work. + connect(view, SIGNAL(dropEventPass(QDropEvent *)), this, SLOT(dropCatch(QDropEvent *))); setupInterface(view); return view; } void GCodeEditorWidget::closeTab(int index) { QUrl url = urlTab.key(m_tabwidget->widget(index)); auto doc = urlDoc[url]; if (doc->closeUrl()) { m_tabwidget->removeTab(index); urlTab.remove(url); urlDoc.remove(url); emit fileClosed(url); } } void GCodeEditorWidget::currentIndexChanged(int index) { emit currentFileChanged(urlTab.key(m_tabwidget->widget(index))); emit updateClientFactory(qobject_cast(m_tabwidget->widget(index))); } + +void GCodeEditorWidget::dropCatch(QDropEvent *event) +{ + if (event->mimeData()->hasUrls()) { + emit droppedUrls(event->mimeData()->urls()); + } +} diff --git a/src/widgets/gcodeeditorwidget.h b/src/widgets/gcodeeditorwidget.h index 57813f7..dc000c8 100644 --- a/src/widgets/gcodeeditorwidget.h +++ b/src/widgets/gcodeeditorwidget.h @@ -1,53 +1,57 @@ /* Atelier KDE Printer Host for 3D Printing Copyright (C) <2016> Author: Lays Rodrigues - lays.rodrigues@kde.org Chris Rizzitello - rizzitello@kde.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include #include #include class GCodeEditorWidget : public QWidget { Q_OBJECT public: explicit GCodeEditorWidget(QWidget *parent = nullptr); void loadFile(const QUrl &file); private: QMap urlDoc; QMap urlTab; KTextEditor::ConfigInterface *m_interface; KTextEditor::Document *newDoc(const QUrl &file); KTextEditor::Editor *m_editor; KTextEditor::View *newView(KTextEditor::Document *doc); QTabWidget *m_tabwidget; void closeTab(int index); void currentIndexChanged(int index); void setupInterface(const KTextEditor::View *view); void setupTabWidget(); signals: void currentFileChanged(const QUrl &file); void updateClientFactory(KTextEditor::View *view); void fileClosed(const QUrl &file); + void droppedUrls(QList fileList); + +private slots: + void dropCatch(QDropEvent *event); };