toLocal8Bit->toUtf8, 'space in filename' test, portable stat(), clang+glib fix.

winconsunicode
Don Bright 2014-02-12 05:36:08 -06:00
parent b3f57fb4a5
commit 5f9053057a
10 changed files with 93 additions and 41 deletions

View File

@ -1,12 +1,14 @@
This README covers a few caveats regarding OpenSCAD for Windows(TM) for
programmers and users.
Summary:
Summary: As of writing, early 2014:
-Windows filenames & cmdline args are UTF16, in conflict with assumptions
about Mac(TM) and Unix(TM) which generally assume everything is UTF8.
-All file i/o must go through PlatformUtils:: wrappers
-Windows filenames & cmdline args are ~UTF16, Linux/Mac are UTF8.
-There is no open source ifstream/ofstream fopen() that works with both.
-Therefore all file i/o must go through PlatformUtils:: wrappers
-std::ifstream & std::ofstream are limited in capability by our wrappers
-Windows stdout/stdin differs in behavior between GUI programs and cmdline
-Windows console has issues outputting unicode of any encoding.
-openscad.com is the command line wrapper for openscad.exe, the gui
00. Encoding of Unicode
@ -98,4 +100,10 @@ before any processing is done on them. Therefore. All file writing should
be in 'binary mode' on Windows and we write sequences of 8-bit bytes,
encoded in UTF8.
5. Boost and portability
Theoretically, Boost provides portable wrapper to some Windows and
Unix/Mac file code, including fstream. In reality, since Boost is built
on top of the libstdc++ that one is using, a version of Boost built
under the current MingW cross-build will not be able to deal with UTF16
filenames.

View File

@ -13,9 +13,22 @@ GLIB2_DIR = $$(GLIB2DIR)
!isEmpty(OPENSCAD_LIBRARIES_DIR) {
isEmpty(GLIB2_INCLUDEPATH) {
GLIB2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include
GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
!isEmpty(GLIB2_DIR) {
GLIB2_INCLUDEPATH = $$GLIB2_DIR/include/glib-2.0
GLIB2_INCLUDEPATH_2 = $$GLIB2_DIR/lib/glib-2.0/include
GLIB2_LIBPATH = $$GLIB2_DIR/lib
} else {
GLIB2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0
GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include
GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
}
}
}
!exists($$GLIB2_INCLUDEPATH/glib.h) {
!exists($$GLIB2_INCLUDEPATH_2/glib.h) {
GLIB2_INCLUDEPATH =
GLIB2_LIBPATH =
}
}

View File

@ -118,8 +118,8 @@ void PlatformUtils::resetArgvToUtf8( int argc, char ** &argv, std::vector<std::s
}
}
/* allow fopen() to work with unicode filenames on windows(TM)
by transcoding to UTF16 and then using _wfopen() */
/* allow fopen() to work with unicode filenames on windows(TM) by
transcoding to UTF16 and then using _wfopen(). See also: ifstream/ofstream */
FILE *PlatformUtils::fopen( const char *utf8path, const char *mode )
{
std::wstring winpath;
@ -129,3 +129,15 @@ FILE *PlatformUtils::fopen( const char *utf8path, const char *mode )
return _wfopen( winpath.c_str() , winmode.c_str() );
}
/* allow stat() to work with unicode filenames on windows(TM) by
transcoding to UTF16 and then using _wstat(). Note we also have to
use a wrapped version of struct stat in PlatformUtils.h */
int PlatformUtils::stat( const char *utf8path, void *buf )
{
std::wstring winpath;
winpath = utf8_to_winapi_wstring( std::string( utf8path ) );
return _wstat( winpath.c_str(), (PlatformUtils::struct_stat *)buf );
}

View File

@ -4,6 +4,11 @@
#include <glib.h>
#include <sstream>
// types/stat/unistd: stat()
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/* Quick and dirty hack to work around floating point rounding differences
across platforms for testing purposes. */
std::string PlatformUtils::formatDouble( const double &op1 )
@ -78,6 +83,11 @@ FILE *PlatformUtils::fopen( const char *utf8path, const char *mode )
{
return std::fopen( utf8path, mode );
}
int PlatformUtils::stat( const char *utf8path, void *buf )
{
return stat( utf8path, (PlatformUtils::struct_stat *)buf );
}
#endif
#include "version_check.h"

