]> git.sur5r.net Git - fstl/blobdiff - src/window.cpp
New upstream version 0.9.3
[fstl] / src / window.cpp
index 3002ecc83daa6008dd3c42c420b933c24605008e..0954b33eb92f46b2951f7330c50221dcfc8917fe 100644 (file)
@@ -6,20 +6,26 @@
 #include "canvas.h"
 #include "loader.h"
 
+const QString Window::RECENT_FILE_KEY = "recentFiles";
+
 Window::Window(QWidget *parent) :
     QMainWindow(parent),
     open_action(new QAction("Open", this)),
     about_action(new QAction("About", this)),
-    quit_action(new QAction("Quit", this))
+    quit_action(new QAction("Quit", this)),
+    perspective_action(new QAction("Perspective", this)),
+    orthogonal_action(new QAction("Orthographic", this)),
+    reload_action(new QAction("Reload", this)),
+    autoreload_action(new QAction("Autoreload", this)),
+    recent_files(new QMenu("Open recent", this)),
+    recent_files_group(new QActionGroup(this)),
+    recent_files_clear_action(new QAction("Clear recent files", this)),
+    watcher(new QFileSystemWatcher(this))
 
 {
     setWindowTitle("fstl");
     setAcceptDrops(true);
 
-    QFile styleFile(":/qt/style.qss");
-    styleFile.open( QFile::ReadOnly );
-    setStyleSheet(styleFile.readAll());
-
     QGLFormat format;
     format.setVersion(2, 1);
     format.setSampleBuffers(true);
@@ -27,6 +33,9 @@ Window::Window(QWidget *parent) :
     canvas = new Canvas(format, this);
     setCentralWidget(canvas);
 
+    QObject::connect(watcher, &QFileSystemWatcher::fileChanged,
+                     this, &Window::on_watched_change);
+
     open_action->setShortcut(QKeySequence::Open);
     QObject::connect(open_action, &QAction::triggered,
                      this, &Window::on_open);
@@ -35,13 +44,50 @@ Window::Window(QWidget *parent) :
     QObject::connect(quit_action, &QAction::triggered,
                      this, &Window::close);
 
+    autoreload_action->setCheckable(true);
+    autoreload_action->setChecked(true);
+    autoreload_action->setEnabled(false);
+    QObject::connect(autoreload_action, &QAction::triggered,
+            this, &Window::on_autoreload_triggered);
+
+    reload_action->setShortcut(QKeySequence::Refresh);
+    reload_action->setEnabled(false);
+    QObject::connect(reload_action, &QAction::triggered,
+                     this, &Window::on_reload);
+
     QObject::connect(about_action, &QAction::triggered,
                      this, &Window::on_about);
 
+    QObject::connect(recent_files_clear_action, &QAction::triggered,
+                     this, &Window::on_clear_recent);
+    QObject::connect(recent_files_group, &QActionGroup::triggered,
+                     this, &Window::on_load_recent);
+
+    rebuild_recent_files();
+
     auto file_menu = menuBar()->addMenu("File");
     file_menu->addAction(open_action);
+    file_menu->addMenu(recent_files);
+    file_menu->addSeparator();
+    file_menu->addAction(reload_action);
+    file_menu->addAction(autoreload_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);
+    auto projections = new QActionGroup(projection_menu);
+    for (auto p : {perspective_action, orthogonal_action})
+    {
+        projections->addAction(p);
+        p->setCheckable(true);
+    }
+    perspective_action->setChecked(true);
+    projections->setExclusive(true);
+    QObject::connect(projections, &QActionGroup::triggered,
+                     this, &Window::on_projection);
+
     auto help_menu = menuBar()->addMenu("Help");
     help_menu->addAction(about_action);
 
@@ -65,25 +111,39 @@ void Window::on_about()
         "<p>A fast viewer for <code>.stl</code> files.<br>"
         "<a href=\"https://github.com/mkeeter/fstl\""
         "   style=\"color: #93a1a1;\">https://github.com/mkeeter/fstl</a></p>"
-        "<p>© 2014 Matthew Keeter<br>"
+        "<p>© 2014-2017 Matthew Keeter<br>"
         "<a href=\"mailto:matt.j.keeter@gmail.com\""
         "   style=\"color: #93a1a1;\">matt.j.keeter@gmail.com</a></p>");
 }
 
-void Window::on_ascii_stl()
+void Window::on_bad_stl()
 {
     QMessageBox::critical(this, "Error",
                           "<b>Error:</b><br>"
-                          "Cannot open ASCII <code>.stl</code> file<br>"
-                          "Please convert to binary <code>.stl</code> and retry");
+                          "This <code>.stl</code> file is invalid or corrupted.<br>"
+                          "Please export it from the original source, verify, and retry.");
 }
 
