xref: /haiku/src/apps/mail/Signature.cpp (revision 70449c90d95aeea01b25f686d51da4217fe41317)
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("Don't save"),
300 			B_TRANSLATE("Cancel"),
301 			B_TRANSLATE("Save"),
302 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
303 		alert->SetShortcut(0, 'd');
304 		alert->SetShortcut(1, B_ESCAPE);
305 		result = alert->Go();
306 		if (result == 1)
307 			return false;
308 		if (result == 2)
309 			Save();
310 	}
311 
312 	delete fFile;
313 	fFile = NULL;
314 	fSigView->fTextView->fDirty = false;
315 	return true;
316 }
317 
318 
319 bool
320 TSignatureWindow::IsDirty()
321 {
322 	char		name[B_FILE_NAME_LENGTH];
323 
324 	if (fFile) {
325 		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
326 		if ((strcmp(name, fSigView->fName->Text())) || (fSigView->fTextView->fDirty))
327 			return true;
328 	}
329 	else {
330 		if ((strlen(fSigView->fName->Text())) ||
331 			(fSigView->fTextView->TextLength()))
332 			return true;
333 	}
334 	return false;
335 }
336 
337 
338 void
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_%ld", 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 //====================================================================
403 //	#pragma mark -
404 
405 
406 TSignatureView::TSignatureView(BRect rect)
407 	: BBox(rect, "SigView", B_FOLLOW_ALL, B_WILL_DRAW)
408 {
409 }
410 
411 
412 void
413 TSignatureView::AttachedToWindow()
414 {
415 	BRect	rect = Bounds();
416 	float	name_text_length = StringWidth(B_TRANSLATE("Title:"));
417 	float	sig_text_length = StringWidth(B_TRANSLATE("Signature:"));
418 	float	divide_length;
419 
420 	if (name_text_length > sig_text_length)
421 		divide_length = name_text_length;
422 	else
423 		divide_length = sig_text_length;
424 
425 	rect.InsetBy(8,0);
426 	rect.top+= 8;
427 
428 	fName = new TNameControl(rect, B_TRANSLATE("Title:"),
429 		new BMessage(NAME_FIELD));
430 	AddChild(fName);
431 
432 	fName->SetDivider(divide_length + 10);
433 	fName->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
434 
435 	rect.OffsetBy(0,fName->Bounds().Height()+5);
436 	rect.bottom = rect.top + kSigHeight;
437 	rect.left = fName->TextView()->Frame().left;
438 
439 	BRect text = rect;
440 	text.OffsetTo(10,0);
441 	fTextView = new TSigTextView(rect, text);
442 	BScrollView *scroller = new BScrollView("SigScroller", fTextView, B_FOLLOW_ALL, 0, false, true);
443 	AddChild(scroller);
444 	scroller->ResizeBy(-1 * scroller->ScrollBar(B_VERTICAL)->Frame().Width() - 9, 0);
445 	scroller->MoveBy(7,0);
446 
447 	/* back up a bit to make room for the label */
448 
449 	rect = scroller->Frame();
450 	BStringView *stringView = new BStringView(rect, "SigLabel",
451 		B_TRANSLATE("Signature:"));
452 	AddChild(stringView);
453 
454 	float tWidth, tHeight;
455 	stringView->GetPreferredSize(&tWidth, &tHeight);
456 
457 	/* the 5 is for the spacer in the TextView */
458 
459 	rect.OffsetBy(-1 *(tWidth) - 5, 0);
460 	rect.right = rect.left + tWidth;
461 	rect.bottom = rect.top + tHeight;
462 
463 	stringView->MoveTo(rect.LeftTop());
464 	stringView->ResizeTo(rect.Width(), rect.Height());
465 
466 	/* Resize the View to the correct height */
467 	scroller->SetResizingMode(B_FOLLOW_NONE);
468 	ResizeTo(Frame().Width(), scroller->Frame().bottom + 8);
469 	scroller->SetResizingMode(B_FOLLOW_ALL);
470 }
471 
472 
473 //====================================================================
474 //	#pragma mark -
475 
476 
477 TNameControl::TNameControl(BRect rect, const char *label, BMessage *msg)
478 			 :BTextControl(rect, "", label, "", msg, B_FOLLOW_LEFT_RIGHT)
479 {
480 	strcpy(fLabel, label);
481 }
482 
483 
484 void
485 TNameControl::AttachedToWindow()
486 {
487 	BTextControl::AttachedToWindow();
488 
489 	SetDivider(StringWidth(fLabel) + 6);
490 	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
491 }
492 
493 
494 void
495 TNameControl::MessageReceived(BMessage *msg)
496 {
497 	switch (msg->what) {
498 		case M_SELECT:
499 			TextView()->Select(0, TextView()->TextLength());
500 			break;
501 
502 		default:
503 			BTextControl::MessageReceived(msg);
504 	}
505 }
506 
507 
508 //====================================================================
509 //	#pragma mark -
510 
511 
512 TSigTextView::TSigTextView(BRect frame, BRect text)
513 			 :BTextView(frame, "SignatureView", text, B_FOLLOW_ALL, B_NAVIGABLE | B_WILL_DRAW)
514 {
515 	fDirty = false;
516 	SetDoesUndo(true);
517 }
518 
519 
520 void
521 TSigTextView::FrameResized(float /*width*/, float /*height*/)
522 {
523 	BRect r(Bounds());
524 	r.InsetBy(3, 3);
525 	SetTextRect(r);
526 }
527 
528 
529 void
530 TSigTextView::DeleteText(int32 offset, int32 len)
531 {
532 	fDirty = true;
533 	BTextView::DeleteText(offset, len);
534 }
535 
536 
537 void
538 TSigTextView::InsertText(const char *text, int32 len, int32 offset,
539 	const text_run_array *runs)
540 {
541 	fDirty = true;
542 	BTextView::InsertText(text, len, offset, runs);
543 }
544 
545 
546 void
547 TSigTextView::KeyDown(const char *key, int32 count)
548 {
549 	bool	up = false;
550 	int32	height;
551 	BRect	r;
552 
553 	switch (key[0]) {
554 		case B_HOME:
555 			Select(0, 0);
556 			ScrollToSelection();
557 			break;
558 
559 		case B_END:
560 			Select(TextLength(), TextLength());
561 			ScrollToSelection();
562 			break;
563 
564 		case B_PAGE_UP:
565 			up = true;
566 		case B_PAGE_DOWN:
567 			r = Bounds();
568 			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
569 			if ((up) && (!r.top))
570 				break;
571 			ScrollBy(0, height);
572 			break;
573 
574 		default:
575 			BTextView::KeyDown(key, count);
576 	}
577 }
578 
579 
580 void
581 TSigTextView::MessageReceived(BMessage *msg)
582 {
583 	char		type[B_FILE_NAME_LENGTH];
584 	char		*text;
585 	int32		end;
586 	int32		start;
587 	BFile		file;
588 	BNodeInfo	*node;
589 	entry_ref	ref;
590 	off_t		size;
591 
592 	switch (msg->what) {
593 		case B_SIMPLE_DATA:
594 			if (msg->HasRef("refs")) {
595 				msg->FindRef("refs", &ref);
596 				file.SetTo(&ref, O_RDONLY);
597 				if (file.InitCheck() == B_NO_ERROR) {
598 					node = new BNodeInfo(&file);
599 					node->GetType(type);
600 					delete node;
601 					file.GetSize(&size);
602 					if ((!strncasecmp(type, "text/", 5)) && (size)) {
603 						text = (char *)malloc(size);
604 						file.Read(text, size);
605 						Delete();
606 						GetSelection(&start, &end);
607 						Insert(text, size);
608 						Select(start, start + size);
609 						free(text);
610 					}
611 				}
612 			}
613 			else
614 				BTextView::MessageReceived(msg);
615 			break;
616 
617 		case M_SELECT:
618 			if (IsSelectable())
619 				Select(0, TextLength());
620 			break;
621 
622 		default:
623 			BTextView::MessageReceived(msg);
624 	}
625 }
626 
627