diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 4ea88a13..b42b3c62 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -1,547 +1,547 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2018 Serge Bazanski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "designwidget.h" -#include "fpgaviewwidget.h" -#include "json_frontend.h" -#include "jsonwrite.h" -#include "log.h" -#include "mainwindow.h" -#include "pythontab.h" -#include "version.h" - -static void initBasenameResource() { Q_INIT_RESOURCE(base); } - -NEXTPNR_NAMESPACE_BEGIN - -BaseMainWindow::BaseMainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : QMainWindow(parent), handler(handler), ctx(std::move(context)), timing_driven(false) -{ - initBasenameResource(); - qRegisterMetaType(); - - log_streams.clear(); - - setObjectName("BaseMainWindow"); - resize(1024, 768); - - task = new TaskManager(); - - // Create and deploy widgets on main screen - QWidget *centralWidget = new QWidget(this); - QGridLayout *gridLayout = new QGridLayout(centralWidget); - gridLayout->setSpacing(6); - gridLayout->setContentsMargins(11, 11, 11, 11); - - QSplitter *splitter_h = new QSplitter(Qt::Horizontal, centralWidget); - QSplitter *splitter_v = new QSplitter(Qt::Vertical, splitter_h); - splitter_h->addWidget(splitter_v); - - gridLayout->addWidget(splitter_h, 0, 0, 1, 1); - - setCentralWidget(centralWidget); - - designview = new DesignWidget(); - designview->setMinimumWidth(300); - splitter_h->addWidget(designview); - - tabWidget = new QTabWidget(); - - console = new PythonTab(); - tabWidget->addTab(console, "Console"); - - centralTabWidget = new QTabWidget(); - centralTabWidget->setTabsClosable(true); - - fpgaView = new FPGAViewWidget(); - centralTabWidget->addTab(fpgaView, "Device"); - centralTabWidget->tabBar()->setTabButton(0, QTabBar::RightSide, 0); - centralTabWidget->tabBar()->setTabButton(0, QTabBar::LeftSide, 0); - - splitter_v->addWidget(centralTabWidget); - splitter_v->addWidget(tabWidget); - - // Connect Worker - connect(task, &TaskManager::log, this, &BaseMainWindow::writeInfo); - connect(task, &TaskManager::pack_finished, this, &BaseMainWindow::pack_finished); - connect(task, &TaskManager::place_finished, this, &BaseMainWindow::place_finished); - connect(task, &TaskManager::route_finished, this, &BaseMainWindow::route_finished); - connect(task, &TaskManager::taskCanceled, this, &BaseMainWindow::taskCanceled); - connect(task, &TaskManager::taskStarted, this, &BaseMainWindow::taskStarted); - connect(task, &TaskManager::taskPaused, this, &BaseMainWindow::taskPaused); - - // Events for context change - connect(this, &BaseMainWindow::contextChanged, task, &TaskManager::contextChanged); - connect(this, &BaseMainWindow::contextChanged, console, &PythonTab::newContext); - connect(this, &BaseMainWindow::contextChanged, fpgaView, &FPGAViewWidget::newContext); - connect(this, &BaseMainWindow::contextChanged, designview, &DesignWidget::newContext); - - // Catch close tab events - connect(centralTabWidget, &QTabWidget::tabCloseRequested, this, &BaseMainWindow::closeTab); - - // Propagate events from design view to device view - connect(designview, &DesignWidget::selected, fpgaView, &FPGAViewWidget::onSelectedArchItem); - connect(designview, &DesignWidget::zoomSelected, fpgaView, &FPGAViewWidget::zoomSelected); - connect(designview, &DesignWidget::highlight, fpgaView, &FPGAViewWidget::onHighlightGroupChanged); - connect(designview, &DesignWidget::hover, fpgaView, &FPGAViewWidget::onHoverItemChanged); - - // Click event on device view - connect(fpgaView, &FPGAViewWidget::clickedBel, designview, &DesignWidget::onClickedBel); - connect(fpgaView, &FPGAViewWidget::clickedWire, designview, &DesignWidget::onClickedWire); - connect(fpgaView, &FPGAViewWidget::clickedPip, designview, &DesignWidget::onClickedPip); - - // Update tree event - connect(this, &BaseMainWindow::updateTreeView, designview, &DesignWidget::updateTree); - - createMenusAndBars(); -} - -BaseMainWindow::~BaseMainWindow() { delete task; } - -void BaseMainWindow::closeTab(int index) { delete centralTabWidget->widget(index); } - -void BaseMainWindow::writeInfo(std::string text) { console->info(text); } - -void BaseMainWindow::about() -{ - QString msg; - QTextStream out(&msg); - out << "nextpnr-" << NPNR_STRINGIFY_MACRO(ARCHNAME) << "\n"; - out << "Version " << GIT_DESCRIBE_STR; - QMessageBox::information(this, "About nextpnr", msg); -} - -void BaseMainWindow::createMenusAndBars() -{ - // File menu / project toolbar actions - QAction *actionExit = new QAction("Exit", this); - actionExit->setIcon(QIcon(":/icons/resources/exit.png")); - actionExit->setShortcuts(QKeySequence::Quit); - actionExit->setStatusTip("Exit the application"); - connect(actionExit, &QAction::triggered, this, &BaseMainWindow::close); - - // Help menu actions - QAction *actionAbout = new QAction("About", this); - connect(actionAbout, &QAction::triggered, this, &BaseMainWindow::about); - - // Gile menu options - actionNew = new QAction("New", this); - actionNew->setIcon(QIcon(":/icons/resources/new.png")); - actionNew->setShortcuts(QKeySequence::New); - actionNew->setStatusTip("New project"); - connect(actionNew, &QAction::triggered, this, &BaseMainWindow::new_proj); - - actionLoadJSON = new QAction("Open JSON", this); - actionLoadJSON->setIcon(QIcon(":/icons/resources/open_json.png")); - actionLoadJSON->setStatusTip("Open an existing JSON file"); - actionLoadJSON->setEnabled(true); - connect(actionLoadJSON, &QAction::triggered, this, &BaseMainWindow::open_json); - - actionSaveJSON = new QAction("Save JSON", this); - actionSaveJSON->setIcon(QIcon(":/icons/resources/save_json.png")); - actionSaveJSON->setStatusTip("Write to JSON file"); - actionSaveJSON->setEnabled(true); - connect(actionSaveJSON, &QAction::triggered, this, &BaseMainWindow::save_json); - - // Design menu options - actionPack = new QAction("Pack", this); - actionPack->setIcon(QIcon(":/icons/resources/pack.png")); - actionPack->setStatusTip("Pack current design"); - actionPack->setEnabled(false); - connect(actionPack, &QAction::triggered, task, &TaskManager::pack); - - actionPlace = new QAction("Place", this); - actionPlace->setIcon(QIcon(":/icons/resources/place.png")); - actionPlace->setStatusTip("Place current design"); - actionPlace->setEnabled(false); - connect(actionPlace, &QAction::triggered, this, &BaseMainWindow::place); - - actionRoute = new QAction("Route", this); - actionRoute->setIcon(QIcon(":/icons/resources/route.png")); - actionRoute->setStatusTip("Route current design"); - actionRoute->setEnabled(false); - connect(actionRoute, &QAction::triggered, task, &TaskManager::route); - - actionExecutePy = new QAction("Execute Python", this); - actionExecutePy->setIcon(QIcon(":/icons/resources/py.png")); - actionExecutePy->setStatusTip("Execute Python script"); - actionExecutePy->setEnabled(true); - connect(actionExecutePy, &QAction::triggered, this, &BaseMainWindow::execute_python); - - // Worker control toolbar actions - actionPlay = new QAction("Play", this); - actionPlay->setIcon(QIcon(":/icons/resources/control_play.png")); - actionPlay->setStatusTip("Continue running task"); - actionPlay->setEnabled(false); - connect(actionPlay, &QAction::triggered, task, &TaskManager::continue_thread); - - actionPause = new QAction("Pause", this); - actionPause->setIcon(QIcon(":/icons/resources/control_pause.png")); - actionPause->setStatusTip("Pause running task"); - actionPause->setEnabled(false); - connect(actionPause, &QAction::triggered, task, &TaskManager::pause_thread); - - actionStop = new QAction("Stop", this); - actionStop->setIcon(QIcon(":/icons/resources/control_stop.png")); - actionStop->setStatusTip("Stop running task"); - actionStop->setEnabled(false); - connect(actionStop, &QAction::triggered, task, &TaskManager::terminate_thread); - - // Device view control toolbar actions - QAction *actionZoomIn = new QAction("Zoom In", this); - actionZoomIn->setIcon(QIcon(":/icons/resources/zoom_in.png")); - connect(actionZoomIn, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomIn); - - QAction *actionZoomOut = new QAction("Zoom Out", this); - actionZoomOut->setIcon(QIcon(":/icons/resources/zoom_out.png")); - connect(actionZoomOut, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomOut); - - QAction *actionZoomSelected = new QAction("Zoom Selected", this); - actionZoomSelected->setIcon(QIcon(":/icons/resources/shape_handles.png")); - connect(actionZoomSelected, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomSelected); - - QAction *actionZoomOutbound = new QAction("Zoom Outbound", this); - actionZoomOutbound->setIcon(QIcon(":/icons/resources/shape_square.png")); - connect(actionZoomOutbound, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomOutbound); - - actionDisplayBel = new QAction("Enable/Disable Bels", this); - actionDisplayBel->setIcon(QIcon(":/icons/resources/bel.png")); - actionDisplayBel->setCheckable(true); - actionDisplayBel->setChecked(true); - connect(actionDisplayBel, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); - - actionDisplayWire = new QAction("Enable/Disable Wires", this); - actionDisplayWire->setIcon(QIcon(":/icons/resources/wire.png")); - actionDisplayWire->setCheckable(true); - actionDisplayWire->setChecked(true); - connect(actionDisplayWire, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); - - actionDisplayPip = new QAction("Enable/Disable Pips", this); - actionDisplayPip->setIcon(QIcon(":/icons/resources/pip.png")); - actionDisplayPip->setCheckable(true); -#ifdef ARCH_ECP5 - actionDisplayPip->setChecked(false); -#else - actionDisplayPip->setChecked(true); -#endif - connect(actionDisplayPip, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); - - actionDisplayGroups = new QAction("Enable/Disable Groups", this); - actionDisplayGroups->setIcon(QIcon(":/icons/resources/group.png")); - actionDisplayGroups->setCheckable(true); - actionDisplayGroups->setChecked(true); - connect(actionDisplayGroups, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); - - actionScreenshot = new QAction("Screenshot", this); - actionScreenshot->setIcon(QIcon(":/icons/resources/camera.png")); - actionScreenshot->setStatusTip("Taking a screenshot"); - connect(actionScreenshot, &QAction::triggered, this, &BaseMainWindow::screenshot); - - actionMovie = new QAction("Recording", this); - actionMovie->setIcon(QIcon(":/icons/resources/film.png")); - actionMovie->setStatusTip("Saving a movie"); - actionMovie->setCheckable(true); - actionMovie->setChecked(false); - connect(actionMovie, &QAction::triggered, this, &BaseMainWindow::saveMovie); - - actionSaveSVG = new QAction("Save SVG", this); - actionSaveSVG->setIcon(QIcon(":/icons/resources/save_svg.png")); - actionSaveSVG->setStatusTip("Saving a SVG"); - connect(actionSaveSVG, &QAction::triggered, this, &BaseMainWindow::saveSVG); - - // set initial state - fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(), - actionDisplayPip->isChecked(), actionDisplayGroups->isChecked()); - - // Add main menu - menuBar = new QMenuBar(); - menuBar->setGeometry(QRect(0, 0, 1024, 27)); - setMenuBar(menuBar); - QMenu *menuFile = new QMenu("&File", menuBar); - QMenu *menuHelp = new QMenu("&Help", menuBar); - menuDesign = new QMenu("&Design", menuBar); - menuBar->addAction(menuFile->menuAction()); - menuBar->addAction(menuDesign->menuAction()); - menuBar->addAction(menuHelp->menuAction()); - - // Add File menu actions - menuFile->addAction(actionNew); - menuFile->addAction(actionLoadJSON); - menuFile->addAction(actionSaveJSON); - menuFile->addSeparator(); - menuFile->addAction(actionExit); - - // Add Design menu actions - menuDesign->addAction(actionPack); - menuDesign->addAction(actionPlace); - menuDesign->addAction(actionRoute); - menuDesign->addSeparator(); - menuDesign->addAction(actionExecutePy); - - // Add Help menu actions - menuHelp->addAction(actionAbout); - - // Main action bar - mainActionBar = new QToolBar("Main"); - addToolBar(Qt::TopToolBarArea, mainActionBar); - mainActionBar->addAction(actionNew); - mainActionBar->addAction(actionLoadJSON); - mainActionBar->addAction(actionSaveJSON); - mainActionBar->addSeparator(); - mainActionBar->addAction(actionPack); - mainActionBar->addAction(actionPlace); - mainActionBar->addAction(actionRoute); - mainActionBar->addAction(actionExecutePy); - - // Add worker control toolbar - QToolBar *workerControlToolBar = new QToolBar("Worker"); - addToolBar(Qt::TopToolBarArea, workerControlToolBar); - workerControlToolBar->addAction(actionPlay); - workerControlToolBar->addAction(actionPause); - workerControlToolBar->addAction(actionStop); - - // Add device view control toolbar - QToolBar *deviceViewToolBar = new QToolBar("Device"); - addToolBar(Qt::TopToolBarArea, deviceViewToolBar); - deviceViewToolBar->addAction(actionZoomIn); - deviceViewToolBar->addAction(actionZoomOut); - deviceViewToolBar->addAction(actionZoomSelected); - deviceViewToolBar->addAction(actionZoomOutbound); - deviceViewToolBar->addSeparator(); - deviceViewToolBar->addAction(actionDisplayBel); - deviceViewToolBar->addAction(actionDisplayWire); - deviceViewToolBar->addAction(actionDisplayPip); - deviceViewToolBar->addAction(actionDisplayGroups); - deviceViewToolBar->addSeparator(); - deviceViewToolBar->addAction(actionScreenshot); - deviceViewToolBar->addAction(actionMovie); - deviceViewToolBar->addAction(actionSaveSVG); - - // Add status bar with progress bar - statusBar = new QStatusBar(); - progressBar = new QProgressBar(statusBar); - progressBar->setAlignment(Qt::AlignRight); - progressBar->setMaximumSize(180, 19); - statusBar->addPermanentWidget(progressBar); - progressBar->setValue(0); - progressBar->setEnabled(false); - setStatusBar(statusBar); -} - -void BaseMainWindow::enableDisableDecals() -{ - fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(), - actionDisplayPip->isChecked(), actionDisplayGroups->isChecked()); - ctx->refreshUi(); -} - -void BaseMainWindow::open_json() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString("Open JSON"), QString(), QString("*.json")); - if (!fileName.isEmpty()) { - disableActions(); - if (ctx->settings.find(ctx->id("synth")) == ctx->settings.end()) { - ArchArgs chipArgs = ctx->getArchArgs(); - ctx = std::unique_ptr(new Context(chipArgs)); - Q_EMIT contextChanged(ctx.get()); - } - handler->load_json(ctx.get(), fileName.toStdString()); - Q_EMIT updateTreeView(); - log("Loading design successful.\n"); - updateActions(); - } -} - -void BaseMainWindow::save_json() -{ - QString fileName = QFileDialog::getSaveFileName(this, QString("Save JSON"), QString(), QString("*.json")); - if (!fileName.isEmpty()) { - std::string fn = fileName.toStdString(); - std::ofstream f(fn); - if (write_json_file(f, fn, ctx.get())) - log("Saving JSON successful.\n"); - else - log("Saving JSON failed.\n"); - } -} - -void BaseMainWindow::screenshot() -{ - QString fileName = QFileDialog::getSaveFileName(this, QString("Save screenshot"), QString(), QString("*.png")); - if (!fileName.isEmpty()) { - QImage image = fpgaView->grabFramebuffer(); - if (!fileName.endsWith(".png")) - fileName += ".png"; - QImageWriter imageWriter(fileName, "png"); - if (imageWriter.write(image)) - log("Saving screenshot successful.\n"); - else - log("Saving screenshot failed.\n"); - } -} - -void BaseMainWindow::saveMovie() -{ - if (actionMovie->isChecked()) { - QString dir = QFileDialog::getExistingDirectory(this, tr("Select Movie Directory"), QDir::currentPath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - if (!dir.isEmpty()) { - bool ok; - int frames = - QInputDialog::getInt(this, "Recording", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000, 1, &ok); - if (ok) { - QMessageBox::StandardButton reply = - QMessageBox::question(this, "Recording", "Skip identical frames ?", - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - fpgaView->movieStart(dir, frames, (reply == QMessageBox::Yes)); - } else - actionMovie->setChecked(false); - } else - actionMovie->setChecked(false); - } else { - fpgaView->movieStop(); - } -} - -void BaseMainWindow::saveSVG() -{ - QString fileName = QFileDialog::getSaveFileName(this, QString("Save SVG"), QString(), QString("*.svg")); - if (!fileName.isEmpty()) { - if (!fileName.endsWith(".svg")) - fileName += ".svg"; - bool ok; - QString options = - QInputDialog::getText(this, "Save SVG", tr("Save options:"), QLineEdit::Normal, "scale=500", &ok); - if (ok) { - try { - ctx->writeSVG(fileName.toStdString(), options.toStdString()); - log("Saving SVG successful.\n"); - } catch (const log_execution_error_exception &ex) { - log("Saving SVG failed.\n"); - } - } - } -} - -void BaseMainWindow::pack_finished(bool status) -{ - disableActions(); - if (status) { - log("Packing design successful.\n"); - Q_EMIT updateTreeView(); - updateActions(); - } else { - log("Packing design failed.\n"); - } -} - -void BaseMainWindow::place_finished(bool status) -{ - disableActions(); - if (status) { - log("Placing design successful.\n"); - Q_EMIT updateTreeView(); - updateActions(); - } else { - log("Placing design failed.\n"); - } -} -void BaseMainWindow::route_finished(bool status) -{ - disableActions(); - if (status) { - log("Routing design successful.\n"); - Q_EMIT updateTreeView(); - updateActions(); - } else - log("Routing design failed.\n"); -} - -void BaseMainWindow::taskCanceled() -{ - log("CANCELED\n"); - disableActions(); -} - -void BaseMainWindow::taskStarted() -{ - disableActions(); - actionPause->setEnabled(true); - actionStop->setEnabled(true); -} - -void BaseMainWindow::taskPaused() -{ - disableActions(); - actionPlay->setEnabled(true); - actionStop->setEnabled(true); -} - -void BaseMainWindow::place() { Q_EMIT task->place(timing_driven); } - -void BaseMainWindow::disableActions() -{ - actionLoadJSON->setEnabled(true); - actionPack->setEnabled(false); - actionPlace->setEnabled(false); - actionRoute->setEnabled(false); - - actionExecutePy->setEnabled(true); - - actionPlay->setEnabled(false); - actionPause->setEnabled(false); - actionStop->setEnabled(false); - - onDisableActions(); -} - -void BaseMainWindow::updateActions() -{ - if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) - actionPack->setEnabled(true); - else if (ctx->settings.find(ctx->id("place")) == ctx->settings.end()) { - actionPlace->setEnabled(true); - } else if (ctx->settings.find(ctx->id("route")) == ctx->settings.end()) - actionRoute->setEnabled(true); - - onUpdateActions(); -} - -void BaseMainWindow::execute_python() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString("Execute Python"), QString(), QString("*.py")); - if (!fileName.isEmpty()) { - console->execute_python(fileName.toStdString()); - } -} - -void BaseMainWindow::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); } - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "designwidget.h" +#include "fpgaviewwidget.h" +#include "json_frontend.h" +#include "jsonwrite.h" +#include "log.h" +#include "mainwindow.h" +#include "pythontab.h" +#include "version.h" + +static void initBasenameResource() { Q_INIT_RESOURCE(base); } + +NEXTPNR_NAMESPACE_BEGIN + +BaseMainWindow::BaseMainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : QMainWindow(parent), handler(handler), ctx(std::move(context)), timing_driven(false) +{ + initBasenameResource(); + qRegisterMetaType(); + + log_streams.clear(); + + setObjectName("BaseMainWindow"); + resize(1024, 768); + + task = new TaskManager(); + + // Create and deploy widgets on main screen + QWidget *centralWidget = new QWidget(this); + QGridLayout *gridLayout = new QGridLayout(centralWidget); + gridLayout->setSpacing(6); + gridLayout->setContentsMargins(11, 11, 11, 11); + + QSplitter *splitter_h = new QSplitter(Qt::Horizontal, centralWidget); + QSplitter *splitter_v = new QSplitter(Qt::Vertical, splitter_h); + splitter_h->addWidget(splitter_v); + + gridLayout->addWidget(splitter_h, 0, 0, 1, 1); + + setCentralWidget(centralWidget); + + designview = new DesignWidget(); + designview->setMinimumWidth(300); + splitter_h->addWidget(designview); + + tabWidget = new QTabWidget(); + + console = new PythonTab(); + tabWidget->addTab(console, "Console"); + + centralTabWidget = new QTabWidget(); + centralTabWidget->setTabsClosable(true); + + fpgaView = new FPGAViewWidget(); + centralTabWidget->addTab(fpgaView, "Device"); + centralTabWidget->tabBar()->setTabButton(0, QTabBar::RightSide, 0); + centralTabWidget->tabBar()->setTabButton(0, QTabBar::LeftSide, 0); + + splitter_v->addWidget(centralTabWidget); + splitter_v->addWidget(tabWidget); + + // Connect Worker + connect(task, &TaskManager::log, this, &BaseMainWindow::writeInfo); + connect(task, &TaskManager::pack_finished, this, &BaseMainWindow::pack_finished); + connect(task, &TaskManager::place_finished, this, &BaseMainWindow::place_finished); + connect(task, &TaskManager::route_finished, this, &BaseMainWindow::route_finished); + connect(task, &TaskManager::taskCanceled, this, &BaseMainWindow::taskCanceled); + connect(task, &TaskManager::taskStarted, this, &BaseMainWindow::taskStarted); + connect(task, &TaskManager::taskPaused, this, &BaseMainWindow::taskPaused); + + // Events for context change + connect(this, &BaseMainWindow::contextChanged, task, &TaskManager::contextChanged); + connect(this, &BaseMainWindow::contextChanged, console, &PythonTab::newContext); + connect(this, &BaseMainWindow::contextChanged, fpgaView, &FPGAViewWidget::newContext); + connect(this, &BaseMainWindow::contextChanged, designview, &DesignWidget::newContext); + + // Catch close tab events + connect(centralTabWidget, &QTabWidget::tabCloseRequested, this, &BaseMainWindow::closeTab); + + // Propagate events from design view to device view + connect(designview, &DesignWidget::selected, fpgaView, &FPGAViewWidget::onSelectedArchItem); + connect(designview, &DesignWidget::zoomSelected, fpgaView, &FPGAViewWidget::zoomSelected); + connect(designview, &DesignWidget::highlight, fpgaView, &FPGAViewWidget::onHighlightGroupChanged); + connect(designview, &DesignWidget::hover, fpgaView, &FPGAViewWidget::onHoverItemChanged); + + // Click event on device view + connect(fpgaView, &FPGAViewWidget::clickedBel, designview, &DesignWidget::onClickedBel); + connect(fpgaView, &FPGAViewWidget::clickedWire, designview, &DesignWidget::onClickedWire); + connect(fpgaView, &FPGAViewWidget::clickedPip, designview, &DesignWidget::onClickedPip); + + // Update tree event + connect(this, &BaseMainWindow::updateTreeView, designview, &DesignWidget::updateTree); + + createMenusAndBars(); +} + +BaseMainWindow::~BaseMainWindow() { delete task; } + +void BaseMainWindow::closeTab(int index) { delete centralTabWidget->widget(index); } + +void BaseMainWindow::writeInfo(std::string text) { console->info(text); } + +void BaseMainWindow::about() +{ + QString msg; + QTextStream out(&msg); + out << "nextpnr-" << NPNR_STRINGIFY_MACRO(ARCHNAME) << "\n"; + out << "Version " << GIT_DESCRIBE_STR; + QMessageBox::information(this, "About nextpnr", msg); +} + +void BaseMainWindow::createMenusAndBars() +{ + // File menu / project toolbar actions + QAction *actionExit = new QAction("Exit", this); + actionExit->setIcon(QIcon(":/icons/resources/exit.png")); + actionExit->setShortcuts(QKeySequence::Quit); + actionExit->setStatusTip("Exit the application"); + connect(actionExit, &QAction::triggered, this, &BaseMainWindow::close); + + // Help menu actions + QAction *actionAbout = new QAction("About", this); + connect(actionAbout, &QAction::triggered, this, &BaseMainWindow::about); + + // Gile menu options + actionNew = new QAction("New", this); + actionNew->setIcon(QIcon(":/icons/resources/new.png")); + actionNew->setShortcuts(QKeySequence::New); + actionNew->setStatusTip("New project"); + connect(actionNew, &QAction::triggered, this, &BaseMainWindow::new_proj); + + actionLoadJSON = new QAction("Open JSON", this); + actionLoadJSON->setIcon(QIcon(":/icons/resources/open_json.png")); + actionLoadJSON->setStatusTip("Open an existing JSON file"); + actionLoadJSON->setEnabled(true); + connect(actionLoadJSON, &QAction::triggered, this, &BaseMainWindow::open_json); + + actionSaveJSON = new QAction("Save JSON", this); + actionSaveJSON->setIcon(QIcon(":/icons/resources/save_json.png")); + actionSaveJSON->setStatusTip("Write to JSON file"); + actionSaveJSON->setEnabled(true); + connect(actionSaveJSON, &QAction::triggered, this, &BaseMainWindow::save_json); + + // Design menu options + actionPack = new QAction("Pack", this); + actionPack->setIcon(QIcon(":/icons/resources/pack.png")); + actionPack->setStatusTip("Pack current design"); + actionPack->setEnabled(false); + connect(actionPack, &QAction::triggered, task, &TaskManager::pack); + + actionPlace = new QAction("Place", this); + actionPlace->setIcon(QIcon(":/icons/resources/place.png")); + actionPlace->setStatusTip("Place current design"); + actionPlace->setEnabled(false); + connect(actionPlace, &QAction::triggered, this, &BaseMainWindow::place); + + actionRoute = new QAction("Route", this); + actionRoute->setIcon(QIcon(":/icons/resources/route.png")); + actionRoute->setStatusTip("Route current design"); + actionRoute->setEnabled(false); + connect(actionRoute, &QAction::triggered, task, &TaskManager::route); + + actionExecutePy = new QAction("Execute Python", this); + actionExecutePy->setIcon(QIcon(":/icons/resources/py.png")); + actionExecutePy->setStatusTip("Execute Python script"); + actionExecutePy->setEnabled(true); + connect(actionExecutePy, &QAction::triggered, this, &BaseMainWindow::execute_python); + + // Worker control toolbar actions + actionPlay = new QAction("Play", this); + actionPlay->setIcon(QIcon(":/icons/resources/control_play.png")); + actionPlay->setStatusTip("Continue running task"); + actionPlay->setEnabled(false); + connect(actionPlay, &QAction::triggered, task, &TaskManager::continue_thread); + + actionPause = new QAction("Pause", this); + actionPause->setIcon(QIcon(":/icons/resources/control_pause.png")); + actionPause->setStatusTip("Pause running task"); + actionPause->setEnabled(false); + connect(actionPause, &QAction::triggered, task, &TaskManager::pause_thread); + + actionStop = new QAction("Stop", this); + actionStop->setIcon(QIcon(":/icons/resources/control_stop.png")); + actionStop->setStatusTip("Stop running task"); + actionStop->setEnabled(false); + connect(actionStop, &QAction::triggered, task, &TaskManager::terminate_thread); + + // Device view control toolbar actions + QAction *actionZoomIn = new QAction("Zoom In", this); + actionZoomIn->setIcon(QIcon(":/icons/resources/zoom_in.png")); + connect(actionZoomIn, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomIn); + + QAction *actionZoomOut = new QAction("Zoom Out", this); + actionZoomOut->setIcon(QIcon(":/icons/resources/zoom_out.png")); + connect(actionZoomOut, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomOut); + + QAction *actionZoomSelected = new QAction("Zoom Selected", this); + actionZoomSelected->setIcon(QIcon(":/icons/resources/shape_handles.png")); + connect(actionZoomSelected, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomSelected); + + QAction *actionZoomOutbound = new QAction("Zoom Outbound", this); + actionZoomOutbound->setIcon(QIcon(":/icons/resources/shape_square.png")); + connect(actionZoomOutbound, &QAction::triggered, fpgaView, &FPGAViewWidget::zoomOutbound); + + actionDisplayBel = new QAction("Enable/Disable Bels", this); + actionDisplayBel->setIcon(QIcon(":/icons/resources/bel.png")); + actionDisplayBel->setCheckable(true); + actionDisplayBel->setChecked(true); + connect(actionDisplayBel, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); + + actionDisplayWire = new QAction("Enable/Disable Wires", this); + actionDisplayWire->setIcon(QIcon(":/icons/resources/wire.png")); + actionDisplayWire->setCheckable(true); + actionDisplayWire->setChecked(true); + connect(actionDisplayWire, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); + + actionDisplayPip = new QAction("Enable/Disable Pips", this); + actionDisplayPip->setIcon(QIcon(":/icons/resources/pip.png")); + actionDisplayPip->setCheckable(true); +#ifdef ARCH_ECP5 + actionDisplayPip->setChecked(false); +#else + actionDisplayPip->setChecked(true); +#endif + connect(actionDisplayPip, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); + + actionDisplayGroups = new QAction("Enable/Disable Groups", this); + actionDisplayGroups->setIcon(QIcon(":/icons/resources/group.png")); + actionDisplayGroups->setCheckable(true); + actionDisplayGroups->setChecked(true); + connect(actionDisplayGroups, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); + + actionScreenshot = new QAction("Screenshot", this); + actionScreenshot->setIcon(QIcon(":/icons/resources/camera.png")); + actionScreenshot->setStatusTip("Taking a screenshot"); + connect(actionScreenshot, &QAction::triggered, this, &BaseMainWindow::screenshot); + + actionMovie = new QAction("Recording", this); + actionMovie->setIcon(QIcon(":/icons/resources/film.png")); + actionMovie->setStatusTip("Saving a movie"); + actionMovie->setCheckable(true); + actionMovie->setChecked(false); + connect(actionMovie, &QAction::triggered, this, &BaseMainWindow::saveMovie); + + actionSaveSVG = new QAction("Save SVG", this); + actionSaveSVG->setIcon(QIcon(":/icons/resources/save_svg.png")); + actionSaveSVG->setStatusTip("Saving a SVG"); + connect(actionSaveSVG, &QAction::triggered, this, &BaseMainWindow::saveSVG); + + // set initial state + fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(), + actionDisplayPip->isChecked(), actionDisplayGroups->isChecked()); + + // Add main menu + menuBar = new QMenuBar(); + menuBar->setGeometry(QRect(0, 0, 1024, 27)); + setMenuBar(menuBar); + QMenu *menuFile = new QMenu("&File", menuBar); + QMenu *menuHelp = new QMenu("&Help", menuBar); + menuDesign = new QMenu("&Design", menuBar); + menuBar->addAction(menuFile->menuAction()); + menuBar->addAction(menuDesign->menuAction()); + menuBar->addAction(menuHelp->menuAction()); + + // Add File menu actions + menuFile->addAction(actionNew); + menuFile->addAction(actionLoadJSON); + menuFile->addAction(actionSaveJSON); + menuFile->addSeparator(); + menuFile->addAction(actionExit); + + // Add Design menu actions + menuDesign->addAction(actionPack); + menuDesign->addAction(actionPlace); + menuDesign->addAction(actionRoute); + menuDesign->addSeparator(); + menuDesign->addAction(actionExecutePy); + + // Add Help menu actions + menuHelp->addAction(actionAbout); + + // Main action bar + mainActionBar = new QToolBar("Main"); + addToolBar(Qt::TopToolBarArea, mainActionBar); + mainActionBar->addAction(actionNew); + mainActionBar->addAction(actionLoadJSON); + mainActionBar->addAction(actionSaveJSON); + mainActionBar->addSeparator(); + mainActionBar->addAction(actionPack); + mainActionBar->addAction(actionPlace); + mainActionBar->addAction(actionRoute); + mainActionBar->addAction(actionExecutePy); + + // Add worker control toolbar + QToolBar *workerControlToolBar = new QToolBar("Worker"); + addToolBar(Qt::TopToolBarArea, workerControlToolBar); + workerControlToolBar->addAction(actionPlay); + workerControlToolBar->addAction(actionPause); + workerControlToolBar->addAction(actionStop); + + // Add device view control toolbar + QToolBar *deviceViewToolBar = new QToolBar("Device"); + addToolBar(Qt::TopToolBarArea, deviceViewToolBar); + deviceViewToolBar->addAction(actionZoomIn); + deviceViewToolBar->addAction(actionZoomOut); + deviceViewToolBar->addAction(actionZoomSelected); + deviceViewToolBar->addAction(actionZoomOutbound); + deviceViewToolBar->addSeparator(); + deviceViewToolBar->addAction(actionDisplayBel); + deviceViewToolBar->addAction(actionDisplayWire); + deviceViewToolBar->addAction(actionDisplayPip); + deviceViewToolBar->addAction(actionDisplayGroups); + deviceViewToolBar->addSeparator(); + deviceViewToolBar->addAction(actionScreenshot); + deviceViewToolBar->addAction(actionMovie); + deviceViewToolBar->addAction(actionSaveSVG); + + // Add status bar with progress bar + statusBar = new QStatusBar(); + progressBar = new QProgressBar(statusBar); + progressBar->setAlignment(Qt::AlignRight); + progressBar->setMaximumSize(180, 19); + statusBar->addPermanentWidget(progressBar); + progressBar->setValue(0); + progressBar->setEnabled(false); + setStatusBar(statusBar); +} + +void BaseMainWindow::enableDisableDecals() +{ + fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(), + actionDisplayPip->isChecked(), actionDisplayGroups->isChecked()); + ctx->refreshUi(); +} + +void BaseMainWindow::open_json() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Open JSON"), QString(), QString("*.json")); + if (!fileName.isEmpty()) { + disableActions(); + if (ctx->settings.find(ctx->id("synth")) == ctx->settings.end()) { + ArchArgs chipArgs = ctx->getArchArgs(); + ctx = std::unique_ptr(new Context(chipArgs)); + Q_EMIT contextChanged(ctx.get()); + } + handler->load_json(ctx.get(), fileName.toStdString()); + Q_EMIT updateTreeView(); + log("Loading design successful.\n"); + updateActions(); + } +} + +void BaseMainWindow::save_json() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save JSON"), QString(), QString("*.json")); + if (!fileName.isEmpty()) { + std::string fn = fileName.toStdString(); + std::ofstream f(fn); + if (write_json_file(f, fn, ctx.get())) + log("Saving JSON successful.\n"); + else + log("Saving JSON failed.\n"); + } +} + +void BaseMainWindow::screenshot() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save screenshot"), QString(), QString("*.png")); + if (!fileName.isEmpty()) { + QImage image = fpgaView->grabFramebuffer(); + if (!fileName.endsWith(".png")) + fileName += ".png"; + QImageWriter imageWriter(fileName, "png"); + if (imageWriter.write(image)) + log("Saving screenshot successful.\n"); + else + log("Saving screenshot failed.\n"); + } +} + +void BaseMainWindow::saveMovie() +{ + if (actionMovie->isChecked()) { + QString dir = QFileDialog::getExistingDirectory(this, tr("Select Movie Directory"), QDir::currentPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!dir.isEmpty()) { + bool ok; + int frames = + QInputDialog::getInt(this, "Recording", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000, 1, &ok); + if (ok) { + QMessageBox::StandardButton reply = + QMessageBox::question(this, "Recording", "Skip identical frames ?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + fpgaView->movieStart(dir, frames, (reply == QMessageBox::Yes)); + } else + actionMovie->setChecked(false); + } else + actionMovie->setChecked(false); + } else { + fpgaView->movieStop(); + } +} + +void BaseMainWindow::saveSVG() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save SVG"), QString(), QString("*.svg")); + if (!fileName.isEmpty()) { + if (!fileName.endsWith(".svg")) + fileName += ".svg"; + bool ok; + QString options = + QInputDialog::getText(this, "Save SVG", tr("Save options:"), QLineEdit::Normal, "scale=500", &ok); + if (ok) { + try { + ctx->writeSVG(fileName.toStdString(), options.toStdString()); + log("Saving SVG successful.\n"); + } catch (const log_execution_error_exception &ex) { + log("Saving SVG failed.\n"); + } + } + } +} + +void BaseMainWindow::pack_finished(bool status) +{ + disableActions(); + if (status) { + log("Packing design successful.\n"); + Q_EMIT updateTreeView(); + updateActions(); + } else { + log("Packing design failed.\n"); + } +} + +void BaseMainWindow::place_finished(bool status) +{ + disableActions(); + if (status) { + log("Placing design successful.\n"); + Q_EMIT updateTreeView(); + updateActions(); + } else { + log("Placing design failed.\n"); + } +} +void BaseMainWindow::route_finished(bool status) +{ + disableActions(); + if (status) { + log("Routing design successful.\n"); + Q_EMIT updateTreeView(); + updateActions(); + } else + log("Routing design failed.\n"); +} + +void BaseMainWindow::taskCanceled() +{ + log("CANCELED\n"); + disableActions(); +} + +void BaseMainWindow::taskStarted() +{ + disableActions(); + actionPause->setEnabled(true); + actionStop->setEnabled(true); +} + +void BaseMainWindow::taskPaused() +{ + disableActions(); + actionPlay->setEnabled(true); + actionStop->setEnabled(true); +} + +void BaseMainWindow::place() { Q_EMIT task->place(timing_driven); } + +void BaseMainWindow::disableActions() +{ + actionLoadJSON->setEnabled(true); + actionPack->setEnabled(false); + actionPlace->setEnabled(false); + actionRoute->setEnabled(false); + + actionExecutePy->setEnabled(true); + + actionPlay->setEnabled(false); + actionPause->setEnabled(false); + actionStop->setEnabled(false); + + onDisableActions(); +} + +void BaseMainWindow::updateActions() +{ + if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) + actionPack->setEnabled(true); + else if (ctx->settings.find(ctx->id("place")) == ctx->settings.end()) { + actionPlace->setEnabled(true); + } else if (ctx->settings.find(ctx->id("route")) == ctx->settings.end()) + actionRoute->setEnabled(true); + + onUpdateActions(); +} + +void BaseMainWindow::execute_python() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Execute Python"), QString(), QString("*.py")); + if (!fileName.isEmpty()) { + console->execute_python(fileName.toStdString()); + } +} + +void BaseMainWindow::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); } + +NEXTPNR_NAMESPACE_END diff --git a/gui/basewindow.h b/gui/basewindow.h index 053bb8e6..00b8f45c 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -1,142 +1,142 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef BASEMAINWINDOW_H -#define BASEMAINWINDOW_H - -#include "command.h" -#include "nextpnr.h" -#include "worker.h" - -#include -#include -#include -#include -#include -#include -#include - -Q_DECLARE_METATYPE(std::string) -Q_DECLARE_METATYPE(NEXTPNR_NAMESPACE_PREFIX DecalXY) - -NEXTPNR_NAMESPACE_BEGIN - -class PythonTab; -class DesignWidget; -class FPGAViewWidget; - -class BaseMainWindow : public QMainWindow -{ - Q_OBJECT - - public: - explicit BaseMainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~BaseMainWindow(); - Context *getContext() { return ctx.get(); } - void updateActions(); - - void notifyChangeContext(); - - protected: - void createMenusAndBars(); - void disableActions(); - void enableDisableDecals(); - - virtual void onDisableActions(){}; - virtual void onUpdateActions(){}; - - protected Q_SLOTS: - void writeInfo(std::string text); - void closeTab(int index); - - virtual void new_proj() = 0; - - void open_json(); - void save_json(); - void place(); - - void execute_python(); - - void pack_finished(bool status); - void place_finished(bool status); - void route_finished(bool status); - - void taskCanceled(); - void taskStarted(); - void taskPaused(); - - void screenshot(); - void saveMovie(); - void saveSVG(); - - void about(); - - Q_SIGNALS: - void contextChanged(Context *ctx); - void updateTreeView(); - - protected: - // state variables - CommandHandler *handler; - std::unique_ptr ctx; - TaskManager *task; - bool timing_driven; - std::string currentProj; - - // main widgets - QTabWidget *tabWidget; - QTabWidget *centralTabWidget; - PythonTab *console; - DesignWidget *designview; - FPGAViewWidget *fpgaView; - - // Menus, bars and actions - QMenuBar *menuBar; - QMenu *menuDesign; - QStatusBar *statusBar; - QToolBar *mainActionBar; - QProgressBar *progressBar; - - QAction *actionNew; - QAction *actionLoadJSON; - QAction *actionSaveJSON; - - QAction *actionPack; - QAction *actionPlace; - QAction *actionRoute; - - QAction *actionExecutePy; - - QAction *actionPlay; - QAction *actionPause; - QAction *actionStop; - - QAction *actionDisplayBel; - QAction *actionDisplayWire; - QAction *actionDisplayPip; - QAction *actionDisplayGroups; - - QAction *actionScreenshot; - QAction *actionMovie; - QAction *actionSaveSVG; -}; - -NEXTPNR_NAMESPACE_END - -#endif // BASEMAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef BASEMAINWINDOW_H +#define BASEMAINWINDOW_H + +#include "command.h" +#include "nextpnr.h" +#include "worker.h" + +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(std::string) +Q_DECLARE_METATYPE(NEXTPNR_NAMESPACE_PREFIX DecalXY) + +NEXTPNR_NAMESPACE_BEGIN + +class PythonTab; +class DesignWidget; +class FPGAViewWidget; + +class BaseMainWindow : public QMainWindow +{ + Q_OBJECT + + public: + explicit BaseMainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~BaseMainWindow(); + Context *getContext() { return ctx.get(); } + void updateActions(); + + void notifyChangeContext(); + + protected: + void createMenusAndBars(); + void disableActions(); + void enableDisableDecals(); + + virtual void onDisableActions(){}; + virtual void onUpdateActions(){}; + + protected Q_SLOTS: + void writeInfo(std::string text); + void closeTab(int index); + + virtual void new_proj() = 0; + + void open_json(); + void save_json(); + void place(); + + void execute_python(); + + void pack_finished(bool status); + void place_finished(bool status); + void route_finished(bool status); + + void taskCanceled(); + void taskStarted(); + void taskPaused(); + + void screenshot(); + void saveMovie(); + void saveSVG(); + + void about(); + + Q_SIGNALS: + void contextChanged(Context *ctx); + void updateTreeView(); + + protected: + // state variables + CommandHandler *handler; + std::unique_ptr ctx; + TaskManager *task; + bool timing_driven; + std::string currentProj; + + // main widgets + QTabWidget *tabWidget; + QTabWidget *centralTabWidget; + PythonTab *console; + DesignWidget *designview; + FPGAViewWidget *fpgaView; + + // Menus, bars and actions + QMenuBar *menuBar; + QMenu *menuDesign; + QStatusBar *statusBar; + QToolBar *mainActionBar; + QProgressBar *progressBar; + + QAction *actionNew; + QAction *actionLoadJSON; + QAction *actionSaveJSON; + + QAction *actionPack; + QAction *actionPlace; + QAction *actionRoute; + + QAction *actionExecutePy; + + QAction *actionPlay; + QAction *actionPause; + QAction *actionStop; + + QAction *actionDisplayBel; + QAction *actionDisplayWire; + QAction *actionDisplayPip; + QAction *actionDisplayGroups; + + QAction *actionScreenshot; + QAction *actionMovie; + QAction *actionSaveSVG; +}; + +NEXTPNR_NAMESPACE_END + +#endif // BASEMAINWINDOW_H diff --git a/gui/designwidget.cc b/gui/designwidget.cc index bc101d80..b7c16d45 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -1,1072 +1,1072 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2018 Serge Bazanski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "designwidget.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "fpgaviewwidget.h" - -NEXTPNR_NAMESPACE_BEGIN - -TreeView::TreeView(QWidget *parent) : QTreeView(parent) {} - -TreeView::~TreeView() {} - -void TreeView::mouseMoveEvent(QMouseEvent *event) -{ - QModelIndex index = indexAt(event->pos()); - if (index != current) { - current = index; - Q_EMIT hoverIndexChanged(index); - } - QTreeView::mouseMoveEvent(event); -} - -void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); } - -DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) -{ - tabWidget = new QTabWidget(); - - // Add tree view - for (int i = 0; i < 6; i++) { - treeView[i] = new TreeView(); - treeModel[i] = new TreeModel::Model(); - treeView[i]->setModel(treeModel[i]); - treeView[i]->setContextMenuPolicy(Qt::CustomContextMenu); - treeView[i]->setSelectionMode(QAbstractItemView::ExtendedSelection); - treeView[i]->viewport()->setMouseTracking(true); - selectionModel[i] = nullptr; - } - - tabWidget->addTab(treeView[0], "Bels"); - tabWidget->addTab(treeView[1], "Wires"); - tabWidget->addTab(treeView[2], "Pips"); - tabWidget->addTab(treeView[3], "Cells"); - tabWidget->addTab(treeView[4], "Nets"); - tabWidget->addTab(treeView[5], "Groups"); - - // Add property view - variantManager = new QtVariantPropertyManager(this); - readOnlyManager = new QtVariantPropertyManager(this); - groupManager = new QtGroupPropertyManager(this); - variantFactory = new QtVariantEditorFactory(this); - propertyEditor = new QtTreePropertyBrowser(this); - propertyEditor->setFactoryForManager(variantManager, variantFactory); - propertyEditor->setPropertiesWithoutValueMarked(true); - propertyEditor->show(); - propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu); - propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection); - propertyEditor->treeWidget()->viewport()->setMouseTracking(true); - - searchEdit = new QLineEdit(); - searchEdit->setClearButtonEnabled(true); - searchEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition); - searchEdit->setPlaceholderText("Search..."); - connect(searchEdit, &QLineEdit::returnPressed, this, &DesignWidget::onSearchInserted); - - actionFirst = new QAction("", this); - actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png")); - actionFirst->setEnabled(false); - connect(actionFirst, &QAction::triggered, this, [this] { - history_ignore = true; - history_index = 0; - auto h = history.at(history_index); - if (tabWidget->currentIndex() != h.first) { - selectionModel[tabWidget->currentIndex()]->clearSelection(); - tabWidget->setCurrentIndex(h.first); - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); - } else { - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); - } - updateButtons(); - }); - - actionPrev = new QAction("", this); - actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png")); - actionPrev->setEnabled(false); - connect(actionPrev, &QAction::triggered, this, [this] { - history_ignore = true; - history_index--; - auto h = history.at(history_index); - if (tabWidget->currentIndex() != h.first) { - selectionModel[tabWidget->currentIndex()]->clearSelection(); - tabWidget->setCurrentIndex(h.first); - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); - } else { - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); - } - updateButtons(); - }); - - actionNext = new QAction("", this); - actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png")); - actionNext->setEnabled(false); - connect(actionNext, &QAction::triggered, this, [this] { - history_ignore = true; - history_index++; - auto h = history.at(history_index); - if (tabWidget->currentIndex() != h.first) { - selectionModel[tabWidget->currentIndex()]->clearSelection(); - tabWidget->setCurrentIndex(h.first); - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); - } else { - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); - } - updateButtons(); - }); - - actionLast = new QAction("", this); - actionLast->setIcon(QIcon(":/icons/resources/resultset_last.png")); - actionLast->setEnabled(false); - connect(actionLast, &QAction::triggered, this, [this] { - history_ignore = true; - history_index = int(history.size() - 1); - auto h = history.at(history_index); - if (tabWidget->currentIndex() != h.first) { - selectionModel[tabWidget->currentIndex()]->clearSelection(); - tabWidget->setCurrentIndex(h.first); - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); - } else { - selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); - } - updateButtons(); - }); - - actionClear = new QAction("", this); - actionClear->setIcon(QIcon(":/icons/resources/cross.png")); - actionClear->setEnabled(true); - connect(actionClear, &QAction::triggered, this, [this] { - history_index = -1; - history.clear(); - int num = tabWidget->currentIndex(); - if (selectionModel[num]->selectedIndexes().size() > 0) { - QModelIndex index = selectionModel[num]->selectedIndexes().at(0); - if (index.isValid()) { - ElementType type = treeModel[num]->nodeFromIndex(index)->type(); - if (type != ElementType::NONE) - addToHistory(num, index); - } - } - updateButtons(); - }); - - QToolBar *toolbar = new QToolBar(); - toolbar->addAction(actionFirst); - toolbar->addAction(actionPrev); - toolbar->addAction(actionNext); - toolbar->addAction(actionLast); - toolbar->addAction(actionClear); - - QWidget *topWidget = new QWidget(); - QVBoxLayout *vbox1 = new QVBoxLayout(); - topWidget->setLayout(vbox1); - vbox1->setSpacing(5); - vbox1->setContentsMargins(0, 0, 0, 0); - vbox1->addWidget(searchEdit); - vbox1->addWidget(tabWidget); - - QWidget *toolbarWidget = new QWidget(); - QHBoxLayout *hbox = new QHBoxLayout; - hbox->setAlignment(Qt::AlignCenter); - toolbarWidget->setLayout(hbox); - hbox->setSpacing(0); - hbox->setContentsMargins(0, 0, 0, 0); - hbox->addWidget(toolbar); - - QWidget *btmWidget = new QWidget(); - - QVBoxLayout *vbox2 = new QVBoxLayout(); - btmWidget->setLayout(vbox2); - vbox2->setSpacing(0); - vbox2->setContentsMargins(0, 0, 0, 0); - vbox2->addWidget(toolbarWidget); - vbox2->addWidget(propertyEditor); - - QSplitter *splitter = new QSplitter(Qt::Vertical); - splitter->addWidget(topWidget); - splitter->addWidget(btmWidget); - - QGridLayout *mainLayout = new QGridLayout(); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->addWidget(splitter); - setLayout(mainLayout); - - // Connection - connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this, - &DesignWidget::prepareMenuProperty); - connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); - connect(propertyEditor, &QtTreePropertyBrowser::hoverPropertyChanged, this, &DesignWidget::onHoverPropertyChanged); - - for (int num = 0; num < 6; num++) { - connect(treeView[num], &TreeView::customContextMenuRequested, - [this, num](const QPoint &pos) { prepareMenuTree(num, pos); }); - connect(treeView[num], &TreeView::doubleClicked, [this](const QModelIndex &index) { onDoubleClicked(index); }); - connect(treeView[num], &TreeView::hoverIndexChanged, - [this, num](QModelIndex index) { onHoverIndexChanged(num, index); }); - selectionModel[num] = treeView[num]->selectionModel(); - connect(selectionModel[num], &QItemSelectionModel::selectionChanged, - [this, num](const QItemSelection &selected, const QItemSelection &deselected) { - onSelectionChanged(num, selected, deselected); - }); - } - - history_index = -1; - history_ignore = false; - - highlightColors[0] = QColor("#6495ed"); - highlightColors[1] = QColor("#7fffd4"); - highlightColors[2] = QColor("#98fb98"); - highlightColors[3] = QColor("#ffd700"); - highlightColors[4] = QColor("#cd5c5c"); - highlightColors[5] = QColor("#fa8072"); - highlightColors[6] = QColor("#ff69b4"); - highlightColors[7] = QColor("#da70d6"); -} - -DesignWidget::~DesignWidget() {} - -void DesignWidget::updateButtons() -{ - int count = int(history.size()); - actionFirst->setEnabled(history_index > 0); - actionPrev->setEnabled(history_index > 0); - actionNext->setEnabled(history_index < (count - 1)); - actionLast->setEnabled(history_index < (count - 1)); -} - -void DesignWidget::addToHistory(int tab, QModelIndex item) -{ - if (!history_ignore) { - int count = int(history.size()); - for (int i = count - 1; i > history_index; i--) - history.pop_back(); - history.push_back(std::make_pair(tab, item)); - history_index++; - } - history_ignore = false; - updateButtons(); -} - -void DesignWidget::newContext(Context *ctx) -{ - if (!ctx) - return; - - highlightSelected.clear(); - history_ignore = false; - history_index = -1; - history.clear(); - updateButtons(); - - highlightSelected.clear(); - this->ctx = ctx; - { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - { - TreeModel::ElementXYRoot::ElementMap belMap; - for (const auto &bel : ctx->getBels()) { - auto loc = ctx->getBelLocation(bel); - belMap[std::pair(loc.x, loc.y)].push_back(bel); - } - auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); }; - - getTreeByElementType(ElementType::BEL) - ->loadData(ctx, - std::unique_ptr>( - new TreeModel::ElementXYRoot(ctx, belMap, belGetter, ElementType::BEL))); - } - - { - TreeModel::ElementXYRoot::ElementMap wireMap; -#ifdef ARCH_ICE40 - for (int i = 0; i < int(ctx->chip_info->wire_data.size()); i++) { - const auto wire = &ctx->chip_info->wire_data[i]; - WireId wireid; - wireid.index = i; - wireMap[std::pair(wire->x, wire->y)].push_back(wireid); - } -#endif -#ifdef ARCH_ECP5 - for (const auto &wire : ctx->getWires()) { - wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); - } -#endif -#ifdef ARCH_MACHXO2 - for (const auto &wire : ctx->getWires()) { - wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); - } -#endif -#ifdef ARCH_GOWIN - for (const auto &wire : ctx->getWires()) { - WireInfo wi = ctx->wire_info(wire); - wireMap[std::pair(wi.x, wi.y)].push_back(wire); - } -#endif -#ifdef ARCH_HIMBAECHEL - for (const auto &wire : ctx->getWires()) { - Loc loc; - tile_xy(ctx->chip_info, wire.tile, loc.x, loc.y); - wireMap[std::pair(loc.x, loc.y)].push_back(wire); - } -#endif - auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; - getTreeByElementType(ElementType::WIRE) - ->loadData(ctx, - std::unique_ptr>(new TreeModel::ElementXYRoot( - ctx, wireMap, wireGetter, ElementType::WIRE))); - } - - { - TreeModel::ElementXYRoot::ElementMap pipMap; - for (const auto &pip : ctx->getPips()) { - auto loc = ctx->getPipLocation(pip); - pipMap[std::pair(loc.x, loc.y)].push_back(pip); - } - auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); }; - - getTreeByElementType(ElementType::PIP) - ->loadData(ctx, - std::unique_ptr>( - new TreeModel::ElementXYRoot(ctx, pipMap, pipGetter, ElementType::PIP))); - } - - getTreeByElementType(ElementType::CELL) - ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::CELL))); - getTreeByElementType(ElementType::NET) - ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::NET))); - } - updateTree(); -} - -void DesignWidget::updateTree() -{ - clearProperties(); - - QMap::iterator i = highlightSelected.begin(); - while (i != highlightSelected.end()) { - QMap::iterator prev = i; - ++i; - if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()[0]) == ctx->nets.end()) { - highlightSelected.erase(prev); - } - if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()[0]) == ctx->cells.end()) { - highlightSelected.erase(prev); - } - } - - { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - std::vector cells; - for (auto &pair : ctx->cells) { - cells.push_back(IdStringList(pair.first)); - } - std::vector nets; - for (auto &pair : ctx->nets) { - nets.push_back(IdStringList(pair.first)); - } - - getTreeByElementType(ElementType::CELL)->updateElements(cells); - getTreeByElementType(ElementType::NET)->updateElements(nets); - } -} -QtProperty *DesignWidget::addTopLevelProperty(const QString &id) -{ - QtProperty *topItem = groupManager->addProperty(id); - propertyToId[topItem] = id; - idToProperty[id] = topItem; - topItem->setSelectable(false); - propertyEditor->addProperty(topItem); - return topItem; -} - -void DesignWidget::clearProperties() -{ - QMap::ConstIterator itProp = propertyToId.constBegin(); - while (itProp != propertyToId.constEnd()) { - delete itProp.key(); - itProp++; - } - propertyToId.clear(); - idToProperty.clear(); -} - -QString DesignWidget::getElementTypeName(ElementType type) -{ - if (type == ElementType::NONE) - return ""; - if (type == ElementType::BEL) - return "BEL"; - if (type == ElementType::WIRE) - return "WIRE"; - if (type == ElementType::PIP) - return "PIP"; - if (type == ElementType::NET) - return "NET"; - if (type == ElementType::CELL) - return "CELL"; - return ""; -} -ElementType DesignWidget::getElementTypeByName(QString type) -{ - if (type == "BEL") - return ElementType::BEL; - if (type == "WIRE") - return ElementType::WIRE; - if (type == "PIP") - return ElementType::PIP; - if (type == "NET") - return ElementType::NET; - if (type == "CELL") - return ElementType::CELL; - return ElementType::NONE; -} - -TreeModel::Model *DesignWidget::getTreeByElementType(ElementType type) -{ - if (type == ElementType::NONE) - return nullptr; - if (type == ElementType::BEL) - return treeModel[0]; - if (type == ElementType::WIRE) - return treeModel[1]; - if (type == ElementType::PIP) - return treeModel[2]; - if (type == ElementType::CELL) - return treeModel[3]; - if (type == ElementType::NET) - return treeModel[4]; - return nullptr; -} -int DesignWidget::getIndexByElementType(ElementType type) -{ - if (type == ElementType::NONE) - return -1; - if (type == ElementType::BEL) - return 0; - if (type == ElementType::WIRE) - return 1; - if (type == ElementType::PIP) - return 2; - if (type == ElementType::CELL) - return 3; - if (type == ElementType::NET) - return 4; - if (type == ElementType::GROUP) - return 5; - return -1; -} -void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, - const ElementType &type) -{ - QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name); - item->setValue(value); - item->setPropertyId(getElementTypeName(type)); - item->setSelectable(type != ElementType::NONE); - topItem->addSubProperty(item); -} - -QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) -{ - QtProperty *item = groupManager->addProperty(name); - item->setSelectable(false); - topItem->addSubProperty(item); - return item; -} - -void DesignWidget::clearAllSelectionModels() -{ - for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) - selectionModel[i]->clearSelection(); -} - -void DesignWidget::onClickedBel(BelId bel, bool keep) -{ - boost::optional item; - { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - item = getTreeByElementType(ElementType::BEL)->nodeForId(ctx->getBelName(bel)); - if (!item) - return; - - Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); - } - int index = getIndexByElementType(ElementType::BEL); - if (!keep) - clearAllSelectionModels(); - if (tabWidget->currentIndex() != index) { - tabWidget->setCurrentIndex(index); - } - selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::BEL)->indexFromNode(*item), - keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); -} - -void DesignWidget::onClickedWire(WireId wire, bool keep) -{ - boost::optional item; - { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - item = getTreeByElementType(ElementType::WIRE)->nodeForId(ctx->getWireName(wire)); - if (!item) - return; - - Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); - } - int index = getIndexByElementType(ElementType::WIRE); - if (!keep) - clearAllSelectionModels(); - if (tabWidget->currentIndex() != index) - tabWidget->setCurrentIndex(index); - selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::WIRE)->indexFromNode(*item), - keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); -} - -void DesignWidget::onClickedPip(PipId pip, bool keep) -{ - boost::optional item; - { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - item = getTreeByElementType(ElementType::PIP)->nodeForId(ctx->getPipName(pip)); - if (!item) - return; - - Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); - } - - int index = getIndexByElementType(ElementType::PIP); - if (!keep) - clearAllSelectionModels(); - if (tabWidget->currentIndex() != index) - tabWidget->setCurrentIndex(index); - selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::PIP)->indexFromNode(*item), - keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); -} - -void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QItemSelection &) -{ - int num_selected = 0; - std::vector decals; - for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { - num_selected += selectionModel[i]->selectedIndexes().size(); - for (auto index : selectionModel[i]->selectedIndexes()) { - TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); - std::vector d = getDecals(item->type(), item->id()); - std::move(d.begin(), d.end(), std::back_inserter(decals)); - } - } - - // Keep other tree seleciton only if Control is pressed - if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) { - Q_EMIT selected(decals, false); - return; - } - - // For deselect and multiple select just send all - if (selectionModel[num]->selectedIndexes().size() != 1) { - Q_EMIT selected(decals, false); - return; - } - - QModelIndex index = selectionModel[num]->selectedIndexes().at(0); - if (!index.isValid()) - return; - TreeModel::Item *clickItem = treeModel[num]->nodeFromIndex(index); - - ElementType type = clickItem->type(); - if (type == ElementType::NONE) - return; - - // Clear other tab selections - for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) - if (i != num) - selectionModel[i]->clearSelection(); - - addToHistory(num, index); - - clearProperties(); - - IdStringList c = clickItem->id(); - Q_EMIT selected(getDecals(type, c), false); - - if (type == ElementType::BEL) { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - BelId bel = ctx->getBelByName(c); - QtProperty *topItem = addTopLevelProperty("Bel"); - - addProperty(topItem, QVariant::String, "Name", ctx->nameOfBel(bel)); - addProperty(topItem, QVariant::String, "Type", ctx->getBelType(bel).c_str(ctx)); - addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel)); - addProperty(topItem, QVariant::String, "Bound Cell", ctx->nameOf(ctx->getBoundBelCell(bel)), ElementType::CELL); - addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->nameOf(ctx->getConflictingBelCell(bel)), - ElementType::CELL); - - QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); - for (auto &item : ctx->getBelAttrs(bel)) { - addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); - } - - QtProperty *belpinsItem = addSubGroup(topItem, "Ports"); - for (const auto &item : ctx->getBelPins(bel)) { - QtProperty *portInfoItem = addSubGroup(belpinsItem, item.c_str(ctx)); - addProperty(portInfoItem, QVariant::String, "Name", item.c_str(ctx)); - addProperty(portInfoItem, QVariant::Int, "Type", int(ctx->getBelPinType(bel, item))); - WireId wire = ctx->getBelPinWire(bel, item); - addProperty(portInfoItem, QVariant::String, "Wire", ctx->nameOfWire(wire), ElementType::WIRE); - } - } else if (type == ElementType::WIRE) { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - WireId wire = ctx->getWireByName(c); - QtProperty *topItem = addTopLevelProperty("Wire"); - - addProperty(topItem, QVariant::String, "Name", ctx->nameOfWire(wire)); - addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx)); - addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire)); - addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundWireNet(wire)), ElementType::NET); - addProperty(topItem, QVariant::String, "Conflicting Wire", ctx->nameOfWire(ctx->getConflictingWireWire(wire)), - ElementType::WIRE); - addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)), - ElementType::NET); - - QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); - for (auto &item : ctx->getWireAttrs(wire)) { - addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); - } - - DelayQuad delay = ctx->getWireDelay(wire); - - QtProperty *delayItem = addSubGroup(topItem, "Delay"); - addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); - addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); - addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); - addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); - - QtProperty *belpinsItem = addSubGroup(topItem, "BelPins"); - for (const auto &item : ctx->getWireBelPins(wire)) { - QString belname = ""; - if (item.bel != BelId()) - belname = ctx->nameOfBel(item.bel); - QString pinname = item.pin.c_str(ctx); - - QtProperty *dhItem = addSubGroup(belpinsItem, belname + "-" + pinname); - addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL); - addProperty(dhItem, QVariant::String, "PortPin", pinname); - } - - int counter = 0; - QtProperty *pipsDownItem = addSubGroup(topItem, "Pips Downhill"); - for (const auto &item : ctx->getPipsDownhill(wire)) { - addProperty(pipsDownItem, QVariant::String, "", ctx->nameOfPip(item), ElementType::PIP); - counter++; - if (counter == 50) { - addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); - break; - } - } - - counter = 0; - QtProperty *pipsUpItem = addSubGroup(topItem, "Pips Uphill"); - for (const auto &item : ctx->getPipsUphill(wire)) { - addProperty(pipsUpItem, QVariant::String, "", ctx->nameOfPip(item), ElementType::PIP); - counter++; - if (counter == 50) { - addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); - break; - } - } - } else if (type == ElementType::PIP) { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - PipId pip = ctx->getPipByName(c); - QtProperty *topItem = addTopLevelProperty("Pip"); - - addProperty(topItem, QVariant::String, "Name", ctx->nameOfPip(pip)); - addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx)); - addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip)); - addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET); - if (ctx->getConflictingPipWire(pip) != WireId()) { - addProperty(topItem, QVariant::String, "Conflicting Wire", ctx->nameOfWire(ctx->getConflictingPipWire(pip)), - ElementType::WIRE); - } else { - addProperty(topItem, QVariant::String, "Conflicting Wire", "", ElementType::NONE); - } - addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)), - ElementType::NET); - addProperty(topItem, QVariant::String, "Src Wire", ctx->nameOfWire(ctx->getPipSrcWire(pip)), ElementType::WIRE); - addProperty(topItem, QVariant::String, "Dest Wire", ctx->nameOfWire(ctx->getPipDstWire(pip)), - ElementType::WIRE); - - QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); - for (auto &item : ctx->getPipAttrs(pip)) { - addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); - } - - DelayQuad delay = ctx->getPipDelay(pip); - - QtProperty *delayItem = addSubGroup(topItem, "Delay"); - addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); - addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); - addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); - addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); - } else if (type == ElementType::NET) { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - NetInfo *net = ctx->nets.at(c[0]).get(); - - QtProperty *topItem = addTopLevelProperty("Net"); - - addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx)); - - QtProperty *driverItem = addSubGroup(topItem, "Driver"); - addProperty(driverItem, QVariant::String, "Port", net->driver.port.c_str(ctx)); - if (net->driver.cell) - addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL); - else - addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL); - - QtProperty *usersItem = addSubGroup(topItem, "Users"); - for (auto &item : net->users) { - QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx)); - - addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx)); - if (item.cell) - addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL); - else - addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL); - } - - QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); - for (auto &item : net->attrs) { - addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), - item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); - } - - QtProperty *wiresItem = addSubGroup(topItem, "Wires"); - for (auto &item : net->wires) { - auto name = ctx->nameOfWire(item.first); - - QtProperty *wireItem = addSubGroup(wiresItem, name); - addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE); - - if (item.second.pip != PipId()) - addProperty(wireItem, QVariant::String, "Pip", ctx->nameOfPip(item.second.pip), ElementType::PIP); - else - addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP); - - addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength); - } - - } else if (type == ElementType::CELL) { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - CellInfo *cell = ctx->cells.at(c[0]).get(); - - QtProperty *topItem = addTopLevelProperty("Cell"); - - addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx)); - addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx)); - if (cell->bel != BelId()) - addProperty(topItem, QVariant::String, "Bel", ctx->nameOfBel(cell->bel), ElementType::BEL); - else - addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL); - addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength)); - - QtProperty *cellPortsItem = addSubGroup(topItem, "Ports"); - for (auto &item : cell->ports) { - PortInfo p = item.second; - - QtProperty *portInfoItem = addSubGroup(cellPortsItem, p.name.c_str(ctx)); - addProperty(portInfoItem, QVariant::String, "Name", p.name.c_str(ctx)); - addProperty(portInfoItem, QVariant::Int, "Type", int(p.type)); - if (p.net) - addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET); - else - addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET); - } - - QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes"); - for (auto &item : cell->attrs) { - addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx), - item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); - } - - QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters"); - for (auto &item : cell->params) { - addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), - item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); - } - } -} - -std::vector DesignWidget::getDecals(ElementType type, IdStringList value) -{ - std::vector decals; - switch (type) { - case ElementType::BEL: { - BelId bel = ctx->getBelByName(value); - if (bel != BelId()) { - decals.push_back(ctx->getBelDecal(bel)); - } - } break; - case ElementType::WIRE: { - WireId wire = ctx->getWireByName(value); - if (wire != WireId()) { - decals.push_back(ctx->getWireDecal(wire)); - } - } break; - case ElementType::PIP: { - PipId pip = ctx->getPipByName(value); - if (pip != PipId()) { - decals.push_back(ctx->getPipDecal(pip)); - } - } break; - case ElementType::NET: { - NetInfo *net = ctx->nets.at(value[0]).get(); - for (auto &item : net->wires) { - decals.push_back(ctx->getWireDecal(item.first)); - if (item.second.pip != PipId()) { - decals.push_back(ctx->getPipDecal(item.second.pip)); - } - } - } break; - case ElementType::CELL: { - CellInfo *cell = ctx->cells.at(value[0]).get(); - if (cell->bel != BelId()) { - decals.push_back(ctx->getBelDecal(cell->bel)); - } - } break; - default: - break; - } - return decals; -} - -void DesignWidget::updateHighlightGroup(QList items, int group) -{ - const bool shouldClear = items.size() == 1; - for (auto item : items) { - if (highlightSelected.contains(item)) { - if (shouldClear && highlightSelected[item] == group) { - highlightSelected.remove(item); - } else - highlightSelected[item] = group; - } else - highlightSelected.insert(item, group); - } - std::vector decals[8]; - - for (auto it : highlightSelected.toStdMap()) { - std::vector d = getDecals(it.first->type(), it.first->id()); - std::move(d.begin(), d.end(), std::back_inserter(decals[it.second])); - } - for (int i = 0; i < 8; i++) - Q_EMIT highlight(decals[i], i); -} - -void DesignWidget::prepareMenuProperty(const QPoint &pos) -{ - QTreeWidget *tree = propertyEditor->treeWidget(); - QList items; - for (auto itemContextMenu : tree->selectedItems()) { - QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu); - if (!browserItem) - continue; - QtProperty *selectedProperty = browserItem->property(); - ElementType type = getElementTypeByName(selectedProperty->propertyId()); - if (type == ElementType::NONE) - continue; - IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); - auto node = getTreeByElementType(type)->nodeForId(value); - if (!node) - continue; - items.append(*node); - } - int selectedIndex = -1; - if (items.size() == 1) { - TreeModel::Item *item = items.at(0); - if (highlightSelected.contains(item)) - selectedIndex = highlightSelected[item]; - } - - QMenu menu(this); - QAction *selectAction = new QAction("&Select", this); - connect(selectAction, &QAction::triggered, this, [this, items] { - std::vector decals; - for (auto clickItem : items) { - std::vector d = getDecals(clickItem->type(), clickItem->id()); - std::move(d.begin(), d.end(), std::back_inserter(decals)); - } - Q_EMIT selected(decals, false); - }); - menu.addAction(selectAction); - - QMenu *subMenu = menu.addMenu("Highlight"); - QActionGroup *group = new QActionGroup(this); - group->setExclusive(true); - for (int i = 0; i < 8; i++) { - QPixmap pixmap(32, 32); - pixmap.fill(QColor(highlightColors[i])); - QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); - action->setCheckable(true); - subMenu->addAction(action); - group->addAction(action); - if (selectedIndex == i) - action->setChecked(true); - connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); - } - menu.exec(tree->mapToGlobal(pos)); -} - -void DesignWidget::prepareMenuTree(int num, const QPoint &pos) -{ - int selectedIndex = -1; - - if (selectionModel[num]->selectedIndexes().size() == 0) - return; - - QList items; - for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { - for (auto index : selectionModel[i]->selectedIndexes()) { - TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); - items.append(item); - } - } - if (items.size() == 1) { - TreeModel::Item *item = items.at(0); - if (highlightSelected.contains(item)) - selectedIndex = highlightSelected[item]; - } - QMenu menu(this); - QMenu *subMenu = menu.addMenu("Highlight"); - QActionGroup *group = new QActionGroup(this); - group->setExclusive(true); - for (int i = 0; i < 8; i++) { - QPixmap pixmap(32, 32); - pixmap.fill(QColor(highlightColors[i])); - QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); - action->setCheckable(true); - subMenu->addAction(action); - group->addAction(action); - if (selectedIndex == i) - action->setChecked(true); - connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); - } - menu.exec(treeView[num]->mapToGlobal(pos)); -} - -void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) -{ - QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property(); - ElementType type = getElementTypeByName(selectedProperty->propertyId()); - if (type == ElementType::NONE) - return; - - IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); - auto it = getTreeByElementType(type)->nodeForId(value); - if (it) { - int num = getIndexByElementType(type); - clearAllSelectionModels(); - if (tabWidget->currentIndex() != num) - tabWidget->setCurrentIndex(num); - selectionModel[num]->setCurrentIndex(getTreeByElementType(type)->indexFromNode(*it), - QItemSelectionModel::ClearAndSelect); - } -} - -void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); } - -void DesignWidget::onSearchInserted() -{ - if (currentSearch == searchEdit->text() && currentIndexTab == tabWidget->currentIndex()) { - currentIndex++; - if (currentIndex >= currentSearchIndexes.size()) - currentIndex = 0; - } else { - std::lock_guard lock_ui(ctx->ui_mutex); - std::lock_guard lock(ctx->mutex); - - currentSearch = searchEdit->text(); - currentSearchIndexes = treeModel[tabWidget->currentIndex()]->search(searchEdit->text()); - currentIndex = 0; - currentIndexTab = tabWidget->currentIndex(); - } - if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size()) - selectionModel[tabWidget->currentIndex()]->setCurrentIndex(currentSearchIndexes.at(currentIndex), - QItemSelectionModel::ClearAndSelect); -} - -void DesignWidget::onHoverIndexChanged(int num, QModelIndex index) -{ - if (index.isValid()) { - TreeModel::Item *item = treeModel[num]->nodeFromIndex(index); - if (item->type() != ElementType::NONE) { - std::vector decals = getDecals(item->type(), item->id()); - if (decals.size() > 0) - Q_EMIT hover(decals.at(0)); - return; - } - } - Q_EMIT hover(DecalXY()); -} - -void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item) -{ - if (item != nullptr) { - QtProperty *selectedProperty = item->property(); - ElementType type = getElementTypeByName(selectedProperty->propertyId()); - if (type != ElementType::NONE) { - IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); - if (value != IdStringList()) { - auto node = getTreeByElementType(type)->nodeForId(value); - if (node) { - std::vector decals = getDecals((*node)->type(), (*node)->id()); - if (decals.size() > 0) - Q_EMIT hover(decals.at(0)); - return; - } - } - } - } - Q_EMIT hover(DecalXY()); -} -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "designwidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "fpgaviewwidget.h" + +NEXTPNR_NAMESPACE_BEGIN + +TreeView::TreeView(QWidget *parent) : QTreeView(parent) {} + +TreeView::~TreeView() {} + +void TreeView::mouseMoveEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (index != current) { + current = index; + Q_EMIT hoverIndexChanged(index); + } + QTreeView::mouseMoveEvent(event); +} + +void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); } + +DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) +{ + tabWidget = new QTabWidget(); + + // Add tree view + for (int i = 0; i < 6; i++) { + treeView[i] = new TreeView(); + treeModel[i] = new TreeModel::Model(); + treeView[i]->setModel(treeModel[i]); + treeView[i]->setContextMenuPolicy(Qt::CustomContextMenu); + treeView[i]->setSelectionMode(QAbstractItemView::ExtendedSelection); + treeView[i]->viewport()->setMouseTracking(true); + selectionModel[i] = nullptr; + } + + tabWidget->addTab(treeView[0], "Bels"); + tabWidget->addTab(treeView[1], "Wires"); + tabWidget->addTab(treeView[2], "Pips"); + tabWidget->addTab(treeView[3], "Cells"); + tabWidget->addTab(treeView[4], "Nets"); + tabWidget->addTab(treeView[5], "Groups"); + + // Add property view + variantManager = new QtVariantPropertyManager(this); + readOnlyManager = new QtVariantPropertyManager(this); + groupManager = new QtGroupPropertyManager(this); + variantFactory = new QtVariantEditorFactory(this); + propertyEditor = new QtTreePropertyBrowser(this); + propertyEditor->setFactoryForManager(variantManager, variantFactory); + propertyEditor->setPropertiesWithoutValueMarked(true); + propertyEditor->show(); + propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu); + propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection); + propertyEditor->treeWidget()->viewport()->setMouseTracking(true); + + searchEdit = new QLineEdit(); + searchEdit->setClearButtonEnabled(true); + searchEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition); + searchEdit->setPlaceholderText("Search..."); + connect(searchEdit, &QLineEdit::returnPressed, this, &DesignWidget::onSearchInserted); + + actionFirst = new QAction("", this); + actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png")); + actionFirst->setEnabled(false); + connect(actionFirst, &QAction::triggered, this, [this] { + history_ignore = true; + history_index = 0; + auto h = history.at(history_index); + if (tabWidget->currentIndex() != h.first) { + selectionModel[tabWidget->currentIndex()]->clearSelection(); + tabWidget->setCurrentIndex(h.first); + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); + } else { + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); + } + updateButtons(); + }); + + actionPrev = new QAction("", this); + actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png")); + actionPrev->setEnabled(false); + connect(actionPrev, &QAction::triggered, this, [this] { + history_ignore = true; + history_index--; + auto h = history.at(history_index); + if (tabWidget->currentIndex() != h.first) { + selectionModel[tabWidget->currentIndex()]->clearSelection(); + tabWidget->setCurrentIndex(h.first); + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); + } else { + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); + } + updateButtons(); + }); + + actionNext = new QAction("", this); + actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png")); + actionNext->setEnabled(false); + connect(actionNext, &QAction::triggered, this, [this] { + history_ignore = true; + history_index++; + auto h = history.at(history_index); + if (tabWidget->currentIndex() != h.first) { + selectionModel[tabWidget->currentIndex()]->clearSelection(); + tabWidget->setCurrentIndex(h.first); + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); + } else { + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); + } + updateButtons(); + }); + + actionLast = new QAction("", this); + actionLast->setIcon(QIcon(":/icons/resources/resultset_last.png")); + actionLast->setEnabled(false); + connect(actionLast, &QAction::triggered, this, [this] { + history_ignore = true; + history_index = int(history.size() - 1); + auto h = history.at(history_index); + if (tabWidget->currentIndex() != h.first) { + selectionModel[tabWidget->currentIndex()]->clearSelection(); + tabWidget->setCurrentIndex(h.first); + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); + } else { + selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); + } + updateButtons(); + }); + + actionClear = new QAction("", this); + actionClear->setIcon(QIcon(":/icons/resources/cross.png")); + actionClear->setEnabled(true); + connect(actionClear, &QAction::triggered, this, [this] { + history_index = -1; + history.clear(); + int num = tabWidget->currentIndex(); + if (selectionModel[num]->selectedIndexes().size() > 0) { + QModelIndex index = selectionModel[num]->selectedIndexes().at(0); + if (index.isValid()) { + ElementType type = treeModel[num]->nodeFromIndex(index)->type(); + if (type != ElementType::NONE) + addToHistory(num, index); + } + } + updateButtons(); + }); + + QToolBar *toolbar = new QToolBar(); + toolbar->addAction(actionFirst); + toolbar->addAction(actionPrev); + toolbar->addAction(actionNext); + toolbar->addAction(actionLast); + toolbar->addAction(actionClear); + + QWidget *topWidget = new QWidget(); + QVBoxLayout *vbox1 = new QVBoxLayout(); + topWidget->setLayout(vbox1); + vbox1->setSpacing(5); + vbox1->setContentsMargins(0, 0, 0, 0); + vbox1->addWidget(searchEdit); + vbox1->addWidget(tabWidget); + + QWidget *toolbarWidget = new QWidget(); + QHBoxLayout *hbox = new QHBoxLayout; + hbox->setAlignment(Qt::AlignCenter); + toolbarWidget->setLayout(hbox); + hbox->setSpacing(0); + hbox->setContentsMargins(0, 0, 0, 0); + hbox->addWidget(toolbar); + + QWidget *btmWidget = new QWidget(); + + QVBoxLayout *vbox2 = new QVBoxLayout(); + btmWidget->setLayout(vbox2); + vbox2->setSpacing(0); + vbox2->setContentsMargins(0, 0, 0, 0); + vbox2->addWidget(toolbarWidget); + vbox2->addWidget(propertyEditor); + + QSplitter *splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(topWidget); + splitter->addWidget(btmWidget); + + QGridLayout *mainLayout = new QGridLayout(); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addWidget(splitter); + setLayout(mainLayout); + + // Connection + connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this, + &DesignWidget::prepareMenuProperty); + connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); + connect(propertyEditor, &QtTreePropertyBrowser::hoverPropertyChanged, this, &DesignWidget::onHoverPropertyChanged); + + for (int num = 0; num < 6; num++) { + connect(treeView[num], &TreeView::customContextMenuRequested, + [this, num](const QPoint &pos) { prepareMenuTree(num, pos); }); + connect(treeView[num], &TreeView::doubleClicked, [this](const QModelIndex &index) { onDoubleClicked(index); }); + connect(treeView[num], &TreeView::hoverIndexChanged, + [this, num](QModelIndex index) { onHoverIndexChanged(num, index); }); + selectionModel[num] = treeView[num]->selectionModel(); + connect(selectionModel[num], &QItemSelectionModel::selectionChanged, + [this, num](const QItemSelection &selected, const QItemSelection &deselected) { + onSelectionChanged(num, selected, deselected); + }); + } + + history_index = -1; + history_ignore = false; + + highlightColors[0] = QColor("#6495ed"); + highlightColors[1] = QColor("#7fffd4"); + highlightColors[2] = QColor("#98fb98"); + highlightColors[3] = QColor("#ffd700"); + highlightColors[4] = QColor("#cd5c5c"); + highlightColors[5] = QColor("#fa8072"); + highlightColors[6] = QColor("#ff69b4"); + highlightColors[7] = QColor("#da70d6"); +} + +DesignWidget::~DesignWidget() {} + +void DesignWidget::updateButtons() +{ + int count = int(history.size()); + actionFirst->setEnabled(history_index > 0); + actionPrev->setEnabled(history_index > 0); + actionNext->setEnabled(history_index < (count - 1)); + actionLast->setEnabled(history_index < (count - 1)); +} + +void DesignWidget::addToHistory(int tab, QModelIndex item) +{ + if (!history_ignore) { + int count = int(history.size()); + for (int i = count - 1; i > history_index; i--) + history.pop_back(); + history.push_back(std::make_pair(tab, item)); + history_index++; + } + history_ignore = false; + updateButtons(); +} + +void DesignWidget::newContext(Context *ctx) +{ + if (!ctx) + return; + + highlightSelected.clear(); + history_ignore = false; + history_index = -1; + history.clear(); + updateButtons(); + + highlightSelected.clear(); + this->ctx = ctx; + { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + { + TreeModel::ElementXYRoot::ElementMap belMap; + for (const auto &bel : ctx->getBels()) { + auto loc = ctx->getBelLocation(bel); + belMap[std::pair(loc.x, loc.y)].push_back(bel); + } + auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); }; + + getTreeByElementType(ElementType::BEL) + ->loadData(ctx, + std::unique_ptr>( + new TreeModel::ElementXYRoot(ctx, belMap, belGetter, ElementType::BEL))); + } + + { + TreeModel::ElementXYRoot::ElementMap wireMap; +#ifdef ARCH_ICE40 + for (int i = 0; i < int(ctx->chip_info->wire_data.size()); i++) { + const auto wire = &ctx->chip_info->wire_data[i]; + WireId wireid; + wireid.index = i; + wireMap[std::pair(wire->x, wire->y)].push_back(wireid); + } +#endif +#ifdef ARCH_ECP5 + for (const auto &wire : ctx->getWires()) { + wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); + } +#endif +#ifdef ARCH_MACHXO2 + for (const auto &wire : ctx->getWires()) { + wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); + } +#endif +#ifdef ARCH_GOWIN + for (const auto &wire : ctx->getWires()) { + WireInfo wi = ctx->wire_info(wire); + wireMap[std::pair(wi.x, wi.y)].push_back(wire); + } +#endif +#ifdef ARCH_HIMBAECHEL + for (const auto &wire : ctx->getWires()) { + Loc loc; + tile_xy(ctx->chip_info, wire.tile, loc.x, loc.y); + wireMap[std::pair(loc.x, loc.y)].push_back(wire); + } +#endif + auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; + getTreeByElementType(ElementType::WIRE) + ->loadData(ctx, + std::unique_ptr>(new TreeModel::ElementXYRoot( + ctx, wireMap, wireGetter, ElementType::WIRE))); + } + + { + TreeModel::ElementXYRoot::ElementMap pipMap; + for (const auto &pip : ctx->getPips()) { + auto loc = ctx->getPipLocation(pip); + pipMap[std::pair(loc.x, loc.y)].push_back(pip); + } + auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); }; + + getTreeByElementType(ElementType::PIP) + ->loadData(ctx, + std::unique_ptr>( + new TreeModel::ElementXYRoot(ctx, pipMap, pipGetter, ElementType::PIP))); + } + + getTreeByElementType(ElementType::CELL) + ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::CELL))); + getTreeByElementType(ElementType::NET) + ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::NET))); + } + updateTree(); +} + +void DesignWidget::updateTree() +{ + clearProperties(); + + QMap::iterator i = highlightSelected.begin(); + while (i != highlightSelected.end()) { + QMap::iterator prev = i; + ++i; + if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()[0]) == ctx->nets.end()) { + highlightSelected.erase(prev); + } + if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()[0]) == ctx->cells.end()) { + highlightSelected.erase(prev); + } + } + + { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + std::vector cells; + for (auto &pair : ctx->cells) { + cells.push_back(IdStringList(pair.first)); + } + std::vector nets; + for (auto &pair : ctx->nets) { + nets.push_back(IdStringList(pair.first)); + } + + getTreeByElementType(ElementType::CELL)->updateElements(cells); + getTreeByElementType(ElementType::NET)->updateElements(nets); + } +} +QtProperty *DesignWidget::addTopLevelProperty(const QString &id) +{ + QtProperty *topItem = groupManager->addProperty(id); + propertyToId[topItem] = id; + idToProperty[id] = topItem; + topItem->setSelectable(false); + propertyEditor->addProperty(topItem); + return topItem; +} + +void DesignWidget::clearProperties() +{ + QMap::ConstIterator itProp = propertyToId.constBegin(); + while (itProp != propertyToId.constEnd()) { + delete itProp.key(); + itProp++; + } + propertyToId.clear(); + idToProperty.clear(); +} + +QString DesignWidget::getElementTypeName(ElementType type) +{ + if (type == ElementType::NONE) + return ""; + if (type == ElementType::BEL) + return "BEL"; + if (type == ElementType::WIRE) + return "WIRE"; + if (type == ElementType::PIP) + return "PIP"; + if (type == ElementType::NET) + return "NET"; + if (type == ElementType::CELL) + return "CELL"; + return ""; +} +ElementType DesignWidget::getElementTypeByName(QString type) +{ + if (type == "BEL") + return ElementType::BEL; + if (type == "WIRE") + return ElementType::WIRE; + if (type == "PIP") + return ElementType::PIP; + if (type == "NET") + return ElementType::NET; + if (type == "CELL") + return ElementType::CELL; + return ElementType::NONE; +} + +TreeModel::Model *DesignWidget::getTreeByElementType(ElementType type) +{ + if (type == ElementType::NONE) + return nullptr; + if (type == ElementType::BEL) + return treeModel[0]; + if (type == ElementType::WIRE) + return treeModel[1]; + if (type == ElementType::PIP) + return treeModel[2]; + if (type == ElementType::CELL) + return treeModel[3]; + if (type == ElementType::NET) + return treeModel[4]; + return nullptr; +} +int DesignWidget::getIndexByElementType(ElementType type) +{ + if (type == ElementType::NONE) + return -1; + if (type == ElementType::BEL) + return 0; + if (type == ElementType::WIRE) + return 1; + if (type == ElementType::PIP) + return 2; + if (type == ElementType::CELL) + return 3; + if (type == ElementType::NET) + return 4; + if (type == ElementType::GROUP) + return 5; + return -1; +} +void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, + const ElementType &type) +{ + QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name); + item->setValue(value); + item->setPropertyId(getElementTypeName(type)); + item->setSelectable(type != ElementType::NONE); + topItem->addSubProperty(item); +} + +QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) +{ + QtProperty *item = groupManager->addProperty(name); + item->setSelectable(false); + topItem->addSubProperty(item); + return item; +} + +void DesignWidget::clearAllSelectionModels() +{ + for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) + selectionModel[i]->clearSelection(); +} + +void DesignWidget::onClickedBel(BelId bel, bool keep) +{ + boost::optional item; + { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + item = getTreeByElementType(ElementType::BEL)->nodeForId(ctx->getBelName(bel)); + if (!item) + return; + + Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); + } + int index = getIndexByElementType(ElementType::BEL); + if (!keep) + clearAllSelectionModels(); + if (tabWidget->currentIndex() != index) { + tabWidget->setCurrentIndex(index); + } + selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::BEL)->indexFromNode(*item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); +} + +void DesignWidget::onClickedWire(WireId wire, bool keep) +{ + boost::optional item; + { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + item = getTreeByElementType(ElementType::WIRE)->nodeForId(ctx->getWireName(wire)); + if (!item) + return; + + Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); + } + int index = getIndexByElementType(ElementType::WIRE); + if (!keep) + clearAllSelectionModels(); + if (tabWidget->currentIndex() != index) + tabWidget->setCurrentIndex(index); + selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::WIRE)->indexFromNode(*item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); +} + +void DesignWidget::onClickedPip(PipId pip, bool keep) +{ + boost::optional item; + { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + item = getTreeByElementType(ElementType::PIP)->nodeForId(ctx->getPipName(pip)); + if (!item) + return; + + Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); + } + + int index = getIndexByElementType(ElementType::PIP); + if (!keep) + clearAllSelectionModels(); + if (tabWidget->currentIndex() != index) + tabWidget->setCurrentIndex(index); + selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::PIP)->indexFromNode(*item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); +} + +void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QItemSelection &) +{ + int num_selected = 0; + std::vector decals; + for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { + num_selected += selectionModel[i]->selectedIndexes().size(); + for (auto index : selectionModel[i]->selectedIndexes()) { + TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); + std::vector d = getDecals(item->type(), item->id()); + std::move(d.begin(), d.end(), std::back_inserter(decals)); + } + } + + // Keep other tree seleciton only if Control is pressed + if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) { + Q_EMIT selected(decals, false); + return; + } + + // For deselect and multiple select just send all + if (selectionModel[num]->selectedIndexes().size() != 1) { + Q_EMIT selected(decals, false); + return; + } + + QModelIndex index = selectionModel[num]->selectedIndexes().at(0); + if (!index.isValid()) + return; + TreeModel::Item *clickItem = treeModel[num]->nodeFromIndex(index); + + ElementType type = clickItem->type(); + if (type == ElementType::NONE) + return; + + // Clear other tab selections + for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) + if (i != num) + selectionModel[i]->clearSelection(); + + addToHistory(num, index); + + clearProperties(); + + IdStringList c = clickItem->id(); + Q_EMIT selected(getDecals(type, c), false); + + if (type == ElementType::BEL) { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + BelId bel = ctx->getBelByName(c); + QtProperty *topItem = addTopLevelProperty("Bel"); + + addProperty(topItem, QVariant::String, "Name", ctx->nameOfBel(bel)); + addProperty(topItem, QVariant::String, "Type", ctx->getBelType(bel).c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel)); + addProperty(topItem, QVariant::String, "Bound Cell", ctx->nameOf(ctx->getBoundBelCell(bel)), ElementType::CELL); + addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->nameOf(ctx->getConflictingBelCell(bel)), + ElementType::CELL); + + QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); + for (auto &item : ctx->getBelAttrs(bel)) { + addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); + } + + QtProperty *belpinsItem = addSubGroup(topItem, "Ports"); + for (const auto &item : ctx->getBelPins(bel)) { + QtProperty *portInfoItem = addSubGroup(belpinsItem, item.c_str(ctx)); + addProperty(portInfoItem, QVariant::String, "Name", item.c_str(ctx)); + addProperty(portInfoItem, QVariant::Int, "Type", int(ctx->getBelPinType(bel, item))); + WireId wire = ctx->getBelPinWire(bel, item); + addProperty(portInfoItem, QVariant::String, "Wire", ctx->nameOfWire(wire), ElementType::WIRE); + } + } else if (type == ElementType::WIRE) { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + WireId wire = ctx->getWireByName(c); + QtProperty *topItem = addTopLevelProperty("Wire"); + + addProperty(topItem, QVariant::String, "Name", ctx->nameOfWire(wire)); + addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire)); + addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundWireNet(wire)), ElementType::NET); + addProperty(topItem, QVariant::String, "Conflicting Wire", ctx->nameOfWire(ctx->getConflictingWireWire(wire)), + ElementType::WIRE); + addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)), + ElementType::NET); + + QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); + for (auto &item : ctx->getWireAttrs(wire)) { + addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); + } + + DelayQuad delay = ctx->getWireDelay(wire); + + QtProperty *delayItem = addSubGroup(topItem, "Delay"); + addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); + addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); + addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); + addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); + + QtProperty *belpinsItem = addSubGroup(topItem, "BelPins"); + for (const auto &item : ctx->getWireBelPins(wire)) { + QString belname = ""; + if (item.bel != BelId()) + belname = ctx->nameOfBel(item.bel); + QString pinname = item.pin.c_str(ctx); + + QtProperty *dhItem = addSubGroup(belpinsItem, belname + "-" + pinname); + addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL); + addProperty(dhItem, QVariant::String, "PortPin", pinname); + } + + int counter = 0; + QtProperty *pipsDownItem = addSubGroup(topItem, "Pips Downhill"); + for (const auto &item : ctx->getPipsDownhill(wire)) { + addProperty(pipsDownItem, QVariant::String, "", ctx->nameOfPip(item), ElementType::PIP); + counter++; + if (counter == 50) { + addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); + break; + } + } + + counter = 0; + QtProperty *pipsUpItem = addSubGroup(topItem, "Pips Uphill"); + for (const auto &item : ctx->getPipsUphill(wire)) { + addProperty(pipsUpItem, QVariant::String, "", ctx->nameOfPip(item), ElementType::PIP); + counter++; + if (counter == 50) { + addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); + break; + } + } + } else if (type == ElementType::PIP) { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + PipId pip = ctx->getPipByName(c); + QtProperty *topItem = addTopLevelProperty("Pip"); + + addProperty(topItem, QVariant::String, "Name", ctx->nameOfPip(pip)); + addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip)); + addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET); + if (ctx->getConflictingPipWire(pip) != WireId()) { + addProperty(topItem, QVariant::String, "Conflicting Wire", ctx->nameOfWire(ctx->getConflictingPipWire(pip)), + ElementType::WIRE); + } else { + addProperty(topItem, QVariant::String, "Conflicting Wire", "", ElementType::NONE); + } + addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)), + ElementType::NET); + addProperty(topItem, QVariant::String, "Src Wire", ctx->nameOfWire(ctx->getPipSrcWire(pip)), ElementType::WIRE); + addProperty(topItem, QVariant::String, "Dest Wire", ctx->nameOfWire(ctx->getPipDstWire(pip)), + ElementType::WIRE); + + QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); + for (auto &item : ctx->getPipAttrs(pip)) { + addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); + } + + DelayQuad delay = ctx->getPipDelay(pip); + + QtProperty *delayItem = addSubGroup(topItem, "Delay"); + addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); + addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); + addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); + addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); + } else if (type == ElementType::NET) { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + NetInfo *net = ctx->nets.at(c[0]).get(); + + QtProperty *topItem = addTopLevelProperty("Net"); + + addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx)); + + QtProperty *driverItem = addSubGroup(topItem, "Driver"); + addProperty(driverItem, QVariant::String, "Port", net->driver.port.c_str(ctx)); + if (net->driver.cell) + addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL); + else + addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL); + + QtProperty *usersItem = addSubGroup(topItem, "Users"); + for (auto &item : net->users) { + QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx)); + + addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx)); + if (item.cell) + addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL); + else + addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL); + } + + QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); + for (auto &item : net->attrs) { + addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), + item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); + } + + QtProperty *wiresItem = addSubGroup(topItem, "Wires"); + for (auto &item : net->wires) { + auto name = ctx->nameOfWire(item.first); + + QtProperty *wireItem = addSubGroup(wiresItem, name); + addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE); + + if (item.second.pip != PipId()) + addProperty(wireItem, QVariant::String, "Pip", ctx->nameOfPip(item.second.pip), ElementType::PIP); + else + addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP); + + addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength); + } + + } else if (type == ElementType::CELL) { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + CellInfo *cell = ctx->cells.at(c[0]).get(); + + QtProperty *topItem = addTopLevelProperty("Cell"); + + addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx)); + addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx)); + if (cell->bel != BelId()) + addProperty(topItem, QVariant::String, "Bel", ctx->nameOfBel(cell->bel), ElementType::BEL); + else + addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL); + addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength)); + + QtProperty *cellPortsItem = addSubGroup(topItem, "Ports"); + for (auto &item : cell->ports) { + PortInfo p = item.second; + + QtProperty *portInfoItem = addSubGroup(cellPortsItem, p.name.c_str(ctx)); + addProperty(portInfoItem, QVariant::String, "Name", p.name.c_str(ctx)); + addProperty(portInfoItem, QVariant::Int, "Type", int(p.type)); + if (p.net) + addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET); + else + addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET); + } + + QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes"); + for (auto &item : cell->attrs) { + addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx), + item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); + } + + QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters"); + for (auto &item : cell->params) { + addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), + item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); + } + } +} + +std::vector DesignWidget::getDecals(ElementType type, IdStringList value) +{ + std::vector decals; + switch (type) { + case ElementType::BEL: { + BelId bel = ctx->getBelByName(value); + if (bel != BelId()) { + decals.push_back(ctx->getBelDecal(bel)); + } + } break; + case ElementType::WIRE: { + WireId wire = ctx->getWireByName(value); + if (wire != WireId()) { + decals.push_back(ctx->getWireDecal(wire)); + } + } break; + case ElementType::PIP: { + PipId pip = ctx->getPipByName(value); + if (pip != PipId()) { + decals.push_back(ctx->getPipDecal(pip)); + } + } break; + case ElementType::NET: { + NetInfo *net = ctx->nets.at(value[0]).get(); + for (auto &item : net->wires) { + decals.push_back(ctx->getWireDecal(item.first)); + if (item.second.pip != PipId()) { + decals.push_back(ctx->getPipDecal(item.second.pip)); + } + } + } break; + case ElementType::CELL: { + CellInfo *cell = ctx->cells.at(value[0]).get(); + if (cell->bel != BelId()) { + decals.push_back(ctx->getBelDecal(cell->bel)); + } + } break; + default: + break; + } + return decals; +} + +void DesignWidget::updateHighlightGroup(QList items, int group) +{ + const bool shouldClear = items.size() == 1; + for (auto item : items) { + if (highlightSelected.contains(item)) { + if (shouldClear && highlightSelected[item] == group) { + highlightSelected.remove(item); + } else + highlightSelected[item] = group; + } else + highlightSelected.insert(item, group); + } + std::vector decals[8]; + + for (auto it : highlightSelected.toStdMap()) { + std::vector d = getDecals(it.first->type(), it.first->id()); + std::move(d.begin(), d.end(), std::back_inserter(decals[it.second])); + } + for (int i = 0; i < 8; i++) + Q_EMIT highlight(decals[i], i); +} + +void DesignWidget::prepareMenuProperty(const QPoint &pos) +{ + QTreeWidget *tree = propertyEditor->treeWidget(); + QList items; + for (auto itemContextMenu : tree->selectedItems()) { + QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu); + if (!browserItem) + continue; + QtProperty *selectedProperty = browserItem->property(); + ElementType type = getElementTypeByName(selectedProperty->propertyId()); + if (type == ElementType::NONE) + continue; + IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); + auto node = getTreeByElementType(type)->nodeForId(value); + if (!node) + continue; + items.append(*node); + } + int selectedIndex = -1; + if (items.size() == 1) { + TreeModel::Item *item = items.at(0); + if (highlightSelected.contains(item)) + selectedIndex = highlightSelected[item]; + } + + QMenu menu(this); + QAction *selectAction = new QAction("&Select", this); + connect(selectAction, &QAction::triggered, this, [this, items] { + std::vector decals; + for (auto clickItem : items) { + std::vector d = getDecals(clickItem->type(), clickItem->id()); + std::move(d.begin(), d.end(), std::back_inserter(decals)); + } + Q_EMIT selected(decals, false); + }); + menu.addAction(selectAction); + + QMenu *subMenu = menu.addMenu("Highlight"); + QActionGroup *group = new QActionGroup(this); + group->setExclusive(true); + for (int i = 0; i < 8; i++) { + QPixmap pixmap(32, 32); + pixmap.fill(QColor(highlightColors[i])); + QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); + action->setCheckable(true); + subMenu->addAction(action); + group->addAction(action); + if (selectedIndex == i) + action->setChecked(true); + connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); + } + menu.exec(tree->mapToGlobal(pos)); +} + +void DesignWidget::prepareMenuTree(int num, const QPoint &pos) +{ + int selectedIndex = -1; + + if (selectionModel[num]->selectedIndexes().size() == 0) + return; + + QList items; + for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { + for (auto index : selectionModel[i]->selectedIndexes()) { + TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); + items.append(item); + } + } + if (items.size() == 1) { + TreeModel::Item *item = items.at(0); + if (highlightSelected.contains(item)) + selectedIndex = highlightSelected[item]; + } + QMenu menu(this); + QMenu *subMenu = menu.addMenu("Highlight"); + QActionGroup *group = new QActionGroup(this); + group->setExclusive(true); + for (int i = 0; i < 8; i++) { + QPixmap pixmap(32, 32); + pixmap.fill(QColor(highlightColors[i])); + QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); + action->setCheckable(true); + subMenu->addAction(action); + group->addAction(action); + if (selectedIndex == i) + action->setChecked(true); + connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); + } + menu.exec(treeView[num]->mapToGlobal(pos)); +} + +void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) +{ + QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property(); + ElementType type = getElementTypeByName(selectedProperty->propertyId()); + if (type == ElementType::NONE) + return; + + IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); + auto it = getTreeByElementType(type)->nodeForId(value); + if (it) { + int num = getIndexByElementType(type); + clearAllSelectionModels(); + if (tabWidget->currentIndex() != num) + tabWidget->setCurrentIndex(num); + selectionModel[num]->setCurrentIndex(getTreeByElementType(type)->indexFromNode(*it), + QItemSelectionModel::ClearAndSelect); + } +} + +void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); } + +void DesignWidget::onSearchInserted() +{ + if (currentSearch == searchEdit->text() && currentIndexTab == tabWidget->currentIndex()) { + currentIndex++; + if (currentIndex >= currentSearchIndexes.size()) + currentIndex = 0; + } else { + std::lock_guard lock_ui(ctx->ui_mutex); + std::lock_guard lock(ctx->mutex); + + currentSearch = searchEdit->text(); + currentSearchIndexes = treeModel[tabWidget->currentIndex()]->search(searchEdit->text()); + currentIndex = 0; + currentIndexTab = tabWidget->currentIndex(); + } + if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size()) + selectionModel[tabWidget->currentIndex()]->setCurrentIndex(currentSearchIndexes.at(currentIndex), + QItemSelectionModel::ClearAndSelect); +} + +void DesignWidget::onHoverIndexChanged(int num, QModelIndex index) +{ + if (index.isValid()) { + TreeModel::Item *item = treeModel[num]->nodeFromIndex(index); + if (item->type() != ElementType::NONE) { + std::vector decals = getDecals(item->type(), item->id()); + if (decals.size() > 0) + Q_EMIT hover(decals.at(0)); + return; + } + } + Q_EMIT hover(DecalXY()); +} + +void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item) +{ + if (item != nullptr) { + QtProperty *selectedProperty = item->property(); + ElementType type = getElementTypeByName(selectedProperty->propertyId()); + if (type != ElementType::NONE) { + IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); + if (value != IdStringList()) { + auto node = getTreeByElementType(type)->nodeForId(value); + if (node) { + std::vector decals = getDecals((*node)->type(), (*node)->id()); + if (decals.size() > 0) + Q_EMIT hover(decals.at(0)); + return; + } + } + } + } + Q_EMIT hover(DecalXY()); +} +NEXTPNR_NAMESPACE_END diff --git a/gui/designwidget.h b/gui/designwidget.h index 5efe2b42..7d59669e 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -1,138 +1,138 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef DESIGNWIDGET_H -#define DESIGNWIDGET_H - -#include -#include -#include -#include -#include "nextpnr.h" -#include "qtgroupboxpropertybrowser.h" -#include "qtpropertymanager.h" -#include "qttreepropertybrowser.h" -#include "qtvariantproperty.h" -#include "treemodel.h" - -NEXTPNR_NAMESPACE_BEGIN - -class TreeView : public QTreeView -{ - Q_OBJECT - - public: - explicit TreeView(QWidget *parent = 0); - ~TreeView(); - void mouseMoveEvent(QMouseEvent *event) override; - void leaveEvent(QEvent *event) override; - - Q_SIGNALS: - void hoverIndexChanged(QModelIndex index); - - private: - QModelIndex current; -}; - -class DesignWidget : public QWidget -{ - Q_OBJECT - - public: - explicit DesignWidget(QWidget *parent = 0); - ~DesignWidget(); - - private: - void clearProperties(); - QtProperty *addTopLevelProperty(const QString &id); - QtProperty *addSubGroup(QtProperty *topItem, const QString &name); - void addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, - const ElementType &type = ElementType::NONE); - QString getElementTypeName(ElementType type); - ElementType getElementTypeByName(QString type); - TreeModel::Model *getTreeByElementType(ElementType type); - int getIndexByElementType(ElementType type); - int getElementIndex(ElementType type); - void updateButtons(); - void addToHistory(int tab, QModelIndex item); - std::vector getDecals(ElementType type, IdStringList value); - void updateHighlightGroup(QList item, int group); - void clearAllSelectionModels(); - Q_SIGNALS: - void selected(std::vector decal, bool keep); - void highlight(std::vector decal, int group); - void hover(DecalXY decal); - void zoomSelected(); - - private Q_SLOTS: - void prepareMenuProperty(const QPoint &pos); - void prepareMenuTree(int num, const QPoint &pos); - void onSelectionChanged(int num, const QItemSelection &selected, const QItemSelection &deselected); - void onItemDoubleClicked(QTreeWidgetItem *item, int column); - void onDoubleClicked(const QModelIndex &index); - void onSearchInserted(); - void onHoverIndexChanged(int num, QModelIndex index); - void onHoverPropertyChanged(QtBrowserItem *item); - public Q_SLOTS: - void newContext(Context *ctx); - void updateTree(); - void onClickedBel(BelId bel, bool keep); - void onClickedWire(WireId wire, bool keep); - void onClickedPip(PipId pip, bool keep); - - private: - Context *ctx; - - QTabWidget *tabWidget; - - TreeView *treeView[6]; - QItemSelectionModel *selectionModel[6]; - TreeModel::Model *treeModel[6]; - QLineEdit *searchEdit; - QtVariantPropertyManager *variantManager; - QtVariantPropertyManager *readOnlyManager; - QtGroupPropertyManager *groupManager; - QtVariantEditorFactory *variantFactory; - QtTreePropertyBrowser *propertyEditor; - - QMap propertyToId; - QMap idToProperty; - - std::vector> history; - int history_index; - bool history_ignore; - - QAction *actionFirst; - QAction *actionPrev; - QAction *actionNext; - QAction *actionLast; - QAction *actionClear; - - QColor highlightColors[8]; - QMap highlightSelected; - - QString currentSearch; - QList currentSearchIndexes; - int currentIndex; - int currentIndexTab; -}; - -NEXTPNR_NAMESPACE_END - -#endif // DESIGNWIDGET_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef DESIGNWIDGET_H +#define DESIGNWIDGET_H + +#include +#include +#include +#include +#include "nextpnr.h" +#include "qtgroupboxpropertybrowser.h" +#include "qtpropertymanager.h" +#include "qttreepropertybrowser.h" +#include "qtvariantproperty.h" +#include "treemodel.h" + +NEXTPNR_NAMESPACE_BEGIN + +class TreeView : public QTreeView +{ + Q_OBJECT + + public: + explicit TreeView(QWidget *parent = 0); + ~TreeView(); + void mouseMoveEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + + Q_SIGNALS: + void hoverIndexChanged(QModelIndex index); + + private: + QModelIndex current; +}; + +class DesignWidget : public QWidget +{ + Q_OBJECT + + public: + explicit DesignWidget(QWidget *parent = 0); + ~DesignWidget(); + + private: + void clearProperties(); + QtProperty *addTopLevelProperty(const QString &id); + QtProperty *addSubGroup(QtProperty *topItem, const QString &name); + void addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, + const ElementType &type = ElementType::NONE); + QString getElementTypeName(ElementType type); + ElementType getElementTypeByName(QString type); + TreeModel::Model *getTreeByElementType(ElementType type); + int getIndexByElementType(ElementType type); + int getElementIndex(ElementType type); + void updateButtons(); + void addToHistory(int tab, QModelIndex item); + std::vector getDecals(ElementType type, IdStringList value); + void updateHighlightGroup(QList item, int group); + void clearAllSelectionModels(); + Q_SIGNALS: + void selected(std::vector decal, bool keep); + void highlight(std::vector decal, int group); + void hover(DecalXY decal); + void zoomSelected(); + + private Q_SLOTS: + void prepareMenuProperty(const QPoint &pos); + void prepareMenuTree(int num, const QPoint &pos); + void onSelectionChanged(int num, const QItemSelection &selected, const QItemSelection &deselected); + void onItemDoubleClicked(QTreeWidgetItem *item, int column); + void onDoubleClicked(const QModelIndex &index); + void onSearchInserted(); + void onHoverIndexChanged(int num, QModelIndex index); + void onHoverPropertyChanged(QtBrowserItem *item); + public Q_SLOTS: + void newContext(Context *ctx); + void updateTree(); + void onClickedBel(BelId bel, bool keep); + void onClickedWire(WireId wire, bool keep); + void onClickedPip(PipId pip, bool keep); + + private: + Context *ctx; + + QTabWidget *tabWidget; + + TreeView *treeView[6]; + QItemSelectionModel *selectionModel[6]; + TreeModel::Model *treeModel[6]; + QLineEdit *searchEdit; + QtVariantPropertyManager *variantManager; + QtVariantPropertyManager *readOnlyManager; + QtGroupPropertyManager *groupManager; + QtVariantEditorFactory *variantFactory; + QtTreePropertyBrowser *propertyEditor; + + QMap propertyToId; + QMap idToProperty; + + std::vector> history; + int history_index; + bool history_ignore; + + QAction *actionFirst; + QAction *actionPrev; + QAction *actionNext; + QAction *actionLast; + QAction *actionClear; + + QColor highlightColors[8]; + QMap highlightSelected; + + QString currentSearch; + QList currentSearchIndexes; + int currentIndex; + int currentIndexTab; +}; + +NEXTPNR_NAMESPACE_END + +#endif // DESIGNWIDGET_H diff --git a/gui/ecp5/mainwindow.cc b/gui/ecp5/mainwindow.cc index 73d4a989..fa44426b 100644 --- a/gui/ecp5/mainwindow.cc +++ b/gui/ecp5/mainwindow.cc @@ -1,166 +1,166 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "mainwindow.h" -#include -#include "bitstream.h" -#include "log.h" - -#include -#include -#include - -static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } - -NEXTPNR_NAMESPACE_BEGIN - -MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : BaseMainWindow(std::move(context), handler, parent) -{ - initMainResource(); - - std::string title = "nextpnr-ecp5 - [EMPTY]"; - setWindowTitle(title.c_str()); - - connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); - - createMenu(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::newContext(Context *ctx) -{ - std::string title = "nextpnr-ecp5 - " + ctx->getChipName() + " ( " + ctx->archArgs().package + " )"; - setWindowTitle(title.c_str()); -} - -void MainWindow::createMenu() -{ - // Add arch specific actions - actionLoadLPF = new QAction("Open LPF", this); - actionLoadLPF->setIcon(QIcon(":/icons/resources/open_lpf.png")); - actionLoadLPF->setStatusTip("Open LPF file"); - actionLoadLPF->setEnabled(false); - connect(actionLoadLPF, &QAction::triggered, this, &MainWindow::open_lpf); - - actionSaveConfig = new QAction("Save Bitstream", this); - actionSaveConfig->setIcon(QIcon(":/icons/resources/save_config.png")); - actionSaveConfig->setStatusTip("Save Bitstream config file"); - actionSaveConfig->setEnabled(false); - connect(actionSaveConfig, &QAction::triggered, this, &MainWindow::save_config); - - // Add actions in menus - mainActionBar->addSeparator(); - mainActionBar->addAction(actionLoadLPF); - mainActionBar->addAction(actionSaveConfig); - - menuDesign->addSeparator(); - menuDesign->addAction(actionLoadLPF); - menuDesign->addAction(actionSaveConfig); -} - -void MainWindow::new_proj() -{ - QMap arch; - if (Arch::is_available(ArchArgs::LFE5U_25F)) - arch.insert("Lattice ECP5 LFE5U-25F", ArchArgs::LFE5U_25F); - if (Arch::is_available(ArchArgs::LFE5U_45F)) - arch.insert("Lattice ECP5 LFE5U-45F", ArchArgs::LFE5U_45F); - if (Arch::is_available(ArchArgs::LFE5U_85F)) - arch.insert("Lattice ECP5 LFE5U-85F", ArchArgs::LFE5U_85F); - if (Arch::is_available(ArchArgs::LFE5UM_25F)) - arch.insert("Lattice ECP5 LFE5UM-25F", ArchArgs::LFE5UM_25F); - if (Arch::is_available(ArchArgs::LFE5UM_45F)) - arch.insert("Lattice ECP5 LFE5UM-45F", ArchArgs::LFE5UM_45F); - if (Arch::is_available(ArchArgs::LFE5UM_85F)) - arch.insert("Lattice ECP5 LFE5UM-85F", ArchArgs::LFE5UM_85F); - if (Arch::is_available(ArchArgs::LFE5UM5G_25F)) - arch.insert("Lattice ECP5 LFE5UM5G-25F", ArchArgs::LFE5UM5G_25F); - if (Arch::is_available(ArchArgs::LFE5UM5G_45F)) - arch.insert("Lattice ECP5 LFE5UM5G-45F", ArchArgs::LFE5UM5G_45F); - if (Arch::is_available(ArchArgs::LFE5UM5G_85F)) - arch.insert("Lattice ECP5 LFE5UM5G-85F", ArchArgs::LFE5UM5G_85F); - - bool ok; - QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); - if (ok && !item.isEmpty()) { - ArchArgs chipArgs; - chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item); - - QStringList packages; - for (auto package : Arch::get_supported_packages(chipArgs.type)) - packages.append(QLatin1String(package.data(), package.size())); - QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok); - - if (ok && !item.isEmpty()) { - handler->clear(); - currentProj = ""; - disableActions(); - chipArgs.package = package.toStdString().c_str(); - ctx = std::unique_ptr(new Context(chipArgs)); - actionLoadJSON->setEnabled(true); - - Q_EMIT contextChanged(ctx.get()); - } - } -} - -void MainWindow::open_lpf() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString("Open LPF"), QString(), QString("*.lpf")); - if (!fileName.isEmpty()) { - std::ifstream in(fileName.toStdString()); - if (ctx->apply_lpf(fileName.toStdString(), in)) { - log("Loading LPF successful.\n"); - actionPack->setEnabled(true); - actionLoadLPF->setEnabled(false); - } else { - actionLoadLPF->setEnabled(true); - log("Loading LPF failed.\n"); - } - } -} - -void MainWindow::save_config() -{ - QString fileName = QFileDialog::getSaveFileName(this, QString("Save Bitstream"), QString(), QString("*.config")); - if (!fileName.isEmpty()) { - std::string fn = fileName.toStdString(); - disableActions(); - write_bitstream(ctx.get(), "", fileName.toStdString()); - log("Saving Bitstream successful.\n"); - } -} - -void MainWindow::onDisableActions() -{ - actionLoadLPF->setEnabled(false); - actionSaveConfig->setEnabled(false); -} - -void MainWindow::onUpdateActions() -{ - if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) - actionLoadLPF->setEnabled(true); - if (ctx->settings.find(ctx->id("route")) != ctx->settings.end()) - actionSaveConfig->setEnabled(true); -} - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" +#include +#include "bitstream.h" +#include "log.h" + +#include +#include +#include + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + + std::string title = "nextpnr-ecp5 - [EMPTY]"; + setWindowTitle(title.c_str()); + + connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); + + createMenu(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-ecp5 - " + ctx->getChipName() + " ( " + ctx->archArgs().package + " )"; + setWindowTitle(title.c_str()); +} + +void MainWindow::createMenu() +{ + // Add arch specific actions + actionLoadLPF = new QAction("Open LPF", this); + actionLoadLPF->setIcon(QIcon(":/icons/resources/open_lpf.png")); + actionLoadLPF->setStatusTip("Open LPF file"); + actionLoadLPF->setEnabled(false); + connect(actionLoadLPF, &QAction::triggered, this, &MainWindow::open_lpf); + + actionSaveConfig = new QAction("Save Bitstream", this); + actionSaveConfig->setIcon(QIcon(":/icons/resources/save_config.png")); + actionSaveConfig->setStatusTip("Save Bitstream config file"); + actionSaveConfig->setEnabled(false); + connect(actionSaveConfig, &QAction::triggered, this, &MainWindow::save_config); + + // Add actions in menus + mainActionBar->addSeparator(); + mainActionBar->addAction(actionLoadLPF); + mainActionBar->addAction(actionSaveConfig); + + menuDesign->addSeparator(); + menuDesign->addAction(actionLoadLPF); + menuDesign->addAction(actionSaveConfig); +} + +void MainWindow::new_proj() +{ + QMap arch; + if (Arch::is_available(ArchArgs::LFE5U_25F)) + arch.insert("Lattice ECP5 LFE5U-25F", ArchArgs::LFE5U_25F); + if (Arch::is_available(ArchArgs::LFE5U_45F)) + arch.insert("Lattice ECP5 LFE5U-45F", ArchArgs::LFE5U_45F); + if (Arch::is_available(ArchArgs::LFE5U_85F)) + arch.insert("Lattice ECP5 LFE5U-85F", ArchArgs::LFE5U_85F); + if (Arch::is_available(ArchArgs::LFE5UM_25F)) + arch.insert("Lattice ECP5 LFE5UM-25F", ArchArgs::LFE5UM_25F); + if (Arch::is_available(ArchArgs::LFE5UM_45F)) + arch.insert("Lattice ECP5 LFE5UM-45F", ArchArgs::LFE5UM_45F); + if (Arch::is_available(ArchArgs::LFE5UM_85F)) + arch.insert("Lattice ECP5 LFE5UM-85F", ArchArgs::LFE5UM_85F); + if (Arch::is_available(ArchArgs::LFE5UM5G_25F)) + arch.insert("Lattice ECP5 LFE5UM5G-25F", ArchArgs::LFE5UM5G_25F); + if (Arch::is_available(ArchArgs::LFE5UM5G_45F)) + arch.insert("Lattice ECP5 LFE5UM5G-45F", ArchArgs::LFE5UM5G_45F); + if (Arch::is_available(ArchArgs::LFE5UM5G_85F)) + arch.insert("Lattice ECP5 LFE5UM5G-85F", ArchArgs::LFE5UM5G_85F); + + bool ok; + QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); + if (ok && !item.isEmpty()) { + ArchArgs chipArgs; + chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item); + + QStringList packages; + for (auto package : Arch::get_supported_packages(chipArgs.type)) + packages.append(QLatin1String(package.data(), package.size())); + QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok); + + if (ok && !item.isEmpty()) { + handler->clear(); + currentProj = ""; + disableActions(); + chipArgs.package = package.toStdString().c_str(); + ctx = std::unique_ptr(new Context(chipArgs)); + actionLoadJSON->setEnabled(true); + + Q_EMIT contextChanged(ctx.get()); + } + } +} + +void MainWindow::open_lpf() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Open LPF"), QString(), QString("*.lpf")); + if (!fileName.isEmpty()) { + std::ifstream in(fileName.toStdString()); + if (ctx->apply_lpf(fileName.toStdString(), in)) { + log("Loading LPF successful.\n"); + actionPack->setEnabled(true); + actionLoadLPF->setEnabled(false); + } else { + actionLoadLPF->setEnabled(true); + log("Loading LPF failed.\n"); + } + } +} + +void MainWindow::save_config() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save Bitstream"), QString(), QString("*.config")); + if (!fileName.isEmpty()) { + std::string fn = fileName.toStdString(); + disableActions(); + write_bitstream(ctx.get(), "", fileName.toStdString()); + log("Saving Bitstream successful.\n"); + } +} + +void MainWindow::onDisableActions() +{ + actionLoadLPF->setEnabled(false); + actionSaveConfig->setEnabled(false); +} + +void MainWindow::onUpdateActions() +{ + if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) + actionLoadLPF->setEnabled(true); + if (ctx->settings.find(ctx->id("route")) != ctx->settings.end()) + actionSaveConfig->setEnabled(true); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/ecp5/mainwindow.h b/gui/ecp5/mainwindow.h index 17c694b8..20d72c68 100644 --- a/gui/ecp5/mainwindow.h +++ b/gui/ecp5/mainwindow.h @@ -1,55 +1,55 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "../basewindow.h" - -NEXTPNR_NAMESPACE_BEGIN - -class MainWindow : public BaseMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~MainWindow(); - - public: - void createMenu(); - - protected: - void onDisableActions() override; - void onUpdateActions() override; - - protected Q_SLOTS: - void new_proj() override; - void newContext(Context *ctx); - void open_lpf(); - void save_config(); - - private: - QAction *actionLoadLPF; - QAction *actionSaveConfig; -}; - -NEXTPNR_NAMESPACE_END - -#endif // MAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected: + void onDisableActions() override; + void onUpdateActions() override; + + protected Q_SLOTS: + void new_proj() override; + void newContext(Context *ctx); + void open_lpf(); + void save_config(); + + private: + QAction *actionLoadLPF; + QAction *actionSaveConfig; +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/fpga_interchange/mainwindow.cc b/gui/fpga_interchange/mainwindow.cc index 3483f6c7..c3fba6c3 100644 --- a/gui/fpga_interchange/mainwindow.cc +++ b/gui/fpga_interchange/mainwindow.cc @@ -1,50 +1,50 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2021 Symbiflow Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "mainwindow.h" - -#include -#include - -static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } - -NEXTPNR_NAMESPACE_BEGIN - -MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : BaseMainWindow(std::move(context), handler, parent) -{ - initMainResource(); - QMessageBox::critical(0, "Error - FIXME", "No GUI support for nextpnr-generic"); - std::exit(1); -} - -MainWindow::~MainWindow() {} - -void MainWindow::newContext(Context *ctx) -{ - std::string title = "nextpnr-generic - " + ctx->getChipName(); - setWindowTitle(title.c_str()); -} - -void MainWindow::createMenu() {} - -void MainWindow::new_proj() {} - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2021 Symbiflow Authors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" + +#include +#include + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + QMessageBox::critical(0, "Error - FIXME", "No GUI support for nextpnr-generic"); + std::exit(1); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-generic - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::createMenu() {} + +void MainWindow::new_proj() {} + +NEXTPNR_NAMESPACE_END diff --git a/gui/fpga_interchange/mainwindow.h b/gui/fpga_interchange/mainwindow.h index 2875cb74..9c199c7d 100644 --- a/gui/fpga_interchange/mainwindow.h +++ b/gui/fpga_interchange/mainwindow.h @@ -1,46 +1,46 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2021 Symbiflow Authors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "../basewindow.h" - -NEXTPNR_NAMESPACE_BEGIN - -class MainWindow : public BaseMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~MainWindow(); - - public: - void createMenu(); - - protected Q_SLOTS: - void new_proj() override; - void newContext(Context *ctx); -}; - -NEXTPNR_NAMESPACE_END - -#endif // MAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2021 Symbiflow Authors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected Q_SLOTS: + void new_proj() override; + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/generic/mainwindow.cc b/gui/generic/mainwindow.cc index 54fe6f3e..de459a56 100644 --- a/gui/generic/mainwindow.cc +++ b/gui/generic/mainwindow.cc @@ -1,52 +1,52 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "mainwindow.h" - -#include -#include - -static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } - -NEXTPNR_NAMESPACE_BEGIN - -MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : BaseMainWindow(std::move(context), handler, parent) -{ - initMainResource(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::newContext(Context *ctx) -{ - std::string title = "nextpnr-generic - " + ctx->getChipName(); - setWindowTitle(title.c_str()); -} - -void MainWindow::createMenu() {} - -void MainWindow::new_proj() -{ - QMessageBox::critical(0, "Error", - "Creating a new project not supported in Viaduct mode, please re-start from command line."); - std::exit(1); -} - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" + +#include +#include + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-generic - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::createMenu() {} + +void MainWindow::new_proj() +{ + QMessageBox::critical(0, "Error", + "Creating a new project not supported in Viaduct mode, please re-start from command line."); + std::exit(1); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/generic/mainwindow.h b/gui/generic/mainwindow.h index 1e39b63f..7cdb238b 100644 --- a/gui/generic/mainwindow.h +++ b/gui/generic/mainwindow.h @@ -1,45 +1,45 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "../basewindow.h" - -NEXTPNR_NAMESPACE_BEGIN - -class MainWindow : public BaseMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~MainWindow(); - - public: - void createMenu(); - - protected Q_SLOTS: - void new_proj() override; - void newContext(Context *ctx); -}; - -NEXTPNR_NAMESPACE_END - -#endif // MAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected Q_SLOTS: + void new_proj() override; + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/gowin/mainwindow.cc b/gui/gowin/mainwindow.cc index c7ba44ab..195a083d 100644 --- a/gui/gowin/mainwindow.cc +++ b/gui/gowin/mainwindow.cc @@ -1,103 +1,103 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "mainwindow.h" - -#include -#include -#include - -#include "cst.h" - -static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } - -NEXTPNR_NAMESPACE_BEGIN - -MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : BaseMainWindow(std::move(context), handler, parent) -{ - initMainResource(); - std::string title = "nextpnr-gowin - [EMPTY]"; - setWindowTitle(title.c_str()); - connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); - createMenu(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::newContext(Context *ctx) -{ - std::string title = "nextpnr-gowin - " + ctx->getChipName(); - setWindowTitle(title.c_str()); -} - -void MainWindow::load_cst(std::string filename) -{ - disableActions(); - std::ifstream f(filename); - if (read_cst(ctx.get(), f)) { - log("Loading CST successful.\n"); - actionPack->setEnabled(true); - } else { - actionLoadCST->setEnabled(true); - log("Loading CST failed.\n"); - } -} - -void MainWindow::createMenu() -{ - actionLoadCST = new QAction("Open CST", this); - actionLoadCST->setIcon(QIcon(":/icons/resources/open_cst.png")); - actionLoadCST->setStatusTip("Open CST file"); - actionLoadCST->setEnabled(false); - connect(actionLoadCST, &QAction::triggered, this, &MainWindow::open_cst); - - // Add actions in menus - mainActionBar->addSeparator(); - mainActionBar->addAction(actionLoadCST); - - menuDesign->addSeparator(); - menuDesign->addAction(actionLoadCST); -} - -void MainWindow::new_proj() {} - -void MainWindow::open_cst() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString("Open CST"), QString(), QString("*.cst")); - if (!fileName.isEmpty()) { - load_cst(fileName.toStdString()); - } -} - -void MainWindow::onDisableActions() { actionLoadCST->setEnabled(false); } - -void MainWindow::onUpdateActions() -{ - if (ctx->settings.find(ctx->id("synth")) != ctx->settings.end()) { - actionLoadCST->setEnabled(true); - } - if (ctx->settings.find(ctx->id("cst")) != ctx->settings.end()) { - actionLoadCST->setEnabled(false); - } - if (ctx->settings.find(ctx->id("pack")) != ctx->settings.end()) { - actionLoadCST->setEnabled(false); - } -} -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" + +#include +#include +#include + +#include "cst.h" + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + std::string title = "nextpnr-gowin - [EMPTY]"; + setWindowTitle(title.c_str()); + connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); + createMenu(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-gowin - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::load_cst(std::string filename) +{ + disableActions(); + std::ifstream f(filename); + if (read_cst(ctx.get(), f)) { + log("Loading CST successful.\n"); + actionPack->setEnabled(true); + } else { + actionLoadCST->setEnabled(true); + log("Loading CST failed.\n"); + } +} + +void MainWindow::createMenu() +{ + actionLoadCST = new QAction("Open CST", this); + actionLoadCST->setIcon(QIcon(":/icons/resources/open_cst.png")); + actionLoadCST->setStatusTip("Open CST file"); + actionLoadCST->setEnabled(false); + connect(actionLoadCST, &QAction::triggered, this, &MainWindow::open_cst); + + // Add actions in menus + mainActionBar->addSeparator(); + mainActionBar->addAction(actionLoadCST); + + menuDesign->addSeparator(); + menuDesign->addAction(actionLoadCST); +} + +void MainWindow::new_proj() {} + +void MainWindow::open_cst() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Open CST"), QString(), QString("*.cst")); + if (!fileName.isEmpty()) { + load_cst(fileName.toStdString()); + } +} + +void MainWindow::onDisableActions() { actionLoadCST->setEnabled(false); } + +void MainWindow::onUpdateActions() +{ + if (ctx->settings.find(ctx->id("synth")) != ctx->settings.end()) { + actionLoadCST->setEnabled(true); + } + if (ctx->settings.find(ctx->id("cst")) != ctx->settings.end()) { + actionLoadCST->setEnabled(false); + } + if (ctx->settings.find(ctx->id("pack")) != ctx->settings.end()) { + actionLoadCST->setEnabled(false); + } +} +NEXTPNR_NAMESPACE_END diff --git a/gui/gowin/mainwindow.h b/gui/gowin/mainwindow.h index 0d65ed1c..4ca901a6 100644 --- a/gui/gowin/mainwindow.h +++ b/gui/gowin/mainwindow.h @@ -1,57 +1,57 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "../basewindow.h" - -NEXTPNR_NAMESPACE_BEGIN - -class MainWindow : public BaseMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~MainWindow(); - - public: - void createMenu(); - - protected: - void onDisableActions() override; - void onUpdateActions() override; - - void load_cst(std::string filename); - - protected Q_SLOTS: - void new_proj() override; - - void open_cst(); - - void newContext(Context *ctx); - - private: - QAction *actionLoadCST; -}; - -NEXTPNR_NAMESPACE_END - -#endif // MAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected: + void onDisableActions() override; + void onUpdateActions() override; + + void load_cst(std::string filename); + + protected Q_SLOTS: + void new_proj() override; + + void open_cst(); + + void newContext(Context *ctx); + + private: + QAction *actionLoadCST; +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc index 54d3b50b..955e58a4 100644 --- a/gui/ice40/mainwindow.cc +++ b/gui/ice40/mainwindow.cc @@ -1,182 +1,182 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "mainwindow.h" -#include -#include -#include -#include -#include -#include -#include -#include "bitstream.h" -#include "design_utils.h" -#include "log.h" -#include "pcf.h" - -static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } - -NEXTPNR_NAMESPACE_BEGIN - -MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) - : BaseMainWindow(std::move(context), handler, parent) -{ - initMainResource(); - - std::string title = "nextpnr-ice40 - [EMPTY]"; - setWindowTitle(title.c_str()); - - connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); - - createMenu(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::createMenu() -{ - // Add arch specific actions - actionLoadPCF = new QAction("Open PCF", this); - actionLoadPCF->setIcon(QIcon(":/icons/resources/open_pcf.png")); - actionLoadPCF->setStatusTip("Open PCF file"); - actionLoadPCF->setEnabled(false); - connect(actionLoadPCF, &QAction::triggered, this, &MainWindow::open_pcf); - - actionSaveAsc = new QAction("Save ASC", this); - actionSaveAsc->setIcon(QIcon(":/icons/resources/save_asc.png")); - actionSaveAsc->setStatusTip("Save ASC file"); - actionSaveAsc->setEnabled(false); - connect(actionSaveAsc, &QAction::triggered, this, &MainWindow::save_asc); - - // Add actions in menus - mainActionBar->addSeparator(); - mainActionBar->addAction(actionLoadPCF); - mainActionBar->addAction(actionSaveAsc); - - menuDesign->addSeparator(); - menuDesign->addAction(actionLoadPCF); - menuDesign->addAction(actionSaveAsc); -} - -void MainWindow::new_proj() -{ - QMap arch; - if (Arch::is_available(ArchArgs::LP384)) - arch.insert("Lattice iCE40LP384", ArchArgs::LP384); - if (Arch::is_available(ArchArgs::LP1K)) - arch.insert("Lattice iCE40LP1K", ArchArgs::LP1K); - if (Arch::is_available(ArchArgs::HX1K)) - arch.insert("Lattice iCE40HX1K", ArchArgs::HX1K); - if (Arch::is_available(ArchArgs::U1K)) - arch.insert("Lattice iCE5LP1K", ArchArgs::U1K); - if (Arch::is_available(ArchArgs::U2K)) - arch.insert("Lattice iCE5LP2K", ArchArgs::U2K); - if (Arch::is_available(ArchArgs::U4K)) - arch.insert("Lattice iCE5LP4K", ArchArgs::U4K); - if (Arch::is_available(ArchArgs::UP3K)) - arch.insert("Lattice iCE40UP3K", ArchArgs::UP3K); - if (Arch::is_available(ArchArgs::UP5K)) - arch.insert("Lattice iCE40UP5K", ArchArgs::UP5K); - if (Arch::is_available(ArchArgs::LP4K)) - arch.insert("Lattice iCE40LP4K", ArchArgs::LP4K); - if (Arch::is_available(ArchArgs::LP8K)) - arch.insert("Lattice iCE40LP8K", ArchArgs::LP8K); - if (Arch::is_available(ArchArgs::HX4K)) - arch.insert("Lattice iCE40HX4K", ArchArgs::HX4K); - if (Arch::is_available(ArchArgs::HX8K)) - arch.insert("Lattice iCE40HX8K", ArchArgs::HX8K); - - bool ok; - QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); - if (ok && !item.isEmpty()) { - ArchArgs chipArgs; - chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item); - - QStringList packages; - for (auto package : Arch::get_supported_packages(chipArgs.type)) - packages.append(QLatin1String(package.data(), package.size())); - QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok); - - if (ok && !item.isEmpty()) { - handler->clear(); - currentProj = ""; - disableActions(); - chipArgs.package = package.toStdString().c_str(); - ctx = std::unique_ptr(new Context(chipArgs)); - actionLoadJSON->setEnabled(true); - - Q_EMIT contextChanged(ctx.get()); - } - } -} - -void MainWindow::load_pcf(std::string filename) -{ - disableActions(); - std::ifstream f(filename); - if (apply_pcf(ctx.get(), filename, f)) { - log("Loading PCF successful.\n"); - actionPack->setEnabled(true); - } else { - actionLoadPCF->setEnabled(true); - log("Loading PCF failed.\n"); - } -} - -void MainWindow::newContext(Context *ctx) -{ - std::string title = "nextpnr-ice40 - " + ctx->getChipName() + " ( " + ctx->archArgs().package + " )"; - setWindowTitle(title.c_str()); -} - -void MainWindow::open_pcf() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString("Open PCF"), QString(), QString("*.pcf")); - if (!fileName.isEmpty()) { - load_pcf(fileName.toStdString()); - } -} - -void MainWindow::save_asc() -{ - QString fileName = QFileDialog::getSaveFileName(this, QString("Save ASC"), QString(), QString("*.asc")); - if (!fileName.isEmpty()) { - std::string fn = fileName.toStdString(); - disableActions(); - std::ofstream f(fn); - write_asc(ctx.get(), f); - log("Saving ASC successful.\n"); - } -} - -void MainWindow::onDisableActions() -{ - actionLoadPCF->setEnabled(false); - actionSaveAsc->setEnabled(false); -} - -void MainWindow::onUpdateActions() -{ - if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) - actionLoadPCF->setEnabled(true); - if (ctx->settings.find(ctx->id("route")) != ctx->settings.end()) - actionSaveAsc->setEnabled(true); -} - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include "bitstream.h" +#include "design_utils.h" +#include "log.h" +#include "pcf.h" + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + + std::string title = "nextpnr-ice40 - [EMPTY]"; + setWindowTitle(title.c_str()); + + connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); + + createMenu(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::createMenu() +{ + // Add arch specific actions + actionLoadPCF = new QAction("Open PCF", this); + actionLoadPCF->setIcon(QIcon(":/icons/resources/open_pcf.png")); + actionLoadPCF->setStatusTip("Open PCF file"); + actionLoadPCF->setEnabled(false); + connect(actionLoadPCF, &QAction::triggered, this, &MainWindow::open_pcf); + + actionSaveAsc = new QAction("Save ASC", this); + actionSaveAsc->setIcon(QIcon(":/icons/resources/save_asc.png")); + actionSaveAsc->setStatusTip("Save ASC file"); + actionSaveAsc->setEnabled(false); + connect(actionSaveAsc, &QAction::triggered, this, &MainWindow::save_asc); + + // Add actions in menus + mainActionBar->addSeparator(); + mainActionBar->addAction(actionLoadPCF); + mainActionBar->addAction(actionSaveAsc); + + menuDesign->addSeparator(); + menuDesign->addAction(actionLoadPCF); + menuDesign->addAction(actionSaveAsc); +} + +void MainWindow::new_proj() +{ + QMap arch; + if (Arch::is_available(ArchArgs::LP384)) + arch.insert("Lattice iCE40LP384", ArchArgs::LP384); + if (Arch::is_available(ArchArgs::LP1K)) + arch.insert("Lattice iCE40LP1K", ArchArgs::LP1K); + if (Arch::is_available(ArchArgs::HX1K)) + arch.insert("Lattice iCE40HX1K", ArchArgs::HX1K); + if (Arch::is_available(ArchArgs::U1K)) + arch.insert("Lattice iCE5LP1K", ArchArgs::U1K); + if (Arch::is_available(ArchArgs::U2K)) + arch.insert("Lattice iCE5LP2K", ArchArgs::U2K); + if (Arch::is_available(ArchArgs::U4K)) + arch.insert("Lattice iCE5LP4K", ArchArgs::U4K); + if (Arch::is_available(ArchArgs::UP3K)) + arch.insert("Lattice iCE40UP3K", ArchArgs::UP3K); + if (Arch::is_available(ArchArgs::UP5K)) + arch.insert("Lattice iCE40UP5K", ArchArgs::UP5K); + if (Arch::is_available(ArchArgs::LP4K)) + arch.insert("Lattice iCE40LP4K", ArchArgs::LP4K); + if (Arch::is_available(ArchArgs::LP8K)) + arch.insert("Lattice iCE40LP8K", ArchArgs::LP8K); + if (Arch::is_available(ArchArgs::HX4K)) + arch.insert("Lattice iCE40HX4K", ArchArgs::HX4K); + if (Arch::is_available(ArchArgs::HX8K)) + arch.insert("Lattice iCE40HX8K", ArchArgs::HX8K); + + bool ok; + QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); + if (ok && !item.isEmpty()) { + ArchArgs chipArgs; + chipArgs.type = (ArchArgs::ArchArgsTypes)arch.value(item); + + QStringList packages; + for (auto package : Arch::get_supported_packages(chipArgs.type)) + packages.append(QLatin1String(package.data(), package.size())); + QString package = QInputDialog::getItem(this, "Select package", "Package:", packages, 0, false, &ok); + + if (ok && !item.isEmpty()) { + handler->clear(); + currentProj = ""; + disableActions(); + chipArgs.package = package.toStdString().c_str(); + ctx = std::unique_ptr(new Context(chipArgs)); + actionLoadJSON->setEnabled(true); + + Q_EMIT contextChanged(ctx.get()); + } + } +} + +void MainWindow::load_pcf(std::string filename) +{ + disableActions(); + std::ifstream f(filename); + if (apply_pcf(ctx.get(), filename, f)) { + log("Loading PCF successful.\n"); + actionPack->setEnabled(true); + } else { + actionLoadPCF->setEnabled(true); + log("Loading PCF failed.\n"); + } +} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-ice40 - " + ctx->getChipName() + " ( " + ctx->archArgs().package + " )"; + setWindowTitle(title.c_str()); +} + +void MainWindow::open_pcf() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Open PCF"), QString(), QString("*.pcf")); + if (!fileName.isEmpty()) { + load_pcf(fileName.toStdString()); + } +} + +void MainWindow::save_asc() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save ASC"), QString(), QString("*.asc")); + if (!fileName.isEmpty()) { + std::string fn = fileName.toStdString(); + disableActions(); + std::ofstream f(fn); + write_asc(ctx.get(), f); + log("Saving ASC successful.\n"); + } +} + +void MainWindow::onDisableActions() +{ + actionLoadPCF->setEnabled(false); + actionSaveAsc->setEnabled(false); +} + +void MainWindow::onUpdateActions() +{ + if (ctx->settings.find(ctx->id("pack")) == ctx->settings.end()) + actionLoadPCF->setEnabled(true); + if (ctx->settings.find(ctx->id("route")) != ctx->settings.end()) + actionSaveAsc->setEnabled(true); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/ice40/mainwindow.h b/gui/ice40/mainwindow.h index 16069e2d..ee5946a3 100644 --- a/gui/ice40/mainwindow.h +++ b/gui/ice40/mainwindow.h @@ -1,59 +1,59 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "../basewindow.h" - -NEXTPNR_NAMESPACE_BEGIN - -class MainWindow : public BaseMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); - virtual ~MainWindow(); - - public: - void createMenu(); - - protected: - void load_pcf(std::string filename); - - void onDisableActions() override; - void onUpdateActions() override; - - protected Q_SLOTS: - void new_proj() override; - - void open_pcf(); - void save_asc(); - - void newContext(Context *ctx); - - private: - QAction *actionLoadPCF; - QAction *actionSaveAsc; -}; - -NEXTPNR_NAMESPACE_END - -#endif // MAINWINDOW_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected: + void load_pcf(std::string filename); + + void onDisableActions() override; + void onUpdateActions() override; + + protected Q_SLOTS: + void new_proj() override; + + void open_pcf(); + void save_asc(); + + void newContext(Context *ctx); + + private: + QAction *actionLoadPCF; + QAction *actionSaveAsc; +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/pythontab.cc b/gui/pythontab.cc index c1008d07..36eab471 100644 --- a/gui/pythontab.cc +++ b/gui/pythontab.cc @@ -1,119 +1,119 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "pythontab.h" -#include -#include "pybindings.h" -#include "pyinterpreter.h" - -NEXTPNR_NAMESPACE_BEGIN - -const QString PythonTab::PROMPT = ">>> "; -const QString PythonTab::MULTILINE_PROMPT = "... "; - -PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false) -{ - QFont f("unexistent"); - f.setStyleHint(QFont::Monospace); - - // Add text area for Python output and input line - console = new PythonConsole(); - console->setMinimumHeight(100); - console->setReadOnly(true); - console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); - console->setFont(f); - - console->setContextMenuPolicy(Qt::CustomContextMenu); - QAction *clearAction = new QAction("Clear &buffer", this); - clearAction->setStatusTip("Clears display buffer"); - connect(clearAction, &QAction::triggered, this, &PythonTab::clearBuffer); - contextMenu = console->createStandardContextMenu(); - contextMenu->addSeparator(); - contextMenu->addAction(clearAction); - connect(console, &PythonConsole::customContextMenuRequested, this, &PythonTab::showContextMenu); - - lineEdit = new LineEditor(&parseHelper); - lineEdit->setMinimumHeight(30); - lineEdit->setMaximumHeight(30); - lineEdit->setFont(f); - lineEdit->setPlaceholderText(PythonTab::PROMPT); - connect(lineEdit, &LineEditor::textLineInserted, this, &PythonTab::editLineReturnPressed); - - QGridLayout *mainLayout = new QGridLayout(); - mainLayout->addWidget(console, 0, 0); - mainLayout->addWidget(lineEdit, 1, 0); - setLayout(mainLayout); - - parseHelper.subscribe(console); - - prompt = PythonTab::PROMPT; -} - -PythonTab::~PythonTab() -{ - if (initialized) { - pyinterpreter_finalize(); - deinit_python(); - } -} - -void PythonTab::editLineReturnPressed(QString text) -{ - console->displayString(prompt + text + "\n"); - - parseHelper.process(text.toStdString()); - - if (parseHelper.buffered()) - prompt = PythonTab::MULTILINE_PROMPT; - else - prompt = PythonTab::PROMPT; - - lineEdit->setPlaceholderText(prompt); -} - -void PythonTab::newContext(Context *ctx) -{ - if (initialized) { - pyinterpreter_finalize(); - deinit_python(); - } - console->clear(); - - pyinterpreter_preinit(); - init_python("nextpnr"); - pyinterpreter_initialize(); - pyinterpreter_aquire(); - python_export_global("ctx", ctx); - pyinterpreter_release(); - - initialized = true; - - QString version = QString("Python %1 on %2\n").arg(Py_GetVersion(), Py_GetPlatform()); - console->displayString(version); -} - -void PythonTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } - -void PythonTab::clearBuffer() { console->clear(); } - -void PythonTab::info(std::string str) { console->displayString(str.c_str()); } - -void PythonTab::execute_python(std::string filename) { console->execute_python(filename); } - -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "pythontab.h" +#include +#include "pybindings.h" +#include "pyinterpreter.h" + +NEXTPNR_NAMESPACE_BEGIN + +const QString PythonTab::PROMPT = ">>> "; +const QString PythonTab::MULTILINE_PROMPT = "... "; + +PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false) +{ + QFont f("unexistent"); + f.setStyleHint(QFont::Monospace); + + // Add text area for Python output and input line + console = new PythonConsole(); + console->setMinimumHeight(100); + console->setReadOnly(true); + console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + console->setFont(f); + + console->setContextMenuPolicy(Qt::CustomContextMenu); + QAction *clearAction = new QAction("Clear &buffer", this); + clearAction->setStatusTip("Clears display buffer"); + connect(clearAction, &QAction::triggered, this, &PythonTab::clearBuffer); + contextMenu = console->createStandardContextMenu(); + contextMenu->addSeparator(); + contextMenu->addAction(clearAction); + connect(console, &PythonConsole::customContextMenuRequested, this, &PythonTab::showContextMenu); + + lineEdit = new LineEditor(&parseHelper); + lineEdit->setMinimumHeight(30); + lineEdit->setMaximumHeight(30); + lineEdit->setFont(f); + lineEdit->setPlaceholderText(PythonTab::PROMPT); + connect(lineEdit, &LineEditor::textLineInserted, this, &PythonTab::editLineReturnPressed); + + QGridLayout *mainLayout = new QGridLayout(); + mainLayout->addWidget(console, 0, 0); + mainLayout->addWidget(lineEdit, 1, 0); + setLayout(mainLayout); + + parseHelper.subscribe(console); + + prompt = PythonTab::PROMPT; +} + +PythonTab::~PythonTab() +{ + if (initialized) { + pyinterpreter_finalize(); + deinit_python(); + } +} + +void PythonTab::editLineReturnPressed(QString text) +{ + console->displayString(prompt + text + "\n"); + + parseHelper.process(text.toStdString()); + + if (parseHelper.buffered()) + prompt = PythonTab::MULTILINE_PROMPT; + else + prompt = PythonTab::PROMPT; + + lineEdit->setPlaceholderText(prompt); +} + +void PythonTab::newContext(Context *ctx) +{ + if (initialized) { + pyinterpreter_finalize(); + deinit_python(); + } + console->clear(); + + pyinterpreter_preinit(); + init_python("nextpnr"); + pyinterpreter_initialize(); + pyinterpreter_aquire(); + python_export_global("ctx", ctx); + pyinterpreter_release(); + + initialized = true; + + QString version = QString("Python %1 on %2\n").arg(Py_GetVersion(), Py_GetPlatform()); + console->displayString(version); +} + +void PythonTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } + +void PythonTab::clearBuffer() { console->clear(); } + +void PythonTab::info(std::string str) { console->displayString(str.c_str()); } + +void PythonTab::execute_python(std::string filename) { console->execute_python(filename); } + +NEXTPNR_NAMESPACE_END diff --git a/gui/pythontab.h b/gui/pythontab.h index 0ab9b94e..1ee2dc5e 100644 --- a/gui/pythontab.h +++ b/gui/pythontab.h @@ -1,64 +1,64 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef PYTHONTAB_H -#define PYTHONTAB_H - -#include -#include -#include -#include "ParseHelper.h" -#include "line_editor.h" -#include "nextpnr.h" -#include "pyconsole.h" - -NEXTPNR_NAMESPACE_BEGIN - -class PythonTab : public QWidget -{ - Q_OBJECT - - public: - explicit PythonTab(QWidget *parent = 0); - ~PythonTab(); - - private Q_SLOTS: - void showContextMenu(const QPoint &pt); - void editLineReturnPressed(QString text); - public Q_SLOTS: - void newContext(Context *ctx); - void info(std::string str); - void clearBuffer(); - void execute_python(std::string filename); - - private: - PythonConsole *console; - LineEditor *lineEdit; - QMenu *contextMenu; - bool initialized; - ParseHelper parseHelper; - QString prompt; - - static const QString PROMPT; - static const QString MULTILINE_PROMPT; -}; - -NEXTPNR_NAMESPACE_END - -#endif // PYTHONTAB_H +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef PYTHONTAB_H +#define PYTHONTAB_H + +#include +#include +#include +#include "ParseHelper.h" +#include "line_editor.h" +#include "nextpnr.h" +#include "pyconsole.h" + +NEXTPNR_NAMESPACE_BEGIN + +class PythonTab : public QWidget +{ + Q_OBJECT + + public: + explicit PythonTab(QWidget *parent = 0); + ~PythonTab(); + + private Q_SLOTS: + void showContextMenu(const QPoint &pt); + void editLineReturnPressed(QString text); + public Q_SLOTS: + void newContext(Context *ctx); + void info(std::string str); + void clearBuffer(); + void execute_python(std::string filename); + + private: + PythonConsole *console; + LineEditor *lineEdit; + QMenu *contextMenu; + bool initialized; + ParseHelper parseHelper; + QString prompt; + + static const QString PROMPT; + static const QString MULTILINE_PROMPT; +}; + +NEXTPNR_NAMESPACE_END + +#endif // PYTHONTAB_H