2015-05-01 12 views
0

我曾经用一个自定义的PrefPane来做,它必须单独安装,但这并不令人满意。如何在OSX上存储基于HTML的QuickLook生成器的首选项?

我有一个基于HTML的QuickLook生成器,它可以创建缩略图和一些不同内容的预览(具有长ASCII标头的文件,以及每个带有一些标头的各种二进制扩展)。

在QL方法OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)中,我尝试使用[NSUserDefaults standardUserDefaults],但它没有效果,并且没有写入任何偏好文件,可能是因为我们在一个包中,而不是一个应用程序。

任何想法如何实现这一目标?我知道一些优秀的QL发电机可以做到,比如BetterZip QL。我试图逆向设计BetterZip QL,但没有成功。

回答

1

我直接联系了BetterZip QL的作者,并一起提出了一个解决方案。这里是。

简而言之:

  1. 首先,创建一个小助手应用程序,将在 发生器内捆绑在一起。这个应用程序将负责编写偏好 文件。
  2. 使这个应用程序注册一个自定义的URL方案,并实施与之相关的处理。
  3. 使您的基于HTML的QL使用该自定义方案,使用Javascript打开一个特殊格式的URL。

好的,现在详细。

首先在Xcode工作区/项目中创建一个小帮手应用程序目标。我的QL发生器被命名为QLFits,我选择了QLFitsConfig

默认情况下,有一个MainMenu.xib关联该应用程序。收下。它由Info.plist使用,它对于调试很有用。事实上,要调试自定义URL方案,可以将NSWindow添加到该xib,并放置可用于显示调试消息的标签。在调试此问题时,我找不到其他方式记录或显示调试消息。

但最后,你有一个小窗口的应用程序。这个应用程序必须具有两种配置。

  1. 指示此应用程序是代理的标志(请参见图片)。它可以防止应用在运行时出现在文档中。
  2. 定制URL方案的声明,其作用为Editor。也看到了一个例子(这里qlfitsconfig)图片

Snapshot of the Info.plist of the help app

接下来,应用程序的实现需要注册的URL方案告诉系统有一个应用程序,它能够打开它。在app的AppDelegate中我的“appDidFinishLaunching”方法的实现下面。

有三部分:自定义URL方案的处理程序的注册。具有与QL生成器共享的套件名称NSUserDefaults对象的实例化。最后,注册首选项的默认值(使用与应用程序捆绑在一起的.plist文件)。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 
{ 
    NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; 
    [appleEventManager setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; 

    [[NSUserDefaults standardUserDefaults] addSuiteNamed:suiteName]; 
    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; 

    NSString *optionsPath = [[NSBundle mainBundle] pathForResource:@"defaultOptions" ofType:@"plist"]; 
    NSDictionary *defaultOptions = [NSDictionary dictionaryWithContentsOfFile:optionsPath]; 
    [defaults registerDefaults:defaultOptions]; 
} 

suiteName变量是一个静态的NSString与反向DNS格式:static NSString *suiteName = @"com.onekiloparsec.qlfitsconfig.user-defaults-suite";

然后,应用程序需要在所述事件的触发作用。因此,必须对事件做些事情,并使用该事件来存储偏好。这是实施。请注意,方法的签名必须正好是那个,而不是因为我们在上面声明它,而是因为这是系统唯一认可的方法。

- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent 
{ 
    NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; 
    if (URLString) { 
     NSURL *URL = [NSURL URLWithString:URLString]; 
     if (URL) { 
      NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; 

      for (NSString *component in [URL pathComponents]) { 
       if ([component containsString:@"="]) { 
        NSArray *keyValue = [component componentsSeparatedByString:@"="]; 
        [defaults setObject:keyValue.lastObject forKey:keyValue.firstObject]; 
       } 
      } 

      [defaults synchronize]; 
     } 
    } 
} 

其基本思想是我们将通过URL参数提供首选项作为键值对。因此,我们在这里将该URL字符串转换为一对首选项,这些首选项存储为字符串。

这就是所有的应用程序。为了测试和调试它,您需要构建并运行它(例如,使用正在运行的/ Utilities/Activity Monitor.app进行检查)。您可以键入以下命令到终端看看会发生什么:

$ open qlfitsconfig://save/option1=value1/option2=value2 

如果你已经保持了上述标签的窗口,你可以用它们来显示/调试您的应用程序接收到什么事件。

现在回到QL发生器。在生成器的“构建阶段”中包含配置应用程序作为“目标依赖项”。此外,添加一个新的“复制文件”构建阶段(在复制包资源构建阶段之后)将该帮助应用程序复制到QL包中(见图片)。

Copy File build phase of the QL generator to bundle the helper app inside it.

现在,在代码中,更确切地说,方法预览方法中:OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)

在开始时,我确定配置帮助应用程序实际上已注册到系统的启动服务。要找到它,必须使用QL生成器的包标识符。根据在Build阶段(Helpers目录)中复制的位置,特别注意构建应用程序的URL的方式。

NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.onekiloparsec.QLFits3"]; 
NSURL *urlConfig = [NSURL fileURLWithPath:[[bundle bundlePath] stringByAppendingPathComponent:@"Contents/Helpers/QLFitsConfig.app"]]; 
LSRegisterURL((__bridge CFURLRef) urlConfig, true); 

最后一行是使用传统的API,但我无法使新的工作。这是一个弱点,应该找到一个更好的方法。

现在,如果某些首选项已保存,则可以使用NSUserDefaults的实例访问它们,前提是我们使用助手应用中定义的相同套件名称对其进行初始化。例如:

static NSString *suiteName = @"com.onekiloparsec.qlfitsconfig.user-defaults-suite"; 
    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; 

    BOOL alwaysShowHeaders = YES; 
    if ([defaults stringForKey:@"alwaysShowHeaders"]) { 
     alwaysShowHeaders = [[defaults stringForKey:@"alwaysShowHeaders"] isEqualToString:@"1"]; 
    } 

这就是Obj-C代码。

最后一部分是Javascript代码。在我的QL生成器(whose code can be checked on GitHub)中,我使用包含所有html和JS代码的template.html文件。你可以在这里组织自己。

我首先打算在复选框切换时更改QL首选项。但它似乎不起作用(没有事件触发)。我做它的唯一方法是,一旦我的复选框设置,用户被要求使用按钮“保存”首选项。我点击该按钮后保存偏好设置。这是我template.html里面的JS代码

<script> 
function saveConfig (a) { 
    a.href = "qlfitsconfig://save"; 
    a.href += "/alwaysShowHeaders=" + (document.getElementById("alwaysShowHeadersInput").checked ? "1" : "0"); 
    a.href += "/showSummaryInThumbnails=" + (document.getElementById("showSummaryInThumbnailsInput").checked ? "1" : "0"); 
    return true; 
} 
</script> 

alwaysShowHeadersInputshowSummaryInThumbnailsInput是我在HTML代码复选框的“身份证”。保存按钮正在触发saveConfig功能。

而且按钮必须在a标签中声明:

<a href="#" onClick="saveConfig(this);return true;" style="float:right;"><input id="save" type="button" value="Save"></a> 

这里的喜好是什么样子,我QL窗口:

The bottom of my QL window with the preferences checkboxes, and the save button

的Et瞧!