File: | /boot/home/haiku/haiku/src/apps/mail/Content.cpp |
Location: | line 3154, column 5 |
Description: | Potential leak of memory pointed to by 'target' |
1 | /* | |||
2 | Open Tracker License | |||
3 | ||||
4 | Terms and Conditions | |||
5 | ||||
6 | Copyright (c) 1991-2001, Be Incorporated. All rights reserved. | |||
7 | ||||
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
9 | this software and associated documentation files (the "Software"), to deal in | |||
10 | the Software without restriction, including without limitation the rights to | |||
11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||
12 | of the Software, and to permit persons to whom the Software is furnished to do | |||
13 | so, subject to the following conditions: | |||
14 | ||||
15 | The above copyright notice and this permission notice applies to all licensees | |||
16 | and shall be included in all copies or substantial portions of the Software. | |||
17 | ||||
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, | |||
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |||
21 | BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN | |||
22 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN | |||
23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
24 | ||||
25 | Except as contained in this notice, the name of Be Incorporated shall not be | |||
26 | used in advertising or otherwise to promote the sale, use or other dealings in | |||
27 | this Software without prior written authorization from Be Incorporated. | |||
28 | ||||
29 | BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or | |||
30 | registered trademarks of Be Incorporated in the United States and other | |||
31 | countries. Other brand product names are registered trademarks or trademarks | |||
32 | of their respective holders. All rights reserved. | |||
33 | */ | |||
34 | ||||
35 | ||||
36 | #include <stdlib.h> | |||
37 | #include <string.h> | |||
38 | #include <stdio.h> | |||
39 | #include <ctype.h> | |||
40 | ||||
41 | #include <Alert.h> | |||
42 | #include <Beep.h> | |||
43 | #include <Clipboard.h> | |||
44 | #include <Debug.h> | |||
45 | #include <E-mail.h> | |||
46 | #include <Input.h> | |||
47 | #include <Locale.h> | |||
48 | #include <MenuItem.h> | |||
49 | #include <Mime.h> | |||
50 | #include <NodeInfo.h> | |||
51 | #include <NodeMonitor.h> | |||
52 | #include <Path.h> | |||
53 | #include <PopUpMenu.h> | |||
54 | #include <Region.h> | |||
55 | #include <Roster.h> | |||
56 | #include <ScrollView.h> | |||
57 | #include <TextView.h> | |||
58 | #include <UTF8.h> | |||
59 | ||||
60 | #include <MailMessage.h> | |||
61 | #include <MailAttachment.h> | |||
62 | #include <mail_util.h> | |||
63 | ||||
64 | #include "MailApp.h" | |||
65 | #include "MailSupport.h" | |||
66 | #include "MailWindow.h" | |||
67 | #include "Messages.h" | |||
68 | #include "Content.h" | |||
69 | #include "Utilities.h" | |||
70 | #include "FieldMsg.h" | |||
71 | #include "Words.h" | |||
72 | ||||
73 | ||||
74 | #define DEBUG_SPELLCHECK0 0 | |||
75 | #if DEBUG_SPELLCHECK0 | |||
76 | # define DSPELL(x); x | |||
77 | #else | |||
78 | # define DSPELL(x); ; | |||
79 | #endif | |||
80 | ||||
81 | ||||
82 | #define B_TRANSLATION_CONTEXT"Mail" "Mail" | |||
83 | ||||
84 | ||||
85 | const rgb_color kNormalTextColor = {0, 0, 0, 255}; | |||
86 | const rgb_color kSpellTextColor = {255, 0, 0, 255}; | |||
87 | const rgb_color kHyperLinkColor = {0, 0, 255, 255}; | |||
88 | const rgb_color kHeaderColor = {72, 72, 72, 255}; | |||
89 | ||||
90 | const rgb_color kQuoteColors[] = { | |||
91 | {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color (blue) | |||
92 | {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color (green) | |||
93 | {0x80, 0, 0, 0} // 2nd, ... (red) | |||
94 | }; | |||
95 | const int32 kNumQuoteColors = 3; | |||
96 | ||||
97 | const rgb_color kDiffColors[] = { | |||
98 | {0xb0, 0, 0, 0}, // '-', red | |||
99 | {0, 0x90, 0, 0}, // '+', green | |||
100 | {0x6a, 0x6a, 0x6a, 0} // '@@', dark grey | |||
101 | }; | |||
102 | ||||
103 | void Unicode2UTF8(int32 c, char **out); | |||
104 | ||||
105 | ||||
106 | inline bool | |||
107 | IsInitialUTF8Byte(uchar b) | |||
108 | { | |||
109 | return ((b & 0xC0) != 0x80); | |||
110 | } | |||
111 | ||||
112 | ||||
113 | void | |||
114 | Unicode2UTF8(int32 c, char **out) | |||
115 | { | |||
116 | char *s = *out; | |||
117 | ||||
118 | ASSERT(c < 0x200000)(void)0; | |||
119 | ||||
120 | if (c < 0x80) | |||
121 | *(s++) = c; | |||
122 | else if (c < 0x800) { | |||
123 | *(s++) = 0xc0 | (c >> 6); | |||
124 | *(s++) = 0x80 | (c & 0x3f); | |||
125 | } else if (c < 0x10000) { | |||
126 | *(s++) = 0xe0 | (c >> 12); | |||
127 | *(s++) = 0x80 | ((c >> 6) & 0x3f); | |||
128 | *(s++) = 0x80 | (c & 0x3f); | |||
129 | } else if (c < 0x200000) { | |||
130 | *(s++) = 0xf0 | (c >> 18); | |||
131 | *(s++) = 0x80 | ((c >> 12) & 0x3f); | |||
132 | *(s++) = 0x80 | ((c >> 6) & 0x3f); | |||
133 | *(s++) = 0x80 | (c & 0x3f); | |||
134 | } | |||
135 | *out = s; | |||
136 | } | |||
137 | ||||
138 | ||||
139 | static bool | |||
140 | FilterHTMLTag(int32 &first, char **t, char *end) | |||
141 | { | |||
142 | const char *newlineTags[] = { | |||
143 | "br", "/p", "/div", "/table", "/tr", | |||
144 | NULL__null}; | |||
145 | ||||
146 | char *a = *t; | |||
147 | ||||
148 | // check for some common entities (in ISO-Latin-1) | |||
149 | if (first == '&') { | |||
150 | // filter out and convert decimal values | |||
151 | if (a[1] == '#' && sscanf(a + 2, "%" B_SCNd32"l" "d" ";", &first) == 1) { | |||
152 | t[0] += strchr(a, ';') - a; | |||
153 | return false; | |||
154 | } | |||
155 | ||||
156 | const struct { const char *name; int32 code; } entities[] = { | |||
157 | // this list is sorted alphabetically to be binary searchable | |||
158 | // the current implementation doesn't do this, though | |||
159 | ||||
160 | // "name" is the entity name, | |||
161 | // "code" is the corresponding unicode | |||
162 | {"AElig;", 0x00c6}, | |||
163 | {"Aacute;", 0x00c1}, | |||
164 | {"Acirc;", 0x00c2}, | |||
165 | {"Agrave;", 0x00c0}, | |||
166 | {"Aring;", 0x00c5}, | |||
167 | {"Atilde;", 0x00c3}, | |||
168 | {"Auml;", 0x00c4}, | |||
169 | {"Ccedil;", 0x00c7}, | |||
170 | {"Eacute;", 0x00c9}, | |||
171 | {"Ecirc;", 0x00ca}, | |||
172 | {"Egrave;", 0x00c8}, | |||
173 | {"Euml;", 0x00cb}, | |||
174 | {"Iacute;", 0x00cd}, | |||
175 | {"Icirc;", 0x00ce}, | |||
176 | {"Igrave;", 0x00cc}, | |||
177 | {"Iuml;", 0x00cf}, | |||
178 | {"Ntilde;", 0x00d1}, | |||
179 | {"Oacute;", 0x00d3}, | |||
180 | {"Ocirc;", 0x00d4}, | |||
181 | {"Ograve;", 0x00d2}, | |||
182 | {"Ouml;", 0x00d6}, | |||
183 | {"Uacute;", 0x00da}, | |||
184 | {"Ucirc;", 0x00db}, | |||
185 | {"Ugrave;", 0x00d9}, | |||
186 | {"Uuml;", 0x00dc}, | |||
187 | {"aacute;", 0x00e1}, | |||
188 | {"acirc;", 0x00e2}, | |||
189 | {"aelig;", 0x00e6}, | |||
190 | {"agrave;", 0x00e0}, | |||
191 | {"amp;", '&'}, | |||
192 | {"aring;", 0x00e5}, | |||
193 | {"atilde;", 0x00e3}, | |||
194 | {"auml;", 0x00e4}, | |||
195 | {"ccedil;", 0x00e7}, | |||
196 | {"copy;", 0x00a9}, | |||
197 | {"eacute;", 0x00e9}, | |||
198 | {"ecirc;", 0x00ea}, | |||
199 | {"egrave;", 0x00e8}, | |||
200 | {"euml;", 0x00eb}, | |||
201 | {"gt;", '>'}, | |||
202 | {"iacute;", 0x00ed}, | |||
203 | {"icirc;", 0x00ee}, | |||
204 | {"igrave;", 0x00ec}, | |||
205 | {"iuml;", 0x00ef}, | |||
206 | {"lt;", '<'}, | |||
207 | {"nbsp;", ' '}, | |||
208 | {"ntilde;", 0x00f1}, | |||
209 | {"oacute;", 0x00f3}, | |||
210 | {"ocirc;", 0x00f4}, | |||
211 | {"ograve;", 0x00f2}, | |||
212 | {"ouml;", 0x00f6}, | |||
213 | {"quot;", '"'}, | |||
214 | {"szlig;", 0x00f6}, | |||
215 | {"uacute;", 0x00fa}, | |||
216 | {"ucirc;", 0x00fb}, | |||
217 | {"ugrave;", 0x00f9}, | |||
218 | {"uuml;", 0x00fc}, | |||
219 | {NULL__null, 0} | |||
220 | }; | |||
221 | ||||
222 | for (int32 i = 0; entities[i].name; i++) { | |||
223 | // entities are case-sensitive | |||
224 | int32 length = strlen(entities[i].name); | |||
225 | if (!strncmp(a + 1, entities[i].name, length)) { | |||
226 | t[0] += length; // note that the '&' is included here | |||
227 | first = entities[i].code; | |||
228 | return false; | |||
229 | } | |||
230 | } | |||
231 | } | |||
232 | ||||
233 | // no tag to filter | |||
234 | if (first != '<') | |||
235 | return false; | |||
236 | ||||
237 | a++; | |||
238 | ||||
239 | // is the tag one of the newline tags? | |||
240 | ||||
241 | bool newline = false; | |||
242 | for (int i = 0; newlineTags[i]; i++) { | |||
243 | int length = strlen(newlineTags[i]); | |||
244 | if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])(__ctype_b[(int)((a[length]))] & (unsigned short int)_ISalnum )) { | |||
245 | newline = true; | |||
246 | break; | |||
247 | } | |||
248 | } | |||
249 | ||||
250 | // oh, it's not, so skip it! | |||
251 | ||||
252 | if (!strncasecmp(a, "head", 4)) { // skip "head" completely | |||
253 | for (; a[0] && a < end; a++) { | |||
254 | // Find the end of the HEAD section, or the start of the BODY, | |||
255 | // which happens for some malformed spam. | |||
256 | if (strncasecmp (a, "</head", 6) == 0 || | |||
257 | strncasecmp (a, "<body", 5) == 0) | |||
258 | break; | |||
259 | } | |||
260 | } | |||
261 | ||||
262 | // skip until tag end | |||
263 | while (a[0] && a[0] != '>' && a < end) | |||
264 | a++; | |||
265 | ||||
266 | t[0] = a; | |||
267 | ||||
268 | if (newline) { | |||
269 | first = '\n'; | |||
270 | return false; | |||
271 | } | |||
272 | ||||
273 | return true; | |||
274 | } | |||
275 | ||||
276 | ||||
277 | /** Returns the type and length of the URL in the string if it is one. | |||
278 | * If the "url" string is specified, it will fill it with the complete | |||
279 | * URL that might differ from the one in the text itself (i.e. because | |||
280 | * of an prepended "http://"). | |||
281 | */ | |||
282 | ||||
283 | static uint8 | |||
284 | CheckForURL(const char *string, size_t &urlLength, BString *url = NULL__null) | |||
285 | { | |||
286 | const char *urlPrefixes[] = { | |||
287 | "http://", | |||
288 | "ftp://", | |||
289 | "shttp://", | |||
290 | "https://", | |||
291 | "finger://", | |||
292 | "telnet://", | |||
293 | "gopher://", | |||
294 | "news://", | |||
295 | "nntp://", | |||
296 | "file://", | |||
297 | NULL__null | |||
298 | }; | |||
299 | ||||
300 | // | |||
301 | // Search for URL prefix | |||
302 | // | |||
303 | uint8 type = 0; | |||
304 | for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) { | |||
305 | if (!cistrncmp(string, *prefix, strlen(*prefix))) { | |||
306 | type = TYPE_URL; | |||
307 | break; | |||
308 | } | |||
309 | } | |||
310 | ||||
311 | // | |||
312 | // Not a URL? check for "mailto:" or "www." | |||
313 | // | |||
314 | if (type == 0 && !cistrncmp(string, "mailto:", 7)) | |||
315 | type = TYPE_MAILTO; | |||
316 | if (type == 0 && !strncmp(string, "www.", 4)) { | |||
317 | // this type will be special cased later (and a http:// is added | |||
318 | // for the enclosure address) | |||
319 | type = TYPE_URL; | |||
320 | } | |||
321 | if (type == 0) { | |||
322 | // check for valid eMail addresses | |||
323 | const char *at = strchr(string, '@'); | |||
324 | if (at) { | |||
325 | const char *pos = string; | |||
326 | bool readName = false; | |||
327 | for (; pos < at; pos++) { | |||
328 | // ToDo: are these all allowed characters? | |||
329 | if (!isalnum(pos[0])(__ctype_b[(int)((pos[0]))] & (unsigned short int)_ISalnum ) && pos[0] != '_' && pos[0] != '.' && pos[0] != '-') | |||
330 | break; | |||
331 | ||||
332 | readName = true; | |||
333 | } | |||
334 | if (pos == at && readName) | |||
335 | type = TYPE_MAILTO; | |||
336 | } | |||
337 | } | |||
338 | ||||
339 | if (type == 0) | |||
340 | return 0; | |||
341 | ||||
342 | int32 index = strcspn(string, " \t<>)\"\\,\r\n"); | |||
343 | ||||
344 | // filter out some punctuation marks if they are the last character | |||
345 | char suffix = string[index - 1]; | |||
346 | if (suffix == '.' | |||
347 | || suffix == ',' | |||
348 | || suffix == '?' | |||
349 | || suffix == '!' | |||
350 | || suffix == ':' | |||
351 | || suffix == ';') | |||
352 | index--; | |||
353 | ||||
354 | if (type == TYPE_URL) { | |||
355 | char *parenthesis = NULL__null; | |||
356 | ||||
357 | // filter out a trailing ')' if there is no left parenthesis before | |||
358 | if (string[index - 1] == ')') { | |||
359 | char *parenthesis = strchr(string, '('); | |||
360 | if (parenthesis == NULL__null || parenthesis > string + index) | |||
361 | index--; | |||
362 | } | |||
363 | ||||
364 | // filter out a trailing ']' if there is no left bracket before | |||
365 | if (parenthesis == NULL__null && string[index - 1] == ']') { | |||
366 | char *parenthesis = strchr(string, '['); | |||
367 | if (parenthesis == NULL__null || parenthesis > string + index) | |||
368 | index--; | |||
369 | } | |||
370 | } | |||
371 | ||||
372 | if (url != NULL__null) { | |||
373 | // copy the address to the specified string | |||
374 | if (type == TYPE_URL && string[0] == 'w') { | |||
375 | // URL starts with "www.", so add the protocol to it | |||
376 | url->SetTo("http://"); | |||
377 | url->Append(string, index); | |||
378 | } else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) { | |||
379 | // eMail address had no "mailto:" prefix | |||
380 | url->SetTo("mailto:"); | |||
381 | url->Append(string, index); | |||
382 | } else | |||
383 | url->SetTo(string, index); | |||
384 | } | |||
385 | urlLength = index; | |||
386 | ||||
387 | return type; | |||
388 | } | |||
389 | ||||
390 | ||||
391 | static void | |||
392 | CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength) | |||
393 | { | |||
394 | // count qoute level (to be able to wrap quotes correctly) | |||
395 | ||||
396 | const char *quote = QUOTE"> "; | |||
397 | int32 level = 0; | |||
398 | for (size_t i = 0; i < length; i++) { | |||
399 | if (text[i] == quote[0]) | |||
400 | level++; | |||
401 | else if (text[i] != ' ' && text[i] != '\t') | |||
402 | break; | |||
403 | } | |||
404 | ||||
405 | // if there are too much quotes, try to preserve the quote color level | |||
406 | if (level > 10) | |||
407 | level = kNumQuoteColors * 3 + (level % kNumQuoteColors); | |||
408 | ||||
409 | // copy the quotes to outText | |||
410 | ||||
411 | const int32 quoteLength = strlen(QUOTE"> "); | |||
412 | outLength = 0; | |||
413 | while (level-- > 0) { | |||
414 | strcpy(outText + outLength, QUOTE"> "); | |||
415 | outLength += quoteLength; | |||
416 | } | |||
417 | } | |||
418 | ||||
419 | ||||
420 | int32 | |||
421 | diff_mode(char c) | |||
422 | { | |||
423 | if (c == '+') | |||
424 | return 2; | |||
425 | if (c == '-') | |||
426 | return 1; | |||
427 | if (c == '@') | |||
428 | return 3; | |||
429 | if (c == ' ') | |||
430 | return 0; | |||
431 | ||||
432 | // everything else ends the diff mode | |||
433 | return -1; | |||
434 | } | |||
435 | ||||
436 | ||||
437 | bool | |||
438 | is_quote_char(char c) | |||
439 | { | |||
440 | return c == '>' || c == '|'; | |||
441 | } | |||
442 | ||||
443 | ||||
444 | /*! Fills the specified text_run_array with the correct values for the | |||
445 | specified text. | |||
446 | If "view" is NULL, it will assume that "line" lies on a line break, | |||
447 | if not, it will correctly retrieve the number of quotes the current | |||
448 | line already has. | |||
449 | */ | |||
450 | void | |||
451 | FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line, | |||
452 | int32 length, const BFont& font, text_run_array* style, int32 maxStyles) | |||
453 | { | |||
454 | text_run* runs = style->runs; | |||
455 | int32 index = style->count; | |||
456 | bool begin; | |||
457 | int32 pos = 0; | |||
458 | int32 diffMode = 0; | |||
459 | bool inDiff = false; | |||
460 | bool wasDiff = false; | |||
461 | int32 level = 0; | |||
462 | ||||
463 | // get index to the beginning of the current line | |||
464 | ||||
465 | if (context != NULL__null) { | |||
466 | level = context->level; | |||
467 | diffMode = context->diff_mode; | |||
468 | begin = context->begin; | |||
469 | inDiff = context->in_diff; | |||
470 | wasDiff = context->was_diff; | |||
471 | } else if (view != NULL__null) { | |||
472 | int32 start, end; | |||
473 | view->GetSelection(&end, &end); | |||
474 | ||||
475 | begin = view->TextLength() == 0 | |||
476 | || view->ByteAt(view->TextLength() - 1) == '\n'; | |||
477 | ||||
478 | // the following line works only reliable when text wrapping is set to | |||
479 | // off; so the complicated version actually used here is necessary: | |||
480 | // start = view->OffsetAt(view->CurrentLine()); | |||
481 | ||||
482 | const char *text = view->Text(); | |||
483 | ||||
484 | if (!begin) { | |||
485 | // if the text is not the start of a new line, go back | |||
486 | // to the first character in the current line | |||
487 | for (start = end; start > 0; start--) { | |||
488 | if (text[start - 1] == '\n') | |||
489 | break; | |||
490 | } | |||
491 | } | |||
492 | ||||
493 | // get number of nested qoutes for current line | |||
494 | ||||
495 | if (!begin && start < end) { | |||
496 | begin = true; | |||
497 | // if there was no text in this line, there may come | |||
498 | // more nested quotes | |||
499 | ||||
500 | diffMode = diff_mode(text[start]); | |||
501 | if (diffMode == 0) { | |||
502 | for (int32 i = start; i < end; i++) { | |||
503 | if (is_quote_char(text[i])) | |||
504 | level++; | |||
505 | else if (text[i] != ' ' && text[i] != '\t') { | |||
506 | begin = false; | |||
507 | break; | |||
508 | } | |||
509 | } | |||
510 | } else | |||
511 | inDiff = true; | |||
512 | ||||
513 | if (begin) { | |||
514 | // skip leading spaces (tabs & newlines aren't allowed here) | |||
515 | while (line[pos] == ' ') | |||
516 | pos++; | |||
517 | } | |||
518 | } | |||
519 | } else | |||
520 | begin = true; | |||
521 | ||||
522 | // set styles for all qoute levels in the text to be inserted | |||
523 | ||||
524 | for (int32 pos = 0; pos < length;) { | |||
525 | int32 next; | |||
526 | if (begin && is_quote_char(line[pos])) { | |||
527 | begin = false; | |||
528 | ||||
529 | while (pos < length && line[pos] != '\n') { | |||
530 | // insert style for each quote level | |||
531 | level++; | |||
532 | ||||
533 | bool search = true; | |||
534 | for (next = pos + 1; next < length; next++) { | |||
535 | if ((search && is_quote_char(line[next])) | |||
536 | || line[next] == '\n') | |||
537 | break; | |||
538 | else if (search && line[next] != ' ' && line[next] != '\t') | |||
539 | search = false; | |||
540 | } | |||
541 | ||||
542 | runs[index].offset = pos; | |||
543 | runs[index].font = font; | |||
544 | runs[index].color = level > 0 | |||
545 | ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; | |||
546 | ||||
547 | pos = next; | |||
548 | if (++index >= maxStyles) | |||
549 | break; | |||
550 | } | |||
551 | } else { | |||
552 | if (begin) { | |||
553 | if (!inDiff) { | |||
554 | inDiff = !strncmp(&line[pos], "--- ", 4); | |||
555 | wasDiff = false; | |||
556 | } | |||
557 | if (inDiff) { | |||
558 | diffMode = diff_mode(line[pos]); | |||
559 | if (diffMode < 0) { | |||
560 | inDiff = false; | |||
561 | wasDiff = true; | |||
562 | } | |||
563 | } | |||
564 | } | |||
565 | ||||
566 | runs[index].offset = pos; | |||
567 | runs[index].font = font; | |||
568 | if (wasDiff) | |||
569 | runs[index].color = kDiffColors[diff_mode('@') - 1]; | |||
570 | else if (diffMode <= 0) { | |||
571 | runs[index].color = level > 0 | |||
572 | ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; | |||
573 | } else | |||
574 | runs[index].color = kDiffColors[diffMode - 1]; | |||
575 | ||||
576 | begin = false; | |||
577 | ||||
578 | for (next = pos; next < length; next++) { | |||
579 | if (line[next] == '\n') { | |||
580 | begin = true; | |||
581 | wasDiff = false; | |||
582 | break; | |||
583 | } | |||
584 | } | |||
585 | ||||
586 | pos = next; | |||
587 | index++; | |||
588 | } | |||
589 | ||||
590 | if (pos < length) | |||
591 | begin = line[pos] == '\n'; | |||
592 | ||||
593 | if (begin) { | |||
594 | pos++; | |||
595 | level = 0; | |||
596 | wasDiff = false; | |||
597 | ||||
598 | // skip one leading space (tabs & newlines aren't allowed here) | |||
599 | if (!inDiff && pos < length && line[pos] == ' ') | |||
600 | pos++; | |||
601 | } | |||
602 | ||||
603 | if (index >= maxStyles) | |||
604 | break; | |||
605 | } | |||
606 | style->count = index; | |||
607 | ||||
608 | if (context) { | |||
609 | // update context for next run | |||
610 | context->level = level; | |||
611 | context->diff_mode = diffMode; | |||
612 | context->begin = begin; | |||
613 | context->in_diff = inDiff; | |||
614 | context->was_diff = wasDiff; | |||
615 | } | |||
616 | } | |||
617 | ||||
618 | ||||
619 | // #pragma mark - | |||
620 | ||||
621 | ||||
622 | TextRunArray::TextRunArray(size_t entries) | |||
623 | : | |||
624 | fNumEntries(entries) | |||
625 | { | |||
626 | fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries); | |||
627 | if (fArray != NULL__null) | |||
628 | fArray->count = 0; | |||
629 | } | |||
630 | ||||
631 | ||||
632 | TextRunArray::~TextRunArray() | |||
633 | { | |||
634 | free(fArray); | |||
635 | } | |||
636 | ||||
637 | ||||
638 | //==================================================================== | |||
639 | // #pragma mark - | |||
640 | ||||
641 | ||||
642 | TContentView::TContentView(BRect rect, bool incoming, BFont* font, | |||
643 | bool showHeader, bool coloredQuotes) | |||
644 | : | |||
645 | BView(rect, "m_content", B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_), | |||
646 | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), | |||
647 | ||||
648 | fFocus(false), | |||
649 | fIncoming(incoming) | |||
650 | { | |||
651 | SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); | |||
652 | fOffset = 12; | |||
653 | ||||
654 | BRect r(rect); | |||
655 | r.OffsetTo(0, 0); | |||
656 | r.right -= B_V_SCROLL_BAR_WIDTH14.0f; | |||
657 | r.bottom -= B_H_SCROLL_BAR_HEIGHT14.0f; | |||
658 | r.top += 4; | |||
659 | BRect text(r); | |||
660 | text.OffsetTo(0, 0); | |||
661 | text.InsetBy(5, 5); | |||
662 | ||||
663 | fTextView = new TTextView(r, text, fIncoming, this, font, showHeader, | |||
664 | coloredQuotes); | |||
665 | BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_), 0, true, true); | |||
666 | AddChild(scroll); | |||
667 | } | |||
668 | ||||
669 | ||||
670 | void | |||
671 | TContentView::MessageReceived(BMessage *msg) | |||
672 | { | |||
673 | switch (msg->what) { | |||
674 | case CHANGE_FONT: | |||
675 | { | |||
676 | BFont *font; | |||
677 | msg->FindPointer("font", (void **)&font); | |||
678 | fTextView->UpdateFont(font); | |||
679 | fTextView->Invalidate(Bounds()); | |||
680 | break; | |||
681 | } | |||
682 | ||||
683 | case M_QUOTE: | |||
684 | { | |||
685 | int32 start, finish; | |||
686 | fTextView->GetSelection(&start, &finish); | |||
687 | fTextView->AddQuote(start, finish); | |||
688 | break; | |||
689 | } | |||
690 | case M_REMOVE_QUOTE: | |||
691 | { | |||
692 | int32 start, finish; | |||
693 | fTextView->GetSelection(&start, &finish); | |||
694 | fTextView->RemoveQuote(start, finish); | |||
695 | break; | |||
696 | } | |||
697 | ||||
698 | case M_SIGNATURE: | |||
699 | { | |||
700 | if (fTextView->IsReaderThreadRunning()) { | |||
701 | // Do not add the signature until the reader thread | |||
702 | // is finished. Resubmit the message for later processing | |||
703 | Window()->PostMessage(msg); | |||
704 | break; | |||
705 | } | |||
706 | ||||
707 | entry_ref ref; | |||
708 | msg->FindRef("ref", &ref); | |||
709 | ||||
710 | BFile file(&ref, B_READ_ONLY0x0000); | |||
711 | if (file.InitCheck() == B_OK((int)0)) { | |||
712 | int32 start, finish; | |||
713 | fTextView->GetSelection(&start, &finish); | |||
714 | ||||
715 | off_t size; | |||
716 | file.GetSize(&size); | |||
717 | if (size > 32768) // safety against corrupt signatures | |||
718 | break; | |||
719 | ||||
720 | char *signature = (char *)malloc(size); | |||
721 | if (signature == NULL__null) | |||
722 | break; | |||
723 | ssize_t bytesRead = file.Read(signature, size); | |||
724 | if (bytesRead < B_OK((int)0)) { | |||
725 | free (signature); | |||
726 | break; | |||
727 | } | |||
728 | ||||
729 | const char *text = fTextView->Text(); | |||
730 | int32 length = fTextView->TextLength(); | |||
731 | ||||
732 | // reserve some empty lines before the signature | |||
733 | const char* newLines = "\n\n\n\n"; | |||
734 | if (length && text[length - 1] == '\n') | |||
735 | newLines++; | |||
736 | ||||
737 | fTextView->Select(length, length); | |||
738 | fTextView->Insert(newLines, strlen(newLines)); | |||
739 | length += strlen(newLines); | |||
740 | ||||
741 | // append the signature | |||
742 | fTextView->Select(length, length); | |||
743 | fTextView->Insert(signature, bytesRead); | |||
744 | fTextView->Select(length, length + bytesRead); | |||
745 | fTextView->ScrollToSelection(); | |||
746 | ||||
747 | // set the editing cursor position | |||
748 | fTextView->Select(length - 2 , length - 2); | |||
749 | fTextView->ScrollToSelection(); | |||
750 | free (signature); | |||
751 | } else { | |||
752 | beep(); | |||
753 | BAlert* alert = new BAlert("", | |||
754 | B_TRANSLATE("An error occurred trying to open this "BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to open this " "signature."), "Mail") | |||
755 | "signature.")BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to open this " "signature."), "Mail"), B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry" ), "Mail")); | |||
756 | alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); | |||
757 | alert->Go(); | |||
758 | } | |||
759 | break; | |||
760 | } | |||
761 | ||||
762 | case M_FIND: | |||
763 | FindString(msg->FindString("findthis")); | |||
764 | break; | |||
765 | ||||
766 | default: | |||
767 | BView::MessageReceived(msg); | |||
768 | } | |||
769 | } | |||
770 | ||||
771 | ||||
772 | void | |||
773 | TContentView::FindString(const char *str) | |||
774 | { | |||
775 | int32 finish; | |||
776 | int32 pass = 0; | |||
777 | int32 start = 0; | |||
778 | ||||
779 | if (str == NULL__null) | |||
780 | return; | |||
781 | ||||
782 | // | |||
783 | // Start from current selection or from the beginning of the pool | |||
784 | // | |||
785 | const char *text = fTextView->Text(); | |||
786 | int32 count = fTextView->TextLength(); | |||
787 | fTextView->GetSelection(&start, &finish); | |||
788 | if (start != finish) | |||
789 | start = finish; | |||
790 | if (!count || text == NULL__null) | |||
791 | return; | |||
792 | ||||
793 | // | |||
794 | // Do the find | |||
795 | // | |||
796 | while (pass < 2) { | |||
797 | long found = -1; | |||
798 | char lc = tolower(str[0])((int)__ctype_tolower[(int)(str[0])]); | |||
799 | char uc = toupper(str[0])((int)__ctype_toupper[(int)(str[0])]); | |||
800 | for (long i = start; i < count; i++) { | |||
801 | if (text[i] == lc || text[i] == uc) { | |||
802 | const char *s = str; | |||
803 | const char *t = text + i; | |||
804 | while (*s && (tolower(*s)((int)__ctype_tolower[(int)(*s)]) == tolower(*t)((int)__ctype_tolower[(int)(*t)]))) { | |||
805 | s++; | |||
806 | t++; | |||
807 | } | |||
808 | if (*s == 0) { | |||
809 | found = i; | |||
810 | break; | |||
811 | } | |||
812 | } | |||
813 | } | |||
814 | ||||
815 | // | |||
816 | // Select the text if it worked | |||
817 | // | |||
818 | if (found != -1) { | |||
819 | Window()->Activate(); | |||
820 | fTextView->Select(found, found + strlen(str)); | |||
821 | fTextView->ScrollToSelection(); | |||
822 | fTextView->MakeFocus(true); | |||
823 | return; | |||
824 | } | |||
825 | else if (start) { | |||
826 | start = 0; | |||
827 | text = fTextView->Text(); | |||
828 | count = fTextView->TextLength(); | |||
829 | pass++; | |||
830 | } else { | |||
831 | beep(); | |||
832 | return; | |||
833 | } | |||
834 | } | |||
835 | } | |||
836 | ||||
837 | ||||
838 | void | |||
839 | TContentView::Focus(bool focus) | |||
840 | { | |||
841 | if (fFocus != focus) { | |||
842 | fFocus = focus; | |||
843 | Draw(Frame()); | |||
844 | } | |||
845 | } | |||
846 | ||||
847 | ||||
848 | void | |||
849 | TContentView::FrameResized(float /* width */, float /* height */) | |||
850 | { | |||
851 | BRect r(fTextView->Bounds()); | |||
852 | r.OffsetTo(0, 0); | |||
853 | r.InsetBy(5, 5); | |||
854 | fTextView->SetTextRect(r); | |||
855 | } | |||
856 | ||||
857 | ||||
858 | //==================================================================== | |||
859 | // #pragma mark - | |||
860 | ||||
861 | ||||
862 | TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view, | |||
863 | BFont *font, bool showHeader, bool coloredQuotes) | |||
864 | : | |||
865 | BTextView(frame, "", text, B_FOLLOW_ALL_rule_(_VIEW_TOP_, _VIEW_LEFT_, _VIEW_BOTTOM_, _VIEW_RIGHT_), B_WILL_DRAW | B_NAVIGABLE), | |||
866 | ||||
867 | fHeader(showHeader), | |||
868 | fColoredQuotes(coloredQuotes), | |||
869 | fReady(false), | |||
870 | fYankBuffer(NULL__null), | |||
871 | fLastPosition(-1), | |||
872 | fMail(NULL__null), | |||
873 | fFont(font), | |||
874 | fParent(view), | |||
875 | fStopLoading(false), | |||
876 | fThread(0), | |||
877 | fPanel(NULL__null), | |||
878 | fIncoming(incoming), | |||
879 | fSpellCheck(false), | |||
880 | fRaw(false), | |||
881 | fCursor(false), | |||
882 | fFirstSpellMark(NULL__null) | |||
883 | { | |||
884 | fStopSem = create_sem(1, "reader_sem"); | |||
885 | SetStylable(true); | |||
886 | ||||
887 | fEnclosures = new BList(); | |||
888 | ||||
889 | // Enclosure pop up menu | |||
890 | fEnclosureMenu = new BPopUpMenu("Enclosure", false, false); | |||
891 | fEnclosureMenu->SetFont(be_plain_font); | |||
892 | fEnclosureMenu->AddItem(new BMenuItem( | |||
893 | B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS)BLocaleRoster::Default()->GetCatalog()->GetString(("Save attachment" "\xE2\x80\xA6"), "Mail"), new BMessage(M_SAVE))); | |||
894 | fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment")BLocaleRoster::Default()->GetCatalog()->GetString(("Open attachment" ), "Mail"), | |||
895 | new BMessage(M_OPEN))); | |||
896 | ||||
897 | // Hyperlink pop up menu | |||
898 | fLinkMenu = new BPopUpMenu("Link", false, false); | |||
899 | fLinkMenu->SetFont(be_plain_font); | |||
900 | fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link")BLocaleRoster::Default()->GetCatalog()->GetString(("Open this link" ), "Mail"), | |||
901 | new BMessage(M_OPEN))); | |||
902 | fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location")BLocaleRoster::Default()->GetCatalog()->GetString(("Copy link location" ), "Mail"), | |||
903 | new BMessage(M_COPY))); | |||
904 | ||||
905 | SetDoesUndo(true); | |||
906 | ||||
907 | //Undo function | |||
908 | fUndoBuffer.On(); | |||
909 | fInputMethodUndoBuffer.On(); | |||
910 | fUndoState.replaced = false; | |||
911 | fUndoState.deleted = false; | |||
912 | fInputMethodUndoState.active = false; | |||
913 | fInputMethodUndoState.replace = false; | |||
914 | } | |||
915 | ||||
916 | ||||
917 | TTextView::~TTextView() | |||
918 | { | |||
919 | ClearList(); | |||
920 | delete fPanel; | |||
921 | ||||
922 | if (fYankBuffer) | |||
923 | free(fYankBuffer); | |||
924 | ||||
925 | delete_sem(fStopSem); | |||
926 | } | |||
927 | ||||
928 | ||||
929 | void | |||
930 | TTextView::UpdateFont(const BFont* newFont) | |||
931 | { | |||
932 | fFont = *newFont; | |||
933 | ||||
934 | // update the text run array safely with new font | |||
935 | text_run_array *runArray = RunArray(0, INT32_MAX(2147483647)); | |||
936 | for (int i = 0; i < runArray->count; i++) | |||
937 | runArray->runs[i].font = *newFont; | |||
938 | ||||
939 | SetRunArray(0, INT32_MAX(2147483647), runArray); | |||
940 | FreeRunArray(runArray); | |||
941 | } | |||
942 | ||||
943 | ||||
944 | void | |||
945 | TTextView::AttachedToWindow() | |||
946 | { | |||
947 | BTextView::AttachedToWindow(); | |||
948 | fFont.SetSpacing(B_FIXED_SPACING); | |||
949 | SetFontAndColor(&fFont); | |||
950 | ||||
951 | if (fMail != NULL__null) { | |||
952 | LoadMessage(fMail, false, NULL__null); | |||
953 | if (fIncoming) | |||
954 | MakeEditable(false); | |||
955 | } | |||
956 | } | |||
957 | ||||
958 | ||||
959 | void | |||
960 | TTextView::KeyDown(const char *key, int32 count) | |||
961 | { | |||
962 | char raw; | |||
963 | int32 end; | |||
964 | int32 start; | |||
965 | uint32 mods; | |||
966 | BMessage *msg; | |||
967 | int32 textLen = TextLength(); | |||
968 | ||||
969 | msg = Window()->CurrentMessage(); | |||
970 | mods = msg->FindInt32("modifiers"); | |||
971 | ||||
972 | switch (key[0]) { | |||
973 | case B_HOME: | |||
974 | if (IsSelectable()) { | |||
975 | if (IsEditable()) | |||
976 | BTextView::KeyDown(key, count); | |||
977 | else { | |||
978 | // scroll to the beginning | |||
979 | Select(0, 0); | |||
980 | ScrollToSelection(); | |||
981 | } | |||
982 | } | |||
983 | break; | |||
984 | ||||
985 | case B_END: | |||
986 | if (IsSelectable()) { | |||
987 | if (IsEditable()) | |||
988 | BTextView::KeyDown(key, count); | |||
989 | else { | |||
990 | // scroll to the end | |||
991 | int32 length = TextLength(); | |||
992 | Select(length, length); | |||
993 | ScrollToSelection(); | |||
994 | } | |||
995 | } | |||
996 | break; | |||
997 | ||||
998 | case 0x02: // ^b - back 1 char | |||
999 | if (IsSelectable()) { | |||
1000 | GetSelection(&start, &end); | |||
1001 | while (!IsInitialUTF8Byte(ByteAt(--start))) { | |||
1002 | if (start < 0) { | |||
1003 | start = 0; | |||
1004 | break; | |||
1005 | } | |||
1006 | } | |||
1007 | if (start >= 0) { | |||
1008 | Select(start, start); | |||
1009 | ScrollToSelection(); | |||
1010 | } | |||
1011 | } | |||
1012 | break; | |||
1013 | ||||
1014 | case B_DELETE: | |||
1015 | if (IsSelectable()) { | |||
1016 | if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) { | |||
1017 | // ^d | |||
1018 | if (IsEditable()) { | |||
1019 | GetSelection(&start, &end); | |||
1020 | if (start != end) | |||
1021 | Delete(); | |||
1022 | else { | |||
1023 | for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) { | |||
1024 | if (end > textLen) { | |||
1025 | end = textLen; | |||
1026 | break; | |||
1027 | } | |||
1028 | } | |||
1029 | Select(start, end); | |||
1030 | Delete(); | |||
1031 | } | |||
1032 | } | |||
1033 | } | |||
1034 | else | |||
1035 | Select(textLen, textLen); | |||
1036 | ScrollToSelection(); | |||
1037 | } | |||
1038 | break; | |||
1039 | ||||
1040 | case 0x05: // ^e - end of line | |||
1041 | if (IsSelectable() && (mods & B_CONTROL_KEY)) { | |||
1042 | if (CurrentLine() == CountLines() - 1) | |||
1043 | Select(TextLength(), TextLength()); | |||
1044 | else { | |||
1045 | GoToLine(CurrentLine() + 1); | |||
1046 | GetSelection(&start, &end); | |||
1047 | Select(start - 1, start - 1); | |||
1048 | } | |||
1049 | } | |||
1050 | break; | |||
1051 | ||||
1052 | case 0x06: // ^f - forward 1 char | |||
1053 | if (IsSelectable()) { | |||
1054 | GetSelection(&start, &end); | |||
1055 | if (end > start) | |||
1056 | start = end; | |||
1057 | else { | |||
1058 | for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); | |||
1059 | end++) { | |||
1060 | if (end > textLen) { | |||
1061 | end = textLen; | |||
1062 | break; | |||
1063 | } | |||
1064 | } | |||
1065 | start = end; | |||
1066 | } | |||
1067 | Select(start, start); | |||
1068 | ScrollToSelection(); | |||
1069 | } | |||
1070 | break; | |||
1071 | ||||
1072 | case 0x0e: // ^n - next line | |||
1073 | if (IsSelectable()) { | |||
1074 | raw = B_DOWN_ARROW; | |||
1075 | BTextView::KeyDown(&raw, 1); | |||
1076 | } | |||
1077 | break; | |||
1078 | ||||
1079 | case 0x0f: // ^o - open line | |||
1080 | if (IsEditable()) { | |||
1081 | GetSelection(&start, &end); | |||
1082 | Delete(); | |||
1083 | ||||
1084 | char newLine = '\n'; | |||
1085 | Insert(&newLine, 1); | |||
1086 | Select(start, start); | |||
1087 | ScrollToSelection(); | |||
1088 | } | |||
1089 | break; | |||
1090 | ||||
1091 | case B_PAGE_UP: | |||
1092 | if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line | |||
1093 | if (IsEditable()) { | |||
1094 | GetSelection(&start, &end); | |||
1095 | if ((start != fLastPosition) && (fYankBuffer)) { | |||
1096 | free(fYankBuffer); | |||
1097 | fYankBuffer = NULL__null; | |||
1098 | } | |||
1099 | fLastPosition = start; | |||
1100 | if (CurrentLine() < CountLines() - 1) { | |||
1101 | GoToLine(CurrentLine() + 1); | |||
1102 | GetSelection(&end, &end); | |||
1103 | end--; | |||
1104 | } | |||
1105 | else | |||
1106 | end = TextLength(); | |||
1107 | if (end < start) | |||
1108 | break; | |||
1109 | if (start == end) | |||
1110 | end++; | |||
1111 | Select(start, end); | |||
1112 | if (fYankBuffer) { | |||
1113 | fYankBuffer = (char *)realloc(fYankBuffer, | |||
1114 | strlen(fYankBuffer) + (end - start) + 1); | |||
1115 | GetText(start, end - start, | |||
1116 | &fYankBuffer[strlen(fYankBuffer)]); | |||
1117 | } else { | |||
1118 | fYankBuffer = (char *)malloc(end - start + 1); | |||
1119 | GetText(start, end - start, fYankBuffer); | |||
1120 | } | |||
1121 | Delete(); | |||
1122 | ScrollToSelection(); | |||
1123 | } | |||
1124 | break; | |||
1125 | } | |||
1126 | ||||
1127 | BTextView::KeyDown(key, count); | |||
1128 | break; | |||
1129 | ||||
1130 | case 0x10: // ^p goto previous line | |||
1131 | if (IsSelectable()) { | |||
1132 | raw = B_UP_ARROW; | |||
1133 | BTextView::KeyDown(&raw, 1); | |||
1134 | } | |||
1135 | break; | |||
1136 | ||||
1137 | case 0x19: // ^y yank text | |||
1138 | if (IsEditable() && fYankBuffer) { | |||
1139 | Delete(); | |||
1140 | Insert(fYankBuffer); | |||
1141 | ScrollToSelection(); | |||
1142 | } | |||
1143 | break; | |||
1144 | ||||
1145 | default: | |||
1146 | BTextView::KeyDown(key, count); | |||
1147 | } | |||
1148 | } | |||
1149 | ||||
1150 | ||||
1151 | void | |||
1152 | TTextView::MakeFocus(bool focus) | |||
1153 | { | |||
1154 | if (!focus) { | |||
1155 | // ToDo: can someone please translate this? Otherwise I will remove it - axeld. | |||
1156 | // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。 | |||
1157 | // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) | |||
1158 | // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 | |||
1159 | fInputMethodUndoState.active = false; | |||
1160 | // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 | |||
1161 | if (fInputMethodUndoBuffer.CountItems() > 0) { | |||
1162 | KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); | |||
1163 | if (item->History == K_INSERTED) { | |||
1164 | fUndoBuffer.MakeNewUndoItem(); | |||
1165 | fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos); | |||
1166 | fUndoBuffer.MakeNewUndoItem(); | |||
1167 | } | |||
1168 | fInputMethodUndoBuffer.MakeEmpty(); | |||
1169 | } | |||
1170 | } | |||
1171 | BTextView::MakeFocus(focus); | |||
1172 | ||||
1173 | fParent->Focus(focus); | |||
1174 | } | |||
1175 | ||||
1176 | ||||
1177 | void | |||
1178 | TTextView::MessageReceived(BMessage *msg) | |||
1179 | { | |||
1180 | switch (msg->what) { | |||
1181 | case B_SIMPLE_DATA: | |||
1182 | { | |||
1183 | if (fIncoming) | |||
1184 | break; | |||
1185 | ||||
1186 | BMessage message(REFS_RECEIVED); | |||
1187 | bool isEnclosure = false; | |||
1188 | bool inserted = false; | |||
1189 | ||||
1190 | off_t len = 0; | |||
1191 | int32 end; | |||
1192 | int32 start; | |||
1193 | ||||
1194 | int32 index = 0; | |||
1195 | entry_ref ref; | |||
1196 | while (msg->FindRef("refs", index++, &ref) == B_OK((int)0)) { | |||
1197 | BFile file(&ref, B_READ_ONLY0x0000); | |||
1198 | if (file.InitCheck() == B_OK((int)0)) { | |||
1199 | BNodeInfo node(&file); | |||
1200 | char type[B_FILE_NAME_LENGTH(256)]; | |||
1201 | node.GetType(type); | |||
1202 | ||||
1203 | off_t size = 0; | |||
1204 | file.GetSize(&size); | |||
1205 | ||||
1206 | if (!strncasecmp(type, "text/", 5) && size > 0) { | |||
1207 | len += size; | |||
1208 | char *text = (char *)malloc(size); | |||
1209 | if (text == NULL__null) { | |||
1210 | puts("no memory!"); | |||
1211 | return; | |||
1212 | } | |||
1213 | if (file.Read(text, size) < B_OK((int)0)) { | |||
1214 | puts("could not read from file"); | |||
1215 | free(text); | |||
1216 | continue; | |||
1217 | } | |||
1218 | if (!inserted) { | |||
1219 | GetSelection(&start, &end); | |||
1220 | Delete(); | |||
1221 | inserted = true; | |||
1222 | } | |||
1223 | ||||
1224 | int32 offset = 0; | |||
1225 | for (int32 loop = 0; loop < size; loop++) { | |||
1226 | if (text[loop] == '\n') { | |||
1227 | Insert(&text[offset], loop - offset + 1); | |||
1228 | offset = loop + 1; | |||
1229 | } else if (text[loop] == '\r') { | |||
1230 | text[loop] = '\n'; | |||
1231 | Insert(&text[offset], loop - offset + 1); | |||
1232 | if ((loop + 1 < size) | |||
1233 | && (text[loop + 1] == '\n')) | |||
1234 | loop++; | |||
1235 | offset = loop + 1; | |||
1236 | } | |||
1237 | } | |||
1238 | free(text); | |||
1239 | } else { | |||
1240 | isEnclosure = true; | |||
1241 | message.AddRef("refs", &ref); | |||
1242 | } | |||
1243 | } | |||
1244 | } | |||
1245 | ||||
1246 | if (index == 1) { | |||
1247 | // message doesn't contain any refs - maybe the parent class likes it | |||
1248 | BTextView::MessageReceived(msg); | |||
1249 | break; | |||
1250 | } | |||
1251 | ||||
1252 | if (inserted) | |||
1253 | Select(start, start + len); | |||
1254 | if (isEnclosure) | |||
1255 | Window()->PostMessage(&message, Window()); | |||
1256 | break; | |||
1257 | } | |||
1258 | ||||
1259 | case M_HEADER: | |||
1260 | msg->FindBool("header", &fHeader); | |||
1261 | SetText(NULL__null); | |||
1262 | LoadMessage(fMail, false, NULL__null); | |||
1263 | break; | |||
1264 | ||||
1265 | case M_RAW: | |||
1266 | StopLoad(); | |||
1267 | ||||
1268 | msg->FindBool("raw", &fRaw); | |||
1269 | SetText(NULL__null); | |||
1270 | LoadMessage(fMail, false, NULL__null); | |||
1271 | break; | |||
1272 | ||||
1273 | case M_SELECT: | |||
1274 | if (IsSelectable()) | |||
1275 | Select(0, TextLength()); | |||
1276 | break; | |||
1277 | ||||
1278 | case M_SAVE: | |||
1279 | Save(msg); | |||
1280 | break; | |||
1281 | ||||
1282 | case B_NODE_MONITOR: | |||
1283 | { | |||
1284 | int32 opcode; | |||
1285 | if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR((int)0)) { | |||
1286 | dev_t device; | |||
1287 | if (msg->FindInt32("device", &device) < B_OK((int)0)) | |||
1288 | break; | |||
1289 | ino_t inode; | |||
1290 | if (msg->FindInt64("node", &inode) < B_OK((int)0)) | |||
1291 | break; | |||
1292 | ||||
1293 | hyper_text *enclosure; | |||
1294 | for (int32 index = 0; | |||
1295 | (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL__null;) { | |||
1296 | if (device == enclosure->node.device | |||
1297 | && inode == enclosure->node.node) { | |||
1298 | if (opcode == B_ENTRY_REMOVED2) { | |||
1299 | enclosure->saved = false; | |||
1300 | enclosure->have_ref = false; | |||
1301 | } else if (opcode == B_ENTRY_MOVED3) { | |||
1302 | enclosure->ref.device = device; | |||
1303 | msg->FindInt64("to directory", &enclosure->ref.directory); | |||
1304 | ||||
1305 | const char *name; | |||
1306 | msg->FindString("name", &name); | |||
1307 | enclosure->ref.set_name(name); | |||
1308 | } | |||
1309 | break; | |||
1310 | } | |||
1311 | } | |||
1312 | } | |||
1313 | break; | |||
1314 | } | |||
1315 | ||||
1316 | // | |||
1317 | // Tracker has responded to a BMessage that was dragged out of | |||
1318 | // this email message. It has created a file for us, we just have to | |||
1319 | // put the stuff in it. | |||
1320 | // | |||
1321 | case B_COPY_TARGET: | |||
1322 | { | |||
1323 | BMessage data; | |||
1324 | if (msg->FindMessage("be:originator-data", &data) == B_OK((int)0)) { | |||
1325 | entry_ref directory; | |||
1326 | const char *name; | |||
1327 | hyper_text *enclosure; | |||
1328 | ||||
1329 | if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK((int)0) | |||
1330 | && msg->FindString("name", &name) == B_OK((int)0) | |||
1331 | && msg->FindRef("directory", &directory) == B_OK((int)0)) { | |||
1332 | switch (enclosure->type) { | |||
1333 | case TYPE_ENCLOSURE: | |||
1334 | case TYPE_BE_ENCLOSURE: | |||
1335 | { | |||
1336 | // | |||
1337 | // Enclosure. Decode the data and write it out. | |||
1338 | // | |||
1339 | BMessage saveMsg(M_SAVE); | |||
1340 | saveMsg.AddString("name", name); | |||
1341 | saveMsg.AddRef("directory", &directory); | |||
1342 | saveMsg.AddPointer("enclosure", enclosure); | |||
1343 | Save(&saveMsg, false); | |||
1344 | break; | |||
1345 | } | |||
1346 | ||||
1347 | case TYPE_URL: | |||
1348 | { | |||
1349 | const char *replyType; | |||
1350 | if (msg->FindString("be:filetypes", &replyType) != B_OK((int)0)) | |||
1351 | // drag recipient didn't ask for any specific type, | |||
1352 | // create a bookmark file as default | |||
1353 | replyType = "application/x-vnd.Be-bookmark"; | |||
1354 | ||||
1355 | BDirectory dir(&directory); | |||
1356 | BFile file(&dir, name, B_READ_WRITE0x0002); | |||
1357 | if (file.InitCheck() == B_OK((int)0)) { | |||
1358 | if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) { | |||
1359 | // we got a request to create a bookmark, stuff | |||
1360 | // it with the url attribute | |||
1361 | file.WriteAttr("META:url", B_STRING_TYPE, 0, | |||
1362 | enclosure->name, strlen(enclosure->name) + 1); | |||
1363 | } else if (strcasecmp(replyType, "text/plain") == 0) { | |||
1364 | // create a plain text file, stuff it with | |||
1365 | // the url as text | |||
1366 | file.Write(enclosure->name, strlen(enclosure->name)); | |||
1367 | } | |||
1368 | ||||
1369 | BNodeInfo fileInfo(&file); | |||
1370 | fileInfo.SetType(replyType); | |||
1371 | } | |||
1372 | break; | |||
1373 | } | |||
1374 | ||||
1375 | case TYPE_MAILTO: | |||
1376 | { | |||
1377 | // | |||
1378 | // Add some attributes to the already created | |||
1379 | // person file. Strip out the 'mailto:' if | |||
1380 | // possible. | |||
1381 | // | |||
1382 | char *addrStart = enclosure->name; | |||
1383 | while (true) { | |||
1384 | if (*addrStart == ':') { | |||
1385 | addrStart++; | |||
1386 | break; | |||
1387 | } | |||
1388 | ||||
1389 | if (*addrStart == '\0') { | |||
1390 | addrStart = enclosure->name; | |||
1391 | break; | |||
1392 | } | |||
1393 | ||||
1394 | addrStart++; | |||
1395 | } | |||
1396 | ||||
1397 | const char *replyType; | |||
1398 | if (msg->FindString("be:filetypes", &replyType) != B_OK((int)0)) | |||
1399 | // drag recipient didn't ask for any specific type, | |||
1400 | // create a bookmark file as default | |||
1401 | replyType = "application/x-vnd.Be-bookmark"; | |||
1402 | ||||
1403 | BDirectory dir(&directory); | |||
1404 | BFile file(&dir, name, B_READ_WRITE0x0002); | |||
1405 | if (file.InitCheck() == B_OK((int)0)) { | |||
1406 | if (!strcmp(replyType, "application/x-person")) { | |||
1407 | // we got a request to create a bookmark, stuff | |||
1408 | // it with the address attribute | |||
1409 | file.WriteAttr("META:email", B_STRING_TYPE, 0, | |||
1410 | addrStart, strlen(enclosure->name) + 1); | |||
1411 | } else if (!strcasecmp(replyType, "text/plain")) { | |||
1412 | // create a plain text file, stuff it with the | |||
1413 | // email as text | |||
1414 | file.Write(addrStart, strlen(addrStart)); | |||
1415 | } | |||
1416 | ||||
1417 | BNodeInfo fileInfo(&file); | |||
1418 | fileInfo.SetType(replyType); | |||
1419 | } | |||
1420 | break; | |||
1421 | } | |||
1422 | } | |||
1423 | } else { | |||
1424 | // | |||
1425 | // Assume this is handled by BTextView... | |||
1426 | // (Probably drag clipping.) | |||
1427 | // | |||
1428 | BTextView::MessageReceived(msg); | |||
1429 | } | |||
1430 | } | |||
1431 | break; | |||
1432 | } | |||
1433 | ||||
1434 | case B_INPUT_METHOD_EVENT: | |||
1435 | { | |||
1436 | int32 im_op; | |||
1437 | if (msg->FindInt32("be:opcode", &im_op) == B_OK((int)0)){ | |||
1438 | switch (im_op) { | |||
1439 | case B_INPUT_METHOD_STARTED: | |||
1440 | fInputMethodUndoState.replace = true; | |||
1441 | fInputMethodUndoState.active = true; | |||
1442 | break; | |||
1443 | case B_INPUT_METHOD_STOPPED: | |||
1444 | fInputMethodUndoState.active = false; | |||
1445 | if (fInputMethodUndoBuffer.CountItems() > 0) { | |||
1446 | KUndoItem *undo = fInputMethodUndoBuffer.ItemAt( | |||
1447 | fInputMethodUndoBuffer.CountItems() - 1); | |||
1448 | if (undo->History == K_INSERTED){ | |||
1449 | fUndoBuffer.MakeNewUndoItem(); | |||
1450 | fUndoBuffer.AddUndo(undo->RedoText, undo->Length, | |||
1451 | undo->Offset, undo->History, undo->CursorPos); | |||
1452 | fUndoBuffer.MakeNewUndoItem(); | |||
1453 | } | |||
1454 | fInputMethodUndoBuffer.MakeEmpty(); | |||
1455 | } | |||
1456 | break; | |||
1457 | case B_INPUT_METHOD_CHANGED: | |||
1458 | fInputMethodUndoState.active = true; | |||
1459 | break; | |||
1460 | case B_INPUT_METHOD_LOCATION_REQUEST: | |||
1461 | fInputMethodUndoState.active = true; | |||
1462 | break; | |||
1463 | } | |||
1464 | } | |||
1465 | BTextView::MessageReceived(msg); | |||
1466 | break; | |||
1467 | } | |||
1468 | ||||
1469 | case M_REDO: | |||
1470 | Redo(); | |||
1471 | break; | |||
1472 | ||||
1473 | default: | |||
1474 | BTextView::MessageReceived(msg); | |||
1475 | } | |||
1476 | } | |||
1477 | ||||
1478 | ||||
1479 | void | |||
1480 | TTextView::MouseDown(BPoint where) | |||
1481 | { | |||
1482 | if (IsEditable()) { | |||
1483 | BPoint point; | |||
1484 | uint32 buttons; | |||
1485 | GetMouse(&point, &buttons); | |||
1486 | if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) { | |||
1487 | int32 offset, start, end, length; | |||
1488 | const char *text = Text(); | |||
1489 | offset = OffsetAt(where); | |||
1490 | if (isalpha(text[offset])(__ctype_b[(int)((text[offset]))] & (unsigned short int)_ISalpha )) { | |||
1491 | length = TextLength(); | |||
1492 | ||||
1493 | //Find start and end of word | |||
1494 | //FindSpellBoundry(length, offset, &start, &end); | |||
1495 | ||||
1496 | char c; | |||
1497 | bool isAlpha, isApost, isCap; | |||
1498 | int32 first; | |||
1499 | ||||
1500 | for (first = offset; | |||
1501 | (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha)); | |||
1502 | first--) {} | |||
1503 | isCap = isupper(text[++first])(__ctype_b[(int)((text[++first]))] & (unsigned short int) _ISupper); | |||
1504 | ||||
1505 | for (start = offset, c = text[start], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c=='\''); | |||
1506 | (start >= 0) && (isAlpha || (isApost | |||
1507 | && (((c = text[start+1]) != 's') || !isCap) && isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha) | |||
1508 | && isalpha(text[start-1])(__ctype_b[(int)((text[start-1]))] & (unsigned short int) _ISalpha))); | |||
1509 | start--, c = text[start], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\'')) {} | |||
1510 | start++; | |||
1511 | ||||
1512 | for (end = offset, c = text[end], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\''); | |||
1513 | (end < length) && (isAlpha || (isApost | |||
1514 | && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha))); | |||
1515 | end++, c = text[end], isAlpha = isalpha(c)(__ctype_b[(int)((c))] & (unsigned short int)_ISalpha), isApost = (c == '\'')) {} | |||
1516 | ||||
1517 | length = end - start; | |||
1518 | BString srcWord; | |||
1519 | srcWord.SetTo(text + start, length); | |||
1520 | ||||
1521 | bool foundWord = false; | |||
1522 | BList matches; | |||
1523 | BString *string; | |||
1524 | ||||
1525 | BMenuItem *menuItem; | |||
1526 | BPopUpMenu menu("Words", false, false); | |||
1527 | ||||
1528 | for (int32 i = 0; i < gDictCount; i++) | |||
1529 | gWords[i]->FindBestMatches(&matches, | |||
1530 | srcWord.String()); | |||
1531 | ||||
1532 | if (matches.CountItems()) { | |||
1533 | sort_word_list(&matches, srcWord.String()); | |||
1534 | for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL__null; i++) { | |||
1535 | menu.AddItem((menuItem = new BMenuItem(string->String(), NULL__null))); | |||
1536 | if (!strcasecmp(string->String(), srcWord.String())) { | |||
1537 | menuItem->SetEnabled(false); | |||
1538 | foundWord = true; | |||
1539 | } | |||
1540 | delete string; | |||
1541 | } | |||
1542 | } else { | |||
1543 | menuItem = new BMenuItem(B_TRANSLATE("No matches")BLocaleRoster::Default()->GetCatalog()->GetString(("No matches" ), "Mail"), NULL__null); | |||
1544 | menuItem->SetEnabled(false); | |||
1545 | menu.AddItem(menuItem); | |||
1546 | } | |||
1547 | ||||
1548 | BMenuItem *addItem = NULL__null; | |||
1549 | if (!foundWord && gUserDict >= 0) { | |||
1550 | menu.AddSeparatorItem(); | |||
1551 | addItem = new BMenuItem(B_TRANSLATE("Add")BLocaleRoster::Default()->GetCatalog()->GetString(("Add" ), "Mail"), NULL__null); | |||
1552 | menu.AddItem(addItem); | |||
1553 | } | |||
1554 | ||||
1555 | point = ConvertToScreen(where); | |||
1556 | if ((menuItem = menu.Go(point, false, false)) != NULL__null) { | |||
1557 | if (menuItem == addItem) { | |||
1558 | BString newItem(srcWord.String()); | |||
1559 | newItem << "\n"; | |||
1560 | gWords[gUserDict]->InitIndex(); | |||
1561 | gExactWords[gUserDict]->InitIndex(); | |||
1562 | gUserDictFile->Write(newItem.String(), newItem.Length()); | |||
1563 | gWords[gUserDict]->BuildIndex(); | |||
1564 | gExactWords[gUserDict]->BuildIndex(); | |||
1565 | ||||
1566 | if (fSpellCheck) | |||
1567 | CheckSpelling(0, TextLength()); | |||
1568 | } else { | |||
1569 | int32 len = strlen(menuItem->Label()); | |||
1570 | Select(start, start); | |||
1571 | Delete(start, end); | |||
1572 | Insert(start, menuItem->Label(), len); | |||
1573 | Select(start+len, start+len); | |||
1574 | } | |||
1575 | } | |||
1576 | } | |||
1577 | return; | |||
1578 | } else if (fSpellCheck && IsEditable()) { | |||
1579 | int32 start, end; | |||
1580 | ||||
1581 | GetSelection(&start, &end); | |||
1582 | FindSpellBoundry(1, start, &start, &end); | |||
1583 | CheckSpelling(start, end); | |||
1584 | } | |||
1585 | } else { | |||
1586 | // is not editable, look for enclosures/links | |||
1587 | ||||
1588 | int32 clickOffset = OffsetAt(where); | |||
1589 | int32 items = fEnclosures->CountItems(); | |||
1590 | for (int32 loop = 0; loop < items; loop++) { | |||
1591 | hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop); | |||
1592 | if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end) | |||
1593 | continue; | |||
1594 | ||||
1595 | // | |||
1596 | // The user is clicking on this attachment | |||
1597 | // | |||
1598 | ||||
1599 | int32 start; | |||
1600 | int32 finish; | |||
1601 | Select(enclosure->text_start, enclosure->text_end); | |||
1602 | GetSelection(&start, &finish); | |||
1603 | Window()->UpdateIfNeeded(); | |||
1604 | ||||
1605 | bool drag = false; | |||
1606 | bool held = false; | |||
1607 | uint32 buttons = 0; | |||
1608 | if (Window()->CurrentMessage()) { | |||
1609 | Window()->CurrentMessage()->FindInt32("buttons", | |||
1610 | (int32 *) &buttons); | |||
1611 | } | |||
1612 | ||||
1613 | // | |||
1614 | // If this is the primary button, wait to see if the user is going | |||
1615 | // to single click, hold, or drag. | |||
1616 | // | |||
1617 | if (buttons != B_SECONDARY_MOUSE_BUTTON) { | |||
1618 | BPoint point = where; | |||
1619 | bigtime_t popupDelay; | |||
1620 | get_click_speed(&popupDelay); | |||
1621 | popupDelay *= 2; | |||
1622 | popupDelay += system_time(); | |||
1623 | while (buttons && abs((int)(point.x - where.x)) < 4 | |||
1624 | && abs((int)(point.y - where.y)) < 4 | |||
1625 | && system_time() < popupDelay) { | |||
1626 | snooze(10000); | |||
1627 | GetMouse(&point, &buttons); | |||
1628 | } | |||
1629 | ||||
1630 | if (system_time() < popupDelay) { | |||
1631 | // | |||
1632 | // The user either dragged this or released the button. | |||
1633 | // check if it was dragged. | |||
1634 | // | |||
1635 | if (!(abs((int)(point.x - where.x)) < 4 | |||
1636 | && abs((int)(point.y - where.y)) < 4) && buttons) | |||
1637 | drag = true; | |||
1638 | } else { | |||
1639 | // | |||
1640 | // The user held the button down. | |||
1641 | // | |||
1642 | held = true; | |||
1643 | } | |||
1644 | } | |||
1645 | ||||
1646 | // | |||
1647 | // If the user has right clicked on this menu, | |||
1648 | // or held the button down on it for a while, | |||
1649 | // pop up a context menu. | |||
1650 | // | |||
1651 | if (buttons == B_SECONDARY_MOUSE_BUTTON || held) { | |||
1652 | // | |||
1653 | // Right mouse click... Display a menu | |||
1654 | // | |||
1655 | BPoint point = where; | |||
1656 | ConvertToScreen(&point); | |||
1657 | ||||
1658 | BMenuItem *item; | |||
1659 | if ((enclosure->type != TYPE_ENCLOSURE) | |||
1660 | && (enclosure->type != TYPE_BE_ENCLOSURE)) | |||
1661 | item = fLinkMenu->Go(point, true); | |||
1662 | else | |||
1663 | item = fEnclosureMenu->Go(point, true); | |||
1664 | ||||
1665 | BMessage *msg; | |||
1666 | if (item && (msg = item->Message()) != NULL__null) { | |||
1667 | if (msg->what == M_SAVE) { | |||
1668 | if (fPanel) | |||
1669 | fPanel->SetEnclosure(enclosure); | |||
1670 | else { | |||
1671 | fPanel = new TSavePanel(enclosure, this); | |||
1672 | fPanel->Window()->Show(); | |||
1673 | } | |||
1674 | } else if (msg->what == M_COPY) { | |||
1675 | // copy link location to clipboard | |||
1676 | ||||
1677 | if (be_clipboard->Lock()) { | |||
1678 | be_clipboard->Clear(); | |||
1679 | ||||
1680 | BMessage *clip; | |||
1681 | if ((clip = be_clipboard->Data()) != NULL__null) { | |||
1682 | clip->AddData("text/plain", B_MIME_TYPE, | |||
1683 | enclosure->name, strlen(enclosure->name)); | |||
1684 | be_clipboard->Commit(); | |||
1685 | } | |||
1686 | be_clipboard->Unlock(); | |||
1687 | } | |||
1688 | } else | |||
1689 | Open(enclosure); | |||
1690 | } | |||
1691 | } else { | |||
1692 | // | |||
1693 | // Left button. If the user single clicks, open this link. | |||
1694 | // Otherwise, initiate a drag. | |||
1695 | // | |||
1696 | if (drag) { | |||
1697 | BMessage dragMessage(B_SIMPLE_DATA); | |||
1698 | dragMessage.AddInt32("be:actions", B_COPY_TARGET); | |||
1699 | dragMessage.AddString("be:types", B_FILE_MIME_TYPE); | |||
1700 | switch (enclosure->type) { | |||
1701 | case TYPE_BE_ENCLOSURE: | |||
1702 | case TYPE_ENCLOSURE: | |||
1703 | // | |||
1704 | // Attachment. The type is specified in the message. | |||
1705 | // | |||
1706 | dragMessage.AddString("be:types", B_FILE_MIME_TYPE); | |||
1707 | dragMessage.AddString("be:filetypes", | |||
1708 | enclosure->content_type ? enclosure->content_type : ""); | |||
1709 | dragMessage.AddString("be:clip_name", enclosure->name); | |||
1710 | break; | |||
1711 | ||||
1712 | case TYPE_URL: | |||
1713 | // | |||
1714 | // URL. The user can drag it into the tracker to | |||
1715 | // create a bookmark file. | |||
1716 | // | |||
1717 | dragMessage.AddString("be:types", B_FILE_MIME_TYPE); | |||
1718 | dragMessage.AddString("be:filetypes", | |||
1719 | "application/x-vnd.Be-bookmark"); | |||
1720 | dragMessage.AddString("be:filetypes", "text/plain"); | |||
1721 | dragMessage.AddString("be:clip_name", "Bookmark"); | |||
1722 | ||||
1723 | dragMessage.AddString("be:url", enclosure->name); | |||
1724 | break; | |||
1725 | ||||
1726 | case TYPE_MAILTO: | |||
1727 | // | |||
1728 | // Mailto address. The user can drag it into the | |||
1729 | // tracker to create a people file. | |||
1730 | // | |||
1731 | dragMessage.AddString("be:types", B_FILE_MIME_TYPE); | |||
1732 | dragMessage.AddString("be:filetypes", | |||
1733 | "application/x-person"); | |||
1734 | dragMessage.AddString("be:filetypes", "text/plain"); | |||
1735 | dragMessage.AddString("be:clip_name", "Person"); | |||
1736 | ||||
1737 | dragMessage.AddString("be:email", enclosure->name); | |||
1738 | break; | |||
1739 | ||||
1740 | default: | |||
1741 | // | |||
1742 | // Otherwise it doesn't have a type that I know how | |||
1743 | // to save. It won't have any types and if any | |||
1744 | // program wants to accept it, more power to them. | |||
1745 | // (tracker won't.) | |||
1746 | // | |||
1747 | dragMessage.AddString("be:clip_name", "Hyperlink"); | |||
1748 | } | |||
1749 | ||||
1750 | BMessage data; | |||
1751 | data.AddPointer("enclosure", enclosure); | |||
1752 | dragMessage.AddMessage("be:originator-data", &data); | |||
1753 | ||||
1754 | BRegion selectRegion; | |||
1755 | GetTextRegion(start, finish, &selectRegion); | |||
1756 | DragMessage(&dragMessage, selectRegion.Frame(), this); | |||
1757 | } else { | |||
1758 | // | |||
1759 | // User Single clicked on the attachment. Open it. | |||
1760 | // | |||
1761 | Open(enclosure); | |||
1762 | } | |||
1763 | } | |||
1764 | return; | |||
1765 | } | |||
1766 | } | |||
1767 | BTextView::MouseDown(where); | |||
1768 | } | |||
1769 | ||||
1770 | ||||
1771 | void | |||
1772 | TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg) | |||
1773 | { | |||
1774 | int32 start = OffsetAt(where); | |||
1775 | ||||
1776 | for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) { | |||
1777 | hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop); | |||
1778 | if ((start >= enclosure->text_start) && (start < enclosure->text_end)) { | |||
1779 | if (!fCursor) | |||
1780 | SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); | |||
1781 | fCursor = true; | |||
1782 | return; | |||
1783 | } | |||
1784 | } | |||
1785 | ||||
1786 | if (fCursor) { | |||
1787 | SetViewCursor(B_CURSOR_I_BEAM); | |||
1788 | fCursor = false; | |||
1789 | } | |||
1790 | ||||
1791 | BTextView::MouseMoved(where, code, msg); | |||
1792 | } | |||
1793 | ||||
1794 | ||||
1795 | void | |||
1796 | TTextView::ClearList() | |||
1797 | { | |||
1798 | hyper_text *enclosure; | |||
1799 | while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL__null) { | |||
1800 | fEnclosures->RemoveItem(enclosure); | |||
1801 | ||||
1802 | if (enclosure->name) | |||
1803 | free(enclosure->name); | |||
1804 | if (enclosure->content_type) | |||
1805 | free(enclosure->content_type); | |||
1806 | if (enclosure->encoding) | |||
1807 | free(enclosure->encoding); | |||
1808 | if (enclosure->have_ref && !enclosure->saved) { | |||
1809 | BEntry entry(&enclosure->ref); | |||
1810 | entry.Remove(); | |||
1811 | } | |||
1812 | ||||
1813 | watch_node(&enclosure->node, B_STOP_WATCHING, this); | |||
1814 | free(enclosure); | |||
1815 | } | |||
1816 | } | |||
1817 | ||||
1818 | ||||
1819 | void | |||
1820 | TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text) | |||
1821 | { | |||
1822 | StopLoad(); | |||
1823 | ||||
1824 | fMail = mail; | |||
1825 | ||||
1826 | ClearList(); | |||
1827 | ||||
1828 | MakeSelectable(true); | |||
1829 | MakeEditable(false); | |||
1830 | if (text) | |||
1831 | Insert(text, strlen(text)); | |||
1832 | ||||
1833 | //attr_info attrInfo; | |||
1834 | TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming, | |||
1835 | text != NULL__null, true, | |||
1836 | // I removed the following, because I absolutely can't imagine why it's | |||
1837 | // there (the mail kit should be able to deal with non-compliant mails) | |||
1838 | // -- axeld. | |||
1839 | // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK, | |||
1840 | this, mail, fEnclosures, fStopSem); | |||
1841 | ||||
1842 | resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY10, reader)); | |||
1843 | } | |||
1844 | ||||
1845 | ||||
1846 | void | |||
1847 | TTextView::Open(hyper_text *enclosure) | |||
1848 | { | |||
1849 | switch (enclosure->type) { | |||
1850 | case TYPE_URL: | |||
1851 | { | |||
1852 | const struct {const char *urlType, *handler; } handlerTable[] = { | |||
1853 | {"http", B_URL_HTTP}, | |||
1854 | {"https", B_URL_HTTPS}, | |||
1855 | {"ftp", B_URL_FTP}, | |||
1856 | {"gopher", B_URL_GOPHER}, | |||
1857 | {"mailto", B_URL_MAILTO}, | |||
1858 | {"news", B_URL_NEWS}, | |||
1859 | {"nntp", B_URL_NNTP}, | |||
1860 | {"telnet", B_URL_TELNET}, | |||
1861 | {"rlogin", B_URL_RLOGIN}, | |||
1862 | {"tn3270", B_URL_TN3270}, | |||
1863 | {"wais", B_URL_WAIS}, | |||
1864 | {"file", B_URL_FILE}, | |||
1865 | {NULL__null, NULL__null} | |||
1866 | }; | |||
1867 | const char *handlerToLaunch = NULL__null; | |||
1868 | ||||
1869 | const char *colonPos = strchr(enclosure->name, ':'); | |||
1870 | if (colonPos) { | |||
1871 | int urlTypeLength = colonPos - enclosure->name; | |||
1872 | ||||
1873 | for (int32 index = 0; handlerTable[index].urlType; index++) { | |||
1874 | if (!strncasecmp(enclosure->name, | |||
1875 | handlerTable[index].urlType, urlTypeLength)) { | |||
1876 | handlerToLaunch = handlerTable[index].handler; | |||
1877 | break; | |||
1878 | } | |||
1879 | } | |||
1880 | } | |||
1881 | if (handlerToLaunch) { | |||
1882 | entry_ref appRef; | |||
1883 | if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK((int)0)) | |||
1884 | handlerToLaunch = NULL__null; | |||
1885 | } | |||
1886 | if (!handlerToLaunch) | |||
1887 | handlerToLaunch = "application/x-vnd.Be-Bookmark"; | |||
1888 | ||||
1889 | status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name); | |||
1890 | if (result != B_NO_ERROR((int)0) && result != B_ALREADY_RUNNING(((-2147483647 - 1) + 0x2000) + 4)) { | |||
1891 | beep(); | |||
1892 | BAlert* alert = new BAlert("", | |||
1893 | B_TRANSLATE("There is no installed handler for "BLocaleRoster::Default()->GetCatalog()->GetString(("There is no installed handler for " "URL links."), "Mail") | |||
1894 | "URL links.")BLocaleRoster::Default()->GetCatalog()->GetString(("There is no installed handler for " "URL links."), "Mail"), B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry" ), "Mail")); | |||
1895 | alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); | |||
1896 | alert->Go(); | |||
1897 | } | |||
1898 | break; | |||
1899 | } | |||
1900 | ||||
1901 | case TYPE_MAILTO: | |||
1902 | if (be_roster->Launch(B_MAIL_TYPE"text/x-email", 1, &enclosure->name) < B_OK((int)0)) { | |||
1903 | char *argv[] = {(char *)"Mail", enclosure->name}; | |||
1904 | be_app->ArgvReceived(2, argv); | |||
1905 | } | |||
1906 | break; | |||
1907 | ||||
1908 | case TYPE_ENCLOSURE: | |||
1909 | case TYPE_BE_ENCLOSURE: | |||
1910 | if (!enclosure->have_ref) { | |||
1911 | BPath path; | |||
1912 | if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR((int)0)) { | |||
1913 | BDirectory dir(path.Path()); | |||
1914 | if (dir.InitCheck() == B_NO_ERROR((int)0)) { | |||
1915 | char name[B_FILE_NAME_LENGTH(256)]; | |||
1916 | char baseName[B_FILE_NAME_LENGTH(256)]; | |||
1917 | strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); | |||
1918 | strcpy(name, baseName); | |||
1919 | for (int32 index = 0; dir.Contains(name); index++) | |||
1920 | sprintf(name, "%s_%" B_PRId32"l" "d", baseName, index); | |||
1921 | ||||
1922 | BEntry entry(path.Path()); | |||
1923 | entry_ref ref; | |||
1924 | entry.GetRef(&ref); | |||
1925 | ||||
1926 | BMessage save(M_SAVE); | |||
1927 | save.AddRef("directory", &ref); | |||
1928 | save.AddString("name", name); | |||
1929 | save.AddPointer("enclosure", enclosure); | |||
1930 | if (Save(&save) != B_NO_ERROR((int)0)) | |||
1931 | break; | |||
1932 | enclosure->saved = false; | |||
1933 | } | |||
1934 | } | |||
1935 | } | |||
1936 | ||||
1937 | BMessenger tracker("application/x-vnd.Be-TRAK"); | |||
1938 | if (tracker.IsValid()) { | |||
1939 | BMessage openMsg(B_REFS_RECEIVED); | |||
1940 | openMsg.AddRef("refs", &enclosure->ref); | |||
1941 | tracker.SendMessage(&openMsg); | |||
1942 | } | |||
1943 | break; | |||
1944 | } | |||
1945 | } | |||
1946 | ||||
1947 | ||||
1948 | status_t | |||
1949 | TTextView::Save(BMessage *msg, bool makeNewFile) | |||
1950 | { | |||
1951 | const char *name; | |||
1952 | entry_ref ref; | |||
1953 | BFile file; | |||
1954 | BPath path; | |||
1955 | hyper_text *enclosure; | |||
1956 | status_t result = B_NO_ERROR((int)0); | |||
1957 | char entry_name[B_FILE_NAME_LENGTH(256)]; | |||
1958 | ||||
1959 | msg->FindString("name", &name); | |||
1960 | msg->FindRef("directory", &ref); | |||
1961 | msg->FindPointer("enclosure", (void **)&enclosure); | |||
1962 | ||||
1963 | BDirectory dir; | |||
1964 | dir.SetTo(&ref); | |||
1965 | result = dir.InitCheck(); | |||
1966 | ||||
1967 | if (result == B_OK((int)0)) { | |||
1968 | if (makeNewFile) { | |||
1969 | // | |||
1970 | // Search for the file and delete it if it already exists. | |||
1971 | // (It may not, that's ok.) | |||
1972 | // | |||
1973 | BEntry entry; | |||
1974 | if (dir.FindEntry(name, &entry) == B_NO_ERROR((int)0)) | |||
1975 | entry.Remove(); | |||
1976 | ||||
1977 | if ((enclosure->have_ref) && (!enclosure->saved)) { | |||
1978 | entry.SetTo(&enclosure->ref); | |||
1979 | ||||
1980 | // | |||
1981 | // Added true arg and entry_name so MoveTo clobbers as | |||
1982 | // before. This may not be the correct behaviour, but | |||
1983 | // it's the preserved behaviour. | |||
1984 | // | |||
1985 | entry.GetName(entry_name); | |||
1986 | result = entry.MoveTo(&dir, entry_name, true); | |||
1987 | if (result == B_NO_ERROR((int)0)) { | |||
1988 | entry.Rename(name); | |||
1989 | entry.GetRef(&enclosure->ref); | |||
1990 | entry.GetNodeRef(&enclosure->node); | |||
1991 | enclosure->saved = true; | |||
1992 | return result; | |||
1993 | } | |||
1994 | } | |||
1995 | ||||
1996 | if (result == B_NO_ERROR((int)0)) { | |||
1997 | result = dir.CreateFile(name, &file); | |||
1998 | if (result == B_NO_ERROR((int)0) && enclosure->content_type) { | |||
1999 | char type[B_MIME_TYPE_LENGTH(((256)-1) - 15)]; | |||
2000 | ||||
2001 | if (!strcasecmp(enclosure->content_type, "message/rfc822")) | |||
2002 | strcpy(type, "text/x-email"); | |||
2003 | else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) | |||
2004 | strcpy(type, "text/plain"); | |||
2005 | else | |||
2006 | strcpy(type, enclosure->content_type); | |||
2007 | ||||
2008 | BNodeInfo info(&file); | |||
2009 | info.SetType(type); | |||
2010 | } | |||
2011 | } | |||
2012 | } else { | |||
2013 | // | |||
2014 | // This file was dragged into the tracker or desktop. The file | |||
2015 | // already exists. | |||
2016 | // | |||
2017 | result = file.SetTo(&dir, name, B_WRITE_ONLY0x0001); | |||
2018 | } | |||
2019 | } | |||
2020 | ||||
2021 | if (enclosure->component == NULL__null) | |||
2022 | result = B_ERROR(-1); | |||
2023 | ||||
2024 | if (result == B_NO_ERROR((int)0)) { | |||
2025 | // | |||
2026 | // Write the data | |||
2027 | // | |||
2028 | enclosure->component->GetDecodedData(&file); | |||
2029 | ||||
2030 | BEntry entry; | |||
2031 | dir.FindEntry(name, &entry); | |||
2032 | entry.GetRef(&enclosure->ref); | |||
2033 | enclosure->have_ref = true; | |||
2034 | enclosure->saved = true; | |||
2035 | entry.GetPath(&path); | |||
2036 | update_mime_info(path.Path(), false, true, | |||
2037 | !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); | |||
2038 | entry.GetNodeRef(&enclosure->node); | |||
2039 | watch_node(&enclosure->node, B_WATCH_NAME, this); | |||
2040 | } | |||
2041 | ||||
2042 | if (result != B_NO_ERROR((int)0)) { | |||
2043 | beep(); | |||
2044 | BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save "BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to save " "the attachment."), "Mail") | |||
2045 | "the attachment.")BLocaleRoster::Default()->GetCatalog()->GetString(("An error occurred trying to save " "the attachment."), "Mail"), B_TRANSLATE("Sorry")BLocaleRoster::Default()->GetCatalog()->GetString(("Sorry" ), "Mail")); | |||
2046 | alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); | |||
2047 | alert->Go(); | |||
2048 | } | |||
2049 | ||||
2050 | return result; | |||
2051 | } | |||
2052 | ||||
2053 | ||||
2054 | void | |||
2055 | TTextView::StopLoad() | |||
2056 | { | |||
2057 | Window()->Unlock(); | |||
2058 | ||||
2059 | thread_info info; | |||
2060 | if (fThread != 0 && get_thread_info(fThread, &info)_get_thread_info((fThread), (&info), sizeof(*(&info)) ) == B_NO_ERROR((int)0)) { | |||
2061 | fStopLoading = true; | |||
2062 | acquire_sem(fStopSem); | |||
2063 | int32 result; | |||
2064 | wait_for_thread(fThread, &result); | |||
2065 | fThread = 0; | |||
2066 | release_sem(fStopSem); | |||
2067 | fStopLoading = false; | |||
2068 | } | |||
2069 | ||||
2070 | Window()->Lock(); | |||
2071 | } | |||
2072 | ||||
2073 | ||||
2074 | bool | |||
2075 | TTextView::IsReaderThreadRunning() | |||
2076 | { | |||
2077 | if (fThread == 0) | |||
2078 | return false; | |||
2079 | ||||
2080 | thread_info info; | |||
2081 | for (int i = 5; i > 0; i--, usleep(100000)) | |||
2082 | if (get_thread_info(fThread, &info)_get_thread_info((fThread), (&info), sizeof(*(&info)) ) != B_OK((int)0)) | |||
2083 | return false; | |||
2084 | return true; | |||
2085 | } | |||
2086 | ||||
2087 | ||||
2088 | void | |||
2089 | TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) | |||
2090 | { | |||
2091 | if (mail == NULL__null) | |||
2092 | return; | |||
2093 | ||||
2094 | int32 textLength = TextLength(); | |||
2095 | const char *text = Text(); | |||
2096 | ||||
2097 | BTextMailComponent *body = mail->Body(); | |||
2098 | if (body == NULL__null) { | |||
2099 | if (mail->SetBody(body = new BTextMailComponent()) < B_OK((int)0)) | |||
2100 | return; | |||
2101 | } | |||
2102 | body->SetEncoding(encoding, charset); | |||
2103 | ||||
2104 | // Just add the text as a whole if we can, or ... | |||
2105 | if (!wrap) { | |||
2106 | body->AppendText(text); | |||
2107 | return; | |||
2108 | } | |||
2109 | ||||
2110 | // ... do word wrapping. | |||
2111 | ||||
2112 | BWindow *window = Window(); | |||
2113 | char *saveText = strdup(text); | |||
2114 | BRect saveTextRect = TextRect(); | |||
2115 | ||||
2116 | // do this before we start messing with the fonts | |||
2117 | // the user will never know... | |||
2118 | window->DisableUpdates(); | |||
2119 | Hide(); | |||
2120 | BScrollBar *vScroller = ScrollBar(B_VERTICAL); | |||
2121 | BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); | |||
2122 | if (vScroller != NULL__null) | |||
2123 | vScroller->SetTarget((BView *)NULL__null); | |||
2124 | if (hScroller != NULL__null) | |||
2125 | hScroller->SetTarget((BView *)NULL__null); | |||
2126 | ||||
2127 | // Temporarily set the font to a fixed width font for line wrapping | |||
2128 | // calculations. If the font doesn't have as many of the symbols as | |||
2129 | // the preferred font, go back to using the user's preferred font. | |||
2130 | ||||
2131 | bool *boolArray; | |||
2132 | int missingCharactersFixedWidth = 0; | |||
2133 | int missingCharactersPreferredFont = 0; | |||
2134 | int32 numberOfCharacters; | |||
2135 | ||||
2136 | numberOfCharacters = BString(text).CountChars(); | |||
2137 | if (numberOfCharacters > 0 | |||
2138 | && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL__null) { | |||
2139 | memset(boolArray, 0, sizeof (bool) * numberOfCharacters); | |||
2140 | be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); | |||
2141 | for (int i = 0; i < numberOfCharacters; i++) { | |||
2142 | if (!boolArray[i]) | |||
2143 | missingCharactersFixedWidth += 1; | |||
2144 | } | |||
2145 | ||||
2146 | memset(boolArray, 0, sizeof (bool) * numberOfCharacters); | |||
2147 | fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); | |||
2148 | for (int i = 0; i < numberOfCharacters; i++) { | |||
2149 | if (!boolArray[i]) | |||
2150 | missingCharactersPreferredFont += 1; | |||
2151 | } | |||
2152 | ||||
2153 | free(boolArray); | |||
2154 | } | |||
2155 | ||||
2156 | if (missingCharactersFixedWidth > missingCharactersPreferredFont) | |||
2157 | SetFontAndColor(0, textLength, &fFont); | |||
2158 | else // All things being equal, the fixed font is better for wrapping. | |||
2159 | SetFontAndColor(0, textLength, be_fixed_font); | |||
2160 | ||||
2161 | // calculate a text rect that is 72 columns wide | |||
2162 | BRect newTextRect = saveTextRect; | |||
2163 | newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; | |||
2164 | SetTextRect(newTextRect); | |||
2165 | ||||
2166 | // hard-wrap, based on TextView's soft-wrapping | |||
2167 | int32 numLines = CountLines(); | |||
2168 | bool spaceMoved = false; | |||
2169 | char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need | |||
2170 | if (content != NULL__null) { | |||
2171 | int32 contentLength = 0; | |||
2172 | ||||
2173 | for (int32 i = 0; i < numLines; i++) { | |||
2174 | int32 startOffset = OffsetAt(i); | |||
2175 | if (spaceMoved) { | |||
2176 | startOffset++; | |||
2177 | spaceMoved = false; | |||
2178 | } | |||
2179 | int32 endOffset = OffsetAt(i + 1); | |||
2180 | int32 lineLength = endOffset - startOffset; | |||
2181 | ||||
2182 | // quick hack to not break URLs into several parts | |||
2183 | for (int32 pos = startOffset; pos < endOffset; pos++) { | |||
2184 | size_t urlLength; | |||
2185 | uint8 type = CheckForURL(text + pos, urlLength); | |||
2186 | if (type != 0) | |||
2187 | pos += urlLength; | |||
2188 | ||||
2189 | if (pos > endOffset) { | |||
2190 | // find first break character after the URL | |||
2191 | for (; text[pos]; pos++) { | |||
2192 | if (isalnum(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISalnum ) || isspace(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISspace )) | |||
2193 | break; | |||
2194 | } | |||
2195 | if (text[pos] && isspace(text[pos])(__ctype_b[(int)((text[pos]))] & (unsigned short int)_ISspace ) && text[pos] != '\n') | |||
2196 | pos++; | |||
2197 | ||||
2198 | endOffset += pos - endOffset; | |||
2199 | lineLength = endOffset - startOffset; | |||
2200 | ||||
2201 | // insert a newline (and the same number of quotes) after the | |||
2202 | // URL to make sure the rest of the text is properly wrapped | |||
2203 | ||||
2204 | char buffer[64]; | |||
2205 | if (text[pos] == '\n') | |||
2206 | buffer[0] = '\0'; | |||
2207 | else | |||
2208 | strcpy(buffer, "\n"); | |||
2209 | ||||
2210 | size_t quoteLength; | |||
2211 | CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); | |||
2212 | ||||
2213 | Insert(pos, buffer, strlen(buffer)); | |||
2214 | numLines = CountLines(); | |||
2215 | text = Text(); | |||
2216 | i++; | |||
2217 | } | |||
2218 | } | |||
2219 | if (text[endOffset - 1] != ' ' | |||
2220 | && text[endOffset - 1] != '\n' | |||
2221 | && text[endOffset] == ' ') { | |||
2222 | // make sure spaces will be part of this line | |||
2223 | endOffset++; | |||
2224 | lineLength++; | |||
2225 | spaceMoved = true; | |||
2226 | } | |||
2227 | ||||
2228 | memcpy(content + contentLength, text + startOffset, lineLength); | |||
2229 | contentLength += lineLength; | |||
2230 | ||||
2231 | // add a newline to every line except for the ones | |||
2232 | // that already end in newlines, and the last line | |||
2233 | if ((text[endOffset - 1] != '\n') && (i < numLines - 1)) { | |||
2234 | content[contentLength++] = '\n'; | |||
2235 | ||||
2236 | // copy quote level of the first line | |||
2237 | size_t quoteLength; | |||
2238 | CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); | |||
2239 | contentLength += quoteLength; | |||
2240 | } | |||
2241 | } | |||
2242 | content[contentLength] = '\0'; | |||
2243 | ||||
2244 | body->AppendText(content); | |||
2245 | free(content); | |||
2246 | } | |||
2247 | ||||
2248 | // reset the text rect and font | |||
2249 | SetTextRect(saveTextRect); | |||
2250 | SetText(saveText); | |||
2251 | free(saveText); | |||
2252 | SetFontAndColor(0, textLength, &fFont); | |||
2253 | ||||
2254 | // should be OK to hook these back up now | |||
2255 | if (vScroller != NULL__null) | |||
2256 | vScroller->SetTarget(this); | |||
2257 | if (hScroller != NULL__null) | |||
2258 | hScroller->SetTarget(this); | |||
2259 | ||||
2260 | Show(); | |||
2261 | window->EnableUpdates(); | |||
2262 | } | |||
2263 | ||||
2264 | ||||
2265 | // #pragma mark - | |||
2266 | ||||
2267 | ||||
2268 | TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, | |||
2269 | bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail, | |||
2270 | BList *list, sem_id sem) | |||
2271 | : | |||
2272 | fHeader(header), | |||
2273 | fRaw(raw), | |||
2274 | fQuote(quote), | |||
2275 | fIncoming(incoming), | |||
2276 | fStripHeader(stripHeader), | |||
2277 | fMime(mime), | |||
2278 | fView(view), | |||
2279 | fMail(mail), | |||
2280 | fEnclosures(list), | |||
2281 | fStopSem(sem) | |||
2282 | { | |||
2283 | } | |||
2284 | ||||
2285 | ||||
2286 | bool | |||
2287 | TTextView::Reader::ParseMail(BMailContainer *container, | |||
2288 | BTextMailComponent *ignore) | |||
2289 | { | |||
2290 | int32 count = 0; | |||
2291 | for (int32 i = 0; i < container->CountComponents(); i++) { | |||
2292 | if (fView->fStopLoading) | |||
2293 | return false; | |||
2294 | ||||
2295 | BMailComponent *component; | |||
2296 | if ((component = container->GetComponent(i)) == NULL__null) { | |||
2297 | if (fView->fStopLoading) | |||
2298 | return false; | |||
2299 | ||||
2300 | hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); | |||
2301 | if (enclosure == NULL__null) | |||
2302 | return false; | |||
2303 | ||||
2304 | memset(enclosure, 0, sizeof(hyper_text)); | |||
2305 | ||||
2306 | enclosure->type = TYPE_ENCLOSURE; | |||
2307 | ||||
2308 | const char *name = "\n<Attachment: could not handle>\n"; | |||
2309 | ||||
2310 | fView->GetSelection(&enclosure->text_start, &enclosure->text_end); | |||
2311 | enclosure->text_start++; | |||
2312 | enclosure->text_end += strlen(name) - 1; | |||
2313 | ||||
2314 | Insert(name, strlen(name), true); | |||
2315 | fEnclosures->AddItem(enclosure); | |||
2316 | continue; | |||
2317 | } | |||
2318 | ||||
2319 | count++; | |||
2320 | if (component == ignore) | |||
2321 | continue; | |||
2322 | ||||
2323 | if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { | |||
2324 | BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i)); | |||
2325 | ASSERT(c != NULL)(void)0; | |||
2326 | ||||
2327 | if (!ParseMail(c, ignore)) | |||
2328 | count--; | |||
2329 | } else if (fIncoming) { | |||
2330 | hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); | |||
2331 | if (enclosure == NULL__null) | |||
2332 | return false; | |||
2333 | ||||
2334 | memset(enclosure, 0, sizeof(hyper_text)); | |||
2335 | ||||
2336 | enclosure->type = TYPE_ENCLOSURE; | |||
2337 | enclosure->component = component; | |||
2338 | ||||
2339 | BString name; | |||
2340 | char fileName[B_FILE_NAME_LENGTH(256)]; | |||
2341 | strcpy(fileName, "untitled"); | |||
2342 | if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component)) | |||
2343 | attachment->FileName(fileName); | |||
2344 | ||||
2345 | BPath path(fileName); | |||
2346 | enclosure->name = strdup(path.Leaf()); | |||
2347 | ||||
2348 | BMimeType type; | |||
2349 | component->MIMEType(&type); | |||
2350 | enclosure->content_type = strdup(type.Type()); | |||
2351 | ||||
2352 | char typeDescription[B_MIME_TYPE_LENGTH(((256)-1) - 15)]; | |||
2353 | if (type.GetShortDescription(typeDescription) != B_OK((int)0)) | |||
2354 | strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); | |||
2355 | ||||
2356 | name = "\n<"; | |||
2357 | name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)",BLocaleRoster::Default()->GetCatalog()->GetString(("Enclosure: %name% (Type: %type%)" ), "Mail", ("Don't translate the variables %name% and %type%." )) | |||
2358 | "Don't translate the variables %name% and %type%.")BLocaleRoster::Default()->GetCatalog()->GetString(("Enclosure: %name% (Type: %type%)" ), "Mail", ("Don't translate the variables %name% and %type%." ))); | |||
2359 | name.Append(">\n"); | |||
2360 | name.ReplaceFirst("%name%", enclosure->name); | |||
2361 | name.ReplaceFirst("%type%", typeDescription); | |||
2362 | ||||
2363 | fView->GetSelection(&enclosure->text_start, &enclosure->text_end); | |||
2364 | enclosure->text_start++; | |||
2365 | enclosure->text_end += strlen(name.String()) - 1; | |||
2366 | ||||
2367 | Insert(name.String(), name.Length(), true); | |||
2368 | fEnclosures->AddItem(enclosure); | |||
2369 | } | |||
2370 | // default: | |||
2371 | // { | |||
2372 | // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i)); | |||
2373 | // const char *text; | |||
2374 | // if (body && (text = body->Text()) != NULL) | |||
2375 | // Insert(text, strlen(text), false); | |||
2376 | // } | |||
2377 | } | |||
2378 | return count > 0; | |||
2379 | } | |||
2380 | ||||
2381 | ||||
2382 | bool | |||
2383 | TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) | |||
2384 | { | |||
2385 | char line[522]; | |||
2386 | int32 count = 0; | |||
2387 | ||||
2388 | for (int32 loop = 0; loop < data_len; loop++) { | |||
2389 | if (fView->fStopLoading) | |||
2390 | return false; | |||
2391 | ||||
2392 | if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { | |||
2393 | strcpy(&line[count], QUOTE"> "); | |||
2394 | count += strlen(QUOTE"> "); | |||
2395 | } | |||
2396 | if (!fRaw && fIncoming && (loop < data_len - 7)) { | |||
2397 | size_t urlLength; | |||
2398 | BString url; | |||
2399 | uint8 type = CheckForURL(data + loop, urlLength, &url); | |||
2400 | ||||
2401 | if (type) { | |||
2402 | if (!Insert(line, count, false, isHeader)) | |||
2403 | return false; | |||
2404 | count = 0; | |||
2405 | ||||
2406 | hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); | |||
2407 | if (enclosure == NULL__null) | |||
2408 | return false; | |||
2409 | ||||
2410 | memset(enclosure, 0, sizeof(hyper_text)); | |||
2411 | fView->GetSelection(&enclosure->text_start, | |||
2412 | &enclosure->text_end); | |||
2413 | enclosure->type = type; | |||
2414 | enclosure->name = strdup(url.String()); | |||
2415 | if (enclosure->name == NULL__null) { | |||
2416 | free(enclosure); | |||
2417 | return false; | |||
2418 | } | |||
2419 | ||||
2420 | Insert(&data[loop], urlLength, true, isHeader); | |||
2421 | enclosure->text_end += urlLength; | |||
2422 | loop += urlLength - 1; | |||
2423 | ||||
2424 | fEnclosures->AddItem(enclosure); | |||
2425 | continue; | |||
2426 | } | |||
2427 | } | |||
2428 | if (!fRaw && fMime && data[loop] == '=') { | |||
2429 | if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) | |||
2430 | loop += 2; | |||
2431 | else | |||
2432 | line[count++] = data[loop]; | |||
2433 | } else if (data[loop] != '\r') | |||
2434 | line[count++] = data[loop]; | |||
2435 | ||||
2436 | if (count > 511 || (count && loop == data_len - 1)) { | |||
2437 | if (!Insert(line, count, false, isHeader)) | |||
2438 | return false; | |||
2439 | count = 0; | |||
2440 | } | |||
2441 | } | |||
2442 | return true; | |||
2443 | } | |||
2444 | ||||
2445 | ||||
2446 | bool | |||
2447 | TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, | |||
2448 | bool isHeader) | |||
2449 | { | |||
2450 | if (!count) | |||
2451 | return true; | |||
2452 | ||||
2453 | BFont font(fView->Font()); | |||
2454 | TextRunArray style(count / 8 + 8); | |||
2455 | ||||
2456 | if (fView->fColoredQuotes && !isHeader && !isHyperLink) { | |||
2457 | FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font, | |||
2458 | &style.Array(), style.MaxEntries()); | |||
2459 | } else { | |||
2460 | text_run_array &array = style.Array(); | |||
2461 | array.count = 1; | |||
2462 | array.runs[0].offset = 0; | |||
2463 | if (isHeader) { | |||
2464 | array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; | |||
2465 | font.SetSize(font.Size() * 0.9); | |||
2466 | } else { | |||
2467 | array.runs[0].color = isHyperLink | |||
2468 | ? kHyperLinkColor : kNormalTextColor; | |||
2469 | } | |||
2470 | array.runs[0].font = font; | |||
2471 | } | |||
2472 | ||||
2473 | if (!fView->Window()->Lock()) | |||
2474 | return false; | |||
2475 | ||||
2476 | fView->Insert(fView->TextLength(), line, count, &style.Array()); | |||
2477 | ||||
2478 | fView->Window()->Unlock(); | |||
2479 | return true; | |||
2480 | } | |||
2481 | ||||
2482 | ||||
2483 | status_t | |||
2484 | TTextView::Reader::Run(void *_this) | |||
2485 | { | |||
2486 | Reader *reader = (Reader *)_this; | |||
2487 | TTextView *view = reader->fView; | |||
2488 | char *msg = NULL__null; | |||
2489 | off_t size = 0; | |||
2490 | int32 len = 0; | |||
2491 | ||||
2492 | if (!reader->Lock()) | |||
2493 | return B_INTERRUPTED((-2147483647 - 1) + 10); | |||
2494 | ||||
2495 | BFile *file = dynamic_cast<BFile *>(reader->fMail->Data()); | |||
2496 | if (file != NULL__null) { | |||
2497 | len = header_len(file); | |||
2498 | ||||
2499 | if (reader->fHeader) | |||
2500 | size = len; | |||
2501 | if (reader->fRaw || !reader->fMime) | |||
2502 | file->GetSize(&size); | |||
2503 | ||||
2504 | if (size != 0 && (msg = (char *)malloc(size)) == NULL__null) | |||
2505 | goto done; | |||
2506 | file->Seek(0, 0); | |||
2507 | ||||
2508 | if (msg) | |||
2509 | size = file->Read(msg, size); | |||
2510 | } | |||
2511 | ||||
2512 | // show the header? | |||
2513 | if (reader->fHeader && len) { | |||
2514 | // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" | |||
2515 | if (reader->fStripHeader) { | |||
2516 | const char *header = msg; | |||
2517 | char *buffer = NULL__null; | |||
2518 | ||||
2519 | while (strncmp(header, "\r\n", 2)) { | |||
2520 | const char *eol = header; | |||
2521 | while ((eol = strstr(eol, "\r\n")) != NULL__null && isspace(eol[2])(__ctype_b[(int)((eol[2]))] & (unsigned short int)_ISspace )) | |||
2522 | eol += 2; | |||
2523 | if (eol == NULL__null) | |||
2524 | break; | |||
2525 | ||||
2526 | eol += 2; // CR+LF belong to the line | |||
2527 | size_t length = eol - header; | |||
2528 | ||||
2529 | buffer = (char *)realloc(buffer, length + 1); | |||
2530 | if (buffer == NULL__null) | |||
2531 | goto done; | |||
2532 | ||||
2533 | memcpy(buffer, header, length); | |||
2534 | ||||
2535 | length = rfc2047_to_utf8(&buffer, &length, length); | |||
2536 | ||||
2537 | if (!strncasecmp(header, "Reply-To: ", 10) | |||
2538 | || !strncasecmp(header, "To: ", 4) | |||
2539 | || !strncasecmp(header, "From: ", 6) | |||
2540 | || !strncasecmp(header, "Subject: ", 8) | |||
2541 | || !strncasecmp(header, "Date: ", 6)) | |||
2542 | reader->Process(buffer, length, true); | |||
2543 | ||||
2544 | header = eol; | |||
2545 | } | |||
2546 | free(buffer); | |||
2547 | reader->Process("\r\n", 2, true); | |||
2548 | } | |||
2549 | else if (!reader->Process(msg, len, true)) | |||
2550 | goto done; | |||
2551 | } | |||
2552 | ||||
2553 | if (reader->fRaw) { | |||
2554 | if (!reader->Process((const char *)msg + len, size - len)) | |||
2555 | goto done; | |||
2556 | } else { | |||
2557 | //reader->fFile->Seek(0, 0); | |||
2558 | //BEmailMessage *mail = new BEmailMessage(reader->fFile); | |||
2559 | BEmailMessage *mail = reader->fMail; | |||
2560 | ||||
2561 | // at first, insert the mail body | |||
2562 | BTextMailComponent *body = NULL__null; | |||
2563 | if (mail->BodyText() && !view->fStopLoading) { | |||
2564 | char *bodyText = const_cast<char *>(mail->BodyText()); | |||
2565 | int32 bodyLength = strlen(bodyText); | |||
2566 | body = mail->Body(); | |||
2567 | bool isHTML = false; | |||
2568 | ||||
2569 | BMimeType type; | |||
2570 | if (body->MIMEType(&type) == B_OK((int)0) && type == "text/html") { | |||
2571 | // strip out HTML tags | |||
2572 | char *t = bodyText, *a, *end = bodyText + bodyLength; | |||
2573 | bodyText = (char *)malloc(bodyLength + 1); | |||
2574 | isHTML = true; | |||
2575 | ||||
2576 | // TODO: is it correct to assume that the text is in Latin-1? | |||
2577 | // because if it isn't, the code below won't work correctly... | |||
2578 | ||||
2579 | for (a = bodyText; t < end; t++) { | |||
2580 | int32 c = *t; | |||
2581 | ||||
2582 | // compact spaces | |||
2583 | bool space = false; | |||
2584 | while (c && (c == ' ' || c == '\t')) { | |||
2585 | c = *(++t); | |||
2586 | space = true; | |||
2587 | } | |||
2588 | if (space) { | |||
2589 | c = ' '; | |||
2590 | t--; | |||
2591 | } else if (FilterHTMLTag(c, &t, end)) // the tag filter | |||
2592 | continue; | |||
2593 | ||||
2594 | Unicode2UTF8(c, &a); | |||
2595 | } | |||
2596 | ||||
2597 | *a = 0; | |||
2598 | bodyLength = strlen(bodyText); | |||
2599 | body = NULL__null; // to add the HTML text as enclosure | |||
2600 | } | |||
2601 | if (!reader->Process(bodyText, bodyLength)) | |||
2602 | goto done; | |||
2603 | ||||
2604 | if (isHTML) | |||
2605 | free(bodyText); | |||
2606 | } | |||
2607 | ||||
2608 | if (!reader->ParseMail(mail, body)) | |||
2609 | goto done; | |||
2610 | ||||
2611 | //reader->fView->fMail = mail; | |||
2612 | } | |||
2613 | ||||
2614 | if (!view->fStopLoading && view->Window()->Lock()) { | |||
2615 | view->Select(0, 0); | |||
2616 | view->MakeSelectable(true); | |||
2617 | if (!reader->fIncoming) | |||
2618 | view->MakeEditable(true); | |||
2619 | ||||
2620 | view->Window()->Unlock(); | |||
2621 | } | |||
2622 | ||||
2623 | done: | |||
2624 | // restore the reading position if available | |||
2625 | view->Window()->PostMessage(M_READ_POS); | |||
2626 | ||||
2627 | reader->Unlock(); | |||
2628 | ||||
2629 | delete reader; | |||
2630 | free(msg); | |||
2631 | ||||
2632 | return B_NO_ERROR((int)0); | |||
2633 | } | |||
2634 | ||||
2635 | ||||
2636 | status_t | |||
2637 | TTextView::Reader::Unlock() | |||
2638 | { | |||
2639 | return release_sem(fStopSem); | |||
2640 | } | |||
2641 | ||||
2642 | ||||
2643 | bool | |||
2644 | TTextView::Reader::Lock() | |||
2645 | { | |||
2646 | if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR((int)0)) | |||
2647 | return false; | |||
2648 | ||||
2649 | return true; | |||
2650 | } | |||
2651 | ||||
2652 | ||||
2653 | //==================================================================== | |||
2654 | // #pragma mark - | |||
2655 | ||||
2656 | ||||
2657 | TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) | |||
2658 | : BFilePanel(B_SAVE_PANEL) | |||
2659 | { | |||
2660 | fEnclosure = enclosure; | |||
2661 | fView = view; | |||
2662 | if (enclosure->name) | |||
2663 | SetSaveText(enclosure->name); | |||
2664 | } | |||
2665 | ||||
2666 | ||||
2667 | void | |||
2668 | TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) | |||
2669 | { | |||
2670 | const char *name = NULL__null; | |||
2671 | BMessage save(M_SAVE); | |||
2672 | entry_ref ref; | |||
2673 | ||||
2674 | if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { | |||
2675 | save.AddPointer("enclosure", fEnclosure); | |||
2676 | save.AddString("name", name); | |||
2677 | save.AddRef("directory", &ref); | |||
2678 | fView->Window()->PostMessage(&save, fView); | |||
2679 | } | |||
2680 | } | |||
2681 | ||||
2682 | ||||
2683 | void | |||
2684 | TSavePanel::SetEnclosure(hyper_text *enclosure) | |||
2685 | { | |||
2686 | fEnclosure = enclosure; | |||
2687 | if (enclosure->name) | |||
2688 | SetSaveText(enclosure->name); | |||
2689 | else | |||
2690 | SetSaveText(""); | |||
2691 | ||||
2692 | if (!IsShowing()) | |||
2693 | Show(); | |||
2694 | Window()->Activate(); | |||
2695 | } | |||
2696 | ||||
2697 | ||||
2698 | //-------------------------------------------------------------------- | |||
2699 | // #pragma mark - | |||
2700 | ||||
2701 | ||||
2702 | void | |||
2703 | TTextView::InsertText(const char *insertText, int32 length, int32 offset, | |||
2704 | const text_run_array *runs) | |||
2705 | { | |||
2706 | ContentChanged(); | |||
2707 | ||||
2708 | // Undo function | |||
2709 | ||||
2710 | int32 cursorPos, dummy; | |||
2711 | GetSelection(&cursorPos, &dummy); | |||
2712 | ||||
2713 | if (fInputMethodUndoState.active) { | |||
2714 | // IMアクティブ時は、一旦別のバッファへ記憶 | |||
2715 | fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); | |||
2716 | fInputMethodUndoState.replace = false; | |||
2717 | } else { | |||
2718 | if (fUndoState.replaced) { | |||
2719 | fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); | |||
2720 | } else { | |||
2721 | if (length == 1 && insertText[0] == 0x0a) | |||
2722 | fUndoBuffer.MakeNewUndoItem(); | |||
2723 | ||||
2724 | fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); | |||
2725 | ||||
2726 | if (length == 1 && insertText[0] == 0x0a) | |||
2727 | fUndoBuffer.MakeNewUndoItem(); | |||
2728 | } | |||
2729 | } | |||
2730 | ||||
2731 | fUndoState.replaced = false; | |||
2732 | fUndoState.deleted = false; | |||
2733 | ||||
2734 | struct text_runs : text_run_array { text_run _runs[1]; } style; | |||
2735 | if (runs == NULL__null && IsEditable()) { | |||
2736 | style.count = 1; | |||
2737 | style.runs[0].offset = 0; | |||
2738 | style.runs[0].font = fFont; | |||
2739 | style.runs[0].color = kNormalTextColor; | |||
2740 | runs = &style; | |||
2741 | } | |||
2742 | ||||
2743 | BTextView::InsertText(insertText, length, offset, runs); | |||
2744 | ||||
2745 | if (fSpellCheck && IsEditable()) | |||
2746 | { | |||
2747 | UpdateSpellMarks(offset, length); | |||
2748 | ||||
2749 | rgb_color color; | |||
2750 | GetFontAndColor(offset - 1, NULL__null, &color); | |||
2751 | const char *text = Text(); | |||
2752 | ||||
2753 | if (length > 1 | |||
2754 | || isalpha(text[offset + 1])(__ctype_b[(int)((text[offset + 1]))] & (unsigned short int )_ISalpha) | |||
2755 | || (!isalpha(text[offset])(__ctype_b[(int)((text[offset]))] & (unsigned short int)_ISalpha ) && text[offset] != '\'') | |||
2756 | || (color.red == kSpellTextColor.red | |||
2757 | && color.green == kSpellTextColor.green | |||
2758 | && color.blue == kSpellTextColor.blue)) | |||
2759 | { | |||
2760 | int32 start, end; | |||
2761 | FindSpellBoundry(length, offset, &start, &end); | |||
2762 | ||||
2763 | DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end));; | |||
2764 | DSPELL(printf("\t\"%10.10s...\"\n", text + start));; | |||
2765 | ||||
2766 | CheckSpelling(start, end); | |||
2767 | } | |||
2768 | } | |||
2769 | } | |||
2770 | ||||
2771 | ||||
2772 | void | |||
2773 | TTextView::DeleteText(int32 start, int32 finish) | |||
2774 | { | |||
2775 | ContentChanged(); | |||
2776 | ||||
2777 | // Undo function | |||
2778 | int32 cursorPos, dummy; | |||
2779 | GetSelection(&cursorPos, &dummy); | |||
2780 | if (fInputMethodUndoState.active) { | |||
2781 | if (fInputMethodUndoState.replace) { | |||
2782 | fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); | |||
2783 | fInputMethodUndoState.replace = false; | |||
2784 | } else { | |||
2785 | fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, | |||
2786 | K_DELETED, cursorPos); | |||
2787 | } | |||
2788 | } else | |||
2789 | fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); | |||
2790 | ||||
2791 | fUndoState.deleted = true; | |||
2792 | fUndoState.replaced = true; | |||
2793 | ||||
2794 | BTextView::DeleteText(start, finish); | |||
2795 | if (fSpellCheck && IsEditable()) { | |||
2796 | UpdateSpellMarks(start, start - finish); | |||
2797 | ||||
2798 | int32 s, e; | |||
2799 | FindSpellBoundry(1, start, &s, &e); | |||
2800 | CheckSpelling(s, e); | |||
2801 | } | |||
2802 | } | |||
2803 | ||||
2804 | ||||
2805 | void | |||
2806 | TTextView::ContentChanged(void) | |||
2807 | { | |||
2808 | BLooper *looper = Looper(); | |||
2809 | if (looper == NULL__null) | |||
2810 | return; | |||
2811 | ||||
2812 | BMessage msg(FIELD_CHANGED); | |||
2813 | msg.AddInt32("bitmask", FIELD_BODY); | |||
2814 | msg.AddPointer("source", this); | |||
2815 | looper->PostMessage(&msg); | |||
2816 | } | |||
2817 | ||||
2818 | ||||
2819 | void | |||
2820 | TTextView::CheckSpelling(int32 start, int32 end, int32 flags) | |||
2821 | { | |||
2822 | const char *text = Text(); | |||
2823 | const char *next, *endPtr, *word = NULL__null; | |||
2824 | int32 wordLength = 0, wordOffset; | |||
2825 | int32 nextHighlight = start; | |||
2826 | BString testWord; | |||
2827 | bool isCap = false; | |||
2828 | bool isAlpha; | |||
2829 | bool isApost; | |||
2830 | ||||
2831 | for (next = text + start, endPtr = text + end; next <= endPtr; next++) { | |||
2832 | //printf("next=%c\n", *next); | |||
2833 | // ToDo: this has to be refined to other languages... | |||
2834 | // Alpha signifies the start of a word | |||
2835 | isAlpha = isalpha(*next)(__ctype_b[(int)((*next))] & (unsigned short int)_ISalpha ); | |||
2836 | isApost = (*next == '\''); | |||
2837 | if (!word && isAlpha) { | |||
2838 | //printf("Found word start\n"); | |||
2839 | word = next; | |||
2840 | wordLength++; | |||
2841 | isCap = isupper(*word)(__ctype_b[(int)((*word))] & (unsigned short int)_ISupper ); | |||
2842 | } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])(__ctype_b[(int)((next[1]))] & (unsigned short int)_ISalpha )) | |||
2843 | && !(isCap && isApost && (next[1] == 's'))) { | |||
2844 | // Word continues check | |||
2845 | wordLength++; | |||
2846 | //printf("Word continues...\n"); | |||
2847 | } else if (word) { | |||
2848 | // End of word reached | |||
2849 | ||||
2850 | //printf("Word End\n"); | |||
2851 | // Don't check single characters | |||
2852 | if (wordLength > 1) { | |||
2853 | bool isUpper = true; | |||
2854 | ||||
2855 | // Look for all uppercase | |||
2856 | for (int32 i = 0; i < wordLength; i++) { | |||
2857 | if (word[i] == '\'') | |||
2858 | break; | |||
2859 | ||||
2860 | if (islower(word[i])(__ctype_b[(int)((word[i]))] & (unsigned short int)_ISlower )) { | |||
2861 | isUpper = false; | |||
2862 | break; | |||
2863 | } | |||
2864 | } | |||
2865 | ||||
2866 | // Don't check all uppercase words | |||
2867 | if (!isUpper) { | |||
2868 | bool foundMatch = false; | |||
2869 | wordOffset = word - text; | |||
2870 | testWord.SetTo(word, wordLength); | |||
2871 | ||||
2872 | testWord = testWord.ToLower(); | |||
2873 | DSPELL(printf("Testing: \"%s\"\n", testWord.String()));; | |||
2874 | ||||
2875 | int32 key = -1; | |||
2876 | if (gDictCount) | |||
2877 | key = gExactWords[0]->GetKey(testWord.String()); | |||
2878 | ||||
2879 | // Search all dictionaries | |||
2880 | for (int32 i = 0; i < gDictCount; i++) { | |||
2881 | if (gExactWords[i]->Lookup(key) >= 0) { | |||
2882 | foundMatch = true; | |||
2883 | break; | |||
2884 | } | |||
2885 | } | |||
2886 | ||||
2887 | if (!foundMatch) { | |||
2888 | if (flags & S_CLEAR_ERRORS) | |||
2889 | RemoveSpellMark(nextHighlight, wordOffset); | |||
2890 | ||||
2891 | if (flags & S_SHOW_ERRORS) | |||
2892 | AddSpellMark(wordOffset, wordOffset + wordLength); | |||
2893 | } else if (flags & S_CLEAR_ERRORS) | |||
2894 | RemoveSpellMark(nextHighlight, wordOffset + wordLength); | |||
2895 | ||||
2896 | nextHighlight = wordOffset + wordLength; | |||
2897 | } | |||
2898 | } | |||
2899 | // Reset state to looking for word | |||
2900 | word = NULL__null; | |||
2901 | wordLength = 0; | |||
2902 | } | |||
2903 | } | |||
2904 | ||||
2905 | if (nextHighlight <= end | |||
2906 | && (flags & S_CLEAR_ERRORS) != 0 | |||
2907 | && nextHighlight < TextLength()) | |||
2908 | SetFontAndColor(nextHighlight, end, NULL__null, B_FONT_ALL, &kNormalTextColor); | |||
2909 | } | |||
2910 | ||||
2911 | ||||
2912 | void | |||
2913 | TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) | |||
2914 | { | |||
2915 | int32 start, end, textLength; | |||
2916 | const char *text = Text(); | |||
2917 | textLength = TextLength(); | |||
2918 | ||||
2919 | for (start = offset - 1; start >= 0 | |||
2920 | && (isalpha(text[start])(__ctype_b[(int)((text[start]))] & (unsigned short int)_ISalpha ) || text[start] == '\''); start--) {} | |||
2921 | ||||
2922 | start++; | |||
2923 | ||||
2924 | for (end = offset + length; end < textLength | |||
2925 | && (isalpha(text[end])(__ctype_b[(int)((text[end]))] & (unsigned short int)_ISalpha ) || text[end] == '\''); end++) {} | |||
2926 | ||||
2927 | *_start = start; | |||
2928 | *_end = end; | |||
2929 | } | |||
2930 | ||||
2931 | ||||
2932 | TTextView::spell_mark * | |||
2933 | TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) | |||
2934 | { | |||
2935 | spell_mark *lastMark = NULL__null; | |||
2936 | ||||
2937 | for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { | |||
2938 | if (spellMark->start < end && spellMark->end > start) { | |||
2939 | if (_previousMark) | |||
2940 | *_previousMark = lastMark; | |||
2941 | return spellMark; | |||
2942 | } | |||
2943 | ||||
2944 | lastMark = spellMark; | |||
2945 | } | |||
2946 | return NULL__null; | |||
2947 | } | |||
2948 | ||||
2949 | ||||
2950 | void | |||
2951 | TTextView::UpdateSpellMarks(int32 offset, int32 length) | |||
2952 | { | |||
2953 | DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length));; | |||
2954 | ||||
2955 | spell_mark *spellMark; | |||
2956 | for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { | |||
2957 | DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));; | |||
2958 | ||||
2959 | if (spellMark->end < offset) | |||
2960 | continue; | |||
2961 | ||||
2962 | if (spellMark->start > offset) | |||
2963 | spellMark->start += length; | |||
2964 | ||||
2965 | spellMark->end += length; | |||
2966 | ||||
2967 | DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end));; | |||
2968 | } | |||
2969 | } | |||
2970 | ||||
2971 | ||||
2972 | status_t | |||
2973 | TTextView::AddSpellMark(int32 start, int32 end) | |||
2974 | { | |||
2975 | DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end));; | |||
2976 | ||||
2977 | // check if there is already a mark for this passage | |||
2978 | spell_mark *spellMark = FindSpellMark(start, end); | |||
2979 | if (spellMark) { | |||
2980 | if (spellMark->start == start && spellMark->end == end) { | |||
2981 | DSPELL(printf("\tfound one\n"));; | |||
2982 | return B_OK((int)0); | |||
2983 | } | |||
2984 | ||||
2985 | DSPELL(printf("\tremove old one\n"));; | |||
2986 | RemoveSpellMark(start, end); | |||
2987 | } | |||
2988 | ||||
2989 | spellMark = (spell_mark *)malloc(sizeof(spell_mark)); | |||
2990 | if (spellMark == NULL__null) | |||
2991 | return B_NO_MEMORY((-2147483647 - 1) + 0); | |||
2992 | ||||
2993 | spellMark->start = start; | |||
2994 | spellMark->end = end; | |||
2995 | spellMark->style = RunArray(start, end); | |||
2996 | ||||
2997 | // set the spell marks appearance | |||
2998 | BFont font(fFont); | |||
2999 | font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); | |||
3000 | SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); | |||
3001 | ||||
3002 | // add it to the queue | |||
3003 | spellMark->next = fFirstSpellMark; | |||
3004 | fFirstSpellMark = spellMark; | |||
3005 | ||||
3006 | return B_OK((int)0); | |||
3007 | } | |||
3008 | ||||
3009 | ||||
3010 | bool | |||
3011 | TTextView::RemoveSpellMark(int32 start, int32 end) | |||
3012 | { | |||
3013 | DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end));; | |||
3014 | ||||
3015 | // find spell mark | |||
3016 | spell_mark *lastMark = NULL__null; | |||
3017 | spell_mark *spellMark = FindSpellMark(start, end, &lastMark); | |||
3018 | if (spellMark == NULL__null) { | |||
3019 | DSPELL(printf("\tnot found!\n"));; | |||
3020 | return false; | |||
3021 | } | |||
3022 | ||||
3023 | DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));; | |||
3024 | ||||
3025 | // dequeue the spell mark | |||
3026 | if (lastMark) | |||
3027 | lastMark->next = spellMark->next; | |||
3028 | else | |||
3029 | fFirstSpellMark = spellMark->next; | |||
3030 | ||||
3031 | if (spellMark->start < start) | |||
3032 | start = spellMark->start; | |||
3033 | if (spellMark->end > end) | |||
3034 | end = spellMark->end; | |||
3035 | ||||
3036 | // reset old text run array | |||
3037 | SetRunArray(start, end, spellMark->style); | |||
3038 | ||||
3039 | free(spellMark->style); | |||
3040 | free(spellMark); | |||
3041 | ||||
3042 | return true; | |||
3043 | } | |||
3044 | ||||
3045 | ||||
3046 | void | |||
3047 | TTextView::RemoveSpellMarks() | |||
3048 | { | |||
3049 | spell_mark *spellMark, *nextMark; | |||
3050 | ||||
3051 | for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { | |||
3052 | nextMark = spellMark->next; | |||
3053 | ||||
3054 | // reset old text run array | |||
3055 | SetRunArray(spellMark->start, spellMark->end, spellMark->style); | |||
3056 | ||||
3057 | free(spellMark->style); | |||
3058 | free(spellMark); | |||
3059 | } | |||
3060 | ||||
3061 | fFirstSpellMark = NULL__null; | |||
3062 | } | |||
3063 | ||||
3064 | ||||
3065 | void | |||
3066 | TTextView::EnableSpellCheck(bool enable) | |||
3067 | { | |||
3068 | if (fSpellCheck == enable) | |||
3069 | return; | |||
3070 | ||||
3071 | fSpellCheck = enable; | |||
3072 | int32 textLength = TextLength(); | |||
3073 | if (fSpellCheck) { | |||
3074 | // work-around for a bug in the BTextView class | |||
3075 | // which causes lots of flicker | |||
3076 | int32 start, end; | |||
3077 | GetSelection(&start, &end); | |||
3078 | if (start != end) | |||
3079 | Select(start, start); | |||
3080 | ||||
3081 | CheckSpelling(0, textLength); | |||
3082 | ||||
3083 | if (start != end) | |||
3084 | Select(start, end); | |||
3085 | } | |||
3086 | else | |||
3087 | RemoveSpellMarks(); | |||
3088 | } | |||
3089 | ||||
3090 | ||||
3091 | void | |||
3092 | TTextView::WindowActivated(bool flag) | |||
3093 | { | |||
3094 | if (!flag) { | |||
3095 | // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 | |||
3096 | // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) | |||
3097 | // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 | |||
3098 | // OpenBeOSで修正されることを願って暫定処置としている。 | |||
3099 | fInputMethodUndoState.active = false; | |||
3100 | // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 | |||
3101 | if (fInputMethodUndoBuffer.CountItems() > 0) { | |||
3102 | KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); | |||
3103 | if (item->History == K_INSERTED) { | |||
3104 | fUndoBuffer.MakeNewUndoItem(); | |||
3105 | fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, | |||
3106 | item->History, item->CursorPos); | |||
3107 | fUndoBuffer.MakeNewUndoItem(); | |||
3108 | } | |||
3109 | fInputMethodUndoBuffer.MakeEmpty(); | |||
3110 | } | |||
3111 | } | |||
3112 | BTextView::WindowActivated(flag); | |||
3113 | } | |||
3114 | ||||
3115 | ||||
3116 | void | |||
3117 | TTextView::AddQuote(int32 start, int32 finish) | |||
3118 | { | |||
3119 | BRect rect = Bounds(); | |||
3120 | ||||
3121 | int32 lineStart; | |||
3122 | GoToLine(CurrentLine()); | |||
3123 | GetSelection(&lineStart, &lineStart); | |||
3124 | ||||
3125 | // make sure that we're changing the whole last line, too | |||
3126 | int32 lineEnd = finish > lineStart ? finish - 1 : finish; | |||
| ||||
3127 | { | |||
3128 | const char *text = Text(); | |||
3129 | while (text[lineEnd] && text[lineEnd] != '\n') | |||
3130 | lineEnd++; | |||
3131 | } | |||
3132 | Select(lineStart, lineEnd); | |||
3133 | ||||
3134 | int32 textLength = lineEnd - lineStart; | |||
3135 | char *text = (char *)malloc(textLength + 1); | |||
3136 | if (text == NULL__null) | |||
3137 | return; | |||
3138 | ||||
3139 | GetText(lineStart, textLength, text); | |||
3140 | ||||
3141 | int32 quoteLength = strlen(QUOTE"> "); | |||
3142 | int32 targetLength = 0; | |||
3143 | char *target = NULL__null; | |||
3144 | int32 lastLine = 0; | |||
3145 | ||||
3146 | for (int32 index = 0; index < textLength; index++) { | |||
3147 | if (text[index] == '\n' || index == textLength - 1) { | |||
3148 | // add quote to this line | |||
3149 | int32 lineLength = index - lastLine + 1; | |||
3150 | ||||
3151 | target = (char *)realloc(target, targetLength + lineLength + quoteLength); | |||
3152 | if (target == NULL__null) { | |||
3153 | // free the old buffer? | |||
3154 | free(text); | |||
| ||||
3155 | return; | |||
3156 | } | |||
3157 | ||||
3158 | // copy the quote sign | |||
3159 | memcpy(&target[targetLength], QUOTE"> ", quoteLength); | |||
3160 | targetLength += quoteLength; | |||
3161 | ||||
3162 | // copy the rest of the line | |||
3163 | memcpy(&target[targetLength], &text[lastLine], lineLength); | |||
3164 | targetLength += lineLength; | |||
3165 | ||||
3166 | lastLine = index + 1; | |||
3167 | } | |||
3168 | } | |||
3169 | ||||
3170 | // replace with quoted text | |||
3171 | free(text); | |||
3172 | Delete(); | |||
3173 | ||||
3174 | if (fColoredQuotes) { | |||
3175 | const BFont *font = Font(); | |||
3176 | TextRunArray style(targetLength / 8 + 8); | |||
3177 | ||||
3178 | FillInQuoteTextRuns(NULL__null, NULL__null, target, targetLength, font, | |||
3179 | &style.Array(), style.MaxEntries()); | |||
3180 | Insert(target, targetLength, &style.Array()); | |||
3181 | } else | |||
3182 | Insert(target, targetLength); | |||
3183 | ||||
3184 | free(target); | |||
3185 | ||||
3186 | // redo the old selection (compute the new start if necessary) | |||
3187 | Select(start + quoteLength, finish + (targetLength - textLength)); | |||
3188 | ||||
3189 | ScrollTo(rect.LeftTop()); | |||
3190 | } | |||
3191 | ||||
3192 | ||||
3193 | void | |||
3194 | TTextView::RemoveQuote(int32 start, int32 finish) | |||
3195 | { | |||
3196 | BRect rect = Bounds(); | |||
3197 | ||||
3198 | GoToLine(CurrentLine()); | |||
3199 | int32 lineStart; | |||
3200 | GetSelection(&lineStart, &lineStart); | |||
3201 | ||||
3202 | // make sure that we're changing the whole last line, too | |||
3203 | int32 lineEnd = finish > lineStart ? finish - 1 : finish; | |||
3204 | const char *text = Text(); | |||
3205 | while (text[lineEnd] && text[lineEnd] != '\n') | |||
3206 | lineEnd++; | |||
3207 | ||||
3208 | Select(lineStart, lineEnd); | |||
3209 | ||||
3210 | int32 length = lineEnd - lineStart; | |||
3211 | char *target = (char *)malloc(length + 1); | |||
3212 | if (target == NULL__null) | |||
3213 | return; | |||
3214 | ||||
3215 | int32 quoteLength = strlen(QUOTE"> "); | |||
3216 | int32 removed = 0; | |||
3217 | text += lineStart; | |||
3218 | ||||
3219 | for (int32 index = 0; index < length;) { | |||
3220 | // find out the length of the current line | |||
3221 | int32 lineLength = 0; | |||
3222 | while (index + lineLength < length && text[lineLength] != '\n') | |||
3223 | lineLength++; | |||
3224 | ||||
3225 | // include the newline to be part of this line | |||
3226 | if (text[lineLength] == '\n' && index + lineLength + 1 < length) | |||
3227 | lineLength++; | |||
3228 | ||||
3229 | if (!strncmp(text, QUOTE"> ", quoteLength)) { | |||
3230 | // remove quote | |||
3231 | length -= quoteLength; | |||
3232 | removed += quoteLength; | |||
3233 | ||||
3234 | lineLength -= quoteLength; | |||
3235 | text += quoteLength; | |||
3236 | } | |||
3237 | ||||
3238 | if (lineLength == 0) { | |||
3239 | target[index] = '\0'; | |||
3240 | break; | |||
3241 | } | |||
3242 | ||||
3243 | memcpy(&target[index], text, lineLength); | |||
3244 | ||||
3245 | text += lineLength; | |||
3246 | index += lineLength; | |||
3247 | } | |||
3248 | ||||
3249 | if (removed) { | |||
3250 | Delete(); | |||
3251 | ||||
3252 | if (fColoredQuotes) { | |||
3253 | const BFont *font = Font(); | |||
3254 | TextRunArray style(length / 8 + 8); | |||
3255 | ||||
3256 | FillInQuoteTextRuns(NULL__null, NULL__null, target, length, font, | |||
3257 | &style.Array(), style.MaxEntries()); | |||
3258 | Insert(target, length, &style.Array()); | |||
3259 | } else | |||
3260 | Insert(target, length); | |||
3261 | ||||
3262 | // redo old selection | |||
3263 | bool noSelection = start == finish; | |||
3264 | ||||
3265 | if (start > lineStart + quoteLength) | |||
3266 | start -= quoteLength; | |||
3267 | else | |||
3268 | start = lineStart; | |||
3269 | ||||
3270 | if (noSelection) | |||
3271 | finish = start; | |||
3272 | else | |||
3273 | finish -= removed; | |||
3274 | } | |||
3275 | ||||
3276 | free(target); | |||
3277 | ||||
3278 | Select(start, finish); | |||
3279 | ScrollTo(rect.LeftTop()); | |||
3280 | } | |||
3281 | ||||
3282 | ||||
3283 | void | |||
3284 | TTextView::Undo(BClipboard */*clipboard*/) | |||
3285 | { | |||
3286 | if (fInputMethodUndoState.active) | |||
3287 | return; | |||
3288 | ||||
3289 | int32 length, offset, cursorPos; | |||
3290 | undo_type history; | |||
3291 | char *text; | |||
3292 | status_t status; | |||
3293 | ||||
3294 | status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); | |||
3295 | if (status == B_OK((int)0)) { | |||
3296 | fUndoBuffer.Off(); | |||
3297 | ||||
3298 | switch (history) { | |||
3299 | case K_INSERTED: | |||
3300 | BTextView::Delete(offset, offset + length); | |||
3301 | Select(offset, offset); | |||
3302 | break; | |||
3303 | ||||
3304 | case K_DELETED: | |||
3305 | BTextView::Insert(offset, text, length); | |||
3306 | Select(offset, offset + length); | |||
3307 | break; | |||
3308 | ||||
3309 | case K_REPLACED: | |||
3310 | BTextView::Delete(offset, offset + length); | |||
3311 | status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); | |||
3312 | if (status == B_OK((int)0) && history == K_DELETED) { | |||
3313 | BTextView::Insert(offset, text, length); | |||
3314 | Select(offset, offset + length); | |||
3315 | } else { | |||
3316 | ::beep(); | |||
3317 | BAlert* alert = new BAlert("", | |||
3318 | B_TRANSLATE("Inconsistency occurred in the undo/redo "BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo " "buffer."), "Mail") | |||
3319 | "buffer.")BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo " "buffer."), "Mail"), B_TRANSLATE("OK")BLocaleRoster::Default()->GetCatalog()->GetString(("OK" ), "Mail")); | |||
3320 | alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); | |||
3321 | alert->Go(); | |||
3322 | } | |||
3323 | break; | |||
3324 | } | |||
3325 | ScrollToSelection(); | |||
3326 | ContentChanged(); | |||
3327 | fUndoBuffer.On(); | |||
3328 | } | |||
3329 | } | |||
3330 | ||||
3331 | ||||
3332 | void | |||
3333 | TTextView::Redo() | |||
3334 | { | |||
3335 | if (fInputMethodUndoState.active) | |||
3336 | return; | |||
3337 | ||||
3338 | int32 length, offset, cursorPos; | |||
3339 | undo_type history; | |||
3340 | char *text; | |||
3341 | status_t status; | |||
3342 | bool replaced; | |||
3343 | ||||
3344 | status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); | |||
3345 | if (status == B_OK((int)0)) { | |||
3346 | fUndoBuffer.Off(); | |||
3347 | ||||
3348 | switch (history) { | |||
3349 | case K_INSERTED: | |||
3350 | BTextView::Insert(offset, text, length); | |||
3351 | Select(offset, offset + length); | |||
3352 | break; | |||
3353 | ||||
3354 | case K_DELETED: | |||
3355 | BTextView::Delete(offset, offset + length); | |||
3356 | if (replaced) { | |||
3357 | fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); | |||
3358 | BTextView::Insert(offset, text, length); | |||
3359 | } | |||
3360 | Select(offset, offset + length); | |||
3361 | break; | |||
3362 | ||||
3363 | case K_REPLACED: | |||
3364 | ::beep(); | |||
3365 | BAlert* alert = new BAlert("", | |||
3366 | B_TRANSLATE("Inconsistency occurred in the undo/redo "BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo " "buffer."), "Mail") | |||
3367 | "buffer.")BLocaleRoster::Default()->GetCatalog()->GetString(("Inconsistency occurred in the undo/redo " "buffer."), "Mail"), B_TRANSLATE("OK")BLocaleRoster::Default()->GetCatalog()->GetString(("OK" ), "Mail")); | |||
3368 | alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); | |||
3369 | alert->Go(); | |||
3370 | break; | |||
3371 | } | |||
3372 | ScrollToSelection(); | |||
3373 | ContentChanged(); | |||
3374 | fUndoBuffer.On(); | |||
3375 | } | |||
3376 | } | |||
3377 |