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