1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of 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
56extern BRect		signature_window;
57extern const char	*kUndoStrings[];
58extern const char	*kRedoStrings[];
59
60
61TSignatureWindow::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
122TSignatureWindow::~TSignatureWindow()
123{
124}
125
126
127void
128TSignatureWindow::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
165void
166TSignatureWindow::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
253bool
254TSignatureWindow::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
268void
269TSignatureWindow::FrameResized(float width, float height)
270{
271	fSigView->FrameResized(width, height);
272}
273
274
275void
276TSignatureWindow::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
290bool
291TSignatureWindow::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("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		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
320bool
321TSignatureWindow::IsDirty()
322{
323	char		name[B_FILE_NAME_LENGTH];
324
325	if (fFile) {
326		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
327		if ((strcmp(name, fSigView->fName->Text())) || (fSigView->fTextView->fDirty))
328			return true;
329	}
330	else {
331		if ((strlen(fSigView->fName->Text())) ||
332			(fSigView->fTextView->TextLength()))
333			return true;
334	}
335	return false;
336}
337
338
339void
340TSignatureWindow::Save()
341{
342	char			name[B_FILE_NAME_LENGTH];
343	int32			index = 0;
344	status_t		result;
345	BDirectory		dir;
346	BEntry			entry;
347	BNodeInfo		*node;
348	BPath			path;
349
350	if (!fFile) {
351		find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
352		dir.SetTo(path.Path());
353
354		if (dir.FindEntry("Mail", &entry) == B_NO_ERROR)
355			dir.SetTo(&entry);
356		else
357			dir.CreateDirectory("Mail", &dir);
358
359		if (dir.InitCheck() != B_NO_ERROR)
360			goto err_exit;
361
362		if (dir.FindEntry("signatures", &entry) == B_NO_ERROR)
363			dir.SetTo(&entry);
364		else
365			dir.CreateDirectory("signatures", &dir);
366
367		if (dir.InitCheck() != B_NO_ERROR)
368			goto err_exit;
369
370		fFile = new BFile();
371		while(true) {
372			sprintf(name, "signature_%ld", index++);
373			if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR)
374				break;
375			if (result != EEXIST)
376				goto err_exit;
377		}
378		dir.FindEntry(name, &fEntry);
379		node = new BNodeInfo(fFile);
380		node->SetType("text/plain");
381		delete node;
382	}
383
384	fSigView->fTextView->fDirty = false;
385	fFile->Seek(0, 0);
386	fFile->Write(fSigView->fTextView->Text(),
387				 fSigView->fTextView->TextLength());
388	fFile->SetSize(fFile->Position());
389	fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(),
390					 strlen(fSigView->fName->Text()) + 1);
391	return;
392
393err_exit:
394	beep();
395	BAlert* alert = new BAlert("",
396		B_TRANSLATE("An error occurred trying to save this signature."),
397		B_TRANSLATE("Sorry"));
398	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
399	alert->Go();
400}
401
402
403//====================================================================
404//	#pragma mark -
405
406
407TSignatureView::TSignatureView(BRect rect)
408	: BBox(rect, "SigView", B_FOLLOW_ALL, B_WILL_DRAW)
409{
410}
411
412
413void
414TSignatureView::AttachedToWindow()
415{
416	BRect	rect = Bounds();
417	float	name_text_length = StringWidth(B_TRANSLATE("Title:"));
418	float	sig_text_length = StringWidth(B_TRANSLATE("Signature:"));
419	float	divide_length;
420
421	if (name_text_length > sig_text_length)
422		divide_length = name_text_length;
423	else
424		divide_length = sig_text_length;
425
426	rect.InsetBy(8,0);
427	rect.top+= 8;
428
429	fName = new TNameControl(rect, B_TRANSLATE("Title:"),
430		new BMessage(NAME_FIELD));
431	AddChild(fName);
432
433	fName->SetDivider(divide_length + 10);
434	fName->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
435
436	rect.OffsetBy(0,fName->Bounds().Height()+5);
437	rect.bottom = rect.top + kSigHeight;
438	rect.left = fName->TextView()->Frame().left;
439
440	BRect text = rect;
441	text.OffsetTo(10,0);
442	fTextView = new TSigTextView(rect, text);
443	BScrollView *scroller = new BScrollView("SigScroller", fTextView, B_FOLLOW_ALL, 0, false, true);
444	AddChild(scroller);
445	scroller->ResizeBy(-1 * scroller->ScrollBar(B_VERTICAL)->Frame().Width() - 9, 0);
446	scroller->MoveBy(7,0);
447
448	/* back up a bit to make room for the label */
449
450	rect = scroller->Frame();
451	BStringView *stringView = new BStringView(rect, "SigLabel",
452		B_TRANSLATE("Signature:"));
453	AddChild(stringView);
454
455	float tWidth, tHeight;
456	stringView->GetPreferredSize(&tWidth, &tHeight);
457
458	/* the 5 is for the spacer in the TextView */
459
460	rect.OffsetBy(-1 *(tWidth) - 5, 0);
461	rect.right = rect.left + tWidth;
462	rect.bottom = rect.top + tHeight;
463
464	stringView->MoveTo(rect.LeftTop());
465	stringView->ResizeTo(rect.Width(), rect.Height());
466
467	/* Resize the View to the correct height */
468	scroller->SetResizingMode(B_FOLLOW_NONE);
469	ResizeTo(Frame().Width(), scroller->Frame().bottom + 8);
470	scroller->SetResizingMode(B_FOLLOW_ALL);
471}
472
473
474//====================================================================
475//	#pragma mark -
476
477
478TNameControl::TNameControl(BRect rect, const char *label, BMessage *msg)
479			 :BTextControl(rect, "", label, "", msg, B_FOLLOW_LEFT_RIGHT)
480{
481	strcpy(fLabel, label);
482}
483
484
485void
486TNameControl::AttachedToWindow()
487{
488	BTextControl::AttachedToWindow();
489
490	SetDivider(StringWidth(fLabel) + 6);
491	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
492}
493
494
495void
496TNameControl::MessageReceived(BMessage *msg)
497{
498	switch (msg->what) {
499		case M_SELECT:
500			TextView()->Select(0, TextView()->TextLength());
501			break;
502
503		default:
504			BTextControl::MessageReceived(msg);
505	}
506}
507
508
509//====================================================================
510//	#pragma mark -
511
512
513TSigTextView::TSigTextView(BRect frame, BRect text)
514			 :BTextView(frame, "SignatureView", text, B_FOLLOW_ALL, B_NAVIGABLE | B_WILL_DRAW)
515{
516	fDirty = false;
517	SetDoesUndo(true);
518}
519
520
521void
522TSigTextView::FrameResized(float /*width*/, float /*height*/)
523{
524	BRect r(Bounds());
525	r.InsetBy(3, 3);
526	SetTextRect(r);
527}
528
529
530void
531TSigTextView::DeleteText(int32 offset, int32 len)
532{
533	fDirty = true;
534	BTextView::DeleteText(offset, len);
535}
536
537
538void
539TSigTextView::InsertText(const char *text, int32 len, int32 offset,
540	const text_run_array *runs)
541{
542	fDirty = true;
543	BTextView::InsertText(text, len, offset, runs);
544}
545
546
547void
548TSigTextView::KeyDown(const char *key, int32 count)
549{
550	bool	up = false;
551	int32	height;
552	BRect	r;
553
554	switch (key[0]) {
555		case B_HOME:
556			Select(0, 0);
557			ScrollToSelection();
558			break;
559
560		case B_END:
561			Select(TextLength(), TextLength());
562			ScrollToSelection();
563			break;
564
565		case B_PAGE_UP:
566			up = true;
567		case B_PAGE_DOWN:
568			r = Bounds();
569			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
570			if ((up) && (!r.top))
571				break;
572			ScrollBy(0, height);
573			break;
574
575		default:
576			BTextView::KeyDown(key, count);
577	}
578}
579
580
581void
582TSigTextView::MessageReceived(BMessage *msg)
583{
584	char		type[B_FILE_NAME_LENGTH];
585	char		*text;
586	int32		end;
587	int32		start;
588	BFile		file;
589	BNodeInfo	*node;
590	entry_ref	ref;
591	off_t		size;
592
593	switch (msg->what) {
594		case B_SIMPLE_DATA:
595			if (msg->HasRef("refs")) {
596				msg->FindRef("refs", &ref);
597				file.SetTo(&ref, O_RDONLY);
598				if (file.InitCheck() == B_NO_ERROR) {
599					node = new BNodeInfo(&file);
600					node->GetType(type);
601					delete node;
602					file.GetSize(&size);
603					if ((!strncasecmp(type, "text/", 5)) && (size)) {
604						text = (char *)malloc(size);
605						file.Read(text, size);
606						Delete();
607						GetSelection(&start, &end);
608						Insert(text, size);
609						Select(start, start + size);
610						free(text);
611					}
612				}
613			}
614			else
615				BTextView::MessageReceived(msg);
616			break;
617
618		case M_SELECT:
619			if (IsSelectable())
620				Select(0, TextLength());
621			break;
622
623		default:
624			BTextView::MessageReceived(msg);
625	}
626}
627
628