1/*
2 * Copyright 2010, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Robert Polic
7 *		Stephan A��mus <superstippi@gmx.de>
8 *
9 * Copyright 1999, Be Incorporated.   All Rights Reserved.
10 * This file may be used under the terms of the Be Sample Code License.
11 */
12
13
14#include "PersonView.h"
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include <BitmapStream.h>
21#include <Catalog.h>
22#include <fs_attr.h>
23#include <Box.h>
24#include <ControlLook.h>
25#include <GridLayout.h>
26#include <Locale.h>
27#include <MenuField.h>
28#include <MenuItem.h>
29#include <PopUpMenu.h>
30#include <Query.h>
31#include <TranslationUtils.h>
32#include <Translator.h>
33#include <VolumeRoster.h>
34#include <Window.h>
35
36#include "AttributeTextControl.h"
37#include "PictureView.h"
38
39
40#undef B_TRANSLATION_CONTEXT
41#define B_TRANSLATION_CONTEXT "People"
42
43
44PersonView::PersonView(const char* name, const char* categoryAttribute,
45		const entry_ref *ref)
46	:
47	BGridView(),
48	fLastModificationTime(0),
49	fGroups(NULL),
50	fControls(20, false),
51	fCategoryAttribute(categoryAttribute),
52	fPictureView(NULL),
53	fSaving(false)
54{
55	SetName(name);
56	SetFlags(Flags() | B_WILL_DRAW);
57
58	if (ref)
59		fFile = new BFile(ref, O_RDWR);
60	else
61		fFile = NULL;
62
63	float spacing = be_control_look->DefaultItemSpacing();
64	BGridLayout* layout = GridLayout();
65	layout->SetInsets(spacing, spacing, spacing, spacing);
66
67	// Add picture "field", using ID photo 35mm x 45mm ratio
68	fPictureView = new PictureView(70, 90, ref);
69
70	layout->AddView(fPictureView, 0, 0, 1, 5);
71	layout->ItemAt(0, 0)->SetExplicitAlignment(
72		BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
73
74	if (fFile)
75		fFile->GetModificationTime(&fLastModificationTime);
76}
77
78
79PersonView::~PersonView()
80{
81	delete fFile;
82}
83
84
85void
86PersonView::AddAttribute(const char* label, const char* attribute)
87{
88	// Check if this attribute has already been added.
89	AttributeTextControl* control = NULL;
90	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
91		if (fControls.ItemAt(i)->Attribute() == attribute) {
92			return;
93		}
94	}
95
96	control = new AttributeTextControl(label, attribute);
97	fControls.AddItem(control);
98
99	BGridLayout* layout = GridLayout();
100	int32 row = fControls.CountItems();
101
102	if (fCategoryAttribute == attribute) {
103		// Special case the category attribute. The Group popup field will
104		// be added as the label instead.
105		fGroups = new BPopUpMenu(label);
106		fGroups->SetRadioMode(false);
107		BuildGroupMenu();
108
109		BMenuField* field = new BMenuField("", "", fGroups);
110		field->SetEnabled(true);
111		layout->AddView(field, 1, row);
112
113		control->SetLabel("");
114		layout->AddView(control, 2, row);
115	} else {
116		layout->AddItem(control->CreateLabelLayoutItem(), 1, row);
117		layout->AddItem(control->CreateTextViewLayoutItem(), 2, row);
118	}
119
120	SetAttribute(attribute, true);
121}
122
123
124void
125PersonView::MakeFocus(bool focus)
126{
127	if (focus && fControls.CountItems() > 0)
128		fControls.ItemAt(0)->MakeFocus();
129	else
130		BView::MakeFocus(focus);
131}
132
133
134void
135PersonView::MessageReceived(BMessage* msg)
136{
137	switch (msg->what) {
138		case M_SAVE:
139			Save();
140			break;
141
142		case M_REVERT:
143			if (fPictureView)
144				fPictureView->Revert();
145
146			for (int32 i = fControls.CountItems() - 1; i >= 0; i--)
147				fControls.ItemAt(i)->Revert();
148			break;
149
150		case M_SELECT:
151			for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
152				BTextView* text = fControls.ItemAt(i)->TextView();
153				if (text->IsFocus()) {
154					text->Select(0, text->TextLength());
155					break;
156				}
157			}
158			break;
159
160		case M_GROUP_MENU:
161		{
162			const char* name = NULL;
163			if (msg->FindString("group", &name) == B_OK)
164				SetAttribute(fCategoryAttribute, name, false);
165			break;
166		}
167
168	}
169}
170
171
172void
173PersonView::Draw(BRect updateRect)
174{
175	if (!fPictureView)
176		return;
177
178	// Draw a alert/get info-like strip
179	BRect stripeRect = Bounds();
180	stripeRect.right = GridLayout()->HorizontalSpacing()
181		+ fPictureView->Bounds().Width() / 2;
182	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
183	FillRect(stripeRect);
184}
185
186
187void
188PersonView::BuildGroupMenu()
189{
190	if (fGroups == NULL)
191		return;
192
193	BMenuItem* item;
194	while ((item = fGroups->ItemAt(0)) != NULL) {
195		fGroups->RemoveItem(item);
196		delete item;
197	}
198
199	int32 count = 0;
200
201	BVolumeRoster volumeRoster;
202	BVolume volume;
203	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
204		BQuery query;
205		query.SetVolume(&volume);
206
207		char buffer[256];
208		snprintf(buffer, sizeof(buffer), "%s=*", fCategoryAttribute.String());
209		query.SetPredicate(buffer);
210		query.Fetch();
211
212		BEntry entry;
213		while (query.GetNextEntry(&entry) == B_OK) {
214			BFile file(&entry, B_READ_ONLY);
215			attr_info info;
216
217			if (file.InitCheck() == B_OK
218				&& file.GetAttrInfo(fCategoryAttribute, &info) == B_OK
219				&& info.size > 1) {
220				if (info.size > sizeof(buffer))
221					info.size = sizeof(buffer);
222
223				if (file.ReadAttr(fCategoryAttribute.String(), B_STRING_TYPE,
224						0, buffer, info.size) < 0) {
225					continue;
226				}
227
228				const char *text = buffer;
229				while (true) {
230					char* offset = strstr(text, ",");
231					if (offset != NULL)
232						offset[0] = '\0';
233
234					if (!fGroups->FindItem(text)) {
235						int32 index = 0;
236						while ((item = fGroups->ItemAt(index)) != NULL) {
237							if (strcmp(text, item->Label()) < 0)
238								break;
239							index++;
240						}
241						BMessage* message = new BMessage(M_GROUP_MENU);
242						message->AddString("group", text);
243						fGroups->AddItem(new BMenuItem(text, message), index);
244						count++;
245					}
246					if (offset) {
247						text = offset + 1;
248						while (*text == ' ')
249							text++;
250					}
251					else
252						break;
253				}
254			}
255		}
256	}
257
258	if (count == 0) {
259		fGroups->AddItem(item = new BMenuItem(
260			B_TRANSLATE_CONTEXT("none", "Groups list"),
261			new BMessage(M_GROUP_MENU)));
262		item->SetEnabled(false);
263	}
264
265	fGroups->SetTargetForItems(this);
266}
267
268
269void
270PersonView::CreateFile(const entry_ref* ref)
271{
272	delete fFile;
273	fFile = new BFile(ref, B_READ_WRITE);
274	Save();
275}
276
277
278bool
279PersonView::IsSaved() const
280{
281	if (fPictureView && fPictureView->HasChanged())
282		return false;
283
284	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
285		if (fControls.ItemAt(i)->HasChanged())
286			return false;
287	}
288
289	return true;
290}
291
292
293void
294PersonView::Save()
295{
296	fSaving = true;
297
298	int32 count = fControls.CountItems();
299	for (int32 i = 0; i < count; i++) {
300		AttributeTextControl* control = fControls.ItemAt(i);
301		const char* value = control->Text();
302		fFile->WriteAttr(control->Attribute().String(), B_STRING_TYPE, 0,
303			value, strlen(value) + 1);
304		control->Update();
305	}
306
307	// Write the picture, if any, in the person file content
308	if (fPictureView) {
309		// Trim any previous content
310		fFile->Seek(0, SEEK_SET);
311		fFile->SetSize(0);
312
313		BBitmap* picture = fPictureView->Bitmap();
314		if (picture) {
315			BBitmapStream stream(picture);
316			// Detach *our* bitmap from stream to avoid its deletion
317			// at stream object destruction
318			stream.DetachBitmap(&picture);
319
320			BTranslatorRoster* roster = BTranslatorRoster::Default();
321			roster->Translate(&stream, NULL, NULL, fFile,
322				fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP,
323				fPictureView->SuggestedMIMEType());
324
325		}
326
327		fPictureView->Update();
328	}
329
330	fFile->GetModificationTime(&fLastModificationTime);
331
332	fSaving = false;
333}
334
335
336const char*
337PersonView::AttributeValue(const char* attribute) const
338{
339	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
340		if (fControls.ItemAt(i)->Attribute() == attribute)
341			return fControls.ItemAt(i)->Text();
342	}
343
344	return "";
345}
346
347
348void
349PersonView::SetAttribute(const char* attribute, bool update)
350{
351	char* value = NULL;
352	attr_info info;
353	if (fFile != NULL && fFile->GetAttrInfo(attribute, &info) == B_OK) {
354		value = (char*)calloc(info.size, 1);
355		fFile->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size);
356	}
357
358	SetAttribute(attribute, value, update);
359
360	free(value);
361}
362
363
364void
365PersonView::SetAttribute(const char* attribute, const char* value,
366	bool update)
367{
368	if (!LockLooper())
369		return;
370
371	AttributeTextControl* control = NULL;
372	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
373		if (fControls.ItemAt(i)->Attribute() == attribute) {
374			control = fControls.ItemAt(i);
375			break;
376		}
377	}
378
379	if (control == NULL)
380		return;
381
382	if (update) {
383		control->SetText(value);
384		control->Update();
385	} else {
386		BTextView* text = control->TextView();
387
388		int32 start, end;
389		text->GetSelection(&start, &end);
390		if (start != end) {
391			text->Delete();
392			text->Insert(value);
393		} else if ((end = text->TextLength())) {
394			text->Select(end, end);
395			text->Insert(",");
396			text->Insert(value);
397			text->Select(text->TextLength(), text->TextLength());
398		} else
399			control->SetText(value);
400	}
401
402	UnlockLooper();
403}
404
405
406void
407PersonView::UpdatePicture(const entry_ref* ref)
408{
409	if (fPictureView == NULL)
410		return;
411
412	if (fSaving)
413		return;
414
415	time_t modificationTime = 0;
416	BEntry entry(ref);
417	entry.GetModificationTime(&modificationTime);
418
419	if (entry.InitCheck() == B_OK
420		&& modificationTime <= fLastModificationTime) {
421		return;
422	}
423
424	fPictureView->Update(ref);
425}
426
427
428bool
429PersonView::IsTextSelected() const
430{
431	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
432		BTextView* text = fControls.ItemAt(i)->TextView();
433
434		int32 start, end;
435		text->GetSelection(&start, &end);
436		if (start != end)
437			return true;
438	}
439	return false;
440}
441