xref: /haiku/src/apps/mail/Signature.cpp (revision d374a27286b8a52974a97dba0d5966ea026a665d)
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_TRANSLATE_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 			if (!(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))->Go())
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 				msg->FindRef("ref", &ref);
217 				fEntry.SetTo(&ref);
218 				fFile = new BFile(&ref, O_RDWR);
219 				if (fFile->InitCheck() == B_NO_ERROR) {
220 					fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
221 					fSigView->fName->SetText(name);
222 					fFile->GetSize(&size);
223 					sig = (char *)malloc(size);
224 					size = fFile->Read(sig, size);
225 					fSigView->fTextView->SetText(sig, size);
226 					fSigView->fName->MakeFocus(true);
227 					text_view = (BTextView *)fSigView->fName->ChildAt(0);
228 					text_view->Select(0, text_view->TextLength());
229 					fSigView->fTextView->fDirty = false;
230 				}
231 				else {
232 					fFile = NULL;
233 					beep();
234 					(new BAlert("",
235 						B_TRANSLATE("Couldn't open this signature. Sorry."),
236 						B_TRANSLATE("OK")))->Go();
237 				}
238 			}
239 			break;
240 
241 		default:
242 			BWindow::MessageReceived(msg);
243 	}
244 }
245 
246 
247 bool
248 TSignatureWindow::QuitRequested()
249 {
250 	if (Clear()) {
251 		BMessage msg(WINDOW_CLOSED);
252 		msg.AddInt32("kind", SIG_WINDOW);
253 		msg.AddRect("window frame", Frame());
254 
255 		be_app->PostMessage(&msg);
256 		return true;
257 	}
258 	return false;
259 }
260 
261 
262 void
263 TSignatureWindow::FrameResized(float width, float height)
264 {
265 	fSigView->FrameResized(width, height);
266 }
267 
268 
269 void
270 TSignatureWindow::Show()
271 {
272 	BTextView	*text_view;
273 
274 	Lock();
275 	text_view = (BTextView *)fSigView->fName->TextView();
276 	fSigView->fName->MakeFocus(true);
277 	text_view->Select(0, text_view->TextLength());
278 	Unlock();
279 
280 	BWindow::Show();
281 }
282 
283 
284 bool
285 TSignatureWindow::Clear()
286 {
287 	int32		result;
288 
289 	if (IsDirty()) {
290 		beep();
291 		BAlert *alert = new BAlert("",
292 			B_TRANSLATE("Save changes to this signature?"),
293 			B_TRANSLATE("Don't save"),
294 			B_TRANSLATE("Cancel"),
295 			B_TRANSLATE("Save"),
296 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
297 		alert->SetShortcut(0, 'd');
298 		alert->SetShortcut(1, B_ESCAPE);
299 		result = alert->Go();
300 		if (result == 1)
301 			return false;
302 		if (result == 2)
303 			Save();
304 	}
305 
306 	delete fFile;
307 	fFile = NULL;
308 	fSigView->fTextView->fDirty = false;
309 	return true;
310 }
311 
312 
313 bool
314 TSignatureWindow::IsDirty()
315 {
316 	char		name[B_FILE_NAME_LENGTH];
317 
318 	if (fFile) {
319 		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
320 		if ((strcmp(name, fSigView->fName->Text())) || (fSigView->fTextView->fDirty))
321 			return true;
322 	}
323 	else {
324 		if ((strlen(fSigView->fName->Text())) ||
325 			(fSigView->fTextView->TextLength()))
326 			return true;
327 	}
328 	return false;
329 }
330 
331 
332 void
333 TSignatureWindow::Save()
334 {
335 	char			name[B_FILE_NAME_LENGTH];
336 	int32			index = 0;
337 	status_t		result;
338 	BDirectory		dir;
339 	BEntry			entry;
340 	BNodeInfo		*node;
341 	BPath			path;
342 
343 	if (!fFile) {
344 		find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
345 		dir.SetTo(path.Path());
346 
347 		if (dir.FindEntry("Mail", &entry) == B_NO_ERROR)
348 			dir.SetTo(&entry);
349 		else
350 			dir.CreateDirectory("Mail", &dir);
351 
352 		if (dir.InitCheck() != B_NO_ERROR)
353 			goto err_exit;
354 
355 		if (dir.FindEntry("signatures", &entry) == B_NO_ERROR)
356 			dir.SetTo(&entry);
357 		else
358 			dir.CreateDirectory("signatures", &dir);
359 
360 		if (dir.InitCheck() != B_NO_ERROR)
361 			goto err_exit;
362 
363 		fFile = new BFile();
364 		while(true) {
365 			sprintf(name, "signature_%ld", index++);
366 			if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR)
367 				break;
368 			if (result != EEXIST)
369 				goto err_exit;
370 		}
371 		dir.FindEntry(name, &fEntry);
372 		node = new BNodeInfo(fFile);
373 		node->SetType("text/plain");
374 		delete node;
375 	}
376 
377 	fSigView->fTextView->fDirty = false;
378 	fFile->Seek(0, 0);
379 	fFile->Write(fSigView->fTextView->Text(),
380 				 fSigView->fTextView->TextLength());
381 	fFile->SetSize(fFile->Position());
382 	fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(),
383 					 strlen(fSigView->fName->Text()) + 1);
384 	return;
385 
386 err_exit:
387 	beep();
388 	(new BAlert("",
389 		B_TRANSLATE("An error occurred trying to save this signature."),
390 		B_TRANSLATE("Sorry")))->Go();
391 }
392 
393 
394 //====================================================================
395 //	#pragma mark -
396 
397 
398 TSignatureView::TSignatureView(BRect rect)
399 	: BBox(rect, "SigView", B_FOLLOW_ALL, B_WILL_DRAW)
400 {
401 }
402 
403 
404 void
405 TSignatureView::AttachedToWindow()
406 {
407 	BRect	rect = Bounds();
408 	float	name_text_length = StringWidth(B_TRANSLATE("Title:"));
409 	float	sig_text_length = StringWidth(B_TRANSLATE("Signature:"));
410 	float	divide_length;
411 
412 	if (name_text_length > sig_text_length)
413 		divide_length = name_text_length;
414 	else
415 		divide_length = sig_text_length;
416 
417 	rect.InsetBy(8,0);
418 	rect.top+= 8;
419 
420 	fName = new TNameControl(rect, B_TRANSLATE("Title:"),
421 		new BMessage(NAME_FIELD));
422 	AddChild(fName);
423 
424 	fName->SetDivider(divide_length + 10);
425 	fName->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
426 
427 	rect.OffsetBy(0,fName->Bounds().Height()+5);
428 	rect.bottom = rect.top + kSigHeight;
429 	rect.left = fName->TextView()->Frame().left;
430 
431 	BRect text = rect;
432 	text.OffsetTo(10,0);
433 	fTextView = new TSigTextView(rect, text);
434 	BScrollView *scroller = new BScrollView("SigScroller", fTextView, B_FOLLOW_ALL, 0, false, true);
435 	AddChild(scroller);
436 	scroller->ResizeBy(-1 * scroller->ScrollBar(B_VERTICAL)->Frame().Width() - 9, 0);
437 	scroller->MoveBy(7,0);
438 
439 	/* back up a bit to make room for the label */
440 
441 	rect = scroller->Frame();
442 	BStringView *stringView = new BStringView(rect, "SigLabel",
443 		B_TRANSLATE("Signature:"));
444 	AddChild(stringView);
445 
446 	float tWidth, tHeight;
447 	stringView->GetPreferredSize(&tWidth, &tHeight);
448 
449 	/* the 5 is for the spacer in the TextView */
450 
451 	rect.OffsetBy(-1 *(tWidth) - 5, 0);
452 	rect.right = rect.left + tWidth;
453 	rect.bottom = rect.top + tHeight;
454 
455 	stringView->MoveTo(rect.LeftTop());
456 	stringView->ResizeTo(rect.Width(), rect.Height());
457 
458 	/* Resize the View to the correct height */
459 	scroller->SetResizingMode(B_FOLLOW_NONE);
460 	ResizeTo(Frame().Width(), scroller->Frame().bottom + 8);
461 	scroller->SetResizingMode(B_FOLLOW_ALL);
462 }
463 
464 
465 //====================================================================
466 //	#pragma mark -
467 
468 
469 TNameControl::TNameControl(BRect rect, const char *label, BMessage *msg)
470 			 :BTextControl(rect, "", label, "", msg, B_FOLLOW_LEFT_RIGHT)
471 {
472 	strcpy(fLabel, label);
473 }
474 
475 
476 void
477 TNameControl::AttachedToWindow()
478 {
479 	BTextControl::AttachedToWindow();
480 
481 	SetDivider(StringWidth(fLabel) + 6);
482 	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
483 }
484 
485 
486 void
487 TNameControl::MessageReceived(BMessage *msg)
488 {
489 	switch (msg->what) {
490 		case M_SELECT:
491 			TextView()->Select(0, TextView()->TextLength());
492 			break;
493 
494 		default:
495 			BTextControl::MessageReceived(msg);
496 	}
497 }
498 
499 
500 //====================================================================
501 //	#pragma mark -
502 
503 
504 TSigTextView::TSigTextView(BRect frame, BRect text)
505 			 :BTextView(frame, "SignatureView", text, B_FOLLOW_ALL, B_NAVIGABLE | B_WILL_DRAW)
506 {
507 	fDirty = false;
508 	SetDoesUndo(true);
509 }
510 
511 
512 void
513 TSigTextView::FrameResized(float /*width*/, float /*height*/)
514 {
515 	BRect r(Bounds());
516 	r.InsetBy(3, 3);
517 	SetTextRect(r);
518 }
519 
520 
521 void
522 TSigTextView::DeleteText(int32 offset, int32 len)
523 {
524 	fDirty = true;
525 	BTextView::DeleteText(offset, len);
526 }
527 
528 
529 void
530 TSigTextView::InsertText(const char *text, int32 len, int32 offset,
531 	const text_run_array *runs)
532 {
533 	fDirty = true;
534 	BTextView::InsertText(text, len, offset, runs);
535 }
536 
537 
538 void
539 TSigTextView::KeyDown(const char *key, int32 count)
540 {
541 	bool	up = false;
542 	int32	height;
543 	BRect	r;
544 
545 	switch (key[0]) {
546 		case B_HOME:
547 			Select(0, 0);
548 			ScrollToSelection();
549 			break;
550 
551 		case B_END:
552 			Select(TextLength(), TextLength());
553 			ScrollToSelection();
554 			break;
555 
556 		case B_PAGE_UP:
557 			up = true;
558 		case B_PAGE_DOWN:
559 			r = Bounds();
560 			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
561 			if ((up) && (!r.top))
562 				break;
563 			ScrollBy(0, height);
564 			break;
565 
566 		default:
567 			BTextView::KeyDown(key, count);
568 	}
569 }
570 
571 
572 void
573 TSigTextView::MessageReceived(BMessage *msg)
574 {
575 	char		type[B_FILE_NAME_LENGTH];
576 	char		*text;
577 	int32		end;
578 	int32		start;
579 	BFile		file;
580 	BNodeInfo	*node;
581 	entry_ref	ref;
582 	off_t		size;
583 
584 	switch (msg->what) {
585 		case B_SIMPLE_DATA:
586 			if (msg->HasRef("refs")) {
587 				msg->FindRef("refs", &ref);
588 				file.SetTo(&ref, O_RDONLY);
589 				if (file.InitCheck() == B_NO_ERROR) {
590 					node = new BNodeInfo(&file);
591 					node->GetType(type);
592 					delete node;
593 					file.GetSize(&size);
594 					if ((!strncasecmp(type, "text/", 5)) && (size)) {
595 						text = (char *)malloc(size);
596 						file.Read(text, size);
597 						Delete();
598 						GetSelection(&start, &end);
599 						Insert(text, size);
600 						Select(start, start + size);
601 						free(text);
602 					}
603 				}
604 			}
605 			else
606 				BTextView::MessageReceived(msg);
607 			break;
608 
609 		case M_SELECT:
610 			if (IsSelectable())
611 				Select(0, TextLength());
612 			break;
613 
614 		default:
615 			BTextView::MessageReceived(msg);
616 	}
617 }
618 
619