1/*
2 * Copyright 2005-2023, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Robert Polic
7 *		Axel D��rfler, axeld@pinc-software.de
8 *		Stephan A��mus <superstippi@gmx.de>
9 *
10 * Copyright 1999, Be Incorporated.   All Rights Reserved.
11 * This file may be used under the terms of the Be Sample Code License.
12 */
13
14
15#include "PeopleApp.h"
16
17#include <Alert.h>
18#include <AutoDeleter.h>
19#include <Bitmap.h>
20#include <Catalog.h>
21#include <Directory.h>
22#include <FindDirectory.h>
23#include <fs_index.h>
24#include <Locale.h>
25#include <Path.h>
26#include <Roster.h>
27#include <Screen.h>
28#include <Volume.h>
29#include <VolumeRoster.h>
30
31#include "PersonWindow.h"
32#include "PersonIcons.h"
33
34#include <string.h>
35
36
37#undef B_TRANSLATION_CONTEXT
38#define B_TRANSLATION_CONTEXT "People"
39
40
41struct DefaultAttribute {
42	const char*	attribute;
43	int32		width;
44	const char*	name;
45};
46
47// TODO: Add flags in attribute info message to find these.
48static const char* kNameAttribute = "META:name";
49static const char* kCategoryAttribute = "META:group";
50
51struct DefaultAttribute sDefaultAttributes[] = {
52	{ kNameAttribute, 120, B_TRANSLATE("Contact name") },
53	{ "META:nickname", 120, B_TRANSLATE("Nickname") },
54	{ "META:company", 120, B_TRANSLATE("Company") },
55	{ "META:address", 120, B_TRANSLATE("Address") },
56	{ "META:city", 90, B_TRANSLATE("City") },
57	{ "META:state", 50, B_TRANSLATE("State") },
58	{ "META:zip", 50, B_TRANSLATE("Zip") },
59	{ "META:country", 120, B_TRANSLATE("Country") },
60	{ "META:hphone", 90, B_TRANSLATE("Home phone") },
61	{ "META:mphone", 90, B_TRANSLATE("Mobile phone") },
62	{ "META:wphone", 90, B_TRANSLATE("Work phone") },
63	{ "META:fax", 90, B_TRANSLATE("Fax") },
64	{ "META:email", 120, B_TRANSLATE("E-mail") },
65	{ "META:url", 120, B_TRANSLATE("URL") },
66	{ kCategoryAttribute, 120, B_TRANSLATE("Group") },
67	{ NULL, 0, NULL }
68};
69
70
71TPeopleApp::TPeopleApp()
72	:
73	BApplication(APP_SIG),
74	fWindowCount(0),
75	fAttributes(20, true)
76{
77	B_TRANSLATE_MARK_SYSTEM_NAME_VOID("People");
78
79	fPosition.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH,
80		TITLE_BAR_HEIGHT + WIND_HEIGHT);
81	BPoint pos = fPosition.LeftTop();
82
83	BPath path;
84	find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
85
86	BDirectory dir(path.Path());
87	BEntry entry;
88	if (dir.FindEntry("People_data", &entry) == B_OK) {
89		fPrefs = new BFile(&entry, B_READ_WRITE);
90		if (fPrefs->InitCheck() == B_NO_ERROR) {
91			fPrefs->Read(&pos, sizeof(BPoint));
92			if (BScreen(B_MAIN_SCREEN_ID).Frame().Contains(pos))
93				fPosition.OffsetTo(pos);
94		}
95	} else {
96		fPrefs = new BFile();
97		if (dir.CreateFile("People_data", fPrefs) != B_OK) {
98			delete fPrefs;
99			fPrefs = NULL;
100		}
101	}
102
103	// Read attributes from person mime type. If it does not exist,
104	// or if it contains no attribute definitions, install a "clean"
105	// person mime type from the hard-coded default attributes.
106
107	bool valid = false;
108	BMimeType mime(B_PERSON_MIMETYPE);
109	if (mime.IsInstalled()) {
110		BMessage info;
111		if (mime.GetAttrInfo(&info) == B_NO_ERROR) {
112			int32 index = 0;
113			while (true) {
114				int32 type;
115				if (info.FindInt32("attr:type", index, &type) != B_OK)
116					break;
117				bool editable;
118				if (info.FindBool("attr:editable", index, &editable) != B_OK)
119					break;
120
121				// TODO: Support other types besides string attributes.
122				if (type != B_STRING_TYPE || !editable)
123					break;
124
125				Attribute* attribute = new Attribute();
126				ObjectDeleter<Attribute> deleter(attribute);
127				if (info.FindString("attr:public_name", index,
128						&attribute->name) != B_OK) {
129					break;
130				}
131				if (info.FindString("attr:name", index,
132						&attribute->attribute) != B_OK) {
133					break;
134				}
135
136				if (!fAttributes.AddItem(attribute))
137					break;
138
139				deleter.Detach();
140				index++;
141			}
142		}
143		if (fAttributes.CountItems() == 0) {
144			valid = false;
145			mime.Delete();
146		} else
147			valid = true;
148	}
149	if (!valid) {
150		mime.Install();
151		mime.SetShortDescription(B_TRANSLATE_CONTEXT("Person",
152			"Short mimetype description"));
153		mime.SetLongDescription(B_TRANSLATE_CONTEXT(
154			"Contact information for a person.",
155			"Long mimetype description"));
156		mime.SetIcon(kPersonIcon, sizeof(kPersonIcon));
157		mime.SetPreferredApp(APP_SIG);
158
159		// add default person fields to meta-mime type
160		BMessage fields;
161		for (int32 i = 0; sDefaultAttributes[i].attribute; i++) {
162			fields.AddString("attr:public_name", sDefaultAttributes[i].name);
163			fields.AddString("attr:name", sDefaultAttributes[i].attribute);
164			fields.AddInt32("attr:type", B_STRING_TYPE);
165			fields.AddBool("attr:viewable", true);
166			fields.AddBool("attr:editable", true);
167			fields.AddInt32("attr:width", sDefaultAttributes[i].width);
168			fields.AddInt32("attr:alignment", B_ALIGN_LEFT);
169			fields.AddBool("attr:extra", false);
170
171			// Add the default attribute to the attribute list, too.
172			Attribute* attribute = new Attribute();
173			attribute->name = sDefaultAttributes[i].name;
174			attribute->attribute = sDefaultAttributes[i].attribute;
175			if (!fAttributes.AddItem(attribute))
176				delete attribute;
177		}
178
179		mime.SetAttrInfo(&fields);
180	}
181
182	// create indices on all volumes for the found attributes.
183
184	int32 count = fAttributes.CountItems();
185	BVolumeRoster volumeRoster;
186	BVolume volume;
187	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
188		for (int32 i = 0; i < count; i++) {
189			Attribute* attribute = fAttributes.ItemAt(i);
190			fs_create_index(volume.Device(), attribute->attribute,
191				B_STRING_TYPE, 0);
192		}
193	}
194
195}
196
197
198TPeopleApp::~TPeopleApp()
199{
200	delete fPrefs;
201}
202
203
204void
205TPeopleApp::ArgvReceived(int32 argc, char** argv)
206{
207	BMessage message(B_REFS_RECEIVED);
208
209	for (int32 i = 1; i < argc; i++) {
210		BEntry entry(argv[i]);
211		entry_ref ref;
212		if (entry.Exists() && entry.GetRef(&ref) == B_OK)
213			message.AddRef("refs", &ref);
214	}
215
216	RefsReceived(&message);
217}
218
219
220void
221TPeopleApp::MessageReceived(BMessage* message)
222{
223	switch (message->what) {
224		case M_NEW:
225		case B_SILENT_RELAUNCH:
226			_NewWindow(NULL, message);
227			break;
228
229		case M_WINDOW_QUITS:
230			_SavePreferences(message);
231			fWindowCount--;
232			if (fWindowCount < 1)
233				PostMessage(B_QUIT_REQUESTED);
234			break;
235
236		case M_CONFIGURE_ATTRIBUTES:
237		{
238			const char* arguments[] = { "-type", B_PERSON_MIMETYPE, 0 };
239			status_t ret = be_roster->Launch(
240				"application/x-vnd.Haiku-FileTypes",
241				sizeof(arguments) / sizeof(const char*) - 1,
242				const_cast<char**>(arguments));
243			if (ret != B_OK && ret != B_ALREADY_RUNNING) {
244				BString errorMsg(B_TRANSLATE("Launching the FileTypes "
245					"preflet to configure Person attributes has failed."
246					"\n\nError: "));
247				errorMsg << strerror(ret);
248				BAlert* alert = new BAlert(B_TRANSLATE("Error"),
249					errorMsg.String(), B_TRANSLATE("OK"), NULL, NULL,
250					B_WIDTH_AS_USUAL, B_STOP_ALERT);
251				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
252				alert->Go(NULL);
253			}
254			break;
255		}
256
257		default:
258			BApplication::MessageReceived(message);
259	}
260}
261
262
263void
264TPeopleApp::RefsReceived(BMessage* message)
265{
266	int32 index = 0;
267	while (message->HasRef("refs", index)) {
268		entry_ref ref;
269		message->FindRef("refs", index++, &ref);
270
271		PersonWindow* window = _FindWindow(ref);
272		if (window != NULL)
273			window->Activate(true);
274		else {
275			BFile file(&ref, B_READ_ONLY);
276			if (file.InitCheck() == B_OK)
277				_NewWindow(&ref, NULL);
278		}
279	}
280}
281
282
283void
284TPeopleApp::ReadyToRun()
285{
286	if (fWindowCount < 1)
287		_NewWindow();
288}
289
290
291// #pragma mark -
292
293
294PersonWindow*
295TPeopleApp::_NewWindow(entry_ref* ref, BMessage* message)
296{
297	PersonWindow* window = new PersonWindow(fPosition,
298		B_TRANSLATE("New person"), kNameAttribute,
299		kCategoryAttribute, ref);
300
301	_AddAttributes(window);
302	if (message != NULL)
303		window->SetInitialValues(message);
304
305	window->Show();
306
307	fWindowCount++;
308
309	// Offset the position for the next window which will be opened and
310	// reset it if it would open outside the screen bounds.
311	fPosition.OffsetBy(20, 20);
312	BScreen screen(window);
313	if (fPosition.bottom > screen.Frame().bottom)
314		fPosition.OffsetTo(fPosition.left, TITLE_BAR_HEIGHT);
315	if (fPosition.right > screen.Frame().right)
316		fPosition.OffsetTo(6, fPosition.top);
317
318	return window;
319}
320
321
322void
323TPeopleApp::_AddAttributes(PersonWindow* window) const
324{
325	int32 count = fAttributes.CountItems();
326	for (int32 i = 0; i < count; i++) {
327		Attribute* attribute = fAttributes.ItemAt(i);
328		const char* label = attribute->name;
329		if (attribute->attribute == kNameAttribute)
330			label = B_TRANSLATE("Name");
331
332		window->AddAttribute(label, attribute->attribute);
333	}
334}
335
336
337PersonWindow*
338TPeopleApp::_FindWindow(const entry_ref& ref) const
339{
340	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
341		PersonWindow* personWindow = dynamic_cast<PersonWindow*>(window);
342		if (personWindow == NULL)
343			continue;
344		if (personWindow->RefersPersonFile(ref))
345			return personWindow;
346	}
347	return NULL;
348}
349
350
351void
352TPeopleApp::_SavePreferences(BMessage* message) const
353{
354	BRect frame;
355	if (message->FindRect("frame", &frame) != B_OK)
356		return;
357
358	BPoint leftTop = frame.LeftTop();
359
360	if (fPrefs != NULL) {
361		fPrefs->Seek(0, 0);
362		fPrefs->Write(&leftTop, sizeof(BPoint));
363	}
364}
365
366