xref: /haiku/src/apps/mail/Signature.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 
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 
123 TSignatureWindow::~TSignatureWindow()
124 {
125 }
126 
127 
128 void
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
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 	}
253 }
254 
255 
256 bool
257 TSignatureWindow::QuitRequested()
258 {
259 	if (Clear()) {
260 		BMessage msg(WINDOW_CLOSED);
261 		msg.AddInt32("kind", SIG_WINDOW);
262 		msg.AddRect("window frame", Frame());
263 
264 		be_app->PostMessage(&msg);
265 		return true;
266 	}
267 	return false;
268 }
269 
270 
271 void
272 TSignatureWindow::FrameResized(float width, float height)
273 {
274 	fSigView->FrameResized(width, height);
275 }
276 
277 
278 void
279 TSignatureWindow::Show()
280 {
281 	Lock();
282 	BTextView* textView = (BTextView *)fSigView->fName->TextView();
283 	fSigView->fName->MakeFocus(true);
284 	textView->Select(0, textView->TextLength());
285 	Unlock();
286 
287 	BWindow::Show();
288 }
289 
290 
291 bool
292 TSignatureWindow::Clear()
293 {
294 	if (IsDirty()) {
295 		beep();
296 		BAlert *alert = new BAlert("",
297 			B_TRANSLATE("Save changes to this signature?"),
298 			B_TRANSLATE("Cancel"),
299 			B_TRANSLATE("Don't save"),
300 			B_TRANSLATE("Save"),
301 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
302 		alert->SetShortcut(0, B_ESCAPE);
303 		alert->SetShortcut(1, 'd');
304 		alert->SetShortcut(2, 's');
305 		int32 result = alert->Go();
306 		if (result == 0)
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 	if (fFile != NULL) {
323 		char name[B_FILE_NAME_LENGTH];
324 		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
325 		if (strcmp(name, fSigView->fName->Text()) != 0
326 			|| fSigView->fTextView->fDirty) {
327 			return true;
328 		}
329 	} else if (fSigView->fName->Text()[0] != '\0'
330 		|| fSigView->fTextView->TextLength() != 0) {
331 		return true;
332 	}
333 	return false;
334 }
335 
336 
337 void
338 TSignatureWindow::Save()
339 {
340 	char			name[B_FILE_NAME_LENGTH];
341 	int32			index = 0;
342 	status_t		result;
343 	BDirectory		dir;
344 	BEntry			entry;
345 	BNodeInfo		*node;
346 	BPath			path;
347 
348 	if (!fFile) {
349 		find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
350 		dir.SetTo(path.Path());
351 
352 		if (dir.FindEntry("Mail", &entry) == B_NO_ERROR)
353 			dir.SetTo(&entry);
354 		else
355 			dir.CreateDirectory("Mail", &dir);
356 
357 		if (dir.InitCheck() != B_NO_ERROR)
358 			goto err_exit;
359 
360 		if (dir.FindEntry("signatures", &entry) == B_NO_ERROR)
361 			dir.SetTo(&entry);
362 		else
363 			dir.CreateDirectory("signatures", &dir);
364 
365 		if (dir.InitCheck() != B_NO_ERROR)
366 			goto err_exit;
367 
368 		fFile = new BFile();
369 		while(true) {
370 			sprintf(name, "signature_%" B_PRId32, index++);
371 			if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR)
372 				break;
373 			if (result != EEXIST)
374 				goto err_exit;
375 		}
376 		dir.FindEntry(name, &fEntry);
377 		node = new BNodeInfo(fFile);
378 		node->SetType("text/plain");
379 		delete node;
380 	}
381 
382 	fSigView->fTextView->fDirty = false;
383 	fFile->Seek(0, 0);
384 	fFile->Write(fSigView->fTextView->Text(),
385 				 fSigView->fTextView->TextLength());
386 	fFile->SetSize(fFile->Position());
387 	fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(),
388 					 strlen(fSigView->fName->Text()) + 1);
389 	return;
390 
391 err_exit:
392 	beep();
393 	BAlert* alert = new BAlert("",
394 		B_TRANSLATE("An error occurred trying to save this signature."),
395 		B_TRANSLATE("Sorry"));
396 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
397 	alert->Go();
398 }
399 
400 
401 // #pragma mark -
402 
403 
404 TSignatureView::TSignatureView()
405 	:
406 	BGridView("SigView")
407 {
408 	GridLayout()->SetInsets(B_USE_DEFAULT_SPACING);
409 
410 	BStringView* nameLabel = new BStringView("NameLabel",
411 		B_TRANSLATE("Title:"));
412 	nameLabel->SetAlignment(B_ALIGN_RIGHT);
413 	GridLayout()->AddView(nameLabel, 0, 0);
414 
415 	fName = new TNameControl("", new BMessage(NAME_FIELD));
416 	GridLayout()->AddItem(fName->CreateTextViewLayoutItem(), 1, 0);
417 
418 	BStringView* signatureLabel = new BStringView("SigLabel",
419 		B_TRANSLATE("Signature:"));
420 	signatureLabel->SetAlignment(B_ALIGN_RIGHT);
421 	GridLayout()->AddView(signatureLabel, 0, 1);
422 
423 	fTextView = new TSigTextView();
424 
425 	font_height fontHeight;
426 	fTextView->GetFontHeight(&fontHeight);
427 	float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
428 
429 	BScrollView* scroller = new BScrollView("SigScroller", fTextView, 0,
430 		false, true);
431 	scroller->SetExplicitPreferredSize(
432 		BSize(fTextView->StringWidth("W") * 30, lineHeight * 6));
433 
434 	GridLayout()->AddView(scroller, 1, 1, 1, 2);
435 
436 	GridLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0, 2);
437 }
438 
439 
440 void
441 TSignatureView::AttachedToWindow()
442 {
443 }
444 
445 
446 // #pragma mark -
447 
448 
449 TNameControl::TNameControl(const char* label, BMessage* invocationMessage)
450 	:
451 	BTextControl("", label, "", invocationMessage)
452 {
453 	strcpy(fLabel, label);
454 }
455 
456 
457 void
458 TNameControl::AttachedToWindow()
459 {
460 	BTextControl::AttachedToWindow();
461 
462 	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
463 }
464 
465 
466 void
467 TNameControl::MessageReceived(BMessage* msg)
468 {
469 	switch (msg->what) {
470 		case M_SELECT:
471 			TextView()->Select(0, TextView()->TextLength());
472 			break;
473 
474 		default:
475 			BTextControl::MessageReceived(msg);
476 	}
477 }
478 
479 
480 // #pragma mark -
481 
482 
483 TSigTextView::TSigTextView()
484 	:
485 	BTextView("SignatureView", B_NAVIGABLE | B_WILL_DRAW)
486 {
487 	fDirty = false;
488 	SetDoesUndo(true);
489 }
490 
491 
492 void
493 TSigTextView::DeleteText(int32 offset, int32 len)
494 {
495 	fDirty = true;
496 	BTextView::DeleteText(offset, len);
497 }
498 
499 
500 void
501 TSigTextView::InsertText(const char *text, int32 len, int32 offset,
502 	const text_run_array *runs)
503 {
504 	fDirty = true;
505 	BTextView::InsertText(text, len, offset, runs);
506 }
507 
508 
509 void
510 TSigTextView::KeyDown(const char *key, int32 count)
511 {
512 	bool	up = false;
513 	int32	height;
514 	BRect	r;
515 
516 	switch (key[0]) {
517 		case B_HOME:
518 			Select(0, 0);
519 			ScrollToSelection();
520 			break;
521 
522 		case B_END:
523 			Select(TextLength(), TextLength());
524 			ScrollToSelection();
525 			break;
526 
527 		case B_PAGE_UP:
528 			up = true;
529 		case B_PAGE_DOWN:
530 			r = Bounds();
531 			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
532 			if ((up) && (!r.top))
533 				break;
534 			ScrollBy(0, height);
535 			break;
536 
537 		default:
538 			BTextView::KeyDown(key, count);
539 	}
540 }
541 
542 
543 void
544 TSigTextView::MessageReceived(BMessage *msg)
545 {
546 	char		type[B_FILE_NAME_LENGTH];
547 	char		*text;
548 	int32		end;
549 	int32		start;
550 	BFile		file;
551 	BNodeInfo	*node;
552 	entry_ref	ref;
553 	off_t		size;
554 
555 	switch (msg->what) {
556 		case B_SIMPLE_DATA:
557 			if (msg->HasRef("refs")) {
558 				msg->FindRef("refs", &ref);
559 				file.SetTo(&ref, O_RDONLY);
560 				if (file.InitCheck() == B_NO_ERROR) {
561 					node = new BNodeInfo(&file);
562 					node->GetType(type);
563 					delete node;
564 					file.GetSize(&size);
565 					if ((!strncasecmp(type, "text/", 5)) && (size)) {
566 						text = (char *)malloc(size);
567 						file.Read(text, size);
568 						Delete();
569 						GetSelection(&start, &end);
570 						Insert(text, size);
571 						Select(start, start + size);
572 						free(text);
573 					}
574 				}
575 			}
576 			else
577 				BTextView::MessageReceived(msg);
578 			break;
579 
580 		case M_SELECT:
581 			if (IsSelectable())
582 				Select(0, TextLength());
583 			break;
584 
585 		default:
586 			BTextView::MessageReceived(msg);
587 	}
588 }
589 
590