xref: /haiku/src/apps/people/PeopleApp.cpp (revision 4c07199d8201fcf267e90be0d24b76799d03cea6)
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 
41 struct 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.
48 static const char* kNameAttribute = "META:name";
49 static const char* kCategoryAttribute = "META:group";
50 
51 struct 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 
71 TPeopleApp::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 
198 TPeopleApp::~TPeopleApp()
199 {
200 	delete fPrefs;
201 }
202 
203 
204 void
205 TPeopleApp::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 
220 void
221 TPeopleApp::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 
263 void
264 TPeopleApp::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 
283 void
284 TPeopleApp::ReadyToRun()
285 {
286 	if (fWindowCount < 1)
287 		_NewWindow();
288 }
289 
290 
291 // #pragma mark -
292 
293 
294 PersonWindow*
295 TPeopleApp::_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 
322 void
323 TPeopleApp::_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 
337 PersonWindow*
338 TPeopleApp::_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 
351 void
352 TPeopleApp::_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