⛏️ index : haiku.git

/*
 * Copyright 2010, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Robert Polic
 *		Stephan Aßmus <superstippi@gmx.de>
 *
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */


#include "PersonView.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <BitmapStream.h>
#include <Catalog.h>
#include <fs_attr.h>
#include <Box.h>
#include <ControlLook.h>
#include <GridLayout.h>
#include <Locale.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <Query.h>
#include <TranslationUtils.h>
#include <Translator.h>
#include <VolumeRoster.h>
#include <Window.h>

#include "AttributeTextControl.h"
#include "PictureView.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "People"


PersonView::PersonView(const char* name, const char* categoryAttribute,
		const entry_ref *ref)
	:
	BGridView(),
	fLastModificationTime(0),
	fGroups(NULL),
	fControls(20, false),
	fCategoryAttribute(categoryAttribute),
	fPictureView(NULL),
	fSaving(false)
{
	SetName(name);
	SetFlags(Flags() | B_WILL_DRAW);

	fRef = ref;
	BFile* file = NULL;
	if (fRef != NULL)
		file = new BFile(fRef, B_READ_ONLY);

	// Add picture "field", using ID photo 35mm x 45mm ratio
	fPictureView = new PictureView(70, 90, ref);

	BGridLayout* layout = GridLayout();

	float spacing = be_control_look->DefaultItemSpacing();
	layout->SetInsets(spacing, spacing, spacing, spacing);

	layout->AddView(fPictureView, 0, 0, 1, 5);
	layout->ItemAt(0, 0)->SetExplicitAlignment(
		BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));

	if (file != NULL)
		file->GetModificationTime(&fLastModificationTime);
	delete file;
}


PersonView::~PersonView()
{
}


void
PersonView::AddAttribute(const char* label, const char* attribute)
{
	// Check if this attribute has already been added.
	AttributeTextControl* control = NULL;
	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
		if (fControls.ItemAt(i)->Attribute() == attribute) {
			return;
		}
	}

	control = new AttributeTextControl(label, attribute);
	fControls.AddItem(control);

	BGridLayout* layout = GridLayout();
	int32 row = fControls.CountItems();

	if (fCategoryAttribute == attribute) {
		// Special case the category attribute. The Group popup field will
		// be added as the label instead.
		fGroups = new BPopUpMenu(label);
		fGroups->SetRadioMode(false);
		BuildGroupMenu();

		BMenuField* field = new BMenuField("", "", fGroups);
		field->SetEnabled(true);
		layout->AddView(field, 1, row);

		control->SetLabel("");
		layout->AddView(control, 2, row);
	} else {
		layout->AddItem(control->CreateLabelLayoutItem(), 1, row);
		layout->AddItem(control->CreateTextViewLayoutItem(), 2, row);
	}

	SetAttribute(attribute, true);
}


void
PersonView::MakeFocus(bool focus)
{
	if (focus && fControls.CountItems() > 0)
		fControls.ItemAt(0)->MakeFocus();
	else
		BView::MakeFocus(focus);
}


void
PersonView::MessageReceived(BMessage* msg)
{
	switch (msg->what) {
		case M_SAVE:
			Save();
			break;

		case M_REVERT:
			if (fPictureView)
				fPictureView->Revert();

			for (int32 i = fControls.CountItems() - 1; i >= 0; i--)
				fControls.ItemAt(i)->Revert();
			break;

		case M_SELECT:
			for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
				BTextView* text = fControls.ItemAt(i)->TextView();
				if (text->IsFocus()) {
					text->Select(0, text->TextLength());
					break;
				}
			}
			break;

		case M_GROUP_MENU:
		{
			const char* name = NULL;
			if (msg->FindString("group", &name) == B_OK)
				SetAttribute(fCategoryAttribute, name, false);
			break;
		}

		default:
			BGridView::MessageReceived(msg);
	}
}


void
PersonView::Draw(BRect updateRect)
{
	if (!fPictureView)
		return;

	// Draw a alert/get info-like strip
	BRect stripeRect = Bounds();
	stripeRect.right = GridLayout()->HorizontalSpacing()
		+ fPictureView->Bounds().Width() / 2;
	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
	FillRect(stripeRect);
}


