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