xref: /haiku/src/apps/people/PersonView.cpp (revision 4b7e219688450694efc9d1890f83f816758c16d3)
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 	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 (file != NULL)
75 		file->GetModificationTime(&fLastModificationTime);
76 	delete file;
77 }
78 
79 
80 PersonView::~PersonView()
81 {
82 }
83 
84 
85 void
86 PersonView::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 
124 void
125 PersonView::MakeFocus(bool focus)
126 {
127 	if (focus && fControls.CountItems() > 0)
128 		fControls.ItemAt(0)->MakeFocus();
129 	else
130 		BView::MakeFocus(focus);
131 }
132 
133 
134 void
135 PersonView::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 
172 void
173 PersonView::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 
187 void
188 PersonView::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 > (off_t)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 
269 void
270 PersonView::CreateFile(const entry_ref* ref)
271 {
272 	fRef = ref;
273 	Save();
274 }
275 
276 
277 bool
278 PersonView::IsSaved() const
279 {
280 	if (fPictureView && fPictureView->HasChanged())
281 		return false;
282 
283 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
284 		if (fControls.ItemAt(i)->HasChanged())
285 			return false;
286 	}
287 
288 	return true;
289 }
290 
291 
292 void
293 PersonView::Save()
294 {
295 	BFile file(fRef, B_READ_WRITE);
296 	if (file.InitCheck() != B_NO_ERROR)
297 		return;
298 
299 	fSaving = true;
300 
301 	int32 count = fControls.CountItems();
302 	for (int32 i = 0; i < count; i++) {
303 		AttributeTextControl* control = fControls.ItemAt(i);
304 		const char* value = control->Text();
305 		file.WriteAttr(control->Attribute().String(), B_STRING_TYPE, 0,
306 			value, strlen(value) + 1);
307 		control->Update();
308 	}
309 
310 	// Write the picture, if any, in the person file content
311 	if (fPictureView) {
312 		// Trim any previous content
313 		file.Seek(0, SEEK_SET);
314 		file.SetSize(0);
315 
316 		BBitmap* picture = fPictureView->Bitmap();
317 		if (picture) {
318 			BBitmapStream stream(picture);
319 			// Detach *our* bitmap from stream to avoid its deletion
320 			// at stream object destruction
321 			stream.DetachBitmap(&picture);
322 
323 			BTranslatorRoster* roster = BTranslatorRoster::Default();
324 			roster->Translate(&stream, NULL, NULL, &file,
325 				fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP,
326 				fPictureView->SuggestedMIMEType());
327 
328 		}
329 
330 		fPictureView->Update();
331 	}
332 
333 	file.GetModificationTime(&fLastModificationTime);
334 
335 	fSaving = false;
336 }
337 
338 
339 const char*
340 PersonView::AttributeValue(const char* attribute) const
341 {
342 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
343 		if (fControls.ItemAt(i)->Attribute() == attribute)
344 			return fControls.ItemAt(i)->Text();
345 	}
346 
347 	return "";
348 }
349 
350 
351 void
352 PersonView::SetAttribute(const char* attribute, bool update)
353 {
354 	char* value = NULL;
355 	attr_info info;
356 	BFile* file = NULL;
357 
358 	if (fRef != NULL)
359 		file = new(std::nothrow) BFile(fRef, B_READ_ONLY);
360 
361 	if (file != NULL && file->GetAttrInfo(attribute, &info) == B_OK) {
362 		value = (char*)calloc(info.size, 1);
363 		file->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size);
364 	}
365 
366 	SetAttribute(attribute, value, update);
367 
368 	free(value);
369 	delete file;
370 }
371 
372 
373 void
374 PersonView::SetAttribute(const char* attribute, const char* value,
375 	bool update)
376 {
377 	if (!LockLooper())
378 		return;
379 
380 	AttributeTextControl* control = NULL;
381 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
382 		if (fControls.ItemAt(i)->Attribute() == attribute) {
383 			control = fControls.ItemAt(i);
384 			break;
385 		}
386 	}
387 
388 	if (control == NULL)
389 		return;
390 
391 	if (update) {
392 		control->SetText(value);
393 		control->Update();
394 	} else {
395 		BTextView* text = control->TextView();
396 
397 		int32 start, end;
398 		text->GetSelection(&start, &end);
399 		if (start != end) {
400 			text->Delete();
401 			text->Insert(value);
402 		} else if ((end = text->TextLength())) {
403 			text->Select(end, end);
404 			text->Insert(",");
405 			text->Insert(value);
406 			text->Select(text->TextLength(), text->TextLength());
407 		} else
408 			control->SetText(value);
409 	}
410 
411 	UnlockLooper();
412 }
413 
414 
415 void
416 PersonView::UpdatePicture(const entry_ref* ref)
417 {
418 	if (fPictureView == NULL)
419 		return;
420 
421 	if (fSaving)
422 		return;
423 
424 	time_t modificationTime = 0;
425 	BEntry entry(ref);
426 	entry.GetModificationTime(&modificationTime);
427 
428 	if (entry.InitCheck() == B_OK
429 		&& modificationTime <= fLastModificationTime) {
430 		return;
431 	}
432 
433 	fPictureView->Update(ref);
434 }
435 
436 
437 bool
438 PersonView::IsTextSelected() const
439 {
440 	for (int32 i = fControls.CountItems() - 1; i >= 0; i--) {
441 		BTextView* text = fControls.ItemAt(i)->TextView();
442 
443 		int32 start, end;
444 		text->GetSelection(&start, &end);
445 		if (start != end)
446 			return true;
447 	}
448 	return false;
449 }
450