void
PersonView::BuildGroupMenu()
{
	if (fGroups == NULL)
		return;

	BMenuItem* item;
	while ((item = fGroups->ItemAt(0)) != NULL) {
		fGroups->RemoveItem(item);
		delete item;
	}

	int32 count = 0;

	BVolumeRoster volumeRoster;
	BVolume volume;
	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
		BQuery query;
		query.SetVolume(&volume);

		char buffer[256];
		snprintf(buffer, sizeof(buffer), "%s=*", fCategoryAttribute.String());
		query.SetPredicate(buffer);
		query.Fetch();

		BEntry entry;
		while (query.GetNextEntry(&entry) == B_OK) {
			BFile file(&entry, B_READ_ONLY);
			attr_info info;

			if (file.InitCheck() == B_OK
				&& file.GetAttrInfo(fCategoryAttribute, &info) == B_OK
				&& info.size > 1) {
				if (info.size > (off_t)sizeof(buffer))
					info.size = sizeof(buffer);

				if (file.ReadAttr(fCategoryAttribute.String(), B_STRING_TYPE,
						0, buffer, info.size) < 0) {
					continue;
				}

				const char *text = buffer;
				while (true) {
					char* offset = strstr(text, ",");
					if (offset != NULL)
						offset[0] = '\0';

					if (!fGroups->FindItem(text)) {
						int32 index = 0;
						while ((item = fGroups->ItemAt(index)) != NULL) {
							if (strcmp(text, item->Label()) < 0)
								break;
							index++;
						}
						BMessage* message = new BMessage(M_GROUP_MENU);
						message->AddString("group", text);
						fGroups->AddItem(new BMenuItem(text, message), index);
						count++;
					}
					if (offset) {
						text = offset + 1;
						while (*text == ' ')
							text++;
					}
					else
						break;
				}
			}
		}
	}

	if (count == 0) {
		fGroups->AddItem(item = new BMenuItem(
			B_TRANSLATE_CONTEXT("none", "Groups list"),
			new BMessage(M_GROUP_MENU)));
		item->SetEnabled(false);
	}

	fGroups->SetTargetForItems(this);
}


void
PersonView::CreateFile(const entry_ref* ref)
{
	fRef = ref;
	Save();
}


bool
PersonView::IsSaved() const
{
	if (fPictureView && fPictureView->HasChanged())
		return false;

	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
		if (fControls.ItemAt(i)->HasChanged())
			return false;
	}

	return true;
}


void
PersonView::Save()
{
	BFile file(fRef, B_READ_WRITE);
	if (file.InitCheck() != B_NO_ERROR)
		return;

	fSaving = true;

	int32 count = fControls.CountItems();
	for (int32 i = 0; i < count; i++) {
		AttributeTextControl* control = fControls.ItemAt(i);
		const char* value = control->Text();
		file.WriteAttr(control->Attribute().String(), B_STRING_TYPE, 0,
			value, strlen(value) + 1);
		control->Update();
	}

	// Write the picture, if any, in the person file content
	if (fPictureView) {
		// Trim any previous content
		file.Seek(0, SEEK_SET);
		file.SetSize(0);

		BBitmap* picture = fPictureView->Bitmap();
		if (picture) {
			BBitmapStream stream(picture);
			// Detach *our* bitmap from stream to avoid its deletion
			// at stream object destruction
			stream.DetachBitmap(&picture);

			BTranslatorRoster* roster = BTranslatorRoster::Default();
			roster->Translate(&stream, NULL, NULL, &file,
				fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP,
				fPictureView->SuggestedMIMEType());

		}

		fPictureView->Update();
	}

	file.GetModificationTime(&fLastModificationTime);

	fSaving = false;
}


const char*
PersonView::AttributeValue(const char* attribute) const
{
	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
		if (fControls.ItemAt(i)->Attribute() == attribute)
			return fControls.ItemAt(i)->Text();
	}

	return "";
}


void
PersonView::SetAttribute(const char* attribute, bool update)
{
	char* value = NULL;
	attr_info info;
	BFile* file = NULL;

	if (fRef != NULL)
		file = new(std::nothrow) BFile(fRef, B_READ_ONLY);

	if (file != NULL && file->GetAttrInfo(attribute, &info) == B_OK) {
		value = (char*)calloc(info.size, 1);
		file->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size);
	}

	SetAttribute(attribute, value, update);

	free(value);
	delete file;
}


void
PersonView::SetAttribute(const char* attribute, const char* value,
	bool update)
{
	if (!LockLooper())
		return;

	AttributeTextControl* control = NULL;
	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
		if (fControls.ItemAt(i)->Attribute() == attribute) {
			control = fControls.ItemAt(i);
			break;
		}
	}

	if (control == NULL) {
		UnlockLooper();
		return;
	}

	if (update) {
		control->SetText(value);
		control->Update();
	} else {
		BTextView* text = control->TextView();

		int32 start, end;
		text->GetSelection(&start, &end);
		if (start != end) {
			text->Delete();
			text->Insert(value);
		} else if ((end = text->TextLength())) {
			text->Select(end, end);
			text->Insert(",");
			text->Insert(value);
			text->Select(text->TextLength(), text->TextLength());
		} else
			control->SetText(value);
	}

	UnlockLooper();
}


void
PersonView::UpdatePicture(const entry_ref* ref)
{
	if (fPictureView == NULL)
		return;

	if (fSaving)
		return;

	time_t modificationTime = 0;
	BEntry entry(ref);
	entry.GetModificationTime(&modificationTime);

	if (entry.InitCheck() == B_OK
		&& modificationTime <= fLastModificationTime) {
		return;
	}

	fPictureView->Update(ref);
}


bool
PersonView::IsTextSelected() const
{
	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
		BTextView* text = fControls.ItemAt(i)->TextView();

		int32 start, end;
		text->GetSelection(&start, &end);
		if (start != end)
			return true;
	}
	return false;
}