]> git.lyx.org Git - lyx.git/blob - src/support/linkback/LinkBack.m
Fix import of latex documents with scaled fonts.
[lyx.git] / src / support / linkback / LinkBack.m
1 //
2 //  LinkBack.m
3 //  LinkBack
4 //
5 //  Created by Charles Jolley on Tue Jun 15 2004.
6 //  Copyright (c) 2004, Nisus Software, Inc.
7 //  All rights reserved.
8
9 //  Redistribution and use in source and binary forms, with or without 
10 //  modification, are permitted provided that the following conditions are met:
11 //
12 //  Redistributions of source code must retain the above copyright notice, 
13 //  this list of conditions and the following disclaimer.
14 //
15 //  Redistributions in binary form must reproduce the above copyright notice, 
16 //  this list of conditions and the following disclaimer in the documentation 
17 //  and/or other materials provided with the distribution.
18 //
19 //  Neither the name of the Nisus Software, Inc. nor the names of its 
20 //  contributors may be used to endorse or promote products derived from this 
21 //  software without specific prior written permission.
22 //
23 //  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
24 //  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
25 //  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 //  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
27 //  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
28 //  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
29 //  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
30 //  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
31 //  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
32 //  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
33 //  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 //
35
36 #import "LinkBack.h"
37 #import "LinkBackServer.h"
38
39 NSString* LinkBackPboardType = @"LinkBackData" ;
40
41 // LinkBack data keys.  These are used in a LinkBack object, which is currently a dictionary.  Do not depend on these values.  They are public for testing purposes only.
42 NSString* LinkBackServerActionKey = @"serverActionKey" ;
43 NSString* LinkBackServerApplicationNameKey = @"serverAppName" ;
44 NSString* LinkBackServerNameKey = @"serverName" ;
45 NSString* LinkBackServerBundleIdentifierKey = @"bundleId" ;
46 NSString* LinkBackVersionKey = @"version" ;
47 NSString* LinkBackApplicationDataKey = @"appData" ;
48 NSString* LinkBackSuggestedRefreshKey = @"refresh" ;
49 NSString* LinkBackApplicationURLKey = @"ApplicationURL" ;
50
51 NSString* LinkBackEditActionName = @"_Edit" ;
52 NSString* LinkBackRefreshActionName = @"_Refresh" ;
53
54 // ...........................................................................
55 // Support Functions
56 //
57
58 id MakeLinkBackData(NSString* serverName, id appData) 
59 {
60         return [NSDictionary linkBackDataWithServerName: serverName appData: appData] ;
61 }
62
63 id LinkBackGetAppData(id LinkBackData) 
64 {
65         return [LinkBackData linkBackAppData] ;
66 }
67
68 NSString* LinkBackUniqueItemKey() 
69 {
70         static int counter = 0 ;
71         
72         NSString* base = [[NSBundle mainBundle] bundleIdentifier] ;
73         unsigned long time = [NSDate timeIntervalSinceReferenceDate] ;
74         return [NSString stringWithFormat: @"%@%.8lx.%.4x",base,time,counter++] ;
75 }
76
77 BOOL LinkBackDataBelongsToActiveApplication(id data) 
78 {
79         return [data linkBackDataBelongsToActiveApplication] ;
80 }
81
82 NSString* LinkBackEditMultipleMenuTitle() 
83 {
84         NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
85         NSString* ret = [bundle localizedStringForKey: @"_EditMultiple" value: @"Edit LinkBack Items" table: @"Localized"] ;
86         return ret ;
87 }
88
89 NSString* LinkBackEditNoneMenuTitle() 
90 {
91         NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
92         NSString* ret = [bundle localizedStringForKey: @"_EditNone" value: @"Edit LinkBack Item" table: @"Localized"] ;
93         return ret ;
94 }
95
96 // ...........................................................................
97 // LinkBack Data Category
98 //
99
100 // Use these methods to create and access linkback data objects.  You can also use the helper functions above.
101
102 @implementation NSDictionary (LinkBackData)
103
104 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData 
105 {
106         return [self linkBackDataWithServerName: serverName appData: appData actionName: nil suggestedRefreshRate: (float)0];
107 }
108
109 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData suggestedRefreshRate:(NSTimeInterval)rate 
110 {
111         return [self linkBackDataWithServerName: serverName appData: appData actionName: LinkBackRefreshActionName suggestedRefreshRate: rate] ;
112 }
113
114 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData actionName:(NSString*)action suggestedRefreshRate:(NSTimeInterval)rate
115 {
116         NSDictionary* appInfo = [[NSBundle mainBundle] infoDictionary] ;
117
118         NSMutableDictionary* ret = [[NSMutableDictionary alloc] init] ;
119         NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
120         NSString* url = [appInfo objectForKey: @"LinkBackApplicationURL"] ;
121         NSString* appName = [[NSProcessInfo processInfo] processName] ;
122         id version = @"A" ;
123
124         if (nil==serverName) [NSException raise: NSInvalidArgumentException format: @"LinkBack Data cannot be created without a server name."] ;
125         
126         // callback information
127         [ret setObject: bundleId forKey: LinkBackServerBundleIdentifierKey]; 
128         [ret setObject: serverName forKey: LinkBackServerNameKey] ;
129         [ret setObject: version forKey: LinkBackVersionKey] ;
130         
131         // additional information
132         if (appName) [ret setObject: appName forKey: LinkBackServerApplicationNameKey] ;
133         if (action) [ret setObject: action forKey: LinkBackServerActionKey] ;
134         if (appData) [ret setObject: appData forKey: LinkBackApplicationDataKey] ;
135         if (url) [ret setObject: url forKey: LinkBackApplicationURLKey] ;
136         [ret setObject: [NSNumber numberWithDouble: rate] forKey: LinkBackSuggestedRefreshKey] ;
137         
138         return [ret autorelease] ;
139 }
140
141 - (BOOL)linkBackDataBelongsToActiveApplication 
142 {
143         NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
144         NSString* dataId = [self objectForKey: LinkBackServerBundleIdentifierKey] ;
145         return (dataId && [dataId isEqualToString: bundleId]) ;
146 }
147
148 - (id)linkBackAppData 
149 {
150         return [self objectForKey: LinkBackApplicationDataKey] ;
151 }
152
153 - (NSString*)linkBackSourceApplicationName 
154 {
155         return [self objectForKey: LinkBackServerApplicationNameKey] ;
156 }
157
158 - (NSString*)linkBackActionName 
159 {
160         NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
161         NSString* ret = [self objectForKey: LinkBackServerActionKey] ;
162         if (nil==ret) ret = LinkBackEditActionName ;
163         
164         ret = [bundle localizedStringForKey: ret value: ret table: @"Localized"] ;
165         return ret ;
166 }
167
168 - (NSString*)linkBackEditMenuTitle
169 {
170         NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
171         NSString* appName = [self linkBackSourceApplicationName] ;
172         NSString* action = [self linkBackActionName] ;
173         NSString* ret = [bundle localizedStringForKey: @"_EditPattern" value: @"%@ in %@" table: @"Localized"] ;
174         ret = [NSString stringWithFormat: ret, action, appName] ;
175         return ret ;
176 }
177
178 - (NSString*)linkBackVersion 
179 {
180         return [self objectForKey: LinkBackVersionKey] ;
181 }
182
183 - (NSTimeInterval)linkBackSuggestedRefreshRate 
184 {
185         id obj = [self objectForKey: LinkBackSuggestedRefreshKey] ;
186         return (obj) ? [obj floatValue] : 0 ;
187 }
188
189 - (NSURL*)linkBackApplicationURL 
190 {
191         id obj = [self objectForKey: LinkBackApplicationURLKey] ;
192         if (obj) obj = [NSURL URLWithString: obj] ;
193         return obj ;
194 }
195
196 @end
197
198 // ...........................................................................
199 // LinkBackServer 
200 //
201 // one of these exists for each registered server name.  This is the receiver of server requests.
202
203 // ...........................................................................
204 // LinkBack Class
205 //
206
207 NSMutableDictionary* keyedLinkBacks = nil ;
208
209 @implementation LinkBack
210
211 + (void)initialize
212 {
213         static BOOL inited = NO ;
214         if (inited) return ;
215         inited=YES; [super initialize] ;
216         keyedLinkBacks = [[NSMutableDictionary alloc] init] ;
217 }
218
219 + (LinkBack*)activeLinkBackForItemKey:(id)aKey 
220 {
221         return [keyedLinkBacks objectForKey: aKey] ;
222 }
223
224 - (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id<LinkBackServerDelegate>)aDel 
225 {
226         if ((self = [super init])) {
227                 peer = [aLinkBack retain] ;
228                 sourceName = [[peer sourceName] copy] ;
229                 sourceApplicationName = [[peer sourceApplicationName] copy] ;
230                 key = [[peer itemKey] copy] ;
231                 isServer = YES ;
232                 delegate = aDel ;
233                 [keyedLinkBacks setObject: self forKey: key] ;
234         }
235         
236         return self ;
237 }
238
239 - (id)initClientWithSourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)aDel itemKey:(NSString*)aKey
240 {
241         if ((self = [super init])) {
242                 isServer = NO ;
243                 delegate = aDel ;
244                 sourceName = [aName copy] ;
245                 sourceApplicationName = [[NSProcessInfo processInfo] processName] ;
246                 pboard = [[NSPasteboard pasteboardWithUniqueName] retain] ;
247                 key = [aKey copy] ;
248         }
249         
250         return self ;
251 }
252
253 - (void)dealloc
254 {
255         [repobj release] ;
256         [sourceName release] ;
257         
258         if (peer) [self closeLink] ;
259         [peer release] ;
260         
261         if (!isServer) [pboard releaseGlobally] ; // client owns the pboard.
262         [pboard release] ;
263         
264         [super dealloc] ;
265 }
266
267 // ...........................................................................
268 // General Use methods
269
270 - (NSPasteboard*)pasteboard 
271 {
272         return pboard ;
273 }
274
275 - (id)representedObject 
276 {
277         return repobj ;
278 }
279
280 - (void)setRepresentedObject:(id)obj 
281 {
282         [obj retain] ;
283         [repobj release] ;
284         repobj = obj ;
285 }
286
287 - (NSString*)sourceName
288 {
289         return sourceName ;
290 }
291
292 - (NSString*)sourceApplicationName 
293 {
294         return sourceApplicationName ;
295 }
296
297 - (NSString*)itemKey
298 {
299         return key ;
300 }
301
302 // this method is called to initial a link closure from this side.
303 - (void)closeLink 
304 {
305         // inform peer of closure
306         if (peer) {
307                 [peer remoteCloseLink] ; 
308                 [peer release] ;
309                 peer = nil ;
310                 [self release] ;
311                 [keyedLinkBacks removeObjectForKey: [self itemKey]]; 
312         }
313 }
314
315 // this method is called whenever the link is about to be or has been closed by the other side.
316 - (oneway void)remoteCloseLink 
317 {
318         if (peer) {
319                 [peer release] ;
320                 peer = nil ;
321                 [self release] ;
322                 [keyedLinkBacks removeObjectForKey: [self itemKey]]; 
323         }
324
325         if (delegate) [delegate linkBackDidClose: self] ;
326 }
327
328 // ...........................................................................
329 // Server-side methods
330 //
331 + (BOOL)publishServerWithName:(NSString*)name delegate:(id<LinkBackServerDelegate>)del 
332 {
333         return [LinkBackServer publishServerWithName: name delegate: del] ;
334 }
335
336 + (void)retractServerWithName:(NSString*)name 
337 {
338         LinkBackServer* server = [LinkBackServer LinkBackServerWithName: name] ;
339         if (server) [server retract] ;
340 }
341
342 - (void)sendEdit 
343 {
344         if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
345         [peer refreshEditWithPasteboardName: [pboard name]] ;
346 }
347
348 // FROM CLIENT LinkBack
349 - (void)requestEditWithPasteboardName:(bycopy NSString*)pboardName
350 {
351         // get the new pasteboard, if needed
352         if ((!pboard) || ![pboardName isEqualToString: [pboard name]]) pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
353
354         // pass onto delegate
355         [delegate performSelectorOnMainThread: @selector(linkBackClientDidRequestEdit:) withObject: self waitUntilDone: NO] ;
356 }
357
358 // ...........................................................................
359 // Client-Side Methods
360 //
361 + (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)del itemKey:(NSString*)aKey
362 {
363         // if an active live link already exists, use that.  Otherwise, create a new one.
364         LinkBack* ret = [keyedLinkBacks objectForKey: aKey] ;
365         
366         if(nil==ret) {
367                 BOOL ok = [data isKindOfClass: [NSDictionary class]] ;
368                 
369                 if (ok) {
370                         // collect server contact information from data.
371                         NSString* serverName = [data objectForKey: LinkBackServerNameKey] ;
372                         NSString* serverId = [data objectForKey: LinkBackServerBundleIdentifierKey];
373                         NSString* appName = [data linkBackSourceApplicationName] ;
374                         NSURL* url = [data linkBackApplicationURL] ;
375
376                         if ( !serverName || !serverId)
377                                 [NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
378
379                         // create the live link object and try to connect to the server.
380                         ret = [[LinkBack alloc] initClientWithSourceName: aName delegate: del itemKey: aKey] ;
381                 
382                         if (![ret connectToServerWithName: serverName inApplication: serverId fallbackURL: url appName: appName]) {
383                                 [ret release] ;
384                                 ret = nil ;
385                         }
386                 } else {
387                         [NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
388                 }
389         }
390         
391         // now with a live link in hand, request an edit
392         if (ret) {
393                 // if connected to server, publish data and inform server.
394                 NSPasteboard* my_pboard = [ret pasteboard] ;
395                 [my_pboard declareTypes: [NSArray arrayWithObject: LinkBackPboardType] owner: ret] ;
396                 [my_pboard setPropertyList: data forType: LinkBackPboardType] ;
397                 
398                 [ret requestEdit] ;
399                 
400         // if connection to server failed, return nil.
401         }
402
403         return ret ;
404 }
405
406 - (BOOL)connectToServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier fallbackURL:(NSURL*)url appName:(NSString*)appName 
407 {
408         // get the LinkBackServer.
409         LinkBackServer* server = [LinkBackServer LinkBackServerWithName: aName inApplication: bundleIdentifier launchIfNeeded: YES fallbackURL: url appName: appName] ;
410         if (!server) return NO ; // failed to get server
411         
412         peer = [[server initiateLinkBackFromClient: self] retain] ;
413         if (!peer) return NO ; // failed to initiate session
414         
415         // if we connected, then add to the list of active keys
416         [keyedLinkBacks setObject: self forKey: [self itemKey]] ;
417         
418         return YES ;
419 }
420
421 - (void)requestEdit 
422 {
423         if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
424         [peer requestEditWithPasteboardName: [pboard name]] ;
425 }
426
427 // RECEIVED FROM SERVER
428 - (void)refreshEditWithPasteboardName:(bycopy NSString*)pboardName
429 {
430         // if pboard has changes, change to new pboard.
431         if (![pboardName isEqualToString: [pboard name]]) {
432                 [pboard release] ;
433                 pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
434         } 
435         
436         // inform delegate
437         [delegate performSelectorOnMainThread: @selector(linkBackServerDidSendEdit:) withObject: self waitUntilDone: NO] ;
438 }
439
440 @end