-void Window::on_bad_stl()
+void Window::on_empty_mesh()
 {
     QMessageBox::critical(this, "Error",
                           "<b>Error:</b><br>"
-                          "This <code>.stl</code> file is invalid or corrupted.<br>"
-                          "Please export it from the original source, verify, and retry.");
+                          "This file is syntactically correct<br>but contains no triangles.");
+}
+
+void Window::on_confusing_stl()
+{
+    QMessageBox::warning(this, "Warning",
+                         "<b>Warning:</b><br>"
+                         "This <code>.stl</code> file begins with <code>solid </code>but appears to be a binary file.<br>"
+                         "<code>fstl</code> loaded it, but other programs may be confused by this file.");
+}
+
+void Window::on_missing_file()
+{
+    QMessageBox::critical(this, "Error",
+                          "<b>Error:</b><br>"
+                          "The target file is missing.<br>");
 }
 
 void Window::enable_open()
@@ -96,22 +156,126 @@ void Window::disable_open()
     open_action->setEnabled(false);
 }
 
-bool Window::load_stl(const QString& filename)
+void Window::set_watched(const QString& filename)
+{
+    const auto files = watcher->files();
+    if (files.size())
+    {
+        watcher->removePaths(watcher->files());
+    }
+    watcher->addPath(filename);
+
+    QSettings settings;
+    auto recent = settings.value(RECENT_FILE_KEY).toStringList();
+    const auto f = QFileInfo(filename).absoluteFilePath();
+    recent.removeAll(f);
+    recent.prepend(f);
+    while (recent.size() > MAX_RECENT_FILES)
+    {
+        recent.pop_back();
+    }
+    settings.setValue(RECENT_FILE_KEY, recent);
+    rebuild_recent_files();
+}
+
+void Window::on_projection(QAction* proj)
+{
+    if (proj == perspective_action)
+    {
+        canvas->view_perspective();
+    }
+    else
+    {
+        canvas->view_orthographic();
+    }
+}
+
+void Window::on_watched_change(const QString& filename)
+{
+    if (autoreload_action->isChecked())
+    {
+        load_stl(filename, true);
+    }
+}
+
+void Window::on_autoreload_triggered(bool b)
+{
+    if (b)
+    {
+        on_reload();
+    }
+}
+
+void Window::on_clear_recent()
+{
+    QSettings settings;
+    settings.setValue(RECENT_FILE_KEY, QStringList());
+    rebuild_recent_files();
+}
+
+void Window::on_load_recent(QAction* a)
+{
+    load_stl(a->data().toString());
+}
+
+void Window::rebuild_recent_files()
+{
+    QSettings settings;
+    QStringList files = settings.value(RECENT_FILE_KEY).toStringList();
+
+    const auto actions = recent_files_group->actions();
+    for (auto a : actions)
+    {
+        recent_files_group->removeAction(a);
+    }
+    recent_files->clear();
+
+    for (auto f : files)
+    {
+        const auto a = new QAction(f, recent_files);
+        a->setData(f);
+        recent_files_group->addAction(a);
+        recent_files->addAction(a);
+    }
+    if (files.size() == 0)
+    {
+        auto a = new QAction("No recent files", recent_files);
+        recent_files->addAction(a);
+        a->setEnabled(false);
+    }
+    recent_files->addSeparator();
+    recent_files->addAction(recent_files_clear_action);
+}
+
+void Window::on_reload()
+{
+    auto fs = watcher->files();
+    if (fs.size() == 1)
+    {
+        load_stl(fs[0], true);
+    }
+}
+
+bool Window::load_stl(const QString& filename, bool is_reload)
 {
     if (!open_action->isEnabled())  return false;
 
     canvas->set_status("Loading " + filename);
 
-    Loader* loader = new Loader(this, filename);
+    Loader* loader = new Loader(this, filename, is_reload);
     connect(loader, &Loader::started,
               this, &Window::disable_open);
 
     connect(loader, &Loader::got_mesh,
             canvas, &Canvas::load_mesh);
-    connect(loader, &Loader::error_ascii_stl,
-              this, &Window::on_ascii_stl);
     connect(loader, &Loader::error_bad_stl,
               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);
 
     connect(loader, &Loader::finished,
             loader, &Loader::deleteLater);
@@ -124,6 +288,10 @@ bool Window::load_stl(const QString& filename)
     {
         connect(loader, &Loader::loaded_file,
                   this, &Window::setWindowTitle);
+        connect(loader, &Loader::loaded_file,
+                  this, &Window::set_watched);
+        autoreload_action->setEnabled(true);
+        reload_action->setEnabled(true);
     }
 
     loader->start();