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
TPeopleApp()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
~TPeopleApp()198 TPeopleApp::~TPeopleApp()
199 {
200 delete fPrefs;
201 }
202
203
204 void
ArgvReceived(int32 argc,char ** argv)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
MessageReceived(BMessage * message)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
RefsReceived(BMessage * message)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
ReadyToRun()284 TPeopleApp::ReadyToRun()
285 {
286 if (fWindowCount < 1)
287 _NewWindow();
288 }
289
290
291 // #pragma mark -
292
293
294 PersonWindow*
_NewWindow(entry_ref * ref,BMessage * message)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
_AddAttributes(PersonWindow * window) const323 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*
_FindWindow(const entry_ref & ref) const338 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
_SavePreferences(BMessage * message) const352 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