View File

@ -13,8 +13,8 @@ namespace PlatformUtils {
void resetArgvToUtf8( int argc, char ** &argv, std::vector<std::string> &argstorage);
FILE *fopen( const char *utf8path, const char *mode );
std::string formatDouble( const double &x );
}
int stat(const char *utf8path, void *buf);
};
#if defined (__MINGW32__) || defined (__MINGW64__)
#define __PLATFORM_MINGW__
@ -23,6 +23,11 @@ namespace PlatformUtils {
#define __PLATFORM_WIN__
#endif
#if defined (__PLATFORM_WIN__)
namespace PlatformUtils { typedef struct _stat struct_stat; }
#else
namespace PlatformUtils { typedef struct stat struct_stat; }
#endif
// MingW ifstream/ofstream: see ../doc/windows_issues.txt & ../patches/minsgream
#ifdef __PLATFORM_MINGW__
@ -30,12 +35,12 @@ namespace PlatformUtils {
namespace PlatformUtils {
typedef omingstream ofstream;
typedef imingstream ifstream;
}
};
#else
namespace PlatformUtils {
typedef std::ofstream ofstream;
typedef std::ifstream ifstream;
}
};
#endif // Mingw ifstream/ofstream

View File

@ -170,7 +170,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="label_csh">
<property name="font">
<font>
<weight>75</weight>

View File

