void FileTabManager::save(unsigned tab, bool saveAs) {
  validateTabIndex(tab);
  if (!saveAs && !isModified(tab)) return;

  // Get absolute path
  NCEdit *editor = (NCEdit *)QTabWidget::widget(tab);
  Project::File &file = *editor->getFile();
  string path = file.getPath();

  // Get type
  bool tpl = editor->isTPL();

  if (saveAs) {
    path = win->openFile("Save file", tpl ? "TPL (*.tpl)" :
                         "GCode (*.nc *.ngc *.gcode)", path, true);
    if (path.empty()) return;

    string ext = SystemUtilities::extension(path);
    if (ext.empty()) path += tpl ? ".tpl" : ".gcode";

    else if (tpl && ext != "tpl") {
      win->warning("TPL file must have .tpl extension");
      return;

    } else if (!tpl && (ext == "xml" || ext == "tpl")) {
      win->warning("GCode file cannot have .tpl or .xml extension");
      return;
    }
  }

  // Save data
  QString content = editor->toPlainText();
  QFile qFile(QString::fromUtf8(path.c_str()));
  if (!qFile.open(QFile::WriteOnly | QIODevice::Truncate))
    THROW("Could not save '" << path << "'");
  qFile.write(content.toUtf8());
  qFile.close();

  // Update file path
  path = SystemUtilities::absolute(path);
  if (path != file.getPath()) {
    file.setPath(path);

    // Update tab title
    QString title(QString::fromUtf8(file.getBasename().c_str()));
    QTabWidget::setTabText(tab, title);
  }

  // Set unmodified
  editor->document()->setModified(false);

  // Notify
  win->showMessage("Saved " + file.getBasename());
}