OSDN Git Service

for macOS 10.14 Mojave
[letter-fix/LetterFix.git] / LetterFix.m
1 //
2 //  LetterFix.m
3 //  LetterFix2
4 //
5 //  Created by kuri on 10/02/01.
6 //  Copyright 2010-2017 kuri. All rights reserved.
7 //
8 #import <objc/runtime.h>
9 #import <objc/objc-runtime.h>
10 #import <WebKit/WebKit.h>
11 #import "LetterFix.h"
12 #import "LFApp.h"
13
14 static NSMutableArray *khash;
15 static NSMutableArray *check_at_save;
16 static LFApp *app;
17 static NSString *roman[] = {@"I", @"II", @"III", @"IV", @"V", @"VI", @"VII", @"VIII", @"IX", @"X"};
18 static NSString *dep[] = {@"ミリ", @"キロ", @"センチ", @"メートル", @"グラム", @"トン", @"アール", @"ヘクタール",
19     @"リットル", @"ワット", @"カロリー", @"ドル", @"セント", @"パーセント", @"ミリバール", @"ページ",
20     @"mm", @"cm", @"km", @"mg", @"kg", @"cc", @"明治", @"大正", @"昭和", @"平成", @"No.", @"K.K.", @"TEL",
21     @"(上)", @"(中)", @"(下)", @"(左)", @"(右)", @"(株)", @"(有)", @"(代)", @"Σ"};
22 static NSString *dayOfWeek = @"㈰㈪㈫㈬㈭㈮㈯㉀㈷㉂㉃㈹㈺㈱㈾㈴㈲㈻㈶㈳㈵㈼㈽㈿㈸";
23 static NSString *subOfDayOfWeek[] = {@"(日)",@"(月)",@"(火)",@"(水)",@"(木)",@"(金)",@"(土)",@"(祭)",@"(祝)",@"(自)",
24     @"(至)",@"(代)",@"(呼)",@"(株)",@"(資)",@"(名)",@"(有)",@"(学)",@"(財)",@"(社)",
25     @"(特)",@"(監)",@"(企)",@"(協)",@"(労)"};
26 static NSString *circledNumbers = @"①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⅠⅡⅢⅣⅤ";
27 static NSString *subOfCircledNum[] = {@"(1)",@"(2)",@"(3)",@"(4)",@"(5)",@"(6)",@"(7)",@"(8)",@"(9)",@"(10)",
28     @"(11)",@"(12)",@"(13)",@"(14)",@"(15)",@"(16)",@"(17)",@"(18)",@"(19)",@"(20)",
29     @"I",@"II",@"III",@"IV",@"V"};
30 static NSString *symbols = @"㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㍾㍽㍼㍻№㏍℡㊤㊥㊦㊧㊨㈱㈲㈹∑";
31
32 static unsigned long long LF_Encoding_ISO2022JP = NSISO2022JPStringEncoding;
33 static unsigned long long LF_Encoding_Auto = 0;
34
35 void swizzlingMethod(Class aClass, SEL aSelector, SEL nSelector, IMP nImplement) {
36     Method orig_method, alt_method;
37     
38     if ((orig_method = class_getInstanceMethod(aClass, aSelector)) == NULL) {
39         NSLog(@"Swizzling Method: Original Method is not found. [%@ %@]", NSStringFromClass(aClass), NSStringFromSelector(aSelector));
40         return; // Original Method is not found.
41     }
42     // If orig_method was the superclass's instance method, so now we'll add the implement as method of self class.
43     if (class_addMethod(aClass, aSelector, method_getImplementation(orig_method), method_getTypeEncoding(orig_method)) == YES) {
44         orig_method = class_getInstanceMethod(aClass, aSelector);
45     }
46     if (class_addMethod(aClass, nSelector, nImplement, method_getTypeEncoding(orig_method)) == NO) {
47         NSLog(@"Swizzling Method: Can't add new method.");
48         return; // Can't add new method
49     }
50     alt_method  = class_getInstanceMethod(aClass, nSelector);
51     method_exchangeImplementations(orig_method, alt_method);
52     //NSLog(@"Swizzling Method: Success.");
53 }
54
55 static void backendSetPreferredEncoding(id backend, unsigned long long encoding)
56 {
57     if ([app ver] == LF_Mavericks) {
58         Ivar ivar = object_getInstanceVariable(backend, "_flags", NULL);
59         _LF_flags *_flags = (_LF_flags *)((char *)backend + ivar_getOffset(ivar));
60         _flags->encodingHint = encoding;
61     } else if ([app ver] == LF_Yosemite || [app ver] == LF_ElCapitan ||
62                [app ver] == LF_Sierra || [app ver] == LF_HighSierra ||
63                [app ver] == LF_Mojave) {
64         [backend setEncodingHint: encoding];
65     }
66 }
67
68 static unsigned long long backendGetPreferredEncoding(id backend)
69 {
70     if ([app ver] == LF_Mavericks) {
71         Ivar ivar = object_getInstanceVariable(backend, "_flags", NULL);
72         _LF_flags *_flags = (_LF_flags *)((char *)backend + ivar_getOffset(ivar));
73         return _flags->encodingHint; //FIXME: [backend _encodingHint] return wrong value?
74     } else if ([app ver] == LF_Yosemite || [app ver] == LF_ElCapitan ||
75                [app ver] == LF_Sierra || [app ver] == LF_HighSierra ||
76                [app ver] == LF_Mojave) {
77         return [backend encodingHint];
78     }
79     return LF_Encoding_Auto;
80 }
81
82 int fixTextNode(DOMText *node, DOMRange *range, BOOL isCheck, BOOL isOpen)
83 {
84     int      replaced = 0;
85     int      c_start = -1;
86     int      c_end = -1;
87     int      i;
88     
89     if ([node nodeType] != DOM_TEXT_NODE) return 0;
90     
91     // 文字列選択位置を保存
92     if ([node isEqualNode:[range startContainer]]) {
93         c_start = [range startOffset];
94     }
95     if ([node isEqualNode:[range endContainer]]) {
96         c_end = [range endOffset];
97     }
98     
99     // 誤ったUnicodeテーブルが用いられている文字を本来の文字に置換
100     for (i = 0; i < [[node data] length]; i++) {
101         switch ([[node data] characterAtIndex:i]) {
102             case 0x2015: // ―
103                 [node replaceData:i length:1 data:@"—"]; // U+2014
104                 break;
105             case 0xff5e: // ~
106                 [node replaceData:i length:1 data:@"〜"]; // U+301c
107                 break;
108             case 0x2225: // ∥
109                 [node replaceData:i length:1 data:@"‖"]; // U+2016
110                 break;
111             case 0xff0d: // -
112                 [node replaceData:i length:1 data:@"−"]; // U+2212
113                 break;
114             case 0xffe0: // ¢
115                 [node replaceData:i length:1 data:@"¢"]; // U+00a2
116                 break;
117             case 0xffe1: // £
118                 [node replaceData:i length:1 data:@"£"]; // U+00a3
119                 break;
120             case 0xffe2: // ¬
121                 [node replaceData:i length:1 data:@"¬"]; // U+00ac
122                 break;
123         }
124     }
125     
126     for (i = 0; i < [[node data] length]; i++) {
127         NSString *us = [[node data] substringWithRange:NSMakeRange(i, 1)];
128         unichar   uc = [us characterAtIndex:0];
129         NSInteger loc;
130         NSString *ns = nil;
131         if ([us canBeConvertedToEncoding:NSISO2022JPStringEncoding] != YES && uc != 0xa0) {
132             replaced++;
133             if (isOpen) { // 編集開始時
134                 if ([app isOsDependentFix] == YES) {
135                     if ((loc = [dayOfWeek rangeOfString:us].location) != NSNotFound) {
136                         if ([app parenSymbolFix] == YES) {
137                             if (((loc == 11)||(loc == 13)||(loc == 16)) &&
138                                 ([app someParenSymbolNotFix] == YES)) {
139                                 ns = subOfDayOfWeek[loc];
140                             } else {
141                                 ns = subOfCircledNum[loc];
142                             }
143                         } else {
144                             ns = subOfDayOfWeek[loc];
145                         }
146                     } else { // (SnowLeopard or Lion) OR (not DayOfWeek)
147                         if ((0x2460 <= uc) && (uc <= 0x2473)) {       // ①〜⑳ -> (1)〜(20)
148                             ns = subOfCircledNum[uc-0x2460];
149                         } else if ((0x2160 <= uc && uc <= 0x2169) && ([app isOsDependentFix]==YES)) {// Ⅰ〜Ⅹ -> I〜X
150                             ns = roman[uc-0x2160];
151                         } else if (((loc = [symbols rangeOfString:us].location) != NSNotFound)
152                                    ) {
153                             ns = dep[loc];
154                         } else {
155                             if ([app isAllLetterFix] == YES) {
156                                 ns = @"〓";
157                             } else { // [app isAllLetterFix] == NO
158                                 ns = us;
159                             }
160                         }
161                     }
162                 } else { // [app isOsDependentFix] == NO
163                     if ([app isAllLetterFix] == YES) {
164                         ns = @"〓";
165                     } else { // [app isAllLetterFix] == NO
166                         ns = us;
167                     }
168                 }
169             } else { // 保存・送信時
170                 if ([app isOsDependentFix] == YES) {
171                     if ((0x2460 <= uc) && (uc <= 0x2473)) {       // ①〜⑳ -> (1)〜(20)
172                         ns = subOfCircledNum[uc-0x2460];
173                     } else if ((0x2160 <= uc && uc <= 0x2169) && ([app isOsDependentFix]==YES)) {// Ⅰ〜Ⅹ -> I〜X
174                         ns = roman[uc-0x2160];
175                     } else if ((loc = [symbols rangeOfString:us].location) != NSNotFound) {
176                         ns = dep[loc];
177                     } else if ((loc = [dayOfWeek rangeOfString:us].location) != NSNotFound) {
178                         ns = subOfDayOfWeek[loc];
179                     } else {
180                         if ([app isAllLetterFix] == YES) {
181                             ns = @"〓";
182                         } else { // [app isAllLetterFix] == NO
183                             ns = us;
184                         }
185                     }
186                 } else { // [app isOsDependentFix] == NO
187                     if ([app isAllLetterFix] == YES) {
188                         ns = @"〓";
189                     } else { // [app isAllLetterFix] == NO
190                         ns = us;
191                     }
192                 }
193             }
194             
195             if ((c_start != -1) && (i < c_start)) {
196                 c_start += [ns length] - 1;
197             }
198             if ((c_end != -1) && (i < c_end)) {
199                 c_end += [ns length] - 1;
200             }
201             if (!isCheck) {
202                 [node replaceData:i length:1 data:ns];
203                 i += [ns length] - 1;
204             }
205         }
206     }
207     if (!isCheck) {
208         if ((c_start != -1) && 0 < replaced) {
209             [range setStart:node offset:c_start];
210         }
211         if ((c_end != -1) && 0 < replaced) {
212             [range setEnd:node offset:c_end];
213         }
214     }
215     return replaced;
216 }
217
218 int fixReflexively(DOMNode *node, DOMRange *range, BOOL isCheck, BOOL isOpen)
219 {
220     DOMNode *n;
221     int      replaced = 0;
222     
223     if ([node hasChildNodes]) {
224         for (n = [node firstChild]; n != NULL; n = [n nextSibling]) {
225             replaced += fixReflexively(n, range, isCheck, isOpen); // 再帰的にノードをたどる
226         }
227     } else {
228         if ([node nodeType] == DOM_TEXT_NODE) {
229             replaced = fixTextNode((DOMText*)node, range, isCheck, isOpen);
230         }
231     }
232     return replaced;
233 }
234
235 int fixLetter(id self, id composeView, BOOL isCheck, BOOL isOpen)
236 {
237     int replaced = 0;
238     
239     @try {
240         WebFrame    *frame     = [(WebView *)composeView mainFrame];
241         DOMDocument *dom       = [frame DOMDocument];
242         DOMElement  *root      = [dom documentElement];
243         DOMNodeList *nodelist  = [root getElementsByTagName:@"body"];
244         
245         if ([nodelist length] != 1) return -1;
246         
247         DOMNode     *body      = [nodelist item:0];
248         DOMRange    *range     = [composeView selectedDOMRange];
249         
250         replaced = fixReflexively(body, range, isCheck, isOpen);
251         
252         if (0 < replaced && !isCheck) {
253             [composeView setSelectedDOMRange:range affinity:NSSelectionAffinityUpstream];
254         }
255     }
256     @catch (NSException * e) {
257         NSLog(@"LetterFix: caught %@ %@", [e name], [e reason]);
258     }
259     
260     return replaced;
261 }
262
263 BOOL checkSubject(id self)
264 {
265     NSMutableString *substr = [NSMutableString stringWithString:[[self backEnd] subject]];
266     NSInteger i;
267     
268     for (i = 0; i < [substr length]; i++) {
269         NSString *us = [substr substringWithRange:NSMakeRange(i, 1)];
270         unichar   uc = [us characterAtIndex:0];
271         switch (uc) {
272             case 0x2015: // ―
273             case 0xff5e: // ~
274             case 0x2225: // ∥
275             case 0xff0d: // -
276             case 0xffe0: // ¢
277             case 0xffe1: // £
278             case 0xffe2: // ¬
279                 return TRUE;
280             default:
281                 if ([us canBeConvertedToEncoding:NSISO2022JPStringEncoding] != YES) return TRUE;
282                 break;
283         }
284     }
285     
286     return FALSE;
287 }
288
289 BOOL checkSubjectOnLoad(id self)
290 {
291     NSMutableString *substr = [NSMutableString stringWithString:[[self backEnd] subject]];
292     NSInteger i, loc;
293     
294     for (i = 0; i < [substr length]; i++) {
295         NSString *us = [substr substringWithRange:NSMakeRange(i, 1)];
296         if ([us canBeConvertedToEncoding:NSISO2022JPStringEncoding] != YES) {
297             if (([app isOsDependentFix] == YES) && ([app parenSymbolFix] == YES) &&
298                 ((loc = [dayOfWeek rangeOfString:us].location) != NSNotFound) &&
299                 ([app someParenSymbolNotFix] == NO || (loc != 11 && loc != 13 && loc != 16))) {
300                 return TRUE;
301             }
302         }
303     }
304     return FALSE;
305 }
306
307 void fixSubjectOnLoad(id self)
308 {
309     NSMutableString *substr = [NSMutableString stringWithString:[[self backEnd] subject]];
310     
311     NSInteger i;
312     for (i = 0; i < [substr length]; i++) {
313         NSString *us = [substr substringWithRange:NSMakeRange(i, 1)];
314         unichar   uc = [us characterAtIndex:0];
315         NSInteger loc;
316         
317         switch (uc) {
318             case 0x2015: // ―
319                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"—"];
320                 break;
321             case 0xff5e: // ~
322                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"〜"];
323                 break;
324             case 0xff0d: // -
325                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"−"];
326                 break;
327             case 0x2225: // ∥
328                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"‖"];
329                 break;
330             case 0xffe0: // ¢
331                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"¢"];
332                 break;
333             case 0xffe1: // £
334                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"£"];
335                 break;
336             case 0xffe2: // ¬
337                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"¬"];
338                 break;
339             default:
340                 if ([us canBeConvertedToEncoding:NSISO2022JPStringEncoding] != YES) {
341                     if (([app isOsDependentFix] == YES) && ([app parenSymbolFix] == YES) &&
342                         ((loc = [dayOfWeek rangeOfString:us].location) != NSNotFound) &&
343                         ([app someParenSymbolNotFix] == NO || (loc != 11 && loc != 13 && loc != 16))) {
344                         [substr replaceCharactersInRange:NSMakeRange(i, 1)
345                                               withString:[circledNumbers substringWithRange:NSMakeRange(loc, 1)]];
346                     }
347                 }
348                 break;
349         }
350     }
351     [[(LetterFix *)self backEnd] setSubject:substr];
352     [[self _windowLF] setTitle:substr];
353     
354     id headers = [self headersEditor];
355     NSTextField *subjectField = nil;
356     object_getInstanceVariable(headers, "_subjectField", (void **)&subjectField);
357     if (subjectField != nil)
358         [subjectField setStringValue:substr];
359 }
360
361 void fixHeader(id self)
362 {
363     NSMutableString *substr = [NSMutableString stringWithString:[[self backEnd] subject]];
364     
365     NSInteger i;
366     for (i = 0; i < [substr length]; i++) {
367         NSString *us = [substr substringWithRange:NSMakeRange(i, 1)];
368         unichar   uc = [us characterAtIndex:0];
369         NSString *ns;
370         NSInteger loc;
371         
372         switch (uc) {
373             case 0x2015: // ―
374                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"—"];
375                 break;
376             case 0xff5e: // ~
377                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"〜"];
378                 break;
379             case 0xff0d: // -
380                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"−"];
381                 break;
382             case 0x2225: // ∥
383                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"‖"];
384                 break;
385             case 0xffe0: // ¢
386                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"¢"];
387                 break;
388             case 0xffe1: // £
389                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"£"];
390                 break;
391             case 0xffe2: // ¬
392                 [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:@"¬"];
393                 break;
394             default:
395                 if ([us canBeConvertedToEncoding:NSISO2022JPStringEncoding] != YES) {
396                     if ((0x2460 <= uc && uc <= 0x2473) && ([app isOsDependentFix]==YES)) {       // ①〜⑳ -> (1)〜(20)
397                         ns = subOfCircledNum[uc-0x2460];
398                     } else if ((0x2160 <= uc && uc <= 0x2169) && ([app isOsDependentFix]==YES)) {// Ⅰ〜Ⅹ -> I〜X
399                         ns = roman[uc-0x2160];
400                     } else if (((loc = [symbols rangeOfString:us].location) != NSNotFound)
401                                && ([app isOsDependentFix]==YES)) {
402                         ns = dep[loc];
403                     } else {
404                         ns = @"〓";
405                     }
406                     [substr replaceCharactersInRange:NSMakeRange(i, 1) withString:ns];
407                     if (1 < [ns length]) {
408                         i += [ns length] - 1;
409                     }
410                 }
411                 break;
412         }
413     }
414     [[(LetterFix *)self backEnd] setSubject:substr];
415     [[self _windowLF] setTitle:substr];
416     
417     id headers = [self headersEditor];
418     NSTextField *subjectField = nil;
419     object_getInstanceVariable(headers, "_subjectField", (void **)&subjectField);
420     if (subjectField != nil)
421         [subjectField setStringValue:substr];
422 }
423
424 BOOL _LF_IMP_isLoaded(id self, SEL _cmd)
425 {
426     BOOL result = [self _LF_isLoaded]; // call swizzled(original) method
427     if ([app isActive] == NO) {
428         return result;
429     }
430     if (result == NO) {
431         [khash removeObject:self];
432         return result;
433     }
434     if ([khash containsObject:self] == YES) return result;
435     
436     @try {
437         id composeView = [self webView];
438         if ((composeView==NULL) || ([(WebView *)composeView isLoading]==YES) || ([(WebView *)composeView isEditable]==NO)) {
439             //[self performSelector:@selector(isLoaded) withObject:nil afterDelay:2];
440             return result;
441         }
442         
443         //id backend   = [self backEnd];
444         //backendSetPreferredEncoding(backend, LF_Encoding_ISO2022JP);
445         [khash addObject:self];
446         
447         switch ([self messageType]) {
448             case 1: // 返信
449             case 2: // 全員に返信
450             case 3: // 転送
451             case 4: // 下書きを開く
452             case 7: // リダイレクト
453             case 8: // 差出人に返信
454                 break;
455             case 5: // 新規メッセージ
456             case 14:// 添付ファイルとして返信
457                 return result;
458             default:
459                 NSLog(@"Unknown MessageType: %d", [self messageType]);
460                 return result;
461         }
462         
463         if (0 < fixLetter(self, [self webView], TRUE, TRUE) || checkSubjectOnLoad(self)) {
464             if ([app operationAtOpen] == 0) {
465                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
466                 [alert addButtonWithTitle:@"変換"];
467                 NSButton *button = [alert addButtonWithTitle:@"変換しない"];
468                 button.keyEquivalent = @"\x1b";
469                 [alert setShowsSuppressionButton:TRUE];
470                 [alert setMessageText:@"編集前にメッセージを変換しますか?"];
471                 [alert setInformativeText:@"このメッセージには ISO 2022-JP でエンコードできない文字が含まれています。"];
472                 [alert setAlertStyle:NSInformationalAlertStyle];
473                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
474                     [alert beginSheetModalForWindow:[self _windowLF] completionHandler:^(NSModalResponse returnCode){
475                         if (returnCode == NSAlertFirstButtonReturn) {
476                             fixSubjectOnLoad(self);
477                             if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
478                                 fixLetter(self, [self webView], FALSE, TRUE);
479                             } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
480                                        [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
481                                 fixLetter(self, [self composeWebView], FALSE, TRUE);
482                             }
483                         } else if (returnCode == NSAlertSecondButtonReturn) {
484                         }
485                         if ([[alert suppressionButton] state] == NSOnState) {
486                             if (returnCode == NSAlertFirstButtonReturn) {
487                                 [app setOperationAtOpen:1];
488                             } else if (returnCode == NSAlertSecondButtonReturn) {
489                                 [app setOperationAtOpen:2];
490                             }
491                         }
492                     }];
493                 });
494             } else if ([app operationAtOpen] == 1) {
495                 fixSubjectOnLoad(self);
496                 fixLetter(self, [self webView], FALSE, TRUE);
497                 [[self backEnd] setHasChanges:FALSE];
498             }
499         }
500     }
501     @catch (NSException * exception) {
502         NSLog(@"LetterFix: caught %@ %@", [exception name], [exception reason]);
503     }
504     
505     return result;
506 }
507
508 void _LF_IMP_finishLoadingEditor(id self, SEL _cmd)
509 {
510     [self _LF_finishLoadingEditor];
511
512     if ([app isActive] == NO) {
513         return;
514     }
515
516     @try {
517         id composeView = [self composeWebView];
518         if ((composeView==NULL) || ([(WebView *)composeView isLoading]==YES) || ([(WebView *)composeView isEditable]==NO)) {
519             NSLog(@"composeView NULL!!");
520             return;
521         }
522         
523         switch ([self messageType]) {
524             case 1: // 返信
525             case 2: // 全員に返信
526             case 3: // 転送
527             case 4: // 下書きを開く
528             case 7: // リダイレクト
529             case 8: // 差出人に返信
530                 break;
531             case 5: // 新規メッセージ
532             case 14:// 添付ファイルとして返信
533                 return;
534             default:
535                 NSLog(@"Unknown MessageType: %d", [self messageType]);
536                 return;
537         }
538         
539         if (0 < fixLetter(self, composeView, TRUE, TRUE) || checkSubjectOnLoad(self)) {
540             if ([app operationAtOpen] == 0) {
541                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
542                 [alert addButtonWithTitle:@"変換"];
543                 NSButton *button = [alert addButtonWithTitle:@"変換しない"];
544                 button.keyEquivalent = @"\x1b";
545                 [alert setShowsSuppressionButton:TRUE];
546                 [alert setMessageText:@"編集前にメッセージを変換しますか?"];
547                 [alert setInformativeText:@"このメッセージには ISO 2022-JP でエンコードできない文字が含まれています。"];
548                 [alert setAlertStyle:NSInformationalAlertStyle];
549                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
550                     [alert beginSheetModalForWindow:[self _windowLF] completionHandler:^(NSModalResponse returnCode){
551                         if (returnCode == NSAlertFirstButtonReturn) {
552                             fixSubjectOnLoad(self);
553                             if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
554                                 [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
555                                 fixLetter(self, [self composeWebView], FALSE, TRUE);
556                             }
557                         } else if (returnCode == NSAlertSecondButtonReturn) {
558                         }
559                         if ([[alert suppressionButton] state] == NSOnState) {
560                             if (returnCode == NSAlertFirstButtonReturn) {
561                                 [app setOperationAtOpen:1];
562                             } else if (returnCode == NSAlertSecondButtonReturn) {
563                                 [app setOperationAtOpen:2];
564                             }
565                         }
566                     }];
567                 });
568             } else if ([app operationAtOpen] == 1) {
569                 fixSubjectOnLoad(self);
570                 fixLetter(self, composeView, FALSE, TRUE);
571                 [[self backEnd] setHasChanges:FALSE];
572             }
573         }
574     }
575     @catch (NSException * exception) {
576         NSLog(@"LetterFix: caught %@ %@", [exception name], [exception reason]);
577     }
578     
579     return;
580 }
581
582 void _LF_IMP_send_(id self, SEL _cmd, id arg1)
583 {
584     if ([app isActive] == NO) {
585         [self _LF_send: arg1];
586         return;
587     }
588
589     id composeView;
590     if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
591         composeView = [self webView];
592     } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
593                [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
594         composeView = [self composeWebView];
595     } else {
596         [self _LF_send: arg1];
597         return;
598     }
599     
600     backendSetPreferredEncoding([self backEnd], LF_Encoding_ISO2022JP);
601     if (0 < fixLetter(self, composeView, TRUE, FALSE)) {
602         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
603         [alert addButtonWithTitle:@"変換して送信"];
604         NSButton *button = [alert addButtonWithTitle:@"キャンセル"];
605         button.keyEquivalent = @"\x1b";
606         [alert addButtonWithTitle:@"変換せずに送信"];
607         [alert addButtonWithTitle:@"変換のみ"];
608         [alert setMessageText:@"メッセージを変換して送信しますか?"];
609         [alert setInformativeText:@"送信しようとしているメッセージには ISO 2022-JP でエンコードできない文字が含まれています。"];
610         [alert setAlertStyle:NSInformationalAlertStyle];
611         [alert beginSheetModalForWindow:[self _windowLF] completionHandler:^(NSModalResponse returnCode) {
612             if (returnCode == NSAlertFirstButtonReturn) {
613                 if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
614                     fixLetter(self, [self webView], FALSE, FALSE);
615                 } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
616                            [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
617                     fixLetter(self, [self composeWebView], FALSE, FALSE);
618                 }
619                 if (backendGetPreferredEncoding([self backEnd]) != LF_Encoding_ISO2022JP) backendSetPreferredEncoding([self backEnd], LF_Encoding_ISO2022JP);
620                 [[alert window] orderOut:arg1];
621                 if (([app isCheckSubject] != NO) && checkSubject(self)) _LF_alertSubject(self, _cmd, arg1);
622                 else [self _LF_send: arg1];
623             } else if (returnCode == NSAlertSecondButtonReturn) {
624                 // Cancel
625             } else if (returnCode == NSAlertThirdButtonReturn) { // 変換せずに送信
626                 if (backendGetPreferredEncoding([self backEnd]) == LF_Encoding_ISO2022JP) {
627                     backendSetPreferredEncoding([self backEnd], LF_Encoding_Auto); // ISO2022JPのままだとISO2022JP-2とかで送ってしまう
628                 }
629                 [[alert window] orderOut:arg1];
630                 [self _LF_send: arg1];
631             } else if (returnCode == (NSAlertThirdButtonReturn + 1)) { // 変換のみ
632                 if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
633                     fixLetter(self, [self webView], FALSE, FALSE);
634                 } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
635                            [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
636                     fixLetter(self, [self composeWebView], FALSE, FALSE);
637                 }
638                 if (backendGetPreferredEncoding([self backEnd]) != LF_Encoding_ISO2022JP) backendSetPreferredEncoding([self backEnd], LF_Encoding_ISO2022JP);
639             }
640         }];
641     } else if (([app isCheckSubject] != NO) && checkSubject(self)) {
642         _LF_alertSubject(self, _cmd, arg1);
643     } else {
644         [self _LF_send: arg1];
645     }
646 }
647
648 void _LF_alertSubject(id self, SEL _cmd, id arg1)
649 {
650     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
651     [alert addButtonWithTitle:@"変換して送信"];
652     NSButton *button = [alert addButtonWithTitle:@"キャンセル"];
653     button.keyEquivalent = @"\x1b";
654     [alert addButtonWithTitle:@"変換せずに送信"];
655     [alert addButtonWithTitle:@"変換のみ"];
656     [alert setMessageText:@"件名を変換して送信しますか?"];
657     [alert setInformativeText:@"送信するメッセージの件名には ISO 2022-JP でエンコードできない文字が含まれています。"];
658     [alert setAlertStyle:NSInformationalAlertStyle];
659     [alert beginSheetModalForWindow:[self _windowLF] completionHandler:^(NSModalResponse returnCode) {
660         if (returnCode == NSAlertFirstButtonReturn) {
661             fixHeader(self);
662             [[alert window] orderOut:arg1];
663             [self _LF_send: arg1];
664         } else if (returnCode == NSAlertSecondButtonReturn) {
665             // Cancel
666         } else if (returnCode == NSAlertThirdButtonReturn) {
667             [[alert window] orderOut:arg1];
668             [self _LF_send: arg1];
669         } else if (returnCode == (NSAlertThirdButtonReturn + 1)) {
670             fixHeader(self);
671         }
672     }];
673 }
674
675 void _LF_IMP_saveDocument_(id self, SEL _cmd, id arg1)
676 {
677     id composeView;
678     if ([app willCheckOnSave] && ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite)) {
679         composeView = [self webView];
680     } else if ([app willCheckOnSave] &&
681                ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
682                 [app ver] == LF_HighSierra|| [app ver] == LF_Mojave)) {
683         composeView = [self composeWebView];
684     } else {
685         [self _LF_saveDocument: arg1];
686         return;
687     }
688     
689     if (([app isActive] != NO) && ([check_at_save containsObject:self]==NO) &&(0 < fixLetter(self, composeView, TRUE, FALSE))) {
690         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
691         [alert addButtonWithTitle:@"変換"];
692         NSButton *button = [alert addButtonWithTitle:@"変換しない"];
693         button.keyEquivalent = @"\x1b";
694         [alert setMessageText:@"保存前にメッセージを変換しますか?"];
695         [alert setInformativeText:@"このメッセージには ISO 2022-JP でエンコードできない文字が含まれています。\n"
696          @"変換しないを選択した場合、メールの送信またはウインドウを開き直すまでエンコーディングの確認を行いません。"];
697         [alert setAlertStyle:NSWarningAlertStyle];
698         [alert beginSheetModalForWindow:[self _windowLF] completionHandler:^(NSModalResponse returnCode) {
699             if (returnCode == NSAlertFirstButtonReturn) {
700                 if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
701                     fixLetter(self, [self webView], FALSE, FALSE);
702                 } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
703                            [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
704                     fixLetter(self, [self composeWebView], FALSE, FALSE);
705                 }
706             } else if (returnCode == NSAlertSecondButtonReturn) {
707                 [check_at_save addObject:self];
708             }
709             [[alert window] orderOut:arg1];
710             [self _LF_saveDocument: (id)arg1];
711         }];
712     } else {
713         [self _LF_saveDocument: arg1];
714     }
715 }
716
717 void _LF_IMP_animationCompleted(id self, SEL _cmd)
718 {
719     [self performSelector:@selector(isLoaded) withObject:nil afterDelay:0.5];
720     [self _LF_animationCompleted];
721 }
722
723 NSWindow *_LF_IMP_window(id self, SEL _cmd)
724 {
725     if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite || [app ver] == LF_ElCapitan ) {
726         return [self window];
727     } else if ([app ver] == LF_Sierra || [app ver] == LF_HighSierra||
728                [app ver] == LF_Mojave) {
729         return [[self view] window];
730     }
731     return nil;
732 }
733
734 #define MVMailBundle        (NSClassFromString(@"MVMailBundle"))
735
736 @implementation LetterFix
737 + (void) initialize
738 {
739     class_setSuperclass([self class], MVMailBundle); // depricated function 10.5対応のためこのワーニングだけは消せません
740     [super initialize];
741     [self registerBundle];
742     
743     khash = [[NSMutableArray alloc] initWithCapacity:1];
744     check_at_save = [[NSMutableArray alloc] initWithCapacity:1];
745     
746     app = [[LFApp alloc] init];
747     
748     Class editorClass = nil;
749     if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
750         editorClass = NSClassFromString(@"DocumentEditor");
751     } else if ([app ver] == LF_ElCapitan || [app ver] == LF_Sierra ||
752                [app ver] == LF_HighSierra|| [app ver] == LF_Mojave) {
753         editorClass = NSClassFromString(@"ComposeViewController");
754     } else {
755         return;
756     }
757     class_addMethod(editorClass, @selector(_windowLF), (IMP)_LF_IMP_window, "@@:");
758     
759     //
760     // swizzling method
761     //
762     if ([app ver] == LF_Mavericks || [app ver] == LF_Yosemite) {
763         swizzlingMethod(editorClass, @selector(isLoaded), @selector(_LF_isLoaded), (IMP)_LF_IMP_isLoaded);
764         swizzlingMethod(editorClass, @selector(_animationCompleted), @selector(_LF_animationCompleted), (IMP)_LF_IMP_animationCompleted);
765     } else if ([app ver] == LF_ElCapitan) {
766         swizzlingMethod(editorClass, @selector(finishLoadingEditor), @selector(_LF_finishLoadingEditor), (IMP)_LF_IMP_finishLoadingEditor);
767     } else if ([app ver] == LF_Sierra || [app ver] == LF_HighSierra || [app ver] == LF_Mojave) {
768         swizzlingMethod(editorClass, @selector(_finishLoadingEditor), @selector(_LF_finishLoadingEditor), (IMP)_LF_IMP_finishLoadingEditor);
769     }
770     swizzlingMethod(editorClass, @selector(saveDocument:), @selector(_LF_saveDocument:), (IMP)_LF_IMP_saveDocument_);
771     swizzlingMethod(editorClass, @selector(send:), @selector(_LF_send:), (IMP)_LF_IMP_send_);
772     //
773     // end of swizzling method
774     //
775     
776     NSLog(@"LetterFix Plugin (version %s/%ld) is registered.", LETTERFIX_VERSION, (unsigned long int)[app verm]);
777 }
778 @end