xref: /haiku/src/apps/people/PeopleApp.cpp (revision b46615c55ad2c8fe6de54412055a0713da3d610a)
1 /*
2  * Copyright 2005-2010, 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_TRANSLATE_CONTEXT
38 #define B_TRANSLATE_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:wphone", 90, B_TRANSLATE("Work phone") },
62 	{ "META:fax", 90, B_TRANSLATE("Fax") },
63 	{ "META:email", 120, B_TRANSLATE("E-mail") },
64 	{ "META:url", 120, B_TRANSLATE("URL") },
65 	{ kCategoryAttribute, 120, B_TRANSLATE("Group") },
66 	{ NULL, 0, NULL }
67 };
68 
69 
70 TPeopleApp::TPeopleApp()
71 	:
72 	BApplication(APP_SIG),
73 	fWindowCount(0),
74 	fAttributes(20, true)
75 {
76 	fPosition.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH,
77 		TITLE_BAR_HEIGHT + WIND_HEIGHT);
78 	BPoint pos = fPosition.LeftTop();
79 
80 	BPath path;
81 	find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
82 
83 	BDirectory dir(path.Path());
84 	BEntry entry;
85 	if (dir.FindEntry("People_data", &entry) == B_OK) {
86 		fPrefs = new BFile(&entry, B_READ_WRITE);
87 		if (fPrefs->InitCheck() == B_NO_ERROR) {
88 			fPrefs->Read(&pos, sizeof(BPoint));
89 			if (BScreen(B_MAIN_SCREEN_ID).Frame().Contains(pos))
90 				fPosition.OffsetTo(pos);
91 		}
92 	} else {
93 		fPrefs = new BFile();
94 		if (dir.CreateFile("People_data", fPrefs) != B_OK) {
95 			delete fPrefs;
96 			fPrefs = NULL;
97 		}
98 	}
99 
100 	// Read attributes from person mime type. If it does not exist,
101 	// or if it contains no attribute definitions, install a "clean"
102 	// person mime type from the hard-coded default attributes.
103 
104 	bool valid = false;
105 	BMimeType mime(B_PERSON_MIMETYPE);
106 	if (mime.IsInstalled()) {
107 		BMessage info;
108 		if (mime.GetAttrInfo(&info) == B_NO_ERROR) {
109 			int32 index = 0;
110 			while (true) {
111 				int32 type;
112 				if (info.FindInt32("attr:type", index, &type) != B_OK)
113 					break;
114 				bool editable;
115 				if (info.FindBool("attr:editable", index, &editable) != B_OK)
116 					break;
117 
118 				// TODO: Support other types besides string attributes.
119 				if (type != B_STRING_TYPE || !editable)
120 					break;
121 
122 				Attribute* attribute = new Attribute();
123 				ObjectDeleter<Attribute> deleter(attribute);
124 				if (info.FindString("attr:public_name", index,
125 						&attribute->name) != B_OK) {
126 					break;
127 				}
128 				if (info.FindString("attr:name", index,
129 						&attribute->attribute) != B_OK) {
130 					break;
131 				}
132 
133 				if (!fAttributes.AddItem(attribute))
134 					break;
135 
136 				deleter.Detach();
137 				index++;
138 			}
139 		}
140 		if (fAttributes.CountItems() == 0) {
141 			valid = false;
142 			mime.Delete();
143 		} else
144 			valid = true;
145 	}
146 	if (!valid) {
147 		mime.Install();
148 		mime.SetShortDescription(B_TRANSLATE_WITH_CONTEXT("Person",
149 			"Short mimetype description"));
150 		mime.SetLongDescription(B_TRANSLATE_WITH_CONTEXT(
151 			"Contact information for a person.",
152 			"Long mimetype description"));
153 		mime.SetIcon(kPersonIcon, sizeof(kPersonIcon));
154 		mime.SetPreferredApp(APP_SIG);
155 
156 		// add default person fields to meta-mime type
157 		BMessage fields;
158 		for (int32 i = 0; sDefaultAttributes[i].attribute; i++) {
159 			fields.AddString("attr:public_name", sDefaultAttributes[i].name);
160 			fields.AddString("attr:name", sDefaultAttributes[i].attribute);
161 			fields.AddInt32("attr:type", B_STRING_TYPE);
162 			fields.AddBool("attr:viewable", true);
163 			fields.AddBool("attr:editable", true);
164 			fields.AddInt32("attr:width", sDefaultAttributes[i].width);
165 			fields.AddInt32("attr:alignment", B_ALIGN_LEFT);
166 			fields.AddBool("attr:extra", false);
167 
168 			// Add the default attribute to the attribute list, too.
169 			Attribute* attribute = new Attribute();
170 			attribute->name = sDefaultAttributes[i].name;
171 			attribute->attribute = sDefaultAttributes[i].attribute;
172 			if (!fAttributes.AddItem(attribute))
173 				delete attribute;
174 		}
175 
176 		mime.SetAttrInfo(&fields);
177 	}
178 
179 	// create indices on all volumes for the found attributes.
180 
181 	int32 count = fAttributes.CountItems();
182 	BVolumeRoster volumeRoster;
183 	BVolume volume;
184 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
185 		for (int32 i = 0; i < count; i++) {
186 			Attribute* attribute = fAttributes.ItemAt(i);
187 			fs_create_index(volume.Device(), attribute->attribute,
188 				B_STRING_TYPE, 0);
189 		}
190 	}
191 
192 }
193 
194 
195 TPeopleApp::~TPeopleApp()
196 {
197 	delete fPrefs;
198 }
199 
200 
201 void
202 TPeopleApp::ArgvReceived(int32 argc, char** argv)
203 {
204 	BMessage message(B_REFS_RECEIVED);
205 
206 	for (int32 i = 1; i < argc; i++) {
207 		BEntry entry(argv[i]);
208 		entry_ref ref;
209 		if (entry.Exists() && entry.GetRef(&ref) == B_OK)
210 			message.AddRef("refs", &ref);
211 	}
212 
213 	RefsReceived(&message);
214 }
215 
216 
217 void
218 TPeopleApp::MessageReceived(BMessage* message)
219 {
220 	switch (message->what) {
221 		case M_NEW:
222 		case B_SILENT_RELAUNCH:
223 			_NewWindow();
224 			break;
225 
226 		case M_WINDOW_QUITS:
227 			_SavePreferences(message);
228 			fWindowCount--;
229 			if (fWindowCount < 1)
230 				PostMessage(B_QUIT_REQUESTED);
231 			break;
232 
233 		case M_CONFIGURE_ATTRIBUTES:
234 		{
235 			const char* arguments[] = { "-type", B_PERSON_MIMETYPE, 0 };
236 			status_t ret = be_roster->Launch(
237 				"application/x-vnd.Haiku-FileTypes",
238 				sizeof(arguments) / sizeof(const char*) - 1,
239 				const_cast<char**>(arguments));
240 			if (ret != B_OK && ret != B_ALREADY_RUNNING) {
241 				BString errorMsg(B_TRANSLATE("Launching the FileTypes "
242 					"preflet to configure Person attributes has failed."
243 					"\n\nError: "));
244 				errorMsg << strerror(ret);
245 				BAlert* alert = new BAlert(B_TRANSLATE("Error"),
246 					errorMsg.String(), B_TRANSLATE("OK"), NULL, NULL,
247 					B_WIDTH_AS_USUAL, B_STOP_ALERT);
248 				alert->Go(NULL);
249 			}
250 			break;
251 		}
252 
253 		default:
254 			BApplication::MessageReceived(message);
255 	}
256 }
257 
258 
259 void
260 TPeopleApp::RefsReceived(BMessage* message)
261 {
262 	int32 index = 0;
263 	while (message->HasRef("refs", index)) {
264 		entry_ref ref;
265 		message->FindRef("refs", index++, &ref);
266 
267 		PersonWindow* window = _FindWindow(ref);
268 		if (window != NULL)
269 			window->Activate(true);
270 		else {
271 			BFile file(&ref, B_READ_ONLY);
272 			if (file.InitCheck() == B_OK)
273 				_NewWindow(&ref);
274 		}
275 	}
276 }
277 
278 
279 void
280 TPeopleApp::ReadyToRun()
281 {
282 	if (fWindowCount < 1)
283 		_NewWindow();
284 }
285 
286 
287 // #pragma mark -
288 
289 
290 PersonWindow*
291 TPeopleApp::_NewWindow(entry_ref* ref)
292 {
293 	PersonWindow* window = new PersonWindow(fPosition,
294 		B_TRANSLATE("New person"), kNameAttribute,
295 		kCategoryAttribute, ref);
296 
297 	_AddAttributes(window);
298 
299 	window->Show();
300 
301 	fWindowCount++;
302 
303 	// Offset the position for the next window which will be opened and
304 	// reset it if it would open outside the screen bounds.
305 	fPosition.OffsetBy(20, 20);
306 	BScreen screen(window);
307 	if (fPosition.bottom > screen.Frame().bottom)
308 		fPosition.OffsetTo(fPosition.left, TITLE_BAR_HEIGHT);
309 	if (fPosition.right > screen.Frame().right)
310 		fPosition.OffsetTo(6, fPosition.top);
311 
312 	return window;
313 }
314 
315 
316 void
317 TPeopleApp::_AddAttributes(PersonWindow* window) const
318 {
319 	int32 count = fAttributes.CountItems();
320 	for (int32 i = 0; i < count; i++) {
321 		Attribute* attribute = fAttributes.ItemAt(i);
322 		const char* label = attribute->name;
323 		if (attribute->attribute == kNameAttribute)
324 			label = B_TRANSLATE("Name");
325 
326 		window->AddAttribute(label, attribute->attribute);
327 	}
328 }
329 
330 
331 PersonWindow*
332 TPeopleApp::_FindWindow(const entry_ref& ref) const
333 {
334 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
335 		PersonWindow* personWindow = dynamic_cast<PersonWindow*>(window);
336 		if (personWindow == NULL)
337 			continue;
338 		if (personWindow->RefersPersonFile(ref))
339 			return personWindow;
340 	}
341 	return NULL;
342 }
343 
344 
345 void
346 TPeopleApp::_SavePreferences(BMessage* message) const
347 {
348 	BRect frame;
349 	if (message->FindRect("frame", &frame) != B_OK)
350 		return;
351 
352 	BPoint leftTop = frame.LeftTop();
353 
354 	if (fPrefs != NULL) {
355 		fPrefs->Seek(0, 0);
356 		fPrefs->Write(&leftTop, sizeof(BPoint));
357 	}
358 }
359 
360