xref: /haiku/src/apps/people/PersonView.cpp (revision 220c5364ab445eb20376f964ade2f7d3a09e163a)
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 
44 PersonView::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 	fRef = ref;
59 	BFile* file = NULL;
60 	if (fRef != NULL)
61 		file = new BFile(fRef, B_READ_ONLY);
62 
63 	// Add picture "field", using ID photo 35mm x 45mm ratio
64 	fPictureView = new PictureView(70, 90, ref);
65 
66 	BGridLayout* layout = GridLayout();
67 	layout->AddView(fPictureView, 0, 0, 1, 5);
68 	layout->ItemAt(0, 0)->SetExplicitAlignment(
69 		BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
70 
71 	if (file != NULL)
72 		file->GetModificationTime(&fLastModificationTime);
73 	delete file;
74 }
75 
76 
77 PersonView::~PersonView()
78 {
79 }
80 
81 
82 void
83 PersonView::AddAttribute(const char* label, const char* attribute)
84 {
85 	// Check if this attribute has already been added.
86 	AttributeTextControl* control = NULL;
87 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
88 		if (fControls.ItemAt(i)->Attribute() == attribute) {
89 			return;
90 		}
91 	}
92 
93 	control = new AttributeTextControl(label, attribute);
94 	fControls.AddItem(control);
95 
96 	BGridLayout* layout = GridLayout();
97 	int32 row = fControls.CountItems();
98 
99 	if (fCategoryAttribute == attribute) {
100 		// Special case the category attribute. The Group popup field will
101 		// be added as the label instead.
102 		fGroups = new BPopUpMenu(label);
103 		fGroups->SetRadioMode(false);
104 		BuildGroupMenu();
105 
106 		BMenuField* field = new BMenuField("", "", fGroups);
107 		field->SetEnabled(true);
108 		layout->AddView(field, 1, row);
109 
110 		control->SetLabel("");
111 		layout->AddView(control, 2, row);
112 	} else {
113 		layout->AddItem(control->CreateLabelLayoutItem(), 1, row);
114 		layout->AddItem(control->CreateTextViewLayoutItem(), 2, row);
115 	}
116 
117 	SetAttribute(attribute, true);
118 }
119 
120 
121 void
122 PersonView::MakeFocus(bool focus)
123 {
124 	if (focus && fControls.CountItems() > 0)
125 		fControls.ItemAt(0)->MakeFocus();
126 	else
127 		BView::MakeFocus(focus);
128 }
129 
130 
131 void
132 PersonView::MessageReceived(BMessage* msg)
133 {
134 	switch (msg->what) {
135 		case M_SAVE:
136 			Save();
137 			break;
138 
139 		case M_REVERT:
140 			if (fPictureView)
141 				fPictureView->Revert();
142 
143 			for (int32 i = fControls.CountItems() - 1; i >= 0; i--)
144 				fControls.ItemAt(i)->Revert();
145 			break;
146 
147 		case M_SELECT:
148 			for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
149 				BTextView* text = fControls.ItemAt(i)->TextView();
150 				if (text->IsFocus()) {
151 					text->Select(0, text->TextLength());
152 					break;
153 				}
154 			}
155 			break;
156 
157 		case M_GROUP_MENU:
158 		{
159 			const char* name = NULL;
160 			if (msg->FindString("group", &name) == B_OK)
161 				SetAttribute(fCategoryAttribute, name, false);
162 			break;
163 		}
164 
165 	}
166 }
167 
168 
169 void
170 PersonView::Draw(BRect updateRect)
171 {
172 	if (!fPictureView)
173 		return;
174 
175 	// Draw a alert/get info-like strip
176 	BRect stripeRect = Bounds();
177 	stripeRect.right = GridLayout()->HorizontalSpacing()
178 		+ fPictureView->Bounds().Width() / 2;
179 	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
180 	FillRect(stripeRect);
181 }
182 
183 
184 void
185 PersonView::BuildGroupMenu()
186 {
187 	if (fGroups == NULL)
188 		return;
189 
190 	BMenuItem* item;
191 	while ((item = fGroups->ItemAt(0)) != NULL) {
192 		fGroups->RemoveItem(item);
193 		delete item;
194 	}
195 
196 	int32 count = 0;
197 
198 	BVolumeRoster volumeRoster;
199 	BVolume volume;
200 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
201 		BQuery query;
202 		query.SetVolume(&volume);
203 
204 		char buffer[256];
205 		snprintf(buffer, sizeof(buffer), "%s=*", fCategoryAttribute.String());
206 		query.SetPredicate(buffer);
207 		query.Fetch();
208 
209 		BEntry entry;
210 		while (query.GetNextEntry(&entry) == B_OK) {
211 			BFile file(&entry, B_READ_ONLY);
212 			attr_info info;
213 
214 			if (file.InitCheck() == B_OK
215 				&& file.GetAttrInfo(fCategoryAttribute, &info) == B_OK
216 				&& info.size > 1) {
217 				if (info.size > (off_t)sizeof(buffer))
218 					info.size = sizeof(buffer);
219 
220 				if (file.ReadAttr(fCategoryAttribute.String(), B_STRING_TYPE,
221 						0, buffer, info.size) < 0) {
222 					continue;
223 				}
224 
225 				const char *text = buffer;
226 				while (true) {
227 					char* offset = strstr(text, ",");
228 					if (offset != NULL)
229 						offset[0] = '\0';
230 
231 					if (!fGroups->FindItem(text)) {
232 						int32 index = 0;
233 						while ((item = fGroups->ItemAt(index)) != NULL) {
234 							if (strcmp(text, item->Label()) < 0)
235 								break;
236 							index++;
237 						}
238 						BMessage* message = new BMessage(M_GROUP_MENU);
239 						message->AddString("group", text);
240 						fGroups->AddItem(new BMenuItem(text, message), index);
241 						count++;
242 					}
243 					if (offset) {
244 						text = offset + 1;
245 						while (*text == ' ')
246 							text++;
247 					}
248 					else
249 						break;
250 				}
251 			}
252 		}
253 	}
254 
255 	if (count == 0) {
256 		fGroups->AddItem(item = new BMenuItem(
257 			B_TRANSLATE_CONTEXT("none", "Groups list"),
258 			new BMessage(M_GROUP_MENU)));
259 		item->SetEnabled(false);
260 	}
261 
262 	fGroups->SetTargetForItems(this);
263 }
264 
265 
266 void
267 PersonView::CreateFile(const entry_ref* ref)
268 {
269 	fRef = ref;
270 	Save();
271 }
272 
273 
274 bool
275 PersonView::IsSaved() const
276 {
277 	if (fPictureView && fPictureView->HasChanged())
278 		return false;
279 
280 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
281 		if (fControls.ItemAt(i)->HasChanged())
282 			return false;
283 	}
284 
285 	return true;
286 }
287 
288 
289 void
290 PersonView::Save()
291 {
292 	BFile file(fRef, B_READ_WRITE);
293 	if (file.InitCheck() != B_NO_ERROR)
294 		return;
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 		file.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 		file.Seek(0, SEEK_SET);
311 		file.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, &file,
322 				fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP,
323 				fPictureView->SuggestedMIMEType());
324 
325 		}
326 
327 		fPictureView->Update();
328 	}
329 
330 	file.GetModificationTime(&fLastModificationTime);
331 
332 	fSaving = false;
333 }
334 
335 
336 const char*
337 PersonView::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 
348 void
349 PersonView::SetAttribute(const char* attribute, bool update)
350 {
351 	char* value = NULL;
352 	attr_info info;
353 	BFile* file = NULL;
354 
355 	if (fRef != NULL)
356 		file = new(std::nothrow) BFile(fRef, B_READ_ONLY);
357 
358 	if (file != NULL && file->GetAttrInfo(attribute, &info) == B_OK) {
359 		value = (char*)calloc(info.size, 1);
360 		file->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size);
361 	}
362 
363 	SetAttribute(attribute, value, update);
364 
365 	free(value);
366 	delete file;
367 }
368 
369 
370 void
371 PersonView::SetAttribute(const char* attribute, const char* value,
372 	bool update)
373 {
374 	if (!LockLooper())
375 		return;
376 
377 	AttributeTextControl* control = NULL;
378 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
379 		if (fControls.ItemAt(i)->Attribute() == attribute) {
380 			control = fControls.ItemAt(i);
381 			break;
382 		}
383 	}
384 
385 	if (control == NULL)
386 		return;
387 
388 	if (update) {
389 		control->SetText(value);
390 		control->Update();
391 	} else {
392 		BTextView* text = control->TextView();
393 
394 		int32 start, end;
395 		text->GetSelection(&start, &end);
396 		if (start != end) {
397 			text->Delete();
398 			text->Insert(value);
399 		} else if ((end = text->TextLength())) {
400 			text->Select(end, end);
401 			text->Insert(",");
402 			text->Insert(value);
403 			text->Select(text->TextLength(), text->TextLength());
404 		} else
405 			control->SetText(value);
406 	}
407 
408 	UnlockLooper();
409 }
410 
411 
412 void
413 PersonView::UpdatePicture(const entry_ref* ref)
414 {
415 	if (fPictureView == NULL)
416 		return;
417 
418 	if (fSaving)
419 		return;
420 
421 	time_t modificationTime = 0;
422 	BEntry entry(ref);
423 	entry.GetModificationTime(&modificationTime);
424 
425 	if (entry.InitCheck() == B_OK
426 		&& modificationTime <= fLastModificationTime) {
427 		return;
428 	}
429 
430 	fPictureView->Update(ref);
431 }
432 
433 
434 bool
435 PersonView::IsTextSelected() const
436 {
437 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
438 		BTextView* text = fControls.ItemAt(i)->TextView();
439 
440 		int32 start, end;
441 		text->GetSelection(&start, &end);
442 		if (start != end)
443 			return true;
444 	}
445 	return false;
446 }
447