X-Git-Url: https://git.sur5r.net/?p=fstl;a=blobdiff_plain;f=src%2Fwindow.cpp;h=09d74eddad444f5fb811d500a570d901b85fb8c4;hp=0954b33eb92f46b2951f7330c50221dcfc8917fe;hb=refs%2Fheads%2Fupstream;hpb=5dd213695da2eb22219ac5143aa8b5cfe7c89559 diff --git a/src/window.cpp b/src/window.cpp index 0954b33..e9f3f94 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,12 +1,16 @@ #include -#include -#include #include "window.h" #include "canvas.h" #include "loader.h" const QString Window::RECENT_FILE_KEY = "recentFiles"; +const QString Window::INVERT_ZOOM_KEY = "invertZoom"; +const QString Window::AUTORELOAD_KEY = "autoreload"; +const QString Window::DRAW_AXES_KEY = "drawAxes"; +const QString Window::PROJECTION_KEY = "projection"; +const QString Window::DRAW_MODE_KEY = "drawMode"; +const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; Window::Window(QWidget *parent) : QMainWindow(parent), @@ -14,9 +18,16 @@ Window::Window(QWidget *parent) : about_action(new QAction("About", this)), quit_action(new QAction("Quit", this)), perspective_action(new QAction("Perspective", this)), - orthogonal_action(new QAction("Orthographic", this)), + orthographic_action(new QAction("Orthographic", this)), + shaded_action(new QAction("Shaded", this)), + wireframe_action(new QAction("Wireframe", this)), + surfaceangle_action(new QAction("Surface Angle", this)), + axes_action(new QAction("Draw Axes", this)), + invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), autoreload_action(new QAction("Autoreload", this)), + save_screenshot_action(new QAction("Save Screenshot", this)), + hide_menuBar_action(new QAction("Hide Menu Bar", this)), recent_files(new QMenu("Open recent", this)), recent_files_group(new QActionGroup(this)), recent_files_clear_action(new QAction("Clear recent files", this)), @@ -26,10 +37,14 @@ Window::Window(QWidget *parent) : setWindowTitle("fstl"); setAcceptDrops(true); - QGLFormat format; + QSurfaceFormat format; + format.setDepthBufferSize(24); + format.setStencilBufferSize(8); format.setVersion(2, 1); - format.setSampleBuffers(true); + format.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(format); + canvas = new Canvas(format, this); setCentralWidget(canvas); @@ -39,14 +54,14 @@ Window::Window(QWidget *parent) : open_action->setShortcut(QKeySequence::Open); QObject::connect(open_action, &QAction::triggered, this, &Window::on_open); + this->addAction(open_action); quit_action->setShortcut(QKeySequence::Quit); QObject::connect(quit_action, &QAction::triggered, this, &Window::close); + this->addAction(quit_action); autoreload_action->setCheckable(true); - autoreload_action->setChecked(true); - autoreload_action->setEnabled(false); QObject::connect(autoreload_action, &QAction::triggered, this, &Window::on_autoreload_triggered); @@ -63,6 +78,10 @@ Window::Window(QWidget *parent) : QObject::connect(recent_files_group, &QActionGroup::triggered, this, &Window::on_load_recent); + save_screenshot_action->setCheckable(false); + QObject::connect(save_screenshot_action, &QAction::triggered, + this, &Window::on_save_screenshot); + rebuild_recent_files(); auto file_menu = menuBar()->addMenu("File"); @@ -71,34 +90,99 @@ Window::Window(QWidget *parent) : file_menu->addSeparator(); file_menu->addAction(reload_action); file_menu->addAction(autoreload_action); + file_menu->addAction(save_screenshot_action); file_menu->addAction(quit_action); auto view_menu = menuBar()->addMenu("View"); auto projection_menu = view_menu->addMenu("Projection"); projection_menu->addAction(perspective_action); - projection_menu->addAction(orthogonal_action); + projection_menu->addAction(orthographic_action); auto projections = new QActionGroup(projection_menu); - for (auto p : {perspective_action, orthogonal_action}) + for (auto p : {perspective_action, orthographic_action}) { projections->addAction(p); p->setCheckable(true); } - perspective_action->setChecked(true); projections->setExclusive(true); QObject::connect(projections, &QActionGroup::triggered, this, &Window::on_projection); + auto draw_menu = view_menu->addMenu("Draw Mode"); + draw_menu->addAction(shaded_action); + draw_menu->addAction(wireframe_action); + draw_menu->addAction(surfaceangle_action); + auto drawModes = new QActionGroup(draw_menu); + for (auto p : {shaded_action, wireframe_action, surfaceangle_action}) + { + drawModes->addAction(p); + p->setCheckable(true); + } + drawModes->setExclusive(true); + QObject::connect(drawModes, &QActionGroup::triggered, + this, &Window::on_drawMode); + view_menu->addAction(axes_action); + axes_action->setCheckable(true); + QObject::connect(axes_action, &QAction::triggered, + this, &Window::on_drawAxes); + + view_menu->addAction(invert_zoom_action); + invert_zoom_action->setCheckable(true); + QObject::connect(invert_zoom_action, &QAction::triggered, + this, &Window::on_invertZoom); + + view_menu->addAction(hide_menuBar_action); + hide_menuBar_action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); + hide_menuBar_action->setCheckable(true); + QObject::connect(hide_menuBar_action, &QAction::toggled, + this, &Window::on_hide_menuBar); + this->addAction(hide_menuBar_action); + auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); + load_persist_settings(); +} + +void Window::load_persist_settings(){ + QSettings settings; + bool invert_zoom = settings.value(INVERT_ZOOM_KEY, false).toBool(); + canvas->invert_zoom(invert_zoom); + invert_zoom_action->setChecked(invert_zoom); + + autoreload_action->setChecked(settings.value(AUTORELOAD_KEY, true).toBool()); + + bool draw_axes = settings.value(DRAW_AXES_KEY, false).toBool(); + canvas->draw_axes(draw_axes); + axes_action->setChecked(draw_axes); + + QString projection = settings.value(PROJECTION_KEY, "perspective").toString(); + if(projection == "perspective"){ + canvas->view_perspective(Canvas::P_PERSPECTIVE, false); + perspective_action->setChecked(true); + }else{ + canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, false); + orthographic_action->setChecked(true); + } + + DrawMode draw_mode = (DrawMode)settings.value(DRAW_MODE_KEY, DRAWMODECOUNT).toInt(); + + if(draw_mode >= DRAWMODECOUNT) + { + draw_mode = shaded; + } + canvas->set_drawMode(draw_mode); + QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action}; + dm_acts[draw_mode]->setChecked(true); + resize(600, 400); + restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); } void Window::on_open() { QString filename = QFileDialog::getOpenFileName( - this, "Load .stl file", QString(), "*.stl"); - if (not filename.isNull()) + this, "Load .stl file", QString(), "STL files (*.stl, *.STL)"); + if (!filename.isNull()) { load_stl(filename); } @@ -107,11 +191,11 @@ void Window::on_open() void Window::on_about() { QMessageBox::about(this, "", - "

