xref: /haiku/src/apps/mail/Signature.cpp (revision ab05d36868610c8bce69e732e9cb3befb3d52c6b)
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 
TSignatureWindow(BRect rect)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 
~TSignatureWindow()123 TSignatureWindow::~TSignatureWindow()
124 {
125 }
126 
127 
128 void
MenusBeginning()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
MessageReceived(BMessage * msg)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 			break;
253 	}
254 }
255 
256 
257 bool
QuitRequested()258 TSignatureWindow::QuitRequested()
259 {
260 	if (Clear()) {
261 		BMessage msg(WINDOW_CLOSED);
262 		msg.AddInt32("kind", SIG_WINDOW);
263 		msg.AddRect("window frame", Frame());
264 
265 		be_app->PostMessage(&msg);
266 		return true;
267 	}
268 	return false;
269 }
270 
271 
272 void
FrameResized(float width,float height)273 TSignatureWindow::FrameResized(float width, float height)
274 {
275 	fSigView->FrameResized(width, height);
276 }
277 
278 
279 void
Show()280 TSignatureWindow::Show()
281 {
282 	Lock();
283 	BTextView* textView = (BTextView *)fSigView->fName->TextView();
284 	fSigView->fName->MakeFocus(true);
285 	textView->Select(0, textView->TextLength());
286 	Unlock();
287 
288 	BWindow::Show();
289 }
290 
291 
292 bool
Clear()293 TSignatureWindow::Clear()
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 		int32 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
IsDirty()321 TSignatureWindow::IsDirty()
322 {
323 	if (fFile != NULL) {
324 		char name[B_FILE_NAME_LENGTH];
325 		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
326 		if (strcmp(name, fSigView->fName->Text()) != 0
327 			|| fSigView->fTextView->fDirty) {
328 			return true;
329 		}
330 	} else if (fSigView->fName->Text()[0] != '\0'
331 		|| fSigView->fTextView->TextLength() != 0) {
332 		return true;
333 	}
334 	return false;
335 }
336 
337 
338 void
Save()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_%" B_PRId32, 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 // #pragma mark -
403 
404 
TSignatureView()405 TSignatureView::TSignatureView()
406 	:
407 	BGridView("SigView")
408 {
409 	GridLayout()->SetInsets(B_USE_DEFAULT_SPACING);
410 
411 	BStringView* nameLabel = new BStringView("NameLabel",
412 		B_TRANSLATE("Title:"));
413 	nameLabel->SetAlignment(B_ALIGN_RIGHT);
414 	GridLayout()->AddView(nameLabel, 0, 0);
415 
416 	fName = new TNameControl("", new BMessage(NAME_FIELD));
417 	GridLayout()->AddItem(fName->CreateTextViewLayoutItem(), 1, 0);
418 
419 	BStringView* signatureLabel = new BStringView("SigLabel",
420 		B_TRANSLATE("Signature:"));
421 	signatureLabel->SetAlignment(B_ALIGN_RIGHT);
422 	GridLayout()->AddView(signatureLabel, 0, 1);
423 
424 	fTextView = new TSigTextView();
425 
426 	font_height fontHeight;
427 	fTextView->GetFontHeight(&fontHeight);
428 	float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
429 
430 	BScrollView* scroller = new BScrollView("SigScroller", fTextView, 0,
431 		false, true);
432 	scroller->SetExplicitPreferredSize(
433 		BSize(fTextView->StringWidth("W") * 30, lineHeight * 6));
434 
435 	GridLayout()->AddView(scroller, 1, 1, 1, 2);
436 
437 	GridLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0, 2);
438 }
439 
440 
441 void
AttachedToWindow()442 TSignatureView::AttachedToWindow()
443 {
444 }
445 
446 
447 // #pragma mark -
448 
449 
TNameControl(const char * label,BMessage * invocationMessage)450 TNameControl::TNameControl(const char* label, BMessage* invocationMessage)
451 	:
452 	BTextControl("", label, "", invocationMessage)
453 {
454 	strcpy(fLabel, label);
455 }
456 
457 
458 void
AttachedToWindow()459 TNameControl::AttachedToWindow()
460 {
461 	BTextControl::AttachedToWindow();
462 
463 	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
464 }
465 
466 
467 void
MessageReceived(BMessage * msg)468 TNameControl::MessageReceived(BMessage* msg)
469 {
470 	switch (msg->what) {
471 		case M_SELECT:
472 			TextView()->Select(0, TextView()->TextLength());
473 			break;
474 
475 		default:
476 			BTextControl::MessageReceived(msg);
477 	}
478 }
479 
480 
481 // #pragma mark -
482 
483 
TSigTextView()484 TSigTextView::TSigTextView()
485 	:
486 	BTextView("SignatureView", B_NAVIGABLE | B_WILL_DRAW)
487 {
488 	fDirty = false;
489 	SetDoesUndo(true);
490 }
491 
492 
493 void
DeleteText(int32 offset,int32 len)494 TSigTextView::DeleteText(int32 offset, int32 len)
495 {
496 	fDirty = true;
497 	BTextView::DeleteText(offset, len);
498 }
499 
500 
501 void
InsertText(const char * text,int32 len,int32 offset,const text_run_array * runs)502 TSigTextView::InsertText(const char *text, int32 len, int32 offset,
503 	const text_run_array *runs)
504 {
505 	fDirty = true;
506 	BTextView::InsertText(text, len, offset, runs);
507 }
508 
509 
510 void
KeyDown(const char * key,int32 count)511 TSigTextView::KeyDown(const char *key, int32 count)
512 {
513 	bool	up = false;
514 	int32	height;
515 	BRect	r;
516 
517 	switch (key[0]) {
518 		case B_HOME:
519 			Select(0, 0);
520 			ScrollToSelection();
521 			break;
522 
523 		case B_END:
524 			Select(TextLength(), TextLength());
525 			ScrollToSelection();
526 			break;
527 
528 		case B_PAGE_UP:
529 			up = true;
530 		case B_PAGE_DOWN:
531 			r = Bounds();
532 			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
533 			if ((up) && (!r.top))
534 				break;
535 			ScrollBy(0, height);
536 			break;
537 
538 		default:
539 			BTextView::KeyDown(key, count);
540 			break;
541 	}
542 }
543 
544 
545 void
MessageReceived(BMessage * msg)546 TSigTextView::MessageReceived(BMessage *msg)
547 {
548 	char		type[B_FILE_NAME_LENGTH];
549 	char		*text;
550 	int32		end;
551 	int32		start;
552 	BFile		file;
553 	BNodeInfo	*node;
554 	entry_ref	ref;
555 	off_t		size;
556 
557 	switch (msg->what) {
558 		case B_SIMPLE_DATA:
559 			if (msg->HasRef("refs")) {
560 				msg->FindRef("refs", &ref);
561 				file.SetTo(&ref, O_RDONLY);
562 				if (file.InitCheck() == B_NO_ERROR) {
563 					node = new BNodeInfo(&file);
564 					node->GetType(type);
565 					delete node;
566 					file.GetSize(&size);
567 					if ((!strncasecmp(type, "text/", 5)) && (size)) {
568 						text = (char *)malloc(size);
569 						file.Read(text, size);
570 						Delete();
571 						GetSelection(&start, &end);
572 						Insert(text, size);
573 						Select(start, start + size);
574 						free(text);
575 					}
576 				}
577 			}
578 			else
579 				BTextView::MessageReceived(msg);
580 			break;
581 
582 		case M_SELECT:
583 			if (IsSelectable())
584 				Select(0, TextLength());
585 			break;
586 
587 		default:
588 			BTextView::MessageReceived(msg);
589 			break;
590 	}
591 }
592