Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Adding QuickLook feature #7491

Merged
merged 9 commits into from Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/CMakeLists.txt
Expand Up @@ -18,7 +18,6 @@ if(BUILD_TEMPLATE)
add_subdirectory(Tools/_TEMPLATE_)
endif(BUILD_TEMPLATE)

# "if" clause moved into local CMakeLists.txt file to support Conda and Homebrew builds
add_subdirectory(MacAppBundle)

if(FREECAD_CREATE_MAC_APP)
add_subdirectory(MacAppBundle)
endif(FREECAD_CREATE_MAC_APP)
2 changes: 1 addition & 1 deletion src/Gui/Thumbnail.cpp
Expand Up @@ -106,7 +106,7 @@ void Thumbnail::SaveDocFile (Base::Writer &writer) const
if (App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Document")->GetBool("AddThumbnailLogo",true)) {
// only scale app icon if an offscreen image could be created
appIcon = appIcon.scaled(this->size / 4, this->size /4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
appIcon = appIcon.scaled(this->size / 4, this->size /4, Qt::KeepAspectRatio, Qt::SmoothTransformation);
px = BitmapFactory().merge(QPixmap::fromImage(img), appIcon, BitmapFactoryInst::BottomRight);
}
else {
Expand Down
30 changes: 28 additions & 2 deletions src/MacAppBundle/CMakeLists.txt
@@ -1,3 +1,27 @@

#
# Build and install QuickLook for FCStd files (org.freecad.fcstd)
# This is used by Homebrew and Conda/Mamba scripts alike.
#
if(FREECAD_CREATE_MAC_APP OR (APPLE AND BUILD_WITH_CONDA))
add_subdirectory(QuickLook)
install(
DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/QuickLook/QuicklookFCStd.qlgenerator"
DESTINATION "${CMAKE_INSTALL_PREFIX}/Library/QuickLook"
)
install(
PROGRAMS "${PROJECT_BINARY_DIR}/src/MacAppBundle/QuickLook/QuicklookFCStd.framework/Versions/A/QuicklookFCStd"
DESTINATION "${CMAKE_INSTALL_PREFIX}/Library/QuickLook/QuicklookFCStd.qlgenerator/Contents/MacOS/"
)
endif()


#
# Build a Bundle in Homebrew.
# This is ignored by Conda/Mamba build scripts.
#
if(FREECAD_CREATE_MAC_APP)

set(PYTHON_DIR_BASENAME python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR})

if(PYTHON_LIBRARY MATCHES "(.*Python\\.framework).*")
Expand Down Expand Up @@ -122,8 +146,8 @@ file(GLOB CONFIG_GCC "${HOMEBREW_PREFIX}/opt/gcc/lib/gcc/current")
execute_process(
COMMAND find -L /usr/local/Cellar/nglib -name MacOS
OUTPUT_VARIABLE CONFIG_NGLIB)

install(CODE
install(CODE
"message(STATUS \"Making bundle relocatable...\")
# The top-level CMakeLists.txt should prevent multiple package manager
# prefixes from being set, so the lib path will resolve correctly...
Expand All @@ -133,3 +157,5 @@ install(CODE
${APP_PATH} ${HOMEBREW_PREFIX}${MACPORTS_PREFIX}/lib ${CONFIG_ICU} ${CONFIG_LLVM} ${CONFIG_GCC} /usr/local/opt ${CONFIG_NGLIB} ${Qt5Core_DIR}/../../.. ${XCTEST_PATH} ${WEBKIT_FRAMEWORK_DIR}
)"
)

endif(FREECAD_CREATE_MAC_APP)
67 changes: 51 additions & 16 deletions src/MacAppBundle/FreeCAD.app/Contents/Info.plist
Expand Up @@ -2,6 +2,56 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>FCStd</string>
<string>FCMat</string>
<string>FCParam</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>freecad-doc.icns</string>
<key>LSIsAppleDefaultForType</key>
<true/>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>FCMacro</string>
<string>FCScript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>freecad-script.icns</string>
<key>LSIsAppleDefaultForType</key>
<true/>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>FreeCAD Document</string>
<key>UTTypeIdentifier</key>
<string>org.freecad.fcstd</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>FCStd</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
Expand Down Expand Up @@ -31,23 +81,8 @@
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>FCStd</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>freecad-doc.icns</string>
<key>LSIsAppleDefaultForType</key>
<true/>
</dict>
</array>
</dict>
</plist>
Binary file not shown.
35 changes: 35 additions & 0 deletions src/MacAppBundle/QuickLook/CMakeLists.txt
@@ -0,0 +1,35 @@

#cmake_minimum_required(VERSION 3.23)
#project(FCQuickLook)

add_library(
QuicklookFCStd
SHARED
GeneratePreviewForURL.m
GenerateThumbnailForURL.m
main.c
)

set_target_properties(
QuicklookFCStd
PROPERTIES
FRAMEWORK TRUE
MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/QuicklookFCStd.qlgenerator/Contents/Info.plist"
#SUFFIX .qlgenerator
)

target_link_libraries(
QuicklookFCStd
"-framework AppKit"
"-framework ApplicationServices"
"-framework CoreData"
"-framework CoreFoundation"
"-framework CoreServices"
"-framework Foundation"
"-framework QuickLook"
)

set_target_properties(
QuicklookFCStd
PROPERTIES LINK_FLAGS "-Wl,-F/Library/Frameworks"
)
74 changes: 74 additions & 0 deletions src/MacAppBundle/QuickLook/GeneratePreviewForURL.m
@@ -0,0 +1,74 @@
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <QuickLook/QuickLook.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>

/* -----------------------------------------------------------------------------
Generate a preview for file

This function's job is to create preview for designated file
----------------------------------------------------------------------------- */


OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
OSStatus ret = coreFoundationUnknownErr;

@autoreleasepool {

// unzip -qq -o -j -d /tmp ~/test.FCStd thumbnails/Thumbnail.png
// -qq : be really quiet
// -o : overwrite without prompt
// -j : don't create archive paths
// -d : destination path (create this path)
// extracts thumbnails/Thumbnail.png to /tmp/Thumbnail.png .
// We add a UUID and use a system temp directory here.

// TODO: do we need to release any of these resources?
NSUUID *uuid = [NSUUID UUID];
NSString *uuidPath = [uuid UUIDString];
NSString *unzipDstPath = [NSString stringWithFormat:@"%@/%@/", NSTemporaryDirectory(), uuidPath];
NSString *unzipDstFile = [NSString stringWithFormat:@"%@%@", unzipDstPath, @"Thumbnail.png"];
NSURL *zipFile = (__bridge NSURL *)url;
NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/unzip"
arguments:@[@"-qq", @"-o", @"-j", @"-d", unzipDstPath, [zipFile path], @"thumbnails/Thumbnail.png"]];
[task waitUntilExit];
// NSLog(@"%@", unzipDstPath);
// NSLog(@"%@", unzipDstFile);

if ( [[NSFileManager defaultManager] fileExistsAtPath:unzipDstFile] )
{
// Preview will be drawn in a vectorized context
CGSize canvasSize = CGSizeMake(512, 512);
CGContextRef cgContext = QLPreviewRequestCreateContext(preview, canvasSize, true, NULL);
if(cgContext)
{
CGDataProviderRef pngDP = CGDataProviderCreateWithFilename([unzipDstFile fileSystemRepresentation]);
CGImageRef image = CGImageCreateWithPNGDataProvider(pngDP, NULL, true, kCGRenderingIntentDefault);

CGContextDrawImage(cgContext,CGRectMake(0, 0, 512, 512), image);

QLPreviewRequestFlushContext(preview, cgContext);
ret = noErr;

CFRelease(cgContext);
CFRelease(image);
}
}

if ( [[NSFileManager defaultManager] fileExistsAtPath:unzipDstFile] )
[[NSFileManager defaultManager] removeItemAtPath:unzipDstFile error:nil];

if ( [[NSFileManager defaultManager] fileExistsAtPath:unzipDstPath] )
[[NSFileManager defaultManager] removeItemAtPath:unzipDstPath error:nil];
}

return ret;
}


