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 "Signature.h"
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <strings.h>
41
42 #include <Clipboard.h>
43 #include <Directory.h>
44 #include <LayoutBuilder.h>
45 #include <Locale.h>
46 #include <ScrollView.h>
47 #include <StringView.h>
48
49 #include "MailApp.h"
50 #include "MailPopUpMenu.h"
51 #include "MailSupport.h"
52 #include "MailWindow.h"
53 #include "Messages.h"
54
55
56 #define B_TRANSLATION_CONTEXT "Mail"
57
58
59 const float kSigHeight = 250;
60 const float kSigWidth = 300;
61
62 extern const char* kUndoStrings[];
63 extern const char* kRedoStrings[];
64
65
TSignatureWindow(BRect rect)66 TSignatureWindow::TSignatureWindow(BRect rect)
67 :
68 BWindow(rect, B_TRANSLATE("Signatures"), B_TITLED_WINDOW,
69 B_AUTO_UPDATE_SIZE_LIMITS),
70 fFile(NULL)
71 {
72 BMenuItem* item;
73
74 // Set up the menu
75 BMenuBar* menuBar = new BMenuBar("MenuBar");
76 BMenu* menu = new BMenu(B_TRANSLATE("Signature"));
77 menu->AddItem(fNew = new BMenuItem(B_TRANSLATE("New"),
78 new BMessage(M_NEW), 'N'));
79 fSignature = new TMenu(B_TRANSLATE("Open"), INDEX_SIGNATURE, M_SIGNATURE);
80 menu->AddItem(new BMenuItem(fSignature));
81 menu->AddSeparatorItem();
82 menu->AddItem(fSave = new BMenuItem(B_TRANSLATE("Save"),
83 new BMessage(M_SAVE), 'S'));
84 menu->AddItem(fDelete = new BMenuItem(B_TRANSLATE("Delete"),
85 new BMessage(M_DELETE), 'T'));
86 menuBar->AddItem(menu);
87
88 menu = new BMenu(B_TRANSLATE("Edit"));
89 menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
90 new BMessage(B_UNDO), 'Z'));
91 fUndo->SetTarget(NULL, this);
92 menu->AddSeparatorItem();
93 menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
94 new BMessage(B_CUT), 'X'));
95 fCut->SetTarget(NULL, this);
96 menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
97 new BMessage(B_COPY), 'C'));
98 fCopy->SetTarget(NULL, this);
99 menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
100 new BMessage(B_PASTE), 'V'));
101 fPaste->SetTarget(NULL, this);
102 menu->AddSeparatorItem();
103 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"),
104 new BMessage(M_SELECT), 'A'));
105 item->SetTarget(NULL, this);
106 menuBar->AddItem(menu);
107
108 fSigView = new TSignatureView();
109
110 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
111 .Add(menuBar)
112 .Add(fSigView);
113
114 if (!rect.IsValid()) {
115 float fontFactor = be_plain_font->Size() / 12.0f;
116 ResizeTo(kSigWidth * fontFactor, kSigHeight * fontFactor);
117 // TODO: this should work, too, but doesn't
118 //ResizeToPreferred();
119 }
120 }
121
122
~TSignatureWindow()123 TSignatureWindow::~TSignatureWindow()
124 {
125 }
126
127
128 void
MenusBeginning()129 TSignatureWindow::MenusBeginning()
130 {
131 fDelete->SetEnabled(fFile);
132 fSave->SetEnabled(IsDirty());
133 fUndo->SetEnabled(false); // ***TODO***
134
135 BTextView* textView = fSigView->fName->TextView();
136 int32 finish = 0;
137 int32 start = 0;
138 if (textView->IsFocus())
139 textView->GetSelection(&start, &finish);
140 else
141 fSigView->fTextView->GetSelection(&start, &finish);
142
143 fCut->SetEnabled(start != finish);
144 fCopy->SetEnabled(start != finish);
145
146 fNew->SetEnabled(textView->TextLength()
147 | fSigView->fTextView->TextLength());
148 be_clipboard->Lock();
149 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain",
150 B_MIME_TYPE));
151 be_clipboard->Unlock();
152
153 // Undo stuff
154 bool isRedo = false;
155 undo_state undoState = B_UNDO_UNAVAILABLE;
156
157 BTextView *focusTextView = dynamic_cast<BTextView *>(CurrentFocus());
158 if (focusTextView != NULL)
159 undoState = focusTextView->UndoState(&isRedo);
160
161 fUndo->SetLabel(isRedo ? kRedoStrings[undoState] : kUndoStrings[undoState]);
162 fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE);
163 }
164
165
166 void
MessageReceived(BMessage * msg)167 TSignatureWindow::MessageReceived(BMessage* msg)
168 {
169 switch (msg->what) {
170 case CHANGE_FONT:
171 {
172 BFont* font;
173 msg->FindPointer("font", (void **)&font);
174 fSigView->fTextView->SetFontAndColor(font);
175 fSigView->fTextView->Invalidate(fSigView->fTextView->Bounds());
176 break;
177 }
178
179 case M_NEW:
180 if (Clear()) {
181 fSigView->fName->SetText("");
182 fSigView->fTextView->SetText("");
183 fSigView->fName->MakeFocus(true);
184 }
185 break;
186
187 case M_SAVE:
188 Save();
189 break;
190
191 case M_DELETE: {
192 BAlert* alert = new BAlert("",
193 B_TRANSLATE("Really delete this signature? This cannot "
194 "be undone."),
195 B_TRANSLATE("Cancel"),
196 B_TRANSLATE("Delete"),
197 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
198 alert->SetShortcut(0, B_ESCAPE);
199 int32 choice = alert->Go();
200
201 if (choice == 0)
202 break;
203
204 if (fFile) {
205 delete fFile;
206 fFile = NULL;
207 fEntry.Remove();
208 fSigView->fName->SetText("");
209 fSigView->fTextView->SetText(NULL, (int32)0);
210 fSigView->fName->MakeFocus(true);
211 }
212 break;
213 }
214 case M_SIGNATURE:
215 if (Clear()) {
216 entry_ref ref;
217 msg->FindRef("ref", &ref);
218 fEntry.SetTo(&ref);
219 fFile = new BFile(&ref, O_RDWR);
220 if (fFile->InitCheck() == B_OK) {
221 char name[B_FILE_NAME_LENGTH];
222 fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name,
223 sizeof(name));
224 fSigView->fName->SetText(name);
225
226 off_t size;
227 fFile->GetSize(&size);
228 char* sig = (char*)malloc(size);
229 if (sig == NULL)
230 break;
231
232 size = fFile->Read(sig, size);
233 fSigView->fTextView->SetText(sig, size);
234 fSigView->fName->MakeFocus(true);
235 BTextView* textView = fSigView->fName->TextView();
236 textView->Select(0, textView->TextLength());
237 fSigView->fTextView->fDirty = false;
238 } else {
239 fFile = NULL;
240 beep();
241 BAlert* alert = new BAlert("",
242 B_TRANSLATE("Couldn't open this signature. Sorry."),
243 B_TRANSLATE("OK"));
244 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
245 alert->Go();
246 }
247 }
248 break;
249
250 default:
251 BWindow::MessageReceived(msg);
252 break;
253 }
254 }
255
256
257 bool
QuitRequested()258 TSignatureWindow::QuitRequested()
259 {
260 if (Clear()) {
261 BMessage msg(WINDOW_CLOSED);
262 msg.AddInt32("kind", SIG_WINDOW);
263 msg.AddRect("window frame", Frame());
264
265 be_app->PostMessage(&msg);
266 return true;
267 }
268 return false;
269 }
270
271
272 void
FrameResized(float width,float height)273 TSignatureWindow::FrameResized(float width, float height)
274 {
275 fSigView->FrameResized(width, height);
276 }
277
278
279 void
Show()280 TSignatureWindow::Show()
281 {
282 Lock();
283 BTextView* textView = (BTextView *)fSigView->fName->TextView();
284 fSigView->fName->MakeFocus(true);
285 textView->Select(0, textView->TextLength());
286 Unlock();
287
288 BWindow::Show();
289 }
290
291
292 bool
Clear()293 TSignatureWindow::Clear()
294 {
295 if (IsDirty()) {
296 beep();
297 BAlert *alert = new BAlert("",
298 B_TRANSLATE("Save changes to this signature?"),
299 B_TRANSLATE("Cancel"),
300 B_TRANSLATE("Don't save"),
301 B_TRANSLATE("Save"),
302 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
303 alert->SetShortcut(0, B_ESCAPE);
304 alert->SetShortcut(1, 'd');
305 alert->SetShortcut(2, 's');
306 int32 result = alert->Go();
307 if (result == 0)
308 return false;
309 if (result == 2)
310 Save();
311 }
312
313 delete fFile;
314 fFile = NULL;
315 fSigView->fTextView->fDirty = false;
316 return true;
317 }
318
319
320 bool
IsDirty()321 TSignatureWindow::IsDirty()
322 {
323 if (fFile != NULL) {
324 char name[B_FILE_NAME_LENGTH];
325 fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
326 if (strcmp(name, fSigView->fName->Text()) != 0
327 || fSigView->fTextView->fDirty) {
328 return true;
329 }
330 } else if (fSigView->fName->Text()[0] != '\0'
331 || fSigView->fTextView->TextLength() != 0) {
332 return true;
333 }
334 return false;
335 }
336
337
338 void
Save()339 TSignatureWindow::Save()
340 {
341 char name[B_FILE_NAME_LENGTH];
342 int32 index = 0;
343 status_t result;
344 BDirectory dir;
345 BEntry entry;
346 BNodeInfo *node;
347 BPath path;
348
349 if (!fFile) {
350 find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
351 dir.SetTo(path.Path());
352
353 if (dir.FindEntry("Mail", &entry) == B_NO_ERROR)
354 dir.SetTo(&entry);
355 else
356 dir.CreateDirectory("Mail", &dir);
357
358 if (dir.InitCheck() != B_NO_ERROR)
359 goto err_exit;
360
361 if (dir.FindEntry("signatures", &entry) == B_NO_ERROR)
362 dir.SetTo(&entry);
363 else
364 dir.CreateDirectory("signatures", &dir);
365
366 if (dir.InitCheck() != B_NO_ERROR)
367 goto err_exit;
368
369 fFile = new BFile();
370 while(true) {
371 sprintf(name, "signature_%" B_PRId32, index++);
372 if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR)
373 break;
374 if (result != EEXIST)
375 goto err_exit;
376 }
377 dir.FindEntry(name, &fEntry);
378 node = new BNodeInfo(fFile);
379 node->SetType("text/plain");
380 delete node;
381 }
382
383 fSigView->fTextView->fDirty = false;
384 fFile->Seek(0, 0);
385 fFile->Write(fSigView->fTextView->Text(),
386 fSigView->fTextView->TextLength());
387 fFile->SetSize(fFile->Position());
388 fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(),
389 strlen(fSigView->fName->Text()) + 1);
390 return;
391
392 err_exit:
393 beep();
394 BAlert* alert = new BAlert("",
395 B_TRANSLATE("An error occurred trying to save this signature."),
396 B_TRANSLATE("Sorry"));
397 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
398 alert->Go();
399 }
400
401
402 // #pragma mark -
403
404
TSignatureView()405 TSignatureView::TSignatureView()
406 :
407 BGridView("SigView")
408 {
409 GridLayout()->SetInsets(B_USE_DEFAULT_SPACING);
410
411 BStringView* nameLabel = new BStringView("NameLabel",
412 B_TRANSLATE("Title:"));
413 nameLabel->SetAlignment(B_ALIGN_RIGHT);
414 GridLayout()->AddView(nameLabel, 0, 0);
415
416 fName = new TNameControl("", new BMessage(NAME_FIELD));
417 GridLayout()->AddItem(fName->CreateTextViewLayoutItem(), 1, 0);
418
419 BStringView* signatureLabel = new BStringView("SigLabel",
420 B_TRANSLATE("Signature:"));
421 signatureLabel->SetAlignment(B_ALIGN_RIGHT);
422 GridLayout()->AddView(signatureLabel, 0, 1);
423
424 fTextView = new TSigTextView();
425
426 font_height fontHeight;
427 fTextView->GetFontHeight(&fontHeight);
428 float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
429
430 BScrollView* scroller = new BScrollView("SigScroller", fTextView, 0,
431 false, true);
432 scroller->SetExplicitPreferredSize(
433 BSize(fTextView->StringWidth("W") * 30, lineHeight * 6));
434
435 GridLayout()->AddView(scroller, 1, 1, 1, 2);
436
437 GridLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0, 2);
438 }
439
440
441 void
AttachedToWindow()442 TSignatureView::AttachedToWindow()
443 {
444 }
445
446
447 // #pragma mark -
448
449
TNameControl(const char * label,BMessage * invocationMessage)450 TNameControl::TNameControl(const char* label, BMessage* invocationMessage)
451 :
452 BTextControl("", label, "", invocationMessage)
453 {
454 strcpy(fLabel, label);
455 }
456
457
458 void
AttachedToWindow()459 TNameControl::AttachedToWindow()
460 {
461 BTextControl::AttachedToWindow();
462
463 TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
464 }
465
466
467 void
MessageReceived(BMessage * msg)468 TNameControl::MessageReceived(BMessage* msg)
469 {
470 switch (msg->what) {
471 case M_SELECT:
472 TextView()->Select(0, TextView()->TextLength());
473 break;
474
475 default:
476 BTextControl::MessageReceived(msg);
477 }
478 }
479
480
481 // #pragma mark -
482
483
TSigTextView()484 TSigTextView::TSigTextView()
485 :
486 BTextView("SignatureView", B_NAVIGABLE | B_WILL_DRAW)
487 {
488 fDirty = false;
489 SetDoesUndo(true);
490 }
491
492
493 void
DeleteText(int32 offset,int32 len)494 TSigTextView::DeleteText(int32 offset, int32 len)
495 {
496 fDirty = true;
497 BTextView::DeleteText(offset, len);
498 }
499
500
501 void
InsertText(const char * text,int32 len,int32 offset,const text_run_array * runs)502 TSigTextView::InsertText(const char *text, int32 len, int32 offset,
503 const text_run_array *runs)
504 {
505 fDirty = true;
506 BTextView::InsertText(text, len, offset, runs);
507 }
508
509
510 void
KeyDown(const char * key,int32 count)511 TSigTextView::KeyDown(const char *key, int32 count)
512 {
513 bool up = false;
514 int32 height;
515 BRect r;
516
517 switch (key[0]) {
518 case B_HOME:
519 Select(0, 0);
520 ScrollToSelection();
521 break;
522
523 case B_END:
524 Select(TextLength(), TextLength());
525 ScrollToSelection();
526 break;
527
528 case B_PAGE_UP:
529 up = true;
530 case B_PAGE_DOWN:
531 r = Bounds();
532 height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
533 if ((up) && (!r.top))
534 break;
535 ScrollBy(0, height);
536 break;
537
538 default:
539 BTextView::KeyDown(key, count);
540 break;
541 }
542 }
543
544
545 void
MessageReceived(BMessage * msg)546 TSigTextView::MessageReceived(BMessage *msg)
547 {
548 char type[B_FILE_NAME_LENGTH];
549 char *text;
550 int32 end;
551 int32 start;
552 BFile file;
553 BNodeInfo *node;
554 entry_ref ref;
555 off_t size;
556
557 switch (msg->what) {
558 case B_SIMPLE_DATA:
559 if (msg->HasRef("refs")) {
560 msg->FindRef("refs", &ref);
561 file.SetTo(&ref, O_RDONLY);
562 if (file.InitCheck() == B_NO_ERROR) {
563 node = new BNodeInfo(&file);
564 node->GetType(type);
565 delete node;
566 file.GetSize(&size);
567 if ((!strncasecmp(type, "text/", 5)) && (size)) {
568 text = (char *)malloc(size);
569 file.Read(text, size);
570 Delete();
571 GetSelection(&start, &end);
572 Insert(text, size);
573 Select(start, start + size);
574 free(text);
575 }
576 }
577 }
578 else
579 BTextView::MessageReceived(msg);
580 break;
581
582 case M_SELECT:
583 if (IsSelectable())
584 Select(0, TextLength());
585 break;
586
587 default:
588 BTextView::MessageReceived(msg);
589 break;
590 }
591 }
592