diff --git a/CMakeLists.txt b/CMakeLists.txt index 401d7113..27aca76b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,5 +184,6 @@ endif() # components add_subdirectory(tools) +add_subdirectory(res) add_subdirectory(src) add_subdirectory(exposed) diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt new file mode 100644 index 00000000..a99057ea --- /dev/null +++ b/res/CMakeLists.txt @@ -0,0 +1,107 @@ +# First, set up registration functions for the kinds of resources we handle. +set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/) +set(resource_list) +if(WIN32) + set(rc_file ${CMAKE_CURRENT_BINARY_DIR}/resources.rc) + file(WRITE ${rc_file} "// Autogenerated; do not edit\n") + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + + list(GET "${ARGN}" 0 id) + if(id STREQUAL NOTFOUND) + string(REPLACE ${resource_root} "" id ${source}) + endif() + list(GET "${ARGN}" 1 type) + if(type STREQUAL NOTFOUND) + set(type RCDATA) + endif() + file(SHA512 "${source}" hash) + file(APPEND ${rc_file} "${id} ${type} \"${source}\" // ${hash}\n") + # CMake doesn't track file dependencies across directories, so we force + # a reconfigure (which changes the RC file because of the hash above) + # every time a resource is changed. + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}") + endfunction() +elseif(APPLE) + set(app_resource_dir ${CMAKE_BINARY_DIR}/src/solvespace.app/Contents/Resources) + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(target ${app_resource_dir}/${name}) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + get_filename_component(target_dir ${target} DIRECTORY) + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} + COMMENT "Copying resource ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() + + function(add_xib name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + get_filename_component(basename ${name} NAME_WE) + set(target ${app_resource_dir}/${basename}.nib) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${app_resource_dir} + COMMAND ibtool --errors --warnings --notices --output-format human-readable-text + --compile ${target} ${source} + COMMENT "Building Interface Builder file ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() + + function(add_iconset name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + get_filename_component(basename ${name} NAME_WE) + set(target ${app_resource_dir}/${basename}.icns) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${app_resource_dir} + COMMAND iconutil -c icns -o ${target} ${source} + COMMENT "Building icon set ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() +else() # Unix + include(GNUInstallDirs) + + set(app_resource_dir ${CMAKE_BINARY_DIR}/res) + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(target ${app_resource_dir}/${name}) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + get_filename_component(target_dir ${target} DIRECTORY) + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} + COMMENT "Copying resource ${name}" + DEPENDS ${source} + VERBATIM) + + get_filename_component(name_dir ${name} DIRECTORY) + install(FILES ${source} + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/solvespace/${name_dir}) + endfunction() +endif() + +# Second, register all resources. +add_resource(banner.txt) + +# Third, distribute the resources. +add_custom_target(resources + DEPENDS ${resource_list}) +if(WIN32) + set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file}) +endif() diff --git a/res/banner.txt b/res/banner.txt new file mode 100644 index 00000000..ad8f4f80 --- /dev/null +++ b/res/banner.txt @@ -0,0 +1 @@ +SolveSpace! diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e15e96b..488288d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -289,6 +289,7 @@ set(solvespace_SOURCES modify.cpp mouse.cpp polygon.cpp + resource.cpp request.cpp solvespace.cpp style.cpp @@ -318,7 +319,11 @@ add_executable(solvespace WIN32 MACOSX_BUNDLE ${generated_SOURCES} ${generated_HEADERS} ${solvespace_HEADERS} - ${solvespace_SOURCES}) + ${solvespace_SOURCES} + $) + +add_dependencies(solvespace + resources) target_link_libraries(solvespace dxfrw diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm index c111ed43..185a5cfe 100644 --- a/src/cocoa/cocoamain.mm +++ b/src/cocoa/cocoamain.mm @@ -1130,6 +1130,26 @@ std::vector SolveSpace::GetFontFiles() { return fonts; } +const void *SolveSpace::LoadResource(const std::string &name, size_t *size) { + static NSMutableDictionary *cache; + + if(cache == nil) { + cache = [[NSMutableDictionary alloc] init]; + } + + NSString *key = [NSString stringWithUTF8String:name.c_str()]; + NSData *data = [cache objectForKey:key]; + if(data == nil) { + NSString *path = [[NSBundle mainBundle] pathForResource:key ofType:nil]; + data = [[NSFileHandle fileHandleForReadingAtPath:path] readDataToEndOfFile]; + if(data == nil) oops(); + [cache setObject:data forKey:key]; + } + + *size = [data length]; + return [data bytes]; +} + /* Application lifecycle */ @interface ApplicationDelegate : NSObject diff --git a/src/config.h.in b/src/config.h.in index aca77577..f8ebe0f1 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -3,6 +3,9 @@ #define PACKAGE_VERSION "@solvespace_VERSION_MAJOR@.@solvespace_VERSION_MINOR@~@solvespace_GIT_HASH@" +/* Non-OS X *nix only */ +#define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace" + /* Do we have the si library on win32, or libspnav on *nix? */ #cmakedefine HAVE_SPACEWARE diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index 6f33eb7c..dec16f1c 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -1474,6 +1474,36 @@ std::vector GetFontFiles() { return fonts; } +static std::string resource_dir; +const void *LoadResource(const std::string &name, size_t *size) { + static std::map> cache; + + auto it = cache.find(name); + if(it == cache.end()) { + struct stat st; + std::string path; + + path = (UNIX_DATADIR "/") + name; + if(stat(path.c_str(), &st)) { + if(errno != ENOENT) oops(); + path = resource_dir + "/" + name; + if(stat(path.c_str(), &st)) oops(); + } + + std::vector data(st.st_size); + FILE *f = ssfopen(path.c_str(), "rb"); + if(!f) oops(); + fread(&data[0], 1, st.st_size, f); + fclose(f); + + cache.emplace(name, std::move(data)); + it = cache.find(name); + } + + *size = (*it).second.size(); + return &(*it).second[0]; +} + /* Space Navigator support */ #ifdef HAVE_SPACEWARE @@ -1536,6 +1566,11 @@ int main(int argc, char** argv) { ambiguous. */ gtk_disable_setlocale(); + resource_dir = argv[0]; // .../src/solvespace + resource_dir.erase(resource_dir.rfind('/')); + resource_dir.erase(resource_dir.rfind('/')); + resource_dir += "/res"; // .../res + Gtk::Main main(argc, argv); #ifdef HAVE_SPACEWARE diff --git a/src/resource.cpp b/src/resource.cpp new file mode 100644 index 00000000..656509a7 --- /dev/null +++ b/src/resource.cpp @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Discovery and loading of our resources (icons, fonts, templates, etc). +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { + +std::string LoadString(const std::string &name) { + size_t size; + const void *data = LoadResource(name, &size); + if(data == NULL) oops(); + + return std::string(static_cast(data), size); +} + +} diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 00000000..10c0ea68 --- /dev/null +++ b/src/resource.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Discovery and loading of our resources (icons, fonts, templates, etc). +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- + +#ifndef __RESOURCE_H +#define __RESOURCE_H + +// Only the following function is platform-specific. +// It returns a pointer to resource contents that is aligned to at least +// sizeof(void*) and has a global lifetime, or NULL if a resource with +// the specified name does not exist. +const void *LoadResource(const std::string &name, size_t *size); + +std::string LoadString(const std::string &name); + +#endif diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 0080e9ac..a13107ca 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -13,6 +13,9 @@ Sketch SolveSpace::SK = {}; std::string SolveSpace::RecentFile[MAX_RECENT] = {}; void SolveSpaceUI::Init() { + // Check that the resource system works. + dbp("%s", LoadString("banner.txt").data()); + SS.tangentArcRadius = 10.0; // Then, load the registry settings. diff --git a/src/solvespace.h b/src/solvespace.h index 46debb9a..25b6a403 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -275,6 +275,8 @@ void MemFree(void *p); void InitHeaps(void); void vl(void); // debug function to validate heaps +#include "resource.h" + // End of platform-specific functions //================ diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 20757cc8..8d2310d9 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -1323,6 +1323,16 @@ static void CreateMainWindows(void) ClientIsSmallerBy = (r.bottom - r.top) - (rc.bottom - rc.top); } +const void *SolveSpace::LoadResource(const std::string &name, size_t *size) { + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + if(!hres) oops(); + HGLOBAL res = LoadResource(NULL, hres); + if(!res) oops(); + + *size = SizeofResource(NULL, hres); + return LockResource(res); +} + #ifdef HAVE_SPACEWARE //----------------------------------------------------------------------------- // Test if a message comes from the SpaceNavigator device. If yes, dispatch