fstl

" + "

fstl
" FSTL_VERSION "

" "

A fast viewer for .stl files.
" - "https://github.com/mkeeter/fstl

" - "

© 2014-2017 Matthew Keeter
" + "https://github.com/fstl-app/fstl

" + "

© 2014-2022 Matthew Keeter
" "matt.j.keeter@gmail.com

"); } @@ -131,14 +215,6 @@ void Window::on_empty_mesh() "This file is syntactically correct
but contains no triangles."); } -void Window::on_confusing_stl() -{ - QMessageBox::warning(this, "Warning", - "Warning:
" - "This .stl file begins with solid but appears to be a binary file.
" - "fstl loaded it, but other programs may be confused by this file."); -} - void Window::on_missing_file() { QMessageBox::critical(this, "Error", @@ -182,14 +258,47 @@ void Window::on_projection(QAction* proj) { if (proj == perspective_action) { - canvas->view_perspective(); + canvas->view_perspective(Canvas::P_PERSPECTIVE, true); + QSettings().setValue(PROJECTION_KEY, "perspective"); } else { - canvas->view_orthographic(); + canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, true); + QSettings().setValue(PROJECTION_KEY, "orthographic"); } } +void Window::on_drawMode(QAction* act) +{ + DrawMode mode; + if (act == shaded_action) + { + mode = shaded; + } + else if (act == wireframe_action) + { + mode = wireframe; + } + else + { + mode = surfaceangle; + } + canvas->set_drawMode(mode); + QSettings().setValue(DRAW_MODE_KEY, mode); +} + +void Window::on_drawAxes(bool d) +{ + canvas->draw_axes(d); + QSettings().setValue(DRAW_AXES_KEY, d); +} + +void Window::on_invertZoom(bool d) +{ + canvas->invert_zoom(d); + QSettings().setValue(INVERT_ZOOM_KEY, d); +} + void Window::on_watched_change(const QString& filename) { if (autoreload_action->isChecked()) @@ -204,6 +313,7 @@ void Window::on_autoreload_triggered(bool b) { on_reload(); } + QSettings().setValue(AUTORELOAD_KEY, b); } void Window::on_clear_recent() @@ -218,6 +328,50 @@ void Window::on_load_recent(QAction* a) load_stl(a->data().toString()); } +void Window::on_loaded(const QString& filename) +{ + current_file = filename; +} + +void Window::on_save_screenshot() +{ + const auto image = canvas->grabFramebuffer(); + auto file_name = QFileDialog::getSaveFileName( + this, + tr("Save Screenshot Image"), + QStandardPaths::standardLocations(QStandardPaths::StandardLocation::PicturesLocation).first(), + "Images (*.png *.jpg)"); + + auto get_file_extension = [](const std::string& file_name) -> std::string + { + const auto location = std::find(file_name.rbegin(), file_name.rend(), '.'); + if (location == file_name.rend()) + { + return ""; + } + + const auto index = std::distance(file_name.rbegin(), location); + return file_name.substr(file_name.size() - index); + }; + + const auto extension = get_file_extension(file_name.toStdString()); + if(extension.empty() || (extension != "png" && extension != "jpg")) + { + file_name.append(".png"); + } + + const auto save_ok = image.save(file_name); + if(!save_ok) + { + QMessageBox::warning(this, tr("Error Saving Image"), tr("Unable to save screen shot image.")); + } +} + +void Window::on_hide_menuBar() +{ + menuBar()->setVisible(!hide_menuBar_action->isChecked()); +} + void Window::rebuild_recent_files() { QSettings settings; @@ -272,8 +426,6 @@ bool Window::load_stl(const QString& filename, bool is_reload) this, &Window::on_bad_stl); connect(loader, &Loader::error_empty_mesh, this, &Window::on_empty_mesh); - connect(loader, &Loader::warning_confusing_stl, - this, &Window::on_confusing_stl); connect(loader, &Loader::error_missing_file, this, &Window::on_missing_file); @@ -290,7 +442,8 @@ bool Window::load_stl(const QString& filename, bool is_reload) this, &Window::setWindowTitle); connect(loader, &Loader::loaded_file, this, &Window::set_watched); - autoreload_action->setEnabled(true); + connect(loader, &Loader::loaded_file, + this, &Window::on_loaded); reload_action->setEnabled(true); } @@ -312,3 +465,148 @@ void Window::dropEvent(QDropEvent *event) { load_stl(event->mimeData()->urls().front().toLocalFile()); } + +void Window::resizeEvent(QResizeEvent *event) +{ + QSettings().setValue(WINDOW_GEOM_KEY, saveGeometry()); + QWidget::resizeEvent(event); +} + +void Window::moveEvent(QMoveEvent *event) +{ + QSettings().setValue(WINDOW_GEOM_KEY, saveGeometry()); + QWidget::moveEvent(event); +} + +void Window::sorted_insert(QStringList& list, const QCollator& collator, const QString& value) +{ + int start = 0; + int end = list.size() - 1; + int index = 0; + while (start <= end){ + int mid = (start+end)/2; + if (list[mid] == value) { + return; + } + int compare = collator.compare(value, list[mid]); + if (compare < 0) { + end = mid-1; + index = mid; + } else { + start = mid+1; + index = start; + } + } + + list.insert(index, value); +} + +void Window::build_folder_file_list() +{ + QString current_folder_path = QFileInfo(current_file).absoluteDir().absolutePath(); + if (!lookup_folder_files.isEmpty()) + { + if (current_folder_path == lookup_folder) { + return; + } + + lookup_folder_files.clear(); + } + lookup_folder = current_folder_path; + + QCollator collator; + collator.setNumericMode(true); + + QDirIterator dirIterator(lookup_folder, QStringList() << "*.stl", QDir::Files | QDir::Readable | QDir::Hidden); + while (dirIterator.hasNext()) { + dirIterator.next(); + + QString name = dirIterator.fileName(); + sorted_insert(lookup_folder_files, collator, name); + } +} + +QPair Window::get_file_neighbors() +{ + if (current_file.isEmpty()) { + return QPair(QString(), QString()); + } + + build_folder_file_list(); + + QFileInfo fileInfo(current_file); + + QString current_dir = fileInfo.absoluteDir().absolutePath(); + QString current_name = fileInfo.fileName(); + + QString prev = QString(); + QString next = QString(); + + QListIterator fileIterator(lookup_folder_files); + while (fileIterator.hasNext()) { + QString name = fileIterator.next(); + + if (name == current_name) { + if (fileIterator.hasNext()) { + next = current_dir + QDir::separator() + fileIterator.next(); + } + break; + } + + prev = name; + } + + if (!prev.isEmpty()) { + prev.prepend(QDir::separator()); + prev.prepend(current_dir); + } + + return QPair(prev, next); +} + +bool Window::load_prev(void) +{ + QPair neighbors = get_file_neighbors(); + if (neighbors.first.isEmpty()) { + return false; + } + + return load_stl(neighbors.first); +} + +bool Window::load_next(void) +{ + QPair neighbors = get_file_neighbors(); + if (neighbors.second.isEmpty()) { + return false; + } + + return load_stl(neighbors.second); +} + +void Window::keyPressEvent(QKeyEvent* event) +{ + if (!open_action->isEnabled()) + { + QMainWindow::keyPressEvent(event); + return; + } + + if (event->key() == Qt::Key_Left) + { + load_prev(); + return; + } + else if (event->key() == Qt::Key_Right) + { + load_next(); + return; + } + else if (event->key() == Qt::Key_Escape) + { + hide_menuBar_action->setChecked(false); + return; + } + + QMainWindow::keyPressEvent(event); +}