text: /*
  Open Tracker License
  
  Terms and Conditions
  
  Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
  
  Permission is hereby granted, free of charge, to any person obtaining a copy of
  this software and associated documentation files (the "Software"), to deal in
  the Software without restriction, including without limitation the rights to
  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  of the Software, and to permit persons to whom the Software is furnished to do
  so, subject to the following conditions:
  
  The above copyright notice and this permission notice applies to all licensees
  and shall be included in all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  Except as contained in this notice, the name of Be Incorporated shall not be
  used in advertising or otherwise to promote the sale, use or other dealings in
  this Software without prior written authorization from Be Incorporated.
  
  BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
  registered trademarks of Be Incorporated in the United States and other
  countries. Other brand product names are registered trademarks or trademarks
  of their respective holders. All rights reserved.
  */
  
  
  #include "Signature.h"
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <strings.h>
  
  #include <Clipboard.h>
  #include <Directory.h>
  #include <LayoutBuilder.h>
  #include <Locale.h>
  #include <ScrollView.h>
  #include <StringView.h>
  
  #include "MailApp.h"
  #include "MailPopUpMenu.h"
  #include "MailSupport.h"
  #include "MailWindow.h"
  #include "Messages.h"
  
  
  #define B_TRANSLATION_CONTEXT "Mail"
  
  
  const float kSigHeight = 250;
  const float kSigWidth = 300;
  
  extern const char* kUndoStrings[];
  extern const char* kRedoStrings[];
  
  
  TSignatureWindow::TSignatureWindow(BRect rect)
  	:
  	BWindow(rect, B_TRANSLATE("Signatures"), B_TITLED_WINDOW,
  		B_AUTO_UPDATE_SIZE_LIMITS),
  	fFile(NULL)
  {
  	BMenuItem* item;
  
  	// Set up the menu
  	BMenuBar* menuBar = new BMenuBar("MenuBar");
  	BMenu* menu = new BMenu(B_TRANSLATE("Signature"));
  	menu->AddItem(fNew = new BMenuItem(B_TRANSLATE("New"),
  		new BMessage(M_NEW), 'N'));
  	fSignature = new TMenu(B_TRANSLATE("Open"), INDEX_SIGNATURE, M_SIGNATURE);
  	menu->AddItem(new BMenuItem(fSignature));
  	menu->AddSeparatorItem();
  	menu->AddItem(fSave = new BMenuItem(B_TRANSLATE("Save"),
  		new BMessage(M_SAVE), 'S'));
  	menu->AddItem(fDelete = new BMenuItem(B_TRANSLATE("Delete"),
  		new BMessage(M_DELETE), 'T'));
  	menuBar->AddItem(menu);
  
  	menu = new BMenu(B_TRANSLATE("Edit"));
  	menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
  		new BMessage(B_UNDO), 'Z'));
  	fUndo->SetTarget(NULL, this);
  	menu->AddSeparatorItem();
  	menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
  		new BMessage(B_CUT), 'X'));
  	fCut->SetTarget(NULL, this);
  	menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
  		new BMessage(B_COPY), 'C'));
  	fCopy->SetTarget(NULL, this);
  	menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
  		new BMessage(B_PASTE), 'V'));
  	fPaste->SetTarget(NULL, this);
  	menu->AddSeparatorItem();
  	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"),
  		new BMessage(M_SELECT), 'A'));
  	item->SetTarget(NULL, this);
  	menuBar->AddItem(menu);
  
  	fSigView = new TSignatureView();
  
  	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
  		.Add(menuBar)
  		.Add(fSigView);
  
  	if (!rect.IsValid()) {
  		float fontFactor = be_plain_font->Size() / 12.0f;
  		ResizeTo(kSigWidth * fontFactor, kSigHeight * fontFactor);
  		// TODO: this should work, too, but doesn't
  		//ResizeToPreferred();
  	}
  }
  
  
  TSignatureWindow::~TSignatureWindow()
  {
  }
  
  
  void
  TSignatureWindow::MenusBeginning()
  {
  	fDelete->SetEnabled(fFile);
  	fSave->SetEnabled(IsDirty());
  	fUndo->SetEnabled(false);		// ***TODO***
  
  	BTextView* textView = fSigView->fName->TextView();
  	int32 finish = 0;
  	int32 start = 0;
  	if (textView->IsFocus())
  		textView->GetSelection(&start, &finish);
  	else
  		fSigView->fTextView->GetSelection(&start, &finish);
  
  	fCut->SetEnabled(start != finish);
  	fCopy->SetEnabled(start != finish);
  
  	fNew->SetEnabled(textView->TextLength()
  		| fSigView->fTextView->TextLength());
  	be_clipboard->Lock();
  	fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain",
  		B_MIME_TYPE));
  	be_clipboard->Unlock();
  
  	// Undo stuff
  	bool isRedo = false;
  	undo_state undoState = B_UNDO_UNAVAILABLE;
  
  	BTextView *focusTextView = dynamic_cast<BTextView *>(CurrentFocus());
  	if (focusTextView != NULL)
  		undoState = focusTextView->UndoState(&isRedo);
  
  	fUndo->SetLabel(isRedo ? kRedoStrings[undoState] : kUndoStrings[undoState]);
  	if(undoState != B_UNDO_UNAVAILABLE){
  		fUndo->SetEnabled(false);
  		undoState = B_UNDO_UNAVAILABLE;
  	}
  	else
  	{
  		fUndo->SetEnabled(true);
  	}
  }
  
  
  void
  TSignatureWindow::MessageReceived(BMessage* msg)
  {
  	switch (msg->what) {
  		case CHANGE_FONT:
  		{
  			BFont* font;
  			msg->FindPointer("font", (void **)&font);
  			fSigView->fTextView->SetFontAndColor(font);
  			fSigView->fTextView->Invalidate(fSigView->fTextView->Bounds());
  			break;
  		}
  
  		case M_NEW:
  			if (Clear()) {
  				fSigView->fName->SetText("");
  				fSigView->fTextView->SetText("");
  				fSigView->fName->MakeFocus(true);
  			}
  			break;
  
  		case M_SAVE:
  			Save();
  			break;
  
  		case M_DELETE: {
  			BAlert* alert = new BAlert("",
  					B_TRANSLATE("Really delete this signature? This cannot "
  						"be undone."),
  					B_TRANSLATE("Cancel"),
  					B_TRANSLATE("Delete"),
  					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
  			alert->SetShortcut(0, B_ESCAPE);
  			int32 choice = alert->Go();
  
  			if (choice == 0)
  				break;
  
  			if (fFile) {
  				delete fFile;
  				fFile = NULL;
  				fEntry.Remove();
  				fSigView->fName->SetText("");
  				fSigView->fTextView->SetText(NULL, (int32)0);
  				fSigView->fName->MakeFocus(true);
  			}
  			break;
  		}
  		case M_SIGNATURE:
  			if (Clear()) {
  				entry_ref ref;
  				msg->FindRef("ref", &ref);
  				fEntry.SetTo(&ref);
  				fFile = new BFile(&ref, O_RDWR);
  				if (fFile->InitCheck() == B_OK) {
  					char name[B_FILE_NAME_LENGTH];
  					fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name,
  						sizeof(name));
  					fSigView->fName->SetText(name);
  
  					off_t size;
  					fFile->GetSize(&size);
  					char* sig = (char*)malloc(size);
  					if (sig == NULL)
  						break;
  
  					size = fFile->Read(sig, size);
  					fSigView->fTextView->SetText(sig, size);
  					fSigView->fName->MakeFocus(true);
  					BTextView* textView = fSigView->fName->TextView();
  					textView->Select(0, textView->TextLength());
  					fSigView->fTextView->fDirty = false;
  				} else {
  					fFile = NULL;
  					beep();
  					BAlert* alert = new BAlert("",
  						B_TRANSLATE("Couldn't open this signature. Sorry."),
  						B_TRANSLATE("OK"));
  					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
  					alert->Go();
  				}
  			}
  			break;
  
  		default:
  			BWindow::MessageReceived(msg);
  	}
  }
  
  
  bool
  TSignatureWindow::QuitRequested()
  {
  	if (Clear()) {
  		BMessage msg(WINDOW_CLOSED);
  		msg.AddInt32("kind", SIG_WINDOW);
  		msg.AddRect("window frame", Frame());
  
  		be_app->PostMessage(&msg);
  		return true;
  	}
  	return false;
  }
  
  
  void
  TSignatureWindow::FrameResized(float width, float height)
  {
  	fSigView->FrameResized(width, height);
  }
  
  
  void
  TSignatureWindow::Show()
  {
  	Lock();
  	BTextView* textView = (BTextView *)fSigView->fName->TextView();
  	fSigView->fName->MakeFocus(true);
  	textView->Select(0, textView->TextLength());
  	Unlock();
  
  	BWindow::Show();
  }
  
  
  bool
  TSignatureWindow::Clear()
  {
  	if (IsDirty()) {
  		beep();
  		BAlert *alert = new BAlert("",
  			B_TRANSLATE("Save changes to this signature?"),
  			B_TRANSLATE("Cancel"),
  			B_TRANSLATE("Don't save"),
  			B_TRANSLATE("Save"),
  			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
  		alert->SetShortcut(0, B_ESCAPE);
  		alert->SetShortcut(1, 'd');
  		alert->SetShortcut(2, 's');
  		int32 result = alert->Go();
  		if (result == 0)
  			return false;
  		if (result == 2)
  			Save();
  	}
  
  	delete fFile;
  	fFile = NULL;
  	fSigView->fTextView->fDirty = false;
  	return true;
  }
  
  
  bool
  TSignatureWindow::IsDirty()
  {
  	if (fFile != NULL) {
  		char name[B_FILE_NAME_LENGTH];
  		fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name));
  		if (strcmp(name, fSigView->fName->Text()) != 0
  			|| fSigView->fTextView->fDirty) {
  			return true;
  		}
  	} else if (fSigView->fName->Text()[0] != '\0'
  		|| fSigView->fTextView->TextLength() != 0) {
  		return true;
  	}
  	return false;
  }
  
  
  void
  TSignatureWindow::Save()
  {
  	char			name[B_FILE_NAME_LENGTH];
  	int32			index = 0;
  	status_t		result;
  	BDirectory		dir;
  	BEntry			entry;
  	BNodeInfo		*node;
  	BPath			path;
  
  	if (!fFile) {
  		find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
  		dir.SetTo(path.Path());
  
  		if (dir.FindEntry("Mail", &entry) == B_NO_ERROR)
  			dir.SetTo(&entry);
  		else
  			dir.CreateDirectory("Mail", &dir);
  
  		if (dir.InitCheck() != B_NO_ERROR)
  			goto err_exit;
  
  		if (dir.FindEntry("signatures", &entry) == B_NO_ERROR)
  			dir.SetTo(&entry);
  		else
  			dir.CreateDirectory("signatures", &dir);
  
  		if (dir.InitCheck() != B_NO_ERROR)
  			goto err_exit;
  
  		fFile = new BFile();
  		while(true) {
  			sprintf(name, "signature_%" B_PRId32, index++);
  			if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR)
  				break;
  			if (result != EEXIST)
  				goto err_exit;
  		}
  		dir.FindEntry(name, &fEntry);
  		node = new BNodeInfo(fFile);
  		node->SetType("text/plain");
  		delete node;
  	}
  
  	fSigView->fTextView->fDirty = false;
  	fFile->Seek(0, 0);
  	fFile->Write(fSigView->fTextView->Text(),
  				 fSigView->fTextView->TextLength());
  	fFile->SetSize(fFile->Position());
  	fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(),
  					 strlen(fSigView->fName->Text()) + 1);
  	return;
  
  err_exit:
  	beep();
  	BAlert* alert = new BAlert("",
  		B_TRANSLATE("An error occurred trying to save this signature."),
  		B_TRANSLATE("Sorry"));
  	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
  	alert->Go();
  }
  
  
  // #pragma mark -
  
  
  TSignatureView::TSignatureView()
  	:
  	BGridView("SigView")
  {
  	GridLayout()->SetInsets(B_USE_DEFAULT_SPACING);
  
  	BStringView* nameLabel = new BStringView("NameLabel",
  		B_TRANSLATE("Title:"));
  	nameLabel->SetAlignment(B_ALIGN_RIGHT);
  	GridLayout()->AddView(nameLabel, 0, 0);
  
  	fName = new TNameControl("", new BMessage(NAME_FIELD));
  	GridLayout()->AddItem(fName->CreateTextViewLayoutItem(), 1, 0);
  
  	BStringView* signatureLabel = new BStringView("SigLabel",
  		B_TRANSLATE("Signature:"));
  	signatureLabel->SetAlignment(B_ALIGN_RIGHT);
  	GridLayout()->AddView(signatureLabel, 0, 1);
  
  	fTextView = new TSigTextView();
  
  	font_height fontHeight;
  	fTextView->GetFontHeight(&fontHeight);
  	float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
  
  	BScrollView* scroller = new BScrollView("SigScroller", fTextView, 0,
  		false, true);
  	scroller->SetExplicitPreferredSize(
  		BSize(fTextView->StringWidth("W") * 30, lineHeight * 6));
  
  	GridLayout()->AddView(scroller, 1, 1, 1, 2);
  
  	GridLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0, 2);
  }
  
  
  void
  TSignatureView::AttachedToWindow()
  {
  }
  
  
  // #pragma mark -
  
  
  TNameControl::TNameControl(const char* label, BMessage* invocationMessage)
  	:
  	BTextControl("", label, "", invocationMessage)
  {
  	strcpy(fLabel, label);
  }
  
  
  void
  TNameControl::AttachedToWindow()
  {
  	BTextControl::AttachedToWindow();
  
  	TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
  }
  
  
  void
  TNameControl::MessageReceived(BMessage* msg)
  {
  	switch (msg->what) {
  		case M_SELECT:
  			TextView()->Select(0, TextView()->TextLength());
  			break;
  
  		default:
  			BTextControl::MessageReceived(msg);
  	}
  }
  
  
  // #pragma mark -
  
  
  TSigTextView::TSigTextView()
  	:
  	BTextView("SignatureView", B_NAVIGABLE | B_WILL_DRAW)
  {
  	fDirty = false;
  	SetDoesUndo(true);
  }
  
  
  void
  TSigTextView::DeleteText(int32 offset, int32 len)
  {
  	fDirty = true;
  	BTextView::DeleteText(offset, len);
  }
  
  
  void
  TSigTextView::InsertText(const char *text, int32 len, int32 offset,
  	const text_run_array *runs)
  {
  	fDirty = true;
  	BTextView::InsertText(text, len, offset, runs);
  }
  
  
  void
  TSigTextView::KeyDown(const char *key, int32 count)
  {
  	bool	up = false;
  	int32	height;
  	BRect	r;
  
  	switch (key[0]) {
  		case B_HOME:
  			Select(0, 0);
  			ScrollToSelection();
  			break;
  
  		case B_END:
  			Select(TextLength(), TextLength());
  			ScrollToSelection();
  			break;
  
  		case B_PAGE_UP:
  			up = true;
  		case B_PAGE_DOWN:
  			r = Bounds();
  			height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25);
  			if ((up) && (!r.top))
  				break;
  			ScrollBy(0, height);
  			break;
  
  		default:
  			BTextView::KeyDown(key, count);
  	}
  }
  
  
  void
  TSigTextView::MessageReceived(BMessage *msg)
  {
  	char		type[B_FILE_NAME_LENGTH];
  	char		*text;
  	int32		end;
  	int32		start;
  	BFile		file;
  	BNodeInfo	*node;
  	entry_ref	ref;
  	off_t		size;
  
  	switch (msg->what) {
  		case B_SIMPLE_DATA:
  			if (msg->HasRef("refs")) {
  				msg->FindRef("refs", &ref);
  				file.SetTo(&ref, O_RDONLY);
  				if (file.InitCheck() == B_NO_ERROR) {
  					node = new BNodeInfo(&file);
  					node->GetType(type);
  					delete node;
  					file.GetSize(&size);
  					if ((!strncasecmp(type, "text/", 5)) && (size)) {
  						text = (char *)malloc(size);
  						file.Read(text, size);
  						Delete();
  						GetSelection(&start, &end);
  						Insert(text, size);
  						Select(start, start + size);
  						free(text);
  					}
  				}
  			}
  			else
  				BTextView::MessageReceived(msg);
  			break;
  
  		case M_SELECT:
  			if (IsSelectable())
  				Select(0, TextLength());
  			break;
  
  		default:
  			BTextView::MessageReceived(msg);
  	}
  }
  
  