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