6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
43 #include <Clipboard.h>
44 #include <ControlLook.h>
52 #include <NodeMonitor.h>
54 #include <PopUpMenu.h>
57 #include <ScrollView.h>
61 #include <MailMessage.h>
62 #include <MailAttachment.h>
63 #include <mail_util.h>
66 #include "MailSupport.h"
67 #include "MailWindow.h"
70 #include "Utilities.h"
75 #define DEBUG_SPELLCHECK 0
83 #define B_TRANSLATION_CONTEXT "Mail"
86 const rgb_color kNormalTextColor
= {0, 0, 0, 255};
87 const rgb_color kSpellTextColor
= {255, 0, 0, 255};
88 const rgb_color kHyperLinkColor
= {0, 0, 255, 255};
89 const rgb_color kHeaderColor
= {72, 72, 72, 255};
91 const rgb_color kQuoteColors
[] = {
92 {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color (blue)
93 {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color (green)
94 {0x80, 0, 0, 0} // 2nd, ... (red)
96 const int32 kNumQuoteColors
= 3;
98 const rgb_color kDiffColors
[] = {
99 {0xb0, 0, 0, 0}, // '-', red
100 {0, 0x90, 0, 0}, // '+', green
101 {0x6a, 0x6a, 0x6a, 0} // '@@', dark grey
104 void Unicode2UTF8(int32 c
, char **out
);
108 IsInitialUTF8Byte(uchar b
)
110 return ((b
& 0xC0) != 0x80);
115 Unicode2UTF8(int32 c
, char **out
)
119 ASSERT(c
< 0x200000);
123 else if (c
< 0x800) {
124 *(s
++) = 0xc0 | (c
>> 6);
125 *(s
++) = 0x80 | (c
& 0x3f);
126 } else if (c
< 0x10000) {
127 *(s
++) = 0xe0 | (c
>> 12);
128 *(s
++) = 0x80 | ((c
>> 6) & 0x3f);
129 *(s
++) = 0x80 | (c
& 0x3f);
130 } else if (c
< 0x200000) {
131 *(s
++) = 0xf0 | (c
>> 18);
132 *(s
++) = 0x80 | ((c
>> 12) & 0x3f);
133 *(s
++) = 0x80 | ((c
>> 6) & 0x3f);
134 *(s
++) = 0x80 | (c
& 0x3f);
141 FilterHTMLTag(int32
&first
, char **t
, char *end
)
143 const char *newlineTags
[] = {
144 "br", "/p", "/div", "/table", "/tr",
149 // check for some common entities (in ISO-Latin-1)
151 // filter out and convert decimal values
152 if (a
[1] == '#' && sscanf(a
+ 2, "%" B_SCNd32
";", &first
) == 1) {
153 t
[0] += strchr(a
, ';') - a
;
157 const struct { const char *name
; int32 code
; } entities
[] = {
158 // this list is sorted alphabetically to be binary searchable
159 // the current implementation doesn't do this, though
161 // "name" is the entity name,
162 // "code" is the corresponding unicode
223 for (int32 i
= 0; entities
[i
].name
; i
++) {
224 // entities are case-sensitive
225 int32 length
= strlen(entities
[i
].name
);
226 if (!strncmp(a
+ 1, entities
[i
].name
, length
)) {
227 t
[0] += length
; // note that the '&' is included here
228 first
= entities
[i
].code
;
240 // is the tag one of the newline tags?
242 bool newline
= false;
243 for (int i
= 0; newlineTags
[i
]; i
++) {
244 int length
= strlen(newlineTags
[i
]);
245 if (!strncasecmp(a
, (char *)newlineTags
[i
], length
) && !isalnum(a
[length
])) {
251 // oh, it's not, so skip it!
253 if (!strncasecmp(a
, "head", 4)) { // skip "head" completely
254 for (; a
[0] && a
< end
; a
++) {
255 // Find the end of the HEAD section, or the start of the BODY,
256 // which happens for some malformed spam.
257 if (strncasecmp (a
, "</head", 6) == 0 ||
258 strncasecmp (a
, "<body", 5) == 0)
263 // skip until tag end
264 while (a
[0] && a
[0] != '>' && a
< end
)
278 /** Returns the type and length of the URL in the string if it is one.
279 * If the "url" string is specified, it will fill it with the complete
280 * URL that might differ from the one in the text itself (i.e. because
281 * of an prepended "http://").
285 CheckForURL(const char *string
, size_t &urlLength
, BString
*url
= NULL
)
287 const char *urlPrefixes
[] = {
302 // Search for URL prefix
305 for (const char **prefix
= urlPrefixes
; *prefix
!= 0; prefix
++) {
306 if (!cistrncmp(string
, *prefix
, strlen(*prefix
))) {
313 // Not a URL? check for "mailto:" or "www."
315 if (type
== 0 && !cistrncmp(string
, "mailto:", 7))
317 if (type
== 0 && !strncmp(string
, "www.", 4)) {
318 // this type will be special cased later (and a http:// is added
319 // for the enclosure address)
323 // check for valid eMail addresses
324 const char *at
= strchr(string
, '@');
326 const char *pos
= string
;
327 bool readName
= false;
328 for (; pos
< at
; pos
++) {
329 // ToDo: are these all allowed characters?
330 if (!isalnum(pos
[0]) && pos
[0] != '_' && pos
[0] != '.' && pos
[0] != '-')
335 if (pos
== at
&& readName
)
343 int32 index
= strcspn(string
, " \t<>)\"\\,\r\n");
345 // filter out some punctuation marks if they are the last character
346 char suffix
= string
[index
- 1];
355 if (type
== TYPE_URL
) {
356 char *parenthesis
= NULL
;
358 // filter out a trailing ')' if there is no left parenthesis before
359 if (string
[index
- 1] == ')') {
360 char *parenthesis
= strchr(string
, '(');
361 if (parenthesis
== NULL
|| parenthesis
> string
+ index
)
365 // filter out a trailing ']' if there is no left bracket before
366 if (parenthesis
== NULL
&& string
[index
- 1] == ']') {
367 char *parenthesis
= strchr(string
, '[');
368 if (parenthesis
== NULL
|| parenthesis
> string
+ index
)
374 // copy the address to the specified string
375 if (type
== TYPE_URL
&& string
[0] == 'w') {
376 // URL starts with "www.", so add the protocol to it
377 url
->SetTo("http://");
378 url
->Append(string
, index
);
379 } else if (type
== TYPE_MAILTO
&& cistrncmp(string
, "mailto:", 7)) {
380 // eMail address had no "mailto:" prefix
381 url
->SetTo("mailto:");
382 url
->Append(string
, index
);
384 url
->SetTo(string
, index
);
393 CopyQuotes(const char *text
, size_t length
, char *outText
, size_t &outLength
)
395 // count qoute level (to be able to wrap quotes correctly)
397 const char *quote
= QUOTE
;
399 for (size_t i
= 0; i
< length
; i
++) {
400 if (text
[i
] == quote
[0])
402 else if (text
[i
] != ' ' && text
[i
] != '\t')
406 // if there are too much quotes, try to preserve the quote color level
408 level
= kNumQuoteColors
* 3 + (level
% kNumQuoteColors
);
410 // copy the quotes to outText
412 const int32 quoteLength
= strlen(QUOTE
);
414 while (level
-- > 0) {
415 strcpy(outText
+ outLength
, QUOTE
);
416 outLength
+= quoteLength
;
433 // everything else ends the diff mode
439 is_quote_char(char c
)
441 return c
== '>' || c
== '|';
445 /*! Fills the specified text_run_array with the correct values for the
447 If "view" is NULL, it will assume that "line" lies on a line break,
448 if not, it will correctly retrieve the number of quotes the current
452 FillInQuoteTextRuns(BTextView
* view
, quote_context
* context
, const char* line
,
453 int32 length
, const BFont
& font
, text_run_array
* style
, int32 maxStyles
)
455 text_run
* runs
= style
->runs
;
456 int32 index
= style
->count
;
461 bool wasDiff
= false;
464 // get index to the beginning of the current line
466 if (context
!= NULL
) {
467 level
= context
->level
;
468 diffMode
= context
->diff_mode
;
469 begin
= context
->begin
;
470 inDiff
= context
->in_diff
;
471 wasDiff
= context
->was_diff
;
472 } else if (view
!= NULL
) {
474 view
->GetSelection(&end
, &end
);
476 begin
= view
->TextLength() == 0
477 || view
->ByteAt(view
->TextLength() - 1) == '\n';
479 // the following line works only reliable when text wrapping is set to
480 // off; so the complicated version actually used here is necessary:
481 // start = view->OffsetAt(view->CurrentLine());
483 const char *text
= view
->Text();
486 // if the text is not the start of a new line, go back
487 // to the first character in the current line
488 for (start
= end
; start
> 0; start
--) {
489 if (text
[start
- 1] == '\n')
494 // get number of nested qoutes for current line
496 if (!begin
&& start
< end
) {
498 // if there was no text in this line, there may come
499 // more nested quotes
501 diffMode
= diff_mode(text
[start
]);
503 for (int32 i
= start
; i
< end
; i
++) {
504 if (is_quote_char(text
[i
]))
506 else if (text
[i
] != ' ' && text
[i
] != '\t') {
515 // skip leading spaces (tabs & newlines aren't allowed here)
516 while (line
[pos
] == ' ')
523 // set styles for all qoute levels in the text to be inserted
525 for (int32 pos
= 0; pos
< length
;) {
527 if (begin
&& is_quote_char(line
[pos
])) {
530 while (pos
< length
&& line
[pos
] != '\n') {
531 // insert style for each quote level
535 for (next
= pos
+ 1; next
< length
; next
++) {
536 if ((search
&& is_quote_char(line
[next
]))
537 || line
[next
] == '\n')
539 else if (search
&& line
[next
] != ' ' && line
[next
] != '\t')
543 runs
[index
].offset
= pos
;
544 runs
[index
].font
= font
;
545 runs
[index
].color
= level
> 0
546 ? kQuoteColors
[level
% kNumQuoteColors
] : kNormalTextColor
;
549 if (++index
>= maxStyles
)
555 inDiff
= !strncmp(&line
[pos
], "--- ", 4);
559 diffMode
= diff_mode(line
[pos
]);
567 runs
[index
].offset
= pos
;
568 runs
[index
].font
= font
;
570 runs
[index
].color
= kDiffColors
[diff_mode('@') - 1];
571 else if (diffMode
<= 0) {
572 runs
[index
].color
= level
> 0
573 ? kQuoteColors
[level
% kNumQuoteColors
] : kNormalTextColor
;
575 runs
[index
].color
= kDiffColors
[diffMode
- 1];
579 for (next
= pos
; next
< length
; next
++) {
580 if (line
[next
] == '\n') {
592 begin
= line
[pos
] == '\n';
599 // skip one leading space (tabs & newlines aren't allowed here)
600 if (!inDiff
&& pos
< length
&& line
[pos
] == ' ')
604 if (index
>= maxStyles
)
607 style
->count
= index
;
610 // update context for next run
611 context
->level
= level
;
612 context
->diff_mode
= diffMode
;
613 context
->begin
= begin
;
614 context
->in_diff
= inDiff
;
615 context
->was_diff
= wasDiff
;
623 TextRunArray::TextRunArray(size_t entries
)
627 fArray
= (text_run_array
*)malloc(sizeof(int32
) + sizeof(text_run
) * entries
);
633 TextRunArray::~TextRunArray()
642 TContentView::TContentView(bool incoming
, BFont
* font
,
643 bool showHeader
, bool coloredQuotes
)
645 BView("m_content", B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
),
649 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
651 BGroupLayout
* layout
= new BGroupLayout(B_VERTICAL
, 0);
654 fTextView
= new TTextView(fIncoming
, this, font
, showHeader
,
657 BScrollView
* scrollView
= new BScrollView("", fTextView
, 0, true, true);
658 scrollView
->SetBorders(BControlLook::B_TOP_BORDER
);
659 AddChild(scrollView
);
664 TContentView::FindString(const char *str
)
674 // Start from current selection or from the beginning of the pool
676 const char *text
= fTextView
->Text();
677 int32 count
= fTextView
->TextLength();
678 fTextView
->GetSelection(&start
, &finish
);
681 if (!count
|| text
== NULL
)
689 char lc
= tolower(str
[0]);
690 char uc
= toupper(str
[0]);
691 for (long i
= start
; i
< count
; i
++) {
692 if (text
[i
] == lc
|| text
[i
] == uc
) {
694 const char *t
= text
+ i
;
695 while (*s
&& (tolower(*s
) == tolower(*t
))) {
707 // Select the text if it worked
710 Window()->Activate();
711 fTextView
->Select(found
, found
+ strlen(str
));
712 fTextView
->ScrollToSelection();
713 fTextView
->MakeFocus(true);
718 text
= fTextView
->Text();
719 count
= fTextView
->TextLength();
730 TContentView::Focus(bool focus
)
732 if (fFocus
!= focus
) {
740 TContentView::MessageReceived(BMessage
*msg
)
746 msg
->FindPointer("font", (void **)&font
);
747 fTextView
->UpdateFont(font
);
748 fTextView
->Invalidate(Bounds());
755 fTextView
->GetSelection(&start
, &finish
);
756 fTextView
->AddQuote(start
, finish
);
762 fTextView
->GetSelection(&start
, &finish
);
763 fTextView
->RemoveQuote(start
, finish
);
769 if (fTextView
->IsReaderThreadRunning()) {
770 // Do not add the signature until the reader thread
771 // is finished. Resubmit the message for later processing
772 Window()->PostMessage(msg
);
777 msg
->FindRef("ref", &ref
);
779 BFile
file(&ref
, B_READ_ONLY
);
780 if (file
.InitCheck() == B_OK
) {
782 fTextView
->GetSelection(&start
, &finish
);
786 if (size
> 32768) // safety against corrupt signatures
789 char *signature
= (char *)malloc(size
);
790 if (signature
== NULL
)
792 ssize_t bytesRead
= file
.Read(signature
, size
);
793 if (bytesRead
< B_OK
) {
798 const char *text
= fTextView
->Text();
799 int32 length
= fTextView
->TextLength();
801 // reserve some empty lines before the signature
802 const char* newLines
= "\n\n\n\n";
803 if (length
&& text
[length
- 1] == '\n')
806 fTextView
->Select(length
, length
);
807 fTextView
->Insert(newLines
, strlen(newLines
));
808 length
+= strlen(newLines
);
810 // append the signature
811 fTextView
->Select(length
, length
);
812 fTextView
->Insert(signature
, bytesRead
);
813 fTextView
->Select(length
, length
+ bytesRead
);
814 fTextView
->ScrollToSelection();
816 // set the editing cursor position
817 fTextView
->Select(length
- 2 , length
- 2);
818 fTextView
->ScrollToSelection();
822 BAlert
* alert
= new BAlert("",
823 B_TRANSLATE("An error occurred trying to open this "
824 "signature."), B_TRANSLATE("Sorry"));
825 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
832 FindString(msg
->FindString("findthis"));
836 BView::MessageReceived(msg
);
844 TTextView::TTextView(bool incoming
, TContentView
*view
,
845 BFont
*font
, bool showHeader
, bool coloredQuotes
)
847 BTextView("", B_WILL_DRAW
| B_NAVIGABLE
),
850 fColoredQuotes(coloredQuotes
),
864 fFirstSpellMark(NULL
)
866 fStopSem
= create_sem(1, "reader_sem");
868 SetInsets(4, 4, 4, 4);
869 // TODO: have some font size related value here
870 // (ideally the same as in BTextControl, etc. from BControlLook)
872 fEnclosures
= new BList();
874 // Enclosure pop up menu
875 fEnclosureMenu
= new BPopUpMenu("Enclosure", false, false);
876 fEnclosureMenu
->SetFont(be_plain_font
);
877 fEnclosureMenu
->AddItem(new BMenuItem(
878 B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS
), new BMessage(M_SAVE
)));
879 fEnclosureMenu
->AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
880 new BMessage(M_OPEN
)));
882 // Hyperlink pop up menu
883 fLinkMenu
= new BPopUpMenu("Link", false, false);
884 fLinkMenu
->SetFont(be_plain_font
);
885 fLinkMenu
->AddItem(new BMenuItem(B_TRANSLATE("Open this link"),
886 new BMessage(M_OPEN
)));
887 fLinkMenu
->AddItem(new BMenuItem(B_TRANSLATE("Copy link location"),
888 new BMessage(M_COPY
)));
894 fInputMethodUndoBuffer
.On();
895 fUndoState
.replaced
= false;
896 fUndoState
.deleted
= false;
897 fInputMethodUndoState
.active
= false;
898 fInputMethodUndoState
.replace
= false;
902 TTextView::~TTextView()
910 delete_sem(fStopSem
);
915 TTextView::UpdateFont(const BFont
* newFont
)
919 // update the text run array safely with new font
920 text_run_array
*runArray
= RunArray(0, INT32_MAX
);
921 for (int i
= 0; i
< runArray
->count
; i
++)
922 runArray
->runs
[i
].font
= *newFont
;
924 SetRunArray(0, INT32_MAX
, runArray
);
925 FreeRunArray(runArray
);
930 TTextView::AttachedToWindow()
932 BTextView::AttachedToWindow();
933 fFont
.SetSpacing(B_FIXED_SPACING
);
934 SetFontAndColor(&fFont
);
937 LoadMessage(fMail
, false, NULL
);
945 TTextView::KeyDown(const char *key
, int32 count
)
952 int32 textLen
= TextLength();
954 msg
= Window()->CurrentMessage();
955 mods
= msg
->FindInt32("modifiers");
959 if (IsSelectable()) {
961 BTextView::KeyDown(key
, count
);
963 // scroll to the beginning
971 if (IsSelectable()) {
973 BTextView::KeyDown(key
, count
);
976 int32 length
= TextLength();
977 Select(length
, length
);
983 case 0x02: // ^b - back 1 char
984 if (IsSelectable()) {
985 GetSelection(&start
, &end
);
986 while (!IsInitialUTF8Byte(ByteAt(--start
))) {
993 Select(start
, start
);
1000 if (IsSelectable()) {
1001 if ((key
[0] == B_DELETE
) || (mods
& B_CONTROL_KEY
)) {
1004 GetSelection(&start
, &end
);
1008 for (end
= start
+ 1; !IsInitialUTF8Byte(ByteAt(end
)); end
++) {
1009 if (end
> textLen
) {
1020 Select(textLen
, textLen
);
1021 ScrollToSelection();
1025 case 0x05: // ^e - end of line
1026 if (IsSelectable() && (mods
& B_CONTROL_KEY
)) {
1027 if (CurrentLine() == CountLines() - 1)
1028 Select(TextLength(), TextLength());
1030 GoToLine(CurrentLine() + 1);
1031 GetSelection(&start
, &end
);
1032 Select(start
- 1, start
- 1);
1037 case 0x06: // ^f - forward 1 char
1038 if (IsSelectable()) {
1039 GetSelection(&start
, &end
);
1043 for (end
= start
+ 1; !IsInitialUTF8Byte(ByteAt(end
));
1045 if (end
> textLen
) {
1052 Select(start
, start
);
1053 ScrollToSelection();
1057 case 0x0e: // ^n - next line
1058 if (IsSelectable()) {
1060 BTextView::KeyDown(&raw
, 1);
1064 case 0x0f: // ^o - open line
1066 GetSelection(&start
, &end
);
1069 char newLine
= '\n';
1070 Insert(&newLine
, 1);
1071 Select(start
, start
);
1072 ScrollToSelection();
1077 if (mods
& B_CONTROL_KEY
) { // ^k kill text from cursor to e-o-line
1079 GetSelection(&start
, &end
);
1080 if ((start
!= fLastPosition
) && (fYankBuffer
)) {
1084 fLastPosition
= start
;
1085 if (CurrentLine() < CountLines() - 1) {
1086 GoToLine(CurrentLine() + 1);
1087 GetSelection(&end
, &end
);
1098 fYankBuffer
= (char *)realloc(fYankBuffer
,
1099 strlen(fYankBuffer
) + (end
- start
) + 1);
1100 GetText(start
, end
- start
,
1101 &fYankBuffer
[strlen(fYankBuffer
)]);
1103 fYankBuffer
= (char *)malloc(end
- start
+ 1);
1104 GetText(start
, end
- start
, fYankBuffer
);
1107 ScrollToSelection();
1112 BTextView::KeyDown(key
, count
);
1115 case 0x10: // ^p goto previous line
1116 if (IsSelectable()) {
1118 BTextView::KeyDown(&raw
, 1);
1122 case 0x19: // ^y yank text
1123 if (IsEditable() && fYankBuffer
) {
1125 Insert(fYankBuffer
);
1126 ScrollToSelection();
1131 BTextView::KeyDown(key
, count
);
1137 TTextView::MakeFocus(bool focus
)
1140 // ToDo: can someone please translate this? Otherwise I will remove it - axeld.
1141 // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。
1142 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
1143 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
1144 fInputMethodUndoState
.active
= false;
1145 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
1146 if (fInputMethodUndoBuffer
.CountItems() > 0) {
1147 KUndoItem
*item
= fInputMethodUndoBuffer
.ItemAt(fInputMethodUndoBuffer
.CountItems() - 1);
1148 if (item
->History
== K_INSERTED
) {
1149 fUndoBuffer
.MakeNewUndoItem();
1150 fUndoBuffer
.AddUndo(item
->RedoText
, item
->Length
, item
->Offset
, item
->History
, item
->CursorPos
);
1151 fUndoBuffer
.MakeNewUndoItem();
1153 fInputMethodUndoBuffer
.MakeEmpty();
1156 BTextView::MakeFocus(focus
);
1158 fParent
->Focus(focus
);
1163 TTextView::MessageReceived(BMessage
*msg
)
1165 switch (msg
->what
) {
1171 BMessage
message(REFS_RECEIVED
);
1172 bool isEnclosure
= false;
1173 bool inserted
= false;
1181 while (msg
->FindRef("refs", index
++, &ref
) == B_OK
) {
1182 BFile
file(&ref
, B_READ_ONLY
);
1183 if (file
.InitCheck() == B_OK
) {
1184 BNodeInfo
node(&file
);
1185 char type
[B_FILE_NAME_LENGTH
];
1189 file
.GetSize(&size
);
1191 if (!strncasecmp(type
, "text/", 5) && size
> 0) {
1193 char *text
= (char *)malloc(size
);
1198 if (file
.Read(text
, size
) < B_OK
) {
1199 puts("could not read from file");
1204 GetSelection(&start
, &end
);
1210 for (int32 loop
= 0; loop
< size
; loop
++) {
1211 if (text
[loop
] == '\n') {
1212 Insert(&text
[offset
], loop
- offset
+ 1);
1214 } else if (text
[loop
] == '\r') {
1216 Insert(&text
[offset
], loop
- offset
+ 1);
1217 if ((loop
+ 1 < size
)
1218 && (text
[loop
+ 1] == '\n'))
1226 message
.AddRef("refs", &ref
);
1232 // message doesn't contain any refs - maybe the parent class likes it
1233 BTextView::MessageReceived(msg
);
1238 Select(start
, start
+ len
);
1240 Window()->PostMessage(&message
, Window());
1245 msg
->FindBool("header", &fHeader
);
1247 LoadMessage(fMail
, false, NULL
);
1253 msg
->FindBool("raw", &fRaw
);
1255 LoadMessage(fMail
, false, NULL
);
1260 Select(0, TextLength());
1267 case B_NODE_MONITOR
:
1270 if (msg
->FindInt32("opcode", &opcode
) == B_NO_ERROR
) {
1272 if (msg
->FindInt32("device", &device
) < B_OK
)
1275 if (msg
->FindInt64("node", &inode
) < B_OK
)
1278 hyper_text
*enclosure
;
1279 for (int32 index
= 0;
1280 (enclosure
= (hyper_text
*)fEnclosures
->ItemAt(index
++)) != NULL
;) {
1281 if (device
== enclosure
->node
.device
1282 && inode
== enclosure
->node
.node
) {
1283 if (opcode
== B_ENTRY_REMOVED
) {
1284 enclosure
->saved
= false;
1285 enclosure
->have_ref
= false;
1286 } else if (opcode
== B_ENTRY_MOVED
) {
1287 enclosure
->ref
.device
= device
;
1288 msg
->FindInt64("to directory", &enclosure
->ref
.directory
);
1291 msg
->FindString("name", &name
);
1292 enclosure
->ref
.set_name(name
);
1302 // Tracker has responded to a BMessage that was dragged out of
1303 // this email message. It has created a file for us, we just have to
1304 // put the stuff in it.
1309 if (msg
->FindMessage("be:originator-data", &data
) == B_OK
) {
1310 entry_ref directory
;
1312 hyper_text
*enclosure
;
1314 if (data
.FindPointer("enclosure", (void **)&enclosure
) == B_OK
1315 && msg
->FindString("name", &name
) == B_OK
1316 && msg
->FindRef("directory", &directory
) == B_OK
) {
1317 switch (enclosure
->type
) {
1318 case TYPE_ENCLOSURE
:
1319 case TYPE_BE_ENCLOSURE
:
1322 // Enclosure. Decode the data and write it out.
1324 BMessage
saveMsg(M_SAVE
);
1325 saveMsg
.AddString("name", name
);
1326 saveMsg
.AddRef("directory", &directory
);
1327 saveMsg
.AddPointer("enclosure", enclosure
);
1328 Save(&saveMsg
, false);
1334 const char *replyType
;
1335 if (msg
->FindString("be:filetypes", &replyType
) != B_OK
)
1336 // drag recipient didn't ask for any specific type,
1337 // create a bookmark file as default
1338 replyType
= "application/x-vnd.Be-bookmark";
1340 BDirectory
dir(&directory
);
1341 BFile
file(&dir
, name
, B_READ_WRITE
);
1342 if (file
.InitCheck() == B_OK
) {
1343 if (strcmp(replyType
, "application/x-vnd.Be-bookmark") == 0) {
1344 // we got a request to create a bookmark, stuff
1345 // it with the url attribute
1346 file
.WriteAttr("META:url", B_STRING_TYPE
, 0,
1347 enclosure
->name
, strlen(enclosure
->name
) + 1);
1348 } else if (strcasecmp(replyType
, "text/plain") == 0) {
1349 // create a plain text file, stuff it with
1351 file
.Write(enclosure
->name
, strlen(enclosure
->name
));
1354 BNodeInfo
fileInfo(&file
);
1355 fileInfo
.SetType(replyType
);
1363 // Add some attributes to the already created
1364 // person file. Strip out the 'mailto:' if
1367 char *addrStart
= enclosure
->name
;
1369 if (*addrStart
== ':') {
1374 if (*addrStart
== '\0') {
1375 addrStart
= enclosure
->name
;
1382 const char *replyType
;
1383 if (msg
->FindString("be:filetypes", &replyType
) != B_OK
)
1384 // drag recipient didn't ask for any specific type,
1385 // create a bookmark file as default
1386 replyType
= "application/x-vnd.Be-bookmark";
1388 BDirectory
dir(&directory
);
1389 BFile
file(&dir
, name
, B_READ_WRITE
);
1390 if (file
.InitCheck() == B_OK
) {
1391 if (!strcmp(replyType
, "application/x-person")) {
1392 // we got a request to create a bookmark, stuff
1393 // it with the address attribute
1394 file
.WriteAttr("META:email", B_STRING_TYPE
, 0,
1395 addrStart
, strlen(enclosure
->name
) + 1);
1396 } else if (!strcasecmp(replyType
, "text/plain")) {
1397 // create a plain text file, stuff it with the
1399 file
.Write(addrStart
, strlen(addrStart
));
1402 BNodeInfo
fileInfo(&file
);
1403 fileInfo
.SetType(replyType
);
1410 // Assume this is handled by BTextView...
1411 // (Probably drag clipping.)
1413 BTextView::MessageReceived(msg
);
1419 case B_INPUT_METHOD_EVENT
:
1422 if (msg
->FindInt32("be:opcode", &im_op
) == B_OK
){
1424 case B_INPUT_METHOD_STARTED
:
1425 fInputMethodUndoState
.replace
= true;
1426 fInputMethodUndoState
.active
= true;
1428 case B_INPUT_METHOD_STOPPED
:
1429 fInputMethodUndoState
.active
= false;
1430 if (fInputMethodUndoBuffer
.CountItems() > 0) {
1431 KUndoItem
*undo
= fInputMethodUndoBuffer
.ItemAt(
1432 fInputMethodUndoBuffer
.CountItems() - 1);
1433 if (undo
->History
== K_INSERTED
){
1434 fUndoBuffer
.MakeNewUndoItem();
1435 fUndoBuffer
.AddUndo(undo
->RedoText
, undo
->Length
,
1436 undo
->Offset
, undo
->History
, undo
->CursorPos
);
1437 fUndoBuffer
.MakeNewUndoItem();
1439 fInputMethodUndoBuffer
.MakeEmpty();
1442 case B_INPUT_METHOD_CHANGED
:
1443 fInputMethodUndoState
.active
= true;
1445 case B_INPUT_METHOD_LOCATION_REQUEST
:
1446 fInputMethodUndoState
.active
= true;
1450 BTextView::MessageReceived(msg
);
1459 BTextView::MessageReceived(msg
);
1465 TTextView::MouseDown(BPoint where
)
1470 GetMouse(&point
, &buttons
);
1471 if (gDictCount
&& (buttons
== B_SECONDARY_MOUSE_BUTTON
)) {
1472 int32 offset
, start
, end
, length
;
1473 const char *text
= Text();
1474 offset
= OffsetAt(where
);
1475 if (isalpha(text
[offset
])) {
1476 length
= TextLength();
1478 //Find start and end of word
1479 //FindSpellBoundry(length, offset, &start, &end);
1482 bool isAlpha
, isApost
, isCap
;
1485 for (first
= offset
;
1486 (first
>= 0) && (((c
= text
[first
]) == '\'') || isalpha(c
));
1488 isCap
= isupper(text
[++first
]);
1490 for (start
= offset
, c
= text
[start
], isAlpha
= isalpha(c
), isApost
= (c
=='\'');
1491 (start
>= 0) && (isAlpha
|| (isApost
1492 && (((c
= text
[start
+1]) != 's') || !isCap
) && isalpha(c
)
1493 && isalpha(text
[start
-1])));
1494 start
--, c
= text
[start
], isAlpha
= isalpha(c
), isApost
= (c
== '\'')) {}
1497 for (end
= offset
, c
= text
[end
], isAlpha
= isalpha(c
), isApost
= (c
== '\'');
1498 (end
< length
) && (isAlpha
|| (isApost
1499 && (((c
= text
[end
+ 1]) != 's') || !isCap
) && isalpha(c
)));
1500 end
++, c
= text
[end
], isAlpha
= isalpha(c
), isApost
= (c
== '\'')) {}
1502 length
= end
- start
;
1504 srcWord
.SetTo(text
+ start
, length
);
1506 bool foundWord
= false;
1510 BMenuItem
*menuItem
;
1511 BPopUpMenu
menu("Words", false, false);
1513 for (int32 i
= 0; i
< gDictCount
; i
++)
1514 gWords
[i
]->FindBestMatches(&matches
,
1517 if (matches
.CountItems()) {
1518 sort_word_list(&matches
, srcWord
.String());
1519 for (int32 i
= 0; (string
= (BString
*)matches
.ItemAt(i
)) != NULL
; i
++) {
1520 menu
.AddItem((menuItem
= new BMenuItem(string
->String(), NULL
)));
1521 if (!strcasecmp(string
->String(), srcWord
.String())) {
1522 menuItem
->SetEnabled(false);
1528 menuItem
= new BMenuItem(B_TRANSLATE("No matches"), NULL
);
1529 menuItem
->SetEnabled(false);
1530 menu
.AddItem(menuItem
);
1533 BMenuItem
*addItem
= NULL
;
1534 if (!foundWord
&& gUserDict
>= 0) {
1535 menu
.AddSeparatorItem();
1536 addItem
= new BMenuItem(B_TRANSLATE("Add"), NULL
);
1537 menu
.AddItem(addItem
);
1540 point
= ConvertToScreen(where
);
1541 if ((menuItem
= menu
.Go(point
, false, false)) != NULL
) {
1542 if (menuItem
== addItem
) {
1543 BString
newItem(srcWord
.String());
1545 gWords
[gUserDict
]->InitIndex();
1546 gExactWords
[gUserDict
]->InitIndex();
1547 gUserDictFile
->Write(newItem
.String(), newItem
.Length());
1548 gWords
[gUserDict
]->BuildIndex();
1549 gExactWords
[gUserDict
]->BuildIndex();
1552 CheckSpelling(0, TextLength());
1554 int32 len
= strlen(menuItem
->Label());
1555 Select(start
, start
);
1557 Insert(start
, menuItem
->Label(), len
);
1558 Select(start
+len
, start
+len
);
1563 } else if (fSpellCheck
&& IsEditable()) {
1566 GetSelection(&start
, &end
);
1567 FindSpellBoundry(1, start
, &start
, &end
);
1568 CheckSpelling(start
, end
);
1571 // is not editable, look for enclosures/links
1573 int32 clickOffset
= OffsetAt(where
);
1574 int32 items
= fEnclosures
->CountItems();
1575 for (int32 loop
= 0; loop
< items
; loop
++) {
1576 hyper_text
*enclosure
= (hyper_text
*) fEnclosures
->ItemAt(loop
);
1577 if (clickOffset
< enclosure
->text_start
|| clickOffset
>= enclosure
->text_end
)
1581 // The user is clicking on this attachment
1586 Select(enclosure
->text_start
, enclosure
->text_end
);
1587 GetSelection(&start
, &finish
);
1588 Window()->UpdateIfNeeded();
1593 if (Window()->CurrentMessage()) {
1594 Window()->CurrentMessage()->FindInt32("buttons",
1595 (int32
*) &buttons
);
1599 // If this is the primary button, wait to see if the user is going
1600 // to single click, hold, or drag.
1602 if (buttons
!= B_SECONDARY_MOUSE_BUTTON
) {
1603 BPoint point
= where
;
1604 bigtime_t popupDelay
;
1605 get_click_speed(&popupDelay
);
1607 popupDelay
+= system_time();
1608 while (buttons
&& abs((int)(point
.x
- where
.x
)) < 4
1609 && abs((int)(point
.y
- where
.y
)) < 4
1610 && system_time() < popupDelay
) {
1612 GetMouse(&point
, &buttons
);
1615 if (system_time() < popupDelay
) {
1617 // The user either dragged this or released the button.
1618 // check if it was dragged.
1620 if (!(abs((int)(point
.x
- where
.x
)) < 4
1621 && abs((int)(point
.y
- where
.y
)) < 4) && buttons
)
1625 // The user held the button down.
1632 // If the user has right clicked on this menu,
1633 // or held the button down on it for a while,
1634 // pop up a context menu.
1636 if (buttons
== B_SECONDARY_MOUSE_BUTTON
|| held
) {
1638 // Right mouse click... Display a menu
1640 BPoint point
= where
;
1641 ConvertToScreen(&point
);
1644 if ((enclosure
->type
!= TYPE_ENCLOSURE
)
1645 && (enclosure
->type
!= TYPE_BE_ENCLOSURE
))
1646 item
= fLinkMenu
->Go(point
, true);
1648 item
= fEnclosureMenu
->Go(point
, true);
1651 if (item
&& (msg
= item
->Message()) != NULL
) {
1652 if (msg
->what
== M_SAVE
) {
1654 fPanel
->SetEnclosure(enclosure
);
1656 fPanel
= new TSavePanel(enclosure
, this);
1657 fPanel
->Window()->Show();
1659 } else if (msg
->what
== M_COPY
) {
1660 // copy link location to clipboard
1662 if (be_clipboard
->Lock()) {
1663 be_clipboard
->Clear();
1666 if ((clip
= be_clipboard
->Data()) != NULL
) {
1667 clip
->AddData("text/plain", B_MIME_TYPE
,
1668 enclosure
->name
, strlen(enclosure
->name
));
1669 be_clipboard
->Commit();
1671 be_clipboard
->Unlock();
1678 // Left button. If the user single clicks, open this link.
1679 // Otherwise, initiate a drag.
1682 BMessage
dragMessage(B_SIMPLE_DATA
);
1683 dragMessage
.AddInt32("be:actions", B_COPY_TARGET
);
1684 dragMessage
.AddString("be:types", B_FILE_MIME_TYPE
);
1685 switch (enclosure
->type
) {
1686 case TYPE_BE_ENCLOSURE
:
1687 case TYPE_ENCLOSURE
:
1689 // Attachment. The type is specified in the message.
1691 dragMessage
.AddString("be:types", B_FILE_MIME_TYPE
);
1692 dragMessage
.AddString("be:filetypes",
1693 enclosure
->content_type
? enclosure
->content_type
: "");
1694 dragMessage
.AddString("be:clip_name", enclosure
->name
);
1699 // URL. The user can drag it into the tracker to
1700 // create a bookmark file.
1702 dragMessage
.AddString("be:types", B_FILE_MIME_TYPE
);
1703 dragMessage
.AddString("be:filetypes",
1704 "application/x-vnd.Be-bookmark");
1705 dragMessage
.AddString("be:filetypes", "text/plain");
1706 dragMessage
.AddString("be:clip_name", "Bookmark");
1708 dragMessage
.AddString("be:url", enclosure
->name
);
1713 // Mailto address. The user can drag it into the
1714 // tracker to create a people file.
1716 dragMessage
.AddString("be:types", B_FILE_MIME_TYPE
);
1717 dragMessage
.AddString("be:filetypes",
1718 "application/x-person");
1719 dragMessage
.AddString("be:filetypes", "text/plain");
1720 dragMessage
.AddString("be:clip_name", "Person");
1722 dragMessage
.AddString("be:email", enclosure
->name
);
1727 // Otherwise it doesn't have a type that I know how
1728 // to save. It won't have any types and if any
1729 // program wants to accept it, more power to them.
1732 dragMessage
.AddString("be:clip_name", "Hyperlink");
1736 data
.AddPointer("enclosure", enclosure
);
1737 dragMessage
.AddMessage("be:originator-data", &data
);
1739 BRegion selectRegion
;
1740 GetTextRegion(start
, finish
, &selectRegion
);
1741 DragMessage(&dragMessage
, selectRegion
.Frame(), this);
1744 // User Single clicked on the attachment. Open it.
1752 BTextView::MouseDown(where
);
1757 TTextView::MouseMoved(BPoint where
, uint32 code
, const BMessage
*msg
)
1759 int32 start
= OffsetAt(where
);
1761 for (int32 loop
= fEnclosures
->CountItems(); loop
-- > 0;) {
1762 hyper_text
*enclosure
= (hyper_text
*)fEnclosures
->ItemAt(loop
);
1763 if ((start
>= enclosure
->text_start
) && (start
< enclosure
->text_end
)) {
1765 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT
);
1772 SetViewCursor(B_CURSOR_I_BEAM
);
1776 BTextView::MouseMoved(where
, code
, msg
);
1781 TTextView::ClearList()
1783 hyper_text
*enclosure
;
1784 while ((enclosure
= (hyper_text
*)fEnclosures
->FirstItem()) != NULL
) {
1785 fEnclosures
->RemoveItem(enclosure
);
1787 if (enclosure
->name
)
1788 free(enclosure
->name
);
1789 if (enclosure
->content_type
)
1790 free(enclosure
->content_type
);
1791 if (enclosure
->encoding
)
1792 free(enclosure
->encoding
);
1793 if (enclosure
->have_ref
&& !enclosure
->saved
) {
1794 BEntry
entry(&enclosure
->ref
);
1798 watch_node(&enclosure
->node
, B_STOP_WATCHING
, this);
1805 TTextView::LoadMessage(BEmailMessage
*mail
, bool quoteIt
, const char *text
)
1813 MakeSelectable(true);
1814 MakeEditable(false);
1816 Insert(text
, strlen(text
));
1818 //attr_info attrInfo;
1819 TTextView::Reader
*reader
= new TTextView::Reader(fHeader
, fRaw
, quoteIt
, fIncoming
,
1821 // I removed the following, because I absolutely can't imagine why it's
1822 // there (the mail kit should be able to deal with non-compliant mails)
1824 // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK,
1825 this, mail
, fEnclosures
, fStopSem
);
1827 resume_thread(fThread
= spawn_thread(Reader::Run
, "reader", B_NORMAL_PRIORITY
, reader
));
1832 TTextView::Open(hyper_text
*enclosure
)
1834 switch (enclosure
->type
) {
1837 const struct {const char *urlType
, *handler
; } handlerTable
[] = {
1838 {"http", B_URL_HTTP
},
1839 {"https", B_URL_HTTPS
},
1841 {"gopher", B_URL_GOPHER
},
1842 {"mailto", B_URL_MAILTO
},
1843 {"news", B_URL_NEWS
},
1844 {"nntp", B_URL_NNTP
},
1845 {"telnet", B_URL_TELNET
},
1846 {"rlogin", B_URL_RLOGIN
},
1847 {"tn3270", B_URL_TN3270
},
1848 {"wais", B_URL_WAIS
},
1849 {"file", B_URL_FILE
},
1852 const char *handlerToLaunch
= NULL
;
1854 const char *colonPos
= strchr(enclosure
->name
, ':');
1856 int urlTypeLength
= colonPos
- enclosure
->name
;
1858 for (int32 index
= 0; handlerTable
[index
].urlType
; index
++) {
1859 if (!strncasecmp(enclosure
->name
,
1860 handlerTable
[index
].urlType
, urlTypeLength
)) {
1861 handlerToLaunch
= handlerTable
[index
].handler
;
1866 if (handlerToLaunch
) {
1868 if (be_roster
->FindApp(handlerToLaunch
, &appRef
) != B_OK
)
1869 handlerToLaunch
= NULL
;
1871 if (!handlerToLaunch
)
1872 handlerToLaunch
= "application/x-vnd.Be-Bookmark";
1874 status_t result
= be_roster
->Launch(handlerToLaunch
, 1, &enclosure
->name
);
1875 if (result
!= B_NO_ERROR
&& result
!= B_ALREADY_RUNNING
) {
1877 BAlert
* alert
= new BAlert("",
1878 B_TRANSLATE("There is no installed handler for "
1879 "URL links."), B_TRANSLATE("Sorry"));
1880 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1887 if (be_roster
->Launch(B_MAIL_TYPE
, 1, &enclosure
->name
) < B_OK
) {
1888 char *argv
[] = {(char *)"Mail", enclosure
->name
};
1889 be_app
->ArgvReceived(2, argv
);
1893 case TYPE_ENCLOSURE
:
1894 case TYPE_BE_ENCLOSURE
:
1895 if (!enclosure
->have_ref
) {
1897 if (find_directory(B_SYSTEM_TEMP_DIRECTORY
, &path
) == B_NO_ERROR
) {
1898 BDirectory
dir(path
.Path());
1899 if (dir
.InitCheck() == B_NO_ERROR
) {
1900 char name
[B_FILE_NAME_LENGTH
];
1901 char baseName
[B_FILE_NAME_LENGTH
];
1902 strcpy(baseName
, enclosure
->name
? enclosure
->name
: "enclosure");
1903 strcpy(name
, baseName
);
1904 for (int32 index
= 0; dir
.Contains(name
); index
++)
1905 sprintf(name
, "%s_%" B_PRId32
, baseName
, index
);
1907 BEntry
entry(path
.Path());
1911 BMessage
save(M_SAVE
);
1912 save
.AddRef("directory", &ref
);
1913 save
.AddString("name", name
);
1914 save
.AddPointer("enclosure", enclosure
);
1915 if (Save(&save
) != B_NO_ERROR
)
1917 enclosure
->saved
= false;
1922 BMessenger
tracker("application/x-vnd.Be-TRAK");
1923 if (tracker
.IsValid()) {
1924 BMessage
openMsg(B_REFS_RECEIVED
);
1925 openMsg
.AddRef("refs", &enclosure
->ref
);
1926 tracker
.SendMessage(&openMsg
);
1934 TTextView::Save(BMessage
*msg
, bool makeNewFile
)
1940 hyper_text
*enclosure
;
1941 status_t result
= B_NO_ERROR
;
1942 char entry_name
[B_FILE_NAME_LENGTH
];
1944 msg
->FindString("name", &name
);
1945 msg
->FindRef("directory", &ref
);
1946 msg
->FindPointer("enclosure", (void **)&enclosure
);
1950 result
= dir
.InitCheck();
1952 if (result
== B_OK
) {
1955 // Search for the file and delete it if it already exists.
1956 // (It may not, that's ok.)
1959 if (dir
.FindEntry(name
, &entry
) == B_NO_ERROR
)
1962 if ((enclosure
->have_ref
) && (!enclosure
->saved
)) {
1963 entry
.SetTo(&enclosure
->ref
);
1966 // Added true arg and entry_name so MoveTo clobbers as
1967 // before. This may not be the correct behaviour, but
1968 // it's the preserved behaviour.
1970 entry
.GetName(entry_name
);
1971 result
= entry
.MoveTo(&dir
, entry_name
, true);
1972 if (result
== B_NO_ERROR
) {
1974 entry
.GetRef(&enclosure
->ref
);
1975 entry
.GetNodeRef(&enclosure
->node
);
1976 enclosure
->saved
= true;
1981 if (result
== B_NO_ERROR
) {
1982 result
= dir
.CreateFile(name
, &file
);
1983 if (result
== B_NO_ERROR
&& enclosure
->content_type
) {
1984 char type
[B_MIME_TYPE_LENGTH
];
1986 if (!strcasecmp(enclosure
->content_type
, "message/rfc822"))
1987 strcpy(type
, "text/x-email");
1988 else if (!strcasecmp(enclosure
->content_type
, "message/delivery-status"))
1989 strcpy(type
, "text/plain");
1991 strcpy(type
, enclosure
->content_type
);
1993 BNodeInfo
info(&file
);
1999 // This file was dragged into the tracker or desktop. The file
2002 result
= file
.SetTo(&dir
, name
, B_WRITE_ONLY
);
2006 if (enclosure
->component
== NULL
)
2009 if (result
== B_NO_ERROR
) {
2013 enclosure
->component
->GetDecodedData(&file
);
2016 dir
.FindEntry(name
, &entry
);
2017 entry
.GetRef(&enclosure
->ref
);
2018 enclosure
->have_ref
= true;
2019 enclosure
->saved
= true;
2020 entry
.GetPath(&path
);
2021 update_mime_info(path
.Path(), false, true,
2022 !cistrcmp("application/octet-stream", enclosure
->content_type
? enclosure
->content_type
: B_EMPTY_STRING
));
2023 entry
.GetNodeRef(&enclosure
->node
);
2024 watch_node(&enclosure
->node
, B_WATCH_NAME
, this);
2027 if (result
!= B_NO_ERROR
) {
2029 BAlert
* alert
= new BAlert("", B_TRANSLATE("An error occurred trying to save "
2030 "the attachment."), B_TRANSLATE("Sorry"));
2031 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2040 TTextView::StopLoad()
2045 if (fThread
!= 0 && get_thread_info(fThread
, &info
) == B_NO_ERROR
) {
2046 fStopLoading
= true;
2047 acquire_sem(fStopSem
);
2049 wait_for_thread(fThread
, &result
);
2051 release_sem(fStopSem
);
2052 fStopLoading
= false;
2060 TTextView::IsReaderThreadRunning()
2066 for (int i
= 5; i
> 0; i
--, usleep(100000))
2067 if (get_thread_info(fThread
, &info
) != B_OK
)
2074 TTextView::AddAsContent(BEmailMessage
*mail
, bool wrap
, uint32 charset
, mail_encoding encoding
)
2079 int32 textLength
= TextLength();
2080 const char *text
= Text();
2082 BTextMailComponent
*body
= mail
->Body();
2084 if (mail
->SetBody(body
= new BTextMailComponent()) < B_OK
)
2087 body
->SetEncoding(encoding
, charset
);
2089 // Just add the text as a whole if we can, or ...
2091 body
->AppendText(text
);
2095 // ... do word wrapping.
2097 BWindow
*window
= Window();
2098 char *saveText
= strdup(text
);
2099 BRect saveTextRect
= TextRect();
2101 // do this before we start messing with the fonts
2102 // the user will never know...
2103 window
->DisableUpdates();
2105 BScrollBar
*vScroller
= ScrollBar(B_VERTICAL
);
2106 BScrollBar
*hScroller
= ScrollBar(B_HORIZONTAL
);
2107 if (vScroller
!= NULL
)
2108 vScroller
->SetTarget((BView
*)NULL
);
2109 if (hScroller
!= NULL
)
2110 hScroller
->SetTarget((BView
*)NULL
);
2112 // Temporarily set the font to a fixed width font for line wrapping
2113 // calculations. If the font doesn't have as many of the symbols as
2114 // the preferred font, go back to using the user's preferred font.
2117 int missingCharactersFixedWidth
= 0;
2118 int missingCharactersPreferredFont
= 0;
2119 int32 numberOfCharacters
;
2121 numberOfCharacters
= BString(text
).CountChars();
2122 if (numberOfCharacters
> 0
2123 && (boolArray
= (bool *)malloc(sizeof(bool) * numberOfCharacters
)) != NULL
) {
2124 memset(boolArray
, 0, sizeof (bool) * numberOfCharacters
);
2125 be_fixed_font
->GetHasGlyphs(text
, numberOfCharacters
, boolArray
);
2126 for (int i
= 0; i
< numberOfCharacters
; i
++) {
2128 missingCharactersFixedWidth
+= 1;
2131 memset(boolArray
, 0, sizeof (bool) * numberOfCharacters
);
2132 fFont
.GetHasGlyphs(text
, numberOfCharacters
, boolArray
);
2133 for (int i
= 0; i
< numberOfCharacters
; i
++) {
2135 missingCharactersPreferredFont
+= 1;
2141 if (missingCharactersFixedWidth
> missingCharactersPreferredFont
)
2142 SetFontAndColor(0, textLength
, &fFont
);
2143 else // All things being equal, the fixed font is better for wrapping.
2144 SetFontAndColor(0, textLength
, be_fixed_font
);
2146 // calculate a text rect that is 72 columns wide
2147 BRect newTextRect
= saveTextRect
;
2148 newTextRect
.right
= newTextRect
.left
+ be_fixed_font
->StringWidth("m") * 72;
2149 SetTextRect(newTextRect
);
2151 // hard-wrap, based on TextView's soft-wrapping
2152 int32 numLines
= CountLines();
2153 bool spaceMoved
= false;
2154 char *content
= (char *)malloc(textLength
+ numLines
* 72); // more we'll ever need
2155 if (content
!= NULL
) {
2156 int32 contentLength
= 0;
2158 for (int32 i
= 0; i
< numLines
; i
++) {
2159 int32 startOffset
= OffsetAt(i
);
2164 int32 endOffset
= OffsetAt(i
+ 1);
2165 int32 lineLength
= endOffset
- startOffset
;
2167 // quick hack to not break URLs into several parts
2168 for (int32 pos
= startOffset
; pos
< endOffset
; pos
++) {
2170 uint8 type
= CheckForURL(text
+ pos
, urlLength
);
2174 if (pos
> endOffset
) {
2175 // find first break character after the URL
2176 for (; text
[pos
]; pos
++) {
2177 if (isalnum(text
[pos
]) || isspace(text
[pos
]))
2180 if (text
[pos
] && isspace(text
[pos
]) && text
[pos
] != '\n')
2183 endOffset
+= pos
- endOffset
;
2184 lineLength
= endOffset
- startOffset
;
2186 // insert a newline (and the same number of quotes) after the
2187 // URL to make sure the rest of the text is properly wrapped
2190 if (text
[pos
] == '\n')
2193 strcpy(buffer
, "\n");
2196 CopyQuotes(text
+ startOffset
, lineLength
, buffer
+ strlen(buffer
), quoteLength
);
2198 Insert(pos
, buffer
, strlen(buffer
));
2199 numLines
= CountLines();
2204 if (text
[endOffset
- 1] != ' '
2205 && text
[endOffset
- 1] != '\n'
2206 && text
[endOffset
] == ' ') {
2207 // make sure spaces will be part of this line
2213 memcpy(content
+ contentLength
, text
+ startOffset
, lineLength
);
2214 contentLength
+= lineLength
;
2216 // add a newline to every line except for the ones
2217 // that already end in newlines, and the last line
2218 if ((text
[endOffset
- 1] != '\n') && (i
< numLines
- 1)) {
2219 content
[contentLength
++] = '\n';
2221 // copy quote level of the first line
2223 CopyQuotes(text
+ startOffset
, lineLength
, content
+ contentLength
, quoteLength
);
2224 contentLength
+= quoteLength
;
2227 content
[contentLength
] = '\0';
2229 body
->AppendText(content
);
2233 // reset the text rect and font
2234 SetTextRect(saveTextRect
);
2237 SetFontAndColor(0, textLength
, &fFont
);
2239 // should be OK to hook these back up now
2240 if (vScroller
!= NULL
)
2241 vScroller
->SetTarget(this);
2242 if (hScroller
!= NULL
)
2243 hScroller
->SetTarget(this);
2246 window
->EnableUpdates();
2253 TTextView::Reader::Reader(bool header
, bool raw
, bool quote
, bool incoming
,
2254 bool stripHeader
, bool mime
, TTextView
*view
, BEmailMessage
*mail
,
2255 BList
*list
, sem_id sem
)
2260 fIncoming(incoming
),
2261 fStripHeader(stripHeader
),
2272 TTextView::Reader::ParseMail(BMailContainer
*container
,
2273 BTextMailComponent
*ignore
)
2276 for (int32 i
= 0; i
< container
->CountComponents(); i
++) {
2277 if (fView
->fStopLoading
)
2280 BMailComponent
*component
;
2281 if ((component
= container
->GetComponent(i
)) == NULL
) {
2282 if (fView
->fStopLoading
)
2285 hyper_text
*enclosure
= (hyper_text
*)malloc(sizeof(hyper_text
));
2286 if (enclosure
== NULL
)
2289 memset(enclosure
, 0, sizeof(hyper_text
));
2291 enclosure
->type
= TYPE_ENCLOSURE
;
2293 const char *name
= "\n<Attachment: could not handle>\n";
2295 fView
->GetSelection(&enclosure
->text_start
, &enclosure
->text_end
);
2296 enclosure
->text_start
++;
2297 enclosure
->text_end
+= strlen(name
) - 1;
2299 Insert(name
, strlen(name
), true);
2300 fEnclosures
->AddItem(enclosure
);
2305 if (component
== ignore
)
2308 if (component
->ComponentType() == B_MAIL_MULTIPART_CONTAINER
) {
2309 BMIMEMultipartMailContainer
*c
= dynamic_cast<BMIMEMultipartMailContainer
*>(container
->GetComponent(i
));
2312 if (!ParseMail(c
, ignore
))
2314 } else if (fIncoming
) {
2315 hyper_text
*enclosure
= (hyper_text
*)malloc(sizeof(hyper_text
));
2316 if (enclosure
== NULL
)
2319 memset(enclosure
, 0, sizeof(hyper_text
));
2321 enclosure
->type
= TYPE_ENCLOSURE
;
2322 enclosure
->component
= component
;
2325 char fileName
[B_FILE_NAME_LENGTH
];
2326 strcpy(fileName
, "untitled");
2327 if (BMailAttachment
*attachment
= dynamic_cast <BMailAttachment
*> (component
))
2328 attachment
->FileName(fileName
);
2330 BPath
path(fileName
);
2331 enclosure
->name
= strdup(path
.Leaf());
2334 component
->MIMEType(&type
);
2335 enclosure
->content_type
= strdup(type
.Type());
2337 char typeDescription
[B_MIME_TYPE_LENGTH
];
2338 if (type
.GetShortDescription(typeDescription
) != B_OK
)
2339 strcpy(typeDescription
, type
.Type() ? type
.Type() : B_EMPTY_STRING
);
2342 name
.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)",
2343 "Don't translate the variables %name% and %type%."));
2345 name
.ReplaceFirst("%name%", enclosure
->name
);
2346 name
.ReplaceFirst("%type%", typeDescription
);
2348 fView
->GetSelection(&enclosure
->text_start
, &enclosure
->text_end
);
2349 enclosure
->text_start
++;
2350 enclosure
->text_end
+= strlen(name
.String()) - 1;
2352 Insert(name
.String(), name
.Length(), true);
2353 fEnclosures
->AddItem(enclosure
);
2357 // PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i));
2358 // const char *text;
2359 // if (body && (text = body->Text()) != NULL)
2360 // Insert(text, strlen(text), false);
2368 TTextView::Reader::Process(const char *data
, int32 data_len
, bool isHeader
)
2373 for (int32 loop
= 0; loop
< data_len
; loop
++) {
2374 if (fView
->fStopLoading
)
2377 if (fQuote
&& (!loop
|| (loop
&& data
[loop
- 1] == '\n'))) {
2378 strcpy(&line
[count
], QUOTE
);
2379 count
+= strlen(QUOTE
);
2381 if (!fRaw
&& fIncoming
&& (loop
< data_len
- 7)) {
2384 uint8 type
= CheckForURL(data
+ loop
, urlLength
, &url
);
2387 if (!Insert(line
, count
, false, isHeader
))
2391 hyper_text
*enclosure
= (hyper_text
*)malloc(sizeof(hyper_text
));
2392 if (enclosure
== NULL
)
2395 memset(enclosure
, 0, sizeof(hyper_text
));
2396 fView
->GetSelection(&enclosure
->text_start
,
2397 &enclosure
->text_end
);
2398 enclosure
->type
= type
;
2399 enclosure
->name
= strdup(url
.String());
2400 if (enclosure
->name
== NULL
) {
2405 Insert(&data
[loop
], urlLength
, true, isHeader
);
2406 enclosure
->text_end
+= urlLength
;
2407 loop
+= urlLength
- 1;
2409 fEnclosures
->AddItem(enclosure
);
2413 if (!fRaw
&& fMime
&& data
[loop
] == '=') {
2414 if ((loop
) && (loop
< data_len
- 1) && (data
[loop
+ 1] == '\r'))
2417 line
[count
++] = data
[loop
];
2418 } else if (data
[loop
] != '\r')
2419 line
[count
++] = data
[loop
];
2421 if (count
> 511 || (count
&& loop
== data_len
- 1)) {
2422 if (!Insert(line
, count
, false, isHeader
))
2432 TTextView::Reader::Insert(const char *line
, int32 count
, bool isHyperLink
,
2438 BFont
font(fView
->Font());
2439 TextRunArray
style(count
/ 8 + 8);
2441 if (fView
->fColoredQuotes
&& !isHeader
&& !isHyperLink
) {
2442 FillInQuoteTextRuns(fView
, &fQuoteContext
, line
, count
, font
,
2443 &style
.Array(), style
.MaxEntries());
2445 text_run_array
&array
= style
.Array();
2447 array
.runs
[0].offset
= 0;
2449 array
.runs
[0].color
= isHyperLink
? kHyperLinkColor
: kHeaderColor
;
2450 font
.SetSize(font
.Size() * 0.9);
2452 array
.runs
[0].color
= isHyperLink
2453 ? kHyperLinkColor
: kNormalTextColor
;
2455 array
.runs
[0].font
= font
;
2458 if (!fView
->Window()->Lock())
2461 fView
->Insert(fView
->TextLength(), line
, count
, &style
.Array());
2463 fView
->Window()->Unlock();
2469 TTextView::Reader::Run(void *_this
)
2471 Reader
*reader
= (Reader
*)_this
;
2472 TTextView
*view
= reader
->fView
;
2477 if (!reader
->Lock())
2478 return B_INTERRUPTED
;
2480 BFile
*file
= dynamic_cast<BFile
*>(reader
->fMail
->Data());
2482 len
= header_len(file
);
2484 if (reader
->fHeader
)
2486 if (reader
->fRaw
|| !reader
->fMime
)
2487 file
->GetSize(&size
);
2489 if (size
!= 0 && (msg
= (char *)malloc(size
)) == NULL
)
2494 size
= file
->Read(msg
, size
);
2498 if (reader
->fHeader
&& len
) {
2499 // strip all headers except "From", "To", "Reply-To", "Subject", and "Date"
2500 if (reader
->fStripHeader
) {
2501 const char *header
= msg
;
2502 char *buffer
= NULL
;
2504 while (strncmp(header
, "\r\n", 2)) {
2505 const char *eol
= header
;
2506 while ((eol
= strstr(eol
, "\r\n")) != NULL
&& isspace(eol
[2]))
2511 eol
+= 2; // CR+LF belong to the line
2512 size_t length
= eol
- header
;
2514 buffer
= (char *)realloc(buffer
, length
+ 1);
2518 memcpy(buffer
, header
, length
);
2520 length
= rfc2047_to_utf8(&buffer
, &length
, length
);
2522 if (!strncasecmp(header
, "Reply-To: ", 10)
2523 || !strncasecmp(header
, "To: ", 4)
2524 || !strncasecmp(header
, "From: ", 6)
2525 || !strncasecmp(header
, "Subject: ", 8)
2526 || !strncasecmp(header
, "Date: ", 6))
2527 reader
->Process(buffer
, length
, true);
2532 reader
->Process("\r\n", 2, true);
2534 else if (!reader
->Process(msg
, len
, true))
2539 if (!reader
->Process((const char *)msg
+ len
, size
- len
))
2542 //reader->fFile->Seek(0, 0);
2543 //BEmailMessage *mail = new BEmailMessage(reader->fFile);
2544 BEmailMessage
*mail
= reader
->fMail
;
2546 // at first, insert the mail body
2547 BTextMailComponent
*body
= NULL
;
2548 if (mail
->BodyText() && !view
->fStopLoading
) {
2549 char *bodyText
= const_cast<char *>(mail
->BodyText());
2550 int32 bodyLength
= strlen(bodyText
);
2551 body
= mail
->Body();
2552 bool isHTML
= false;
2555 if (body
->MIMEType(&type
) == B_OK
&& type
== "text/html") {
2556 // strip out HTML tags
2557 char *t
= bodyText
, *a
, *end
= bodyText
+ bodyLength
;
2558 bodyText
= (char *)malloc(bodyLength
+ 1);
2561 // TODO: is it correct to assume that the text is in Latin-1?
2562 // because if it isn't, the code below won't work correctly...
2564 for (a
= bodyText
; t
< end
; t
++) {
2569 while (c
&& (c
== ' ' || c
== '\t')) {
2576 } else if (FilterHTMLTag(c
, &t
, end
)) // the tag filter
2579 Unicode2UTF8(c
, &a
);
2583 bodyLength
= strlen(bodyText
);
2584 body
= NULL
; // to add the HTML text as enclosure
2586 if (!reader
->Process(bodyText
, bodyLength
))
2593 if (!reader
->ParseMail(mail
, body
))
2596 //reader->fView->fMail = mail;
2599 if (!view
->fStopLoading
&& view
->Window()->Lock()) {
2601 view
->MakeSelectable(true);
2602 if (!reader
->fIncoming
)
2603 view
->MakeEditable(true);
2605 view
->Window()->Unlock();
2609 // restore the reading position if available
2610 view
->Window()->PostMessage(M_READ_POS
);
2622 TTextView::Reader::Unlock()
2624 return release_sem(fStopSem
);
2629 TTextView::Reader::Lock()
2631 if (acquire_sem_etc(fStopSem
, 1, B_TIMEOUT
, 0) != B_NO_ERROR
)
2638 //====================================================================
2642 TSavePanel::TSavePanel(hyper_text
*enclosure
, TTextView
*view
)
2643 : BFilePanel(B_SAVE_PANEL
)
2645 fEnclosure
= enclosure
;
2647 if (enclosure
->name
)
2648 SetSaveText(enclosure
->name
);
2653 TSavePanel::SendMessage(const BMessenger
* /* messenger */, BMessage
*msg
)
2655 const char *name
= NULL
;
2656 BMessage
save(M_SAVE
);
2659 if ((!msg
->FindRef("directory", &ref
)) && (!msg
->FindString("name", &name
))) {
2660 save
.AddPointer("enclosure", fEnclosure
);
2661 save
.AddString("name", name
);
2662 save
.AddRef("directory", &ref
);
2663 fView
->Window()->PostMessage(&save
, fView
);
2669 TSavePanel::SetEnclosure(hyper_text
*enclosure
)
2671 fEnclosure
= enclosure
;
2672 if (enclosure
->name
)
2673 SetSaveText(enclosure
->name
);
2679 Window()->Activate();
2683 //--------------------------------------------------------------------
2688 TTextView::InsertText(const char *insertText
, int32 length
, int32 offset
,
2689 const text_run_array
*runs
)
2695 int32 cursorPos
, dummy
;
2696 GetSelection(&cursorPos
, &dummy
);
2698 if (fInputMethodUndoState
.active
) {
2699 // IMアクティブ時は、一旦別のバッファへ記憶
2700 fInputMethodUndoBuffer
.AddUndo(insertText
, length
, offset
, K_INSERTED
, cursorPos
);
2701 fInputMethodUndoState
.replace
= false;
2703 if (fUndoState
.replaced
) {
2704 fUndoBuffer
.AddUndo(insertText
, length
, offset
, K_REPLACED
, cursorPos
);
2706 if (length
== 1 && insertText
[0] == 0x0a)
2707 fUndoBuffer
.MakeNewUndoItem();
2709 fUndoBuffer
.AddUndo(insertText
, length
, offset
, K_INSERTED
, cursorPos
);
2711 if (length
== 1 && insertText
[0] == 0x0a)
2712 fUndoBuffer
.MakeNewUndoItem();
2716 fUndoState
.replaced
= false;
2717 fUndoState
.deleted
= false;
2719 struct text_runs
: text_run_array
{ text_run _runs
[1]; } style
;
2720 if (runs
== NULL
&& IsEditable()) {
2722 style
.runs
[0].offset
= 0;
2723 style
.runs
[0].font
= fFont
;
2724 style
.runs
[0].color
= kNormalTextColor
;
2728 BTextView::InsertText(insertText
, length
, offset
, runs
);
2730 if (fSpellCheck
&& IsEditable())
2732 UpdateSpellMarks(offset
, length
);
2735 GetFontAndColor(offset
- 1, NULL
, &color
);
2736 const char *text
= Text();
2739 || isalpha(text
[offset
+ 1])
2740 || (!isalpha(text
[offset
]) && text
[offset
] != '\'')
2741 || (color
.red
== kSpellTextColor
.red
2742 && color
.green
== kSpellTextColor
.green
2743 && color
.blue
== kSpellTextColor
.blue
))
2746 FindSpellBoundry(length
, offset
, &start
, &end
);
2748 DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset
, start
, end
));
2749 DSPELL(printf("\t\"%10.10s...\"\n", text
+ start
));
2751 CheckSpelling(start
, end
);
2758 TTextView::DeleteText(int32 start
, int32 finish
)
2763 int32 cursorPos
, dummy
;
2764 GetSelection(&cursorPos
, &dummy
);
2765 if (fInputMethodUndoState
.active
) {
2766 if (fInputMethodUndoState
.replace
) {
2767 fUndoBuffer
.AddUndo(&Text()[start
], finish
- start
, start
, K_DELETED
, cursorPos
);
2768 fInputMethodUndoState
.replace
= false;
2770 fInputMethodUndoBuffer
.AddUndo(&Text()[start
], finish
- start
, start
,
2771 K_DELETED
, cursorPos
);
2774 fUndoBuffer
.AddUndo(&Text()[start
], finish
- start
, start
, K_DELETED
, cursorPos
);
2776 fUndoState
.deleted
= true;
2777 fUndoState
.replaced
= true;
2779 BTextView::DeleteText(start
, finish
);
2780 if (fSpellCheck
&& IsEditable()) {
2781 UpdateSpellMarks(start
, start
- finish
);
2784 FindSpellBoundry(1, start
, &s
, &e
);
2785 CheckSpelling(s
, e
);
2791 TTextView::ContentChanged(void)
2793 BLooper
*looper
= Looper();
2797 BMessage
msg(FIELD_CHANGED
);
2798 msg
.AddInt32("bitmask", FIELD_BODY
);
2799 msg
.AddPointer("source", this);
2800 looper
->PostMessage(&msg
);
2805 TTextView::CheckSpelling(int32 start
, int32 end
, int32 flags
)
2807 const char *text
= Text();
2808 const char *next
, *endPtr
, *word
= NULL
;
2809 int32 wordLength
= 0, wordOffset
;
2810 int32 nextHighlight
= start
;
2816 for (next
= text
+ start
, endPtr
= text
+ end
; next
<= endPtr
; next
++) {
2817 //printf("next=%c\n", *next);
2818 // ToDo: this has to be refined to other languages...
2819 // Alpha signifies the start of a word
2820 isAlpha
= isalpha(*next
);
2821 isApost
= (*next
== '\'');
2822 if (!word
&& isAlpha
) {
2823 //printf("Found word start\n");
2826 isCap
= isupper(*word
);
2827 } else if (word
&& (isAlpha
|| isApost
) && !(isApost
&& !isalpha(next
[1]))
2828 && !(isCap
&& isApost
&& (next
[1] == 's'))) {
2829 // Word continues check
2831 //printf("Word continues...\n");
2833 // End of word reached
2835 //printf("Word End\n");
2836 // Don't check single characters
2837 if (wordLength
> 1) {
2838 bool isUpper
= true;
2840 // Look for all uppercase
2841 for (int32 i
= 0; i
< wordLength
; i
++) {
2842 if (word
[i
] == '\'')
2845 if (islower(word
[i
])) {
2851 // Don't check all uppercase words
2853 bool foundMatch
= false;
2854 wordOffset
= word
- text
;
2855 testWord
.SetTo(word
, wordLength
);
2857 testWord
= testWord
.ToLower();
2858 DSPELL(printf("Testing: \"%s\"\n", testWord
.String()));
2862 key
= gExactWords
[0]->GetKey(testWord
.String());
2864 // Search all dictionaries
2865 for (int32 i
= 0; i
< gDictCount
; i
++) {
2866 if (gExactWords
[i
]->Lookup(key
) >= 0) {
2873 if (flags
& S_CLEAR_ERRORS
)
2874 RemoveSpellMark(nextHighlight
, wordOffset
);
2876 if (flags
& S_SHOW_ERRORS
)
2877 AddSpellMark(wordOffset
, wordOffset
+ wordLength
);
2878 } else if (flags
& S_CLEAR_ERRORS
)
2879 RemoveSpellMark(nextHighlight
, wordOffset
+ wordLength
);
2881 nextHighlight
= wordOffset
+ wordLength
;
2884 // Reset state to looking for word
2890 if (nextHighlight
<= end
2891 && (flags
& S_CLEAR_ERRORS
) != 0
2892 && nextHighlight
< TextLength())
2893 SetFontAndColor(nextHighlight
, end
, NULL
, B_FONT_ALL
, &kNormalTextColor
);
2898 TTextView::FindSpellBoundry(int32 length
, int32 offset
, int32
*_start
, int32
*_end
)
2900 int32 start
, end
, textLength
;
2901 const char *text
= Text();
2902 textLength
= TextLength();
2904 for (start
= offset
- 1; start
>= 0
2905 && (isalpha(text
[start
]) || text
[start
] == '\''); start
--) {}
2909 for (end
= offset
+ length
; end
< textLength
2910 && (isalpha(text
[end
]) || text
[end
] == '\''); end
++) {}
2917 TTextView::spell_mark
*
2918 TTextView::FindSpellMark(int32 start
, int32 end
, spell_mark
**_previousMark
)
2920 spell_mark
*lastMark
= NULL
;
2922 for (spell_mark
*spellMark
= fFirstSpellMark
; spellMark
; spellMark
= spellMark
->next
) {
2923 if (spellMark
->start
< end
&& spellMark
->end
> start
) {
2925 *_previousMark
= lastMark
;
2929 lastMark
= spellMark
;
2936 TTextView::UpdateSpellMarks(int32 offset
, int32 length
)
2938 DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset
, length
));
2940 spell_mark
*spellMark
;
2941 for (spellMark
= fFirstSpellMark
; spellMark
; spellMark
= spellMark
->next
) {
2942 DSPELL(printf("\tfound: %ld - %ld\n", spellMark
->start
, spellMark
->end
));
2944 if (spellMark
->end
< offset
)
2947 if (spellMark
->start
> offset
)
2948 spellMark
->start
+= length
;
2950 spellMark
->end
+= length
;
2952 DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark
->start
, spellMark
->end
));
2958 TTextView::AddSpellMark(int32 start
, int32 end
)
2960 DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start
, end
));
2962 // check if there is already a mark for this passage
2963 spell_mark
*spellMark
= FindSpellMark(start
, end
);
2965 if (spellMark
->start
== start
&& spellMark
->end
== end
) {
2966 DSPELL(printf("\tfound one\n"));
2970 DSPELL(printf("\tremove old one\n"));
2971 RemoveSpellMark(start
, end
);
2974 spellMark
= (spell_mark
*)malloc(sizeof(spell_mark
));
2975 if (spellMark
== NULL
)
2978 spellMark
->start
= start
;
2979 spellMark
->end
= end
;
2980 spellMark
->style
= RunArray(start
, end
);
2982 // set the spell marks appearance
2984 font
.SetFace(B_BOLD_FACE
| B_ITALIC_FACE
);
2985 SetFontAndColor(start
, end
, &font
, B_FONT_ALL
, &kSpellTextColor
);
2987 // add it to the queue
2988 spellMark
->next
= fFirstSpellMark
;
2989 fFirstSpellMark
= spellMark
;
2996 TTextView::RemoveSpellMark(int32 start
, int32 end
)
2998 DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start
, end
));
3001 spell_mark
*lastMark
= NULL
;
3002 spell_mark
*spellMark
= FindSpellMark(start
, end
, &lastMark
);
3003 if (spellMark
== NULL
) {
3004 DSPELL(printf("\tnot found!\n"));
3008 DSPELL(printf("\tfound: %ld - %ld\n", spellMark
->start
, spellMark
->end
));
3010 // dequeue the spell mark
3012 lastMark
->next
= spellMark
->next
;
3014 fFirstSpellMark
= spellMark
->next
;
3016 if (spellMark
->start
< start
)
3017 start
= spellMark
->start
;
3018 if (spellMark
->end
> end
)
3019 end
= spellMark
->end
;
3021 // reset old text run array
3022 SetRunArray(start
, end
, spellMark
->style
);
3024 free(spellMark
->style
);
3032 TTextView::RemoveSpellMarks()
3034 spell_mark
*spellMark
, *nextMark
;
3036 for (spellMark
= fFirstSpellMark
; spellMark
; spellMark
= nextMark
) {
3037 nextMark
= spellMark
->next
;
3039 // reset old text run array
3040 SetRunArray(spellMark
->start
, spellMark
->end
, spellMark
->style
);
3042 free(spellMark
->style
);
3046 fFirstSpellMark
= NULL
;
3051 TTextView::EnableSpellCheck(bool enable
)
3053 if (fSpellCheck
== enable
)
3056 fSpellCheck
= enable
;
3057 int32 textLength
= TextLength();
3059 // work-around for a bug in the BTextView class
3060 // which causes lots of flicker
3062 GetSelection(&start
, &end
);
3064 Select(start
, start
);
3066 CheckSpelling(0, textLength
);
3077 TTextView::WindowActivated(bool flag
)
3080 // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。
3081 // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
3082 // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
3083 // OpenBeOSで修正されることを願って暫定処置としている。
3084 fInputMethodUndoState
.active
= false;
3085 // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
3086 if (fInputMethodUndoBuffer
.CountItems() > 0) {
3087 KUndoItem
*item
= fInputMethodUndoBuffer
.ItemAt(fInputMethodUndoBuffer
.CountItems() - 1);
3088 if (item
->History
== K_INSERTED
) {
3089 fUndoBuffer
.MakeNewUndoItem();
3090 fUndoBuffer
.AddUndo(item
->RedoText
, item
->Length
, item
->Offset
,
3091 item
->History
, item
->CursorPos
);
3092 fUndoBuffer
.MakeNewUndoItem();
3094 fInputMethodUndoBuffer
.MakeEmpty();
3097 BTextView::WindowActivated(flag
);
3102 TTextView::AddQuote(int32 start
, int32 finish
)
3104 BRect rect
= Bounds();
3107 GoToLine(CurrentLine());
3108 GetSelection(&lineStart
, &lineStart
);
3109 lineStart
= LineStart(lineStart
);
3111 // make sure that we're changing the whole last line, too
3112 int32 lineEnd
= finish
> lineStart
? finish
- 1 : finish
;
3114 const char *text
= Text();
3115 while (text
[lineEnd
] && text
[lineEnd
] != '\n')
3118 Select(lineStart
, lineEnd
);
3120 int32 textLength
= lineEnd
- lineStart
;
3121 char *text
= (char *)malloc(textLength
+ 1);
3125 GetText(lineStart
, textLength
, text
);
3127 int32 quoteLength
= strlen(QUOTE
);
3128 int32 targetLength
= 0;
3129 char *target
= NULL
;
3132 for (int32 index
= 0; index
< textLength
; index
++) {
3133 if (text
[index
] == '\n' || index
== textLength
- 1) {
3134 // add quote to this line
3135 int32 lineLength
= index
- lastLine
+ 1;
3137 target
= (char *)realloc(target
, targetLength
+ lineLength
+ quoteLength
);
3138 if (target
== NULL
) {
3139 // free the old buffer?
3144 // copy the quote sign
3145 memcpy(&target
[targetLength
], QUOTE
, quoteLength
);
3146 targetLength
+= quoteLength
;
3148 // copy the rest of the line
3149 memcpy(&target
[targetLength
], &text
[lastLine
], lineLength
);
3150 targetLength
+= lineLength
;
3152 lastLine
= index
+ 1;
3156 // replace with quoted text
3160 if (fColoredQuotes
) {
3161 const BFont
*font
= Font();
3162 TextRunArray
style(targetLength
/ 8 + 8);
3164 FillInQuoteTextRuns(NULL
, NULL
, target
, targetLength
, font
,
3165 &style
.Array(), style
.MaxEntries());
3166 Insert(target
, targetLength
, &style
.Array());
3168 Insert(target
, targetLength
);
3172 // redo the old selection (compute the new start if necessary)
3173 Select(start
+ quoteLength
, finish
+ (targetLength
- textLength
));
3175 ScrollTo(rect
.LeftTop());
3180 TTextView::RemoveQuote(int32 start
, int32 finish
)
3182 BRect rect
= Bounds();
3184 GoToLine(CurrentLine());
3186 GetSelection(&lineStart
, &lineStart
);
3187 lineStart
= LineStart(lineStart
);
3189 // make sure that we're changing the whole last line, too
3190 int32 lineEnd
= finish
> lineStart
? finish
- 1 : finish
;
3191 const char *text
= Text();
3192 while (text
[lineEnd
] && text
[lineEnd
] != '\n')
3195 Select(lineStart
, lineEnd
);
3197 int32 length
= lineEnd
- lineStart
;
3198 char *target
= (char *)malloc(length
+ 1);
3202 int32 quoteLength
= strlen(QUOTE
);
3206 for (int32 index
= 0; index
< length
;) {
3207 // find out the length of the current line
3208 int32 lineLength
= 0;
3209 while (index
+ lineLength
< length
&& text
[lineLength
] != '\n')
3212 // include the newline to be part of this line
3213 if (text
[lineLength
] == '\n' && index
+ lineLength
+ 1 < length
)
3216 if (!strncmp(text
, QUOTE
, quoteLength
)) {
3218 length
-= quoteLength
;
3219 removed
+= quoteLength
;
3221 lineLength
-= quoteLength
;
3222 text
+= quoteLength
;
3225 if (lineLength
== 0) {
3226 target
[index
] = '\0';
3230 memcpy(&target
[index
], text
, lineLength
);
3233 index
+= lineLength
;
3239 if (fColoredQuotes
) {
3240 const BFont
*font
= Font();
3241 TextRunArray
style(length
/ 8 + 8);
3243 FillInQuoteTextRuns(NULL
, NULL
, target
, length
, font
,
3244 &style
.Array(), style
.MaxEntries());
3245 Insert(target
, length
, &style
.Array());
3247 Insert(target
, length
);
3249 // redo old selection
3250 bool noSelection
= start
== finish
;
3252 if (start
> lineStart
+ quoteLength
)
3253 start
-= quoteLength
;
3265 Select(start
, finish
);
3266 ScrollTo(rect
.LeftTop());
3271 TTextView::LineStart(int32 offset
)
3276 while (offset
> 0) {
3277 offset
= PreviousByte(offset
);
3278 if (ByteAt(offset
) == B_ENTER
)
3287 TTextView::PreviousByte(int32 offset
) const
3294 for (--offset
; offset
> 0 && count
; --offset
, --count
) {
3295 if ((ByteAt(offset
) & 0xC0) != 0x80)
3299 return count
? offset
: 0;
3304 TTextView::Undo(BClipboard */
*clipboard*/
)
3306 if (fInputMethodUndoState
.active
)
3309 int32 length
, offset
, cursorPos
;
3314 status
= fUndoBuffer
.Undo(&text
, &length
, &offset
, &history
, &cursorPos
);
3315 if (status
== B_OK
) {
3320 BTextView::Delete(offset
, offset
+ length
);
3321 Select(offset
, offset
);
3325 BTextView::Insert(offset
, text
, length
);
3326 Select(offset
, offset
+ length
);
3330 BTextView::Delete(offset
, offset
+ length
);
3331 status
= fUndoBuffer
.Undo(&text
, &length
, &offset
, &history
, &cursorPos
);
3332 if (status
== B_OK
&& history
== K_DELETED
) {
3333 BTextView::Insert(offset
, text
, length
);
3334 Select(offset
, offset
+ length
);
3337 BAlert
* alert
= new BAlert("",
3338 B_TRANSLATE("Inconsistency occurred in the undo/redo "
3339 "buffer."), B_TRANSLATE("OK"));
3340 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
3345 ScrollToSelection();
3355 if (fInputMethodUndoState
.active
)
3358 int32 length
, offset
, cursorPos
;
3364 status
= fUndoBuffer
.Redo(&text
, &length
, &offset
, &history
, &cursorPos
, &replaced
);
3365 if (status
== B_OK
) {
3370 BTextView::Insert(offset
, text
, length
);
3371 Select(offset
, offset
+ length
);
3375 BTextView::Delete(offset
, offset
+ length
);
3377 fUndoBuffer
.Redo(&text
, &length
, &offset
, &history
, &cursorPos
, &replaced
);
3378 BTextView::Insert(offset
, text
, length
);
3380 Select(offset
, offset
+ length
);
3385 BAlert
* alert
= new BAlert("",
3386 B_TRANSLATE("Inconsistency occurred in the undo/redo "
3387 "buffer."), B_TRANSLATE("OK"));
3388 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
3392 ScrollToSelection();