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