@ -81,7 +81,6 @@
#include <algorithm>
#include <boost/version.hpp>
#include <boost/foreach.hpp>
#include <sys/stat.h>
#ifdef ENABLE_CGAL
@ -554,7 +553,7 @@ MainWindow::setFileName(const QString &filename)
this->fileName = fileinfo.fileName();
}
this->top_ctx.setDocumentPath(fileinfo.dir().absolutePath().toLocal8Bit().constData());
this->top_ctx.setDocumentPath(fileinfo.dir().absolutePath().toUtf8().constData());
QDir::setCurrent(fileinfo.dir().absolutePath());
}
@ -618,13 +617,13 @@ void MainWindow::refreshDocument()
QFile file(this->fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
PRINTB("Failed to open file %s: %s",
this->fileName.toLocal8Bit().constData() % file.errorString().toLocal8Bit().constData());
this->fileName.toUtf8().constData() % file.errorString().toUtf8().constData());
}
else {
QTextStream reader(&file);
reader.setCodec("UTF-8");
QString text = reader.readAll();
PRINTB("Loaded design '%s'.", this->fileName.toLocal8Bit().constData());
PRINTB("Loaded design '%s'.", this->fileName.toUtf8().constData());
editor->setPlainText(text);
}
}
@ -1022,7 +1021,7 @@ void MainWindow::actionSave()
setCurrentOutput();
QFile file(this->fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
PRINTB("Failed to open file for writing: %s (%s)", this->fileName.toLocal8Bit().constData() % file.errorString().toLocal8Bit().constData());
PRINTB("Failed to open file for writing: %s (%s)", this->fileName.toUtf8().constData() % file.errorString().toUtf8().constData());
QMessageBox::warning(this, windowTitle(), tr("Failed to open file for writing:\n %1 (%2)")
.arg(this->fileName).arg(file.errorString()));
}
@ -1030,7 +1029,7 @@ void MainWindow::actionSave()
QTextStream writer(&file);
writer.setCodec("UTF-8");
writer << this->editor->toPlainText();
PRINTB("Saved design '%s'.", this->fileName.toLocal8Bit().constData());
PRINTB("Saved design '%s'.", this->fileName.toUtf8().constData());
this->editor->setContentModified(false);
}
clearCurrentOutput();
@ -1230,9 +1229,9 @@ void MainWindow::updateTemporalVariables()
bool MainWindow::fileChangedOnDisk()
{
if (!this->fileName.isEmpty()) {
struct stat st;
memset(&st, 0, sizeof(struct stat));
bool valid = (stat(this->fileName.toLocal8Bit(), &st) == 0);
PlatformUtils::struct_stat st;
memset(&st, 0, sizeof(PlatformUtils::struct_stat));
bool valid = (PlatformUtils::stat(this->fileName.toUtf8().constData(), &st) == 0);
// If file isn't there, just return and use current editor text
if (!valid) return false;
@ -1255,7 +1254,7 @@ void MainWindow::compileTopLevelDocument()
this->last_compiled_doc = editor->toPlainText();
std::string fulltext =
std::string(this->last_compiled_doc.toLocal8Bit().constData()) +
std::string(this->last_compiled_doc.toUtf8().constData()) +
"\n" + commandline_commands;
delete this->root_module;
@ -1264,7 +1263,7 @@ void MainWindow::compileTopLevelDocument()
this->root_module = parse(fulltext.c_str(),
this->fileName.isEmpty() ?
"" :
QFileInfo(this->fileName).absolutePath().toLocal8Bit(),
QFileInfo(this->fileName).absolutePath().toUtf8(),
false);
if (!animate_panel->isVisible()) {
@ -1489,7 +1488,7 @@ void MainWindow::actionDisplayAST()
e->setWindowTitle("AST Dump");
e->setReadOnly(true);
if (root_module) {
e->setPlainText(QString::fromLocal8Bit(root_module->dump("", "").c_str()));
e->setPlainText(QString::fromUtf8(root_module->dump("", "").c_str()));
} else {
e->setPlainText("No AST to dump. Please try compiling first...");
}
@ -1507,7 +1506,7 @@ void MainWindow::actionDisplayCSGTree()
e->setWindowTitle("CSG Tree Dump");
e->setReadOnly(true);
if (this->root_node) {
e->setPlainText(QString::fromLocal8Bit(this->tree.getString(*this->root_node).c_str()));
e->setPlainText(QString::fromUtf8(this->tree.getString(*this->root_node).c_str()));
} else {
e->setPlainText("No CSG to dump. Please try compiling first...");
}
@ -1525,11 +1524,11 @@ void MainWindow::actionDisplayCSGProducts()
e->setWindowTitle("CSG Products Dump");
e->setReadOnly(true);
e->setPlainText(QString("\nCSG before normalization:\n%1\n\n\nCSG after normalization:\n%2\n\n\nCSG rendering chain:\n%3\n\n\nHighlights CSG rendering chain:\n%4\n\n\nBackground CSG rendering chain:\n%5\n")
.arg(root_raw_term ? QString::fromLocal8Bit(root_raw_term->dump().c_str()) : "N/A",
root_norm_term ? QString::fromLocal8Bit(root_norm_term->dump().c_str()) : "N/A",
this->root_chain ? QString::fromLocal8Bit(this->root_chain->dump().c_str()) : "N/A",
highlights_chain ? QString::fromLocal8Bit(highlights_chain->dump().c_str()) : "N/A",
background_chain ? QString::fromLocal8Bit(background_chain->dump().c_str()) : "N/A"));
.arg(root_raw_term ? QString::fromUtf8(root_raw_term->dump().c_str()) : "N/A",
root_norm_term ? QString::fromUtf8(root_norm_term->dump().c_str()) : "N/A",
this->root_chain ? QString::fromUtf8(this->root_chain->dump().c_str()) : "N/A",
highlights_chain ? QString::fromUtf8(highlights_chain->dump().c_str()) : "N/A",
background_chain ? QString::fromUtf8(background_chain->dump().c_str()) : "N/A"));
e->show();
e->resize(600, 400);
clearCurrentOutput();
@ -1605,7 +1604,7 @@ void MainWindow::actionExportSTLorOFF(bool)
PlatformUtils::ofstream fstream(stl_filename.toUtf8());
if (!fstream.is_open()) {
PRINTB("Can't open file \"%s\" for export", stl_filename.toLocal8Bit().constData());
PRINTB("Can't open file \"%s\" for export", stl_filename.toUtf8().constData());
}
else {
if (stl_mode) exportFile(this->root_geom.get(), fstream, OPENSCAD_STL);
@ -1658,7 +1657,7 @@ void MainWindow::actionExportDXF()
PlatformUtils::ofstream fstream(dxf_filename.toUtf8());
if (!fstream.is_open()) {
PRINTB("Can't open file \"%s\" for export", dxf_filename.toLocal8Bit().constData());
PRINTB("Can't open file \"%s\" for export", dxf_filename.toUtf8().constData());
}
else {
exportFile(this->root_geom.get(), fstream, OPENSCAD_DXF);
@ -1691,7 +1690,7 @@ void MainWindow::actionExportCSG()
PlatformUtils::ofstream fstream(csg_filename.toUtf8());
if (!fstream.is_open()) {
PRINTB("Can't open file \"%s\" for export", csg_filename.toLocal8Bit().constData());
PRINTB("Can't open file \"%s\" for export", csg_filename.toUtf8().constData());
}
else {
fstream << this->tree.getString(*this->root_node) << "\n";
@ -1711,7 +1710,7 @@ void MainWindow::actionExportImage()
if (img_filename.isEmpty()) {
PRINT("No filename specified. Image export aborted.");
} else {
qglview->save(img_filename.toLocal8Bit().constData());
qglview->save(img_filename.toUtf8().constData());
}
clearCurrentOutput();
return;
@ -2061,7 +2060,7 @@ void MainWindow::consoleOutput(const std::string &msg, void *userdata)
// originates in a worker thread.
MainWindow *thisp = static_cast<MainWindow*>(userdata);
QMetaObject::invokeMethod(thisp->console, "append", Qt::QueuedConnection,
Q_ARG(QString, QString::fromLocal8Bit(msg.c_str())));
Q_ARG(QString, QString::fromUtf8(msg.c_str())));
}
void MainWindow::setCurrentOutput()

View File

@ -206,7 +206,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c
{
#ifdef OPENSCAD_QTGUI
QCoreApplication app(argc, argv);
const std::string application_path = QApplication::instance()->applicationDirPath().toLocal8Bit().constData();
const std::string application_path = QApplication::instance()->applicationDirPath().toUtf8().constData();
#else
const std::string application_path = boosty::stringy(boosty::absolute(boost::filesystem::path(argv[0]).parent_path()));
(void)argc;
@ -461,8 +461,8 @@ Q_DECLARE_METATYPE(shared_ptr<const Geometry>);
static QString assemblePath(const fs::path& absoluteBaseDir,
const string& fileName) {
if (fileName.empty()) return "";
QString qsDir( boosty::stringy( absoluteBaseDir ).c_str() );
QString qsFile( fileName.c_str() );
QString qsDir = QString::fromUtf8( boosty::stringy( absoluteBaseDir ).c_str() );
QString qsFile = QString::fromUtf8( fileName.c_str() );
QFileInfo info( qsDir, qsFile ); // if qsfile is absolute, dir is ignored.
return info.absoluteFilePath();
}
@ -526,7 +526,7 @@ int gui(vector<string> &inputFiles, const fs::path &original_path, int argc, cha
qexamplesdir = exdir.path();
}
MainWindow::setExamplesDir(qexamplesdir);
parser_init(app_path.toLocal8Bit().constData());
parser_init(app_path.toUtf8().constData());
#ifdef Q_OS_MAC
installAppleEventHandlers();

2
testdata/scad/misc/my file.scad vendored Normal file
View File

@ -0,0 +1,2 @@
cube();

View File

@ -1024,6 +1024,9 @@ add_cmdline_test(openscad-nonascii EXE ${OPENSCAD_BINPATH} ARGS -o
SUFFIX csg
FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/sfære.scad)
add_cmdline_test(openscad-space-name EXE ${OPENSCAD_BINPATH} ARGS -o
SUFFIX csg
FILES "${CMAKE_SOURCE_DIR}/../testdata/scad/misc/my file.scad")
# Image output
add_cmdline_test(openscad-imgsize EXE ${OPENSCAD_BINPATH}