void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview)
{
// implement only if supported
}
72 changes: 72 additions & 0 deletions src/MacAppBundle/QuickLook/GenerateThumbnailForURL.m
@@ -0,0 +1,72 @@
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <QuickLook/QuickLook.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>

/* -----------------------------------------------------------------------------
Generate a thumbnail for file

This function's job is to create thumbnail for designated file as fast as possible
----------------------------------------------------------------------------- */

OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize)
{
OSStatus ret = coreFoundationUnknownErr;

@autoreleasepool {

// unzip -qq -o -j -d /tmp ~/test.FCStd thumbnails/Thumbnail.png
// -qq : be really quiet
// -o : overwrite without prompt
// -j : don't create archive paths
// -d : destination path (create this path)
// extracts thumbnails/Thumbnail.png to /tmp/Thumbnail.png .
// We add a UUID and use a system temp directory here.

NSUUID *uuid = [NSUUID UUID];
NSString *uuidPath = [uuid UUIDString];
NSString *unzipDstPath = [NSString stringWithFormat:@"%@/%@/", NSTemporaryDirectory(), uuidPath];
NSString *unzipDstFile = [NSString stringWithFormat:@"%@%@", unzipDstPath, @"Thumbnail.png"];
NSURL *zipFile = (__bridge NSURL *)url;
NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/unzip"
arguments:@[@"-qq", @"-o", @"-j", @"-d", unzipDstPath, [zipFile path], @"thumbnails/Thumbnail.png"]];
[task waitUntilExit];
// NSLog(@"%@", unzipDstPath);
// NSLog(@"%@", unzipDstFile);

if ( [[NSFileManager defaultManager] fileExistsAtPath:unzipDstFile] )
{
CGSize canvasSize = CGSizeMake(128, 128);
CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, /*maxSize*/canvasSize, true, NULL);
if(cgContext)
{
CGDataProviderRef pngDP = CGDataProviderCreateWithFilename([unzipDstFile fileSystemRepresentation]);
CGImageRef image = CGImageCreateWithPNGDataProvider(pngDP, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(pngDP);

CGContextDrawImage(cgContext,CGRectMake(0, 0, 128, 128), image);

QLThumbnailRequestFlushContext(thumbnail, cgContext);
ret = noErr;

CFRelease(cgContext);
CFRelease(image);
}
}

if ( [[NSFileManager defaultManager] isDeletableFileAtPath:unzipDstFile] )
[[NSFileManager defaultManager] removeItemAtPath:unzipDstFile error:nil];

if ( [[NSFileManager defaultManager] isDeletableFileAtPath:unzipDstPath] )
[[NSFileManager defaultManager] removeItemAtPath:unzipDstPath error:nil];
}

return ret;
}


void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail)
{
// implement only if supported
}