1 /* 2 * Copyright 2010, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Robert Polic 7 * Stephan Aßmus <superstippi@gmx.de> 8 * 9 * Copyright 1999, Be Incorporated. All Rights Reserved. 10 * This file may be used under the terms of the Be Sample Code License. 11 */ 12 13 14 #include "PersonView.h" 15 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <string.h> 19 20 #include <BitmapStream.h> 21 #include <Catalog.h> 22 #include <fs_attr.h> 23 #include <Box.h> 24 #include <ControlLook.h> 25 #include <GridLayout.h> 26 #include <Locale.h> 27 #include <MenuField.h> 28 #include <MenuItem.h> 29 #include <PopUpMenu.h> 30 #include <Query.h> 31 #include <TranslationUtils.h> 32 #include <Translator.h> 33 #include <VolumeRoster.h> 34 #include <Window.h> 35 36 #include "AttributeTextControl.h" 37 #include "PictureView.h" 38 39 40 #undef B_TRANSLATION_CONTEXT 41 #define B_TRANSLATION_CONTEXT "People" 42 43 44 PersonView::PersonView(const char* name, const char* categoryAttribute, 45 const entry_ref *ref) 46 : 47 BGridView(), 48 fLastModificationTime(0), 49 fGroups(NULL), 50 fControls(20, false), 51 fCategoryAttribute(categoryAttribute), 52 fPictureView(NULL), 53 fSaving(false) 54 { 55 SetName(name); 56 SetFlags(Flags() | B_WILL_DRAW); 57 58 fRef = ref; 59 BFile* file = NULL; 60 if (fRef != NULL) 61 file = new BFile(fRef, B_READ_ONLY); 62 63 // Add picture "field", using ID photo 35mm x 45mm ratio 64 fPictureView = new PictureView(70, 90, ref); 65 66 BGridLayout* layout = GridLayout(); 67 layout->AddView(fPictureView, 0, 0, 1, 5); 68 layout->ItemAt(0, 0)->SetExplicitAlignment( 69 BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP)); 70 71 if (file != NULL) 72 file->GetModificationTime(&fLastModificationTime); 73 delete file; 74 } 75 76 77 PersonView::~PersonView() 78 { 79 } 80 81 82 void 83 PersonView::AddAttribute(const char* label, const char* attribute) 84 { 85 // Check if this attribute has already been added. 86 AttributeTextControl* control = NULL; 87 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 88 if (fControls.ItemAt(i)->Attribute() == attribute) { 89 return; 90 } 91 } 92 93 control = new AttributeTextControl(label, attribute); 94 fControls.AddItem(control); 95 96 BGridLayout* layout = GridLayout(); 97 int32 row = fControls.CountItems(); 98 99 if (fCategoryAttribute == attribute) { 100 // Special case the category attribute. The Group popup field will 101 // be added as the label instead. 102 fGroups = new BPopUpMenu(label); 103 fGroups->SetRadioMode(false); 104 BuildGroupMenu(); 105 106 BMenuField* field = new BMenuField("", "", fGroups); 107 field->SetEnabled(true); 108 layout->AddView(field, 1, row); 109 110 control->SetLabel(""); 111 layout->AddView(control, 2, row); 112 } else { 113 layout->AddItem(control->CreateLabelLayoutItem(), 1, row); 114 layout->AddItem(control->CreateTextViewLayoutItem(), 2, row); 115 } 116 117 SetAttribute(attribute, true); 118 } 119 120 121 void 122 PersonView::MakeFocus(bool focus) 123 { 124 if (focus && fControls.CountItems() > 0) 125 fControls.ItemAt(0)->MakeFocus(); 126 else 127 BView::MakeFocus(focus); 128 } 129 130 131 void 132 PersonView::MessageReceived(BMessage* msg) 133 { 134 switch (msg->what) { 135 case M_SAVE: 136 Save(); 137 break; 138 139 case M_REVERT: 140 if (fPictureView) 141 fPictureView->Revert(); 142 143 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) 144 fControls.ItemAt(i)->Revert(); 145 break; 146 147 case M_SELECT: 148 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 149 BTextView* text = fControls.ItemAt(i)->TextView(); 150 if (text->IsFocus()) { 151 text->Select(0, text->TextLength()); 152 break; 153 } 154 } 155 break; 156 157 case M_GROUP_MENU: 158 { 159 const char* name = NULL; 160 if (msg->FindString("group", &name) == B_OK) 161 SetAttribute(fCategoryAttribute, name, false); 162 break; 163 } 164 165 } 166 } 167 168 169 void 170 PersonView::Draw(BRect updateRect) 171 { 172 if (!fPictureView) 173 return; 174 175 // Draw a alert/get info-like strip 176 BRect stripeRect = Bounds(); 177 stripeRect.right = GridLayout()->HorizontalSpacing() 178 + fPictureView->Bounds().Width() / 2; 179 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 180 FillRect(stripeRect); 181 } 182 183 184 void 185 PersonView::BuildGroupMenu() 186 { 187 if (fGroups == NULL) 188 return; 189 190 BMenuItem* item; 191 while ((item = fGroups->ItemAt(0)) != NULL) { 192 fGroups->RemoveItem(item); 193 delete item; 194 } 195 196 int32 count = 0; 197 198 BVolumeRoster volumeRoster; 199 BVolume volume; 200 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 201 BQuery query; 202 query.SetVolume(&volume); 203 204 char buffer[256]; 205 snprintf(buffer, sizeof(buffer), "%s=*", fCategoryAttribute.String()); 206 query.SetPredicate(buffer); 207 query.Fetch(); 208 209 BEntry entry; 210 while (query.GetNextEntry(&entry) == B_OK) { 211 BFile file(&entry, B_READ_ONLY); 212 attr_info info; 213 214 if (file.InitCheck() == B_OK 215 && file.GetAttrInfo(fCategoryAttribute, &info) == B_OK 216 && info.size > 1) { 217 if (info.size > (off_t)sizeof(buffer)) 218 info.size = sizeof(buffer); 219 220 if (file.ReadAttr(fCategoryAttribute.String(), B_STRING_TYPE, 221 0, buffer, info.size) < 0) { 222 continue; 223 } 224 225 const char *text = buffer; 226 while (true) { 227 char* offset = strstr(text, ","); 228 if (offset != NULL) 229 offset[0] = '\0'; 230 231 if (!fGroups->FindItem(text)) { 232 int32 index = 0; 233 while ((item = fGroups->ItemAt(index)) != NULL) { 234 if (strcmp(text, item->Label()) < 0) 235 break; 236 index++; 237 } 238 BMessage* message = new BMessage(M_GROUP_MENU); 239 message->AddString("group", text); 240 fGroups->AddItem(new BMenuItem(text, message), index); 241 count++; 242 } 243 if (offset) { 244 text = offset + 1; 245 while (*text == ' ') 246 text++; 247 } 248 else 249 break; 250 } 251 } 252 } 253 } 254 255 if (count == 0) { 256 fGroups->AddItem(item = new BMenuItem( 257 B_TRANSLATE_CONTEXT("none", "Groups list"), 258 new BMessage(M_GROUP_MENU))); 259 item->SetEnabled(false); 260 } 261 262 fGroups->SetTargetForItems(this); 263 } 264 265 266 void 267 PersonView::CreateFile(const entry_ref* ref) 268 { 269 fRef = ref; 270 Save(); 271 } 272 273 274 bool 275 PersonView::IsSaved() const 276 { 277 if (fPictureView && fPictureView->HasChanged()) 278 return false; 279 280 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 281 if (fControls.ItemAt(i)->HasChanged()) 282 return false; 283 } 284 285 return true; 286 } 287 288 289 void 290 PersonView::Save() 291 { 292 BFile file(fRef, B_READ_WRITE); 293 if (file.InitCheck() != B_NO_ERROR) 294 return; 295 296 fSaving = true; 297 298 int32 count = fControls.CountItems(); 299 for (int32 i = 0; i < count; i++) { 300 AttributeTextControl* control = fControls.ItemAt(i); 301 const char* value = control->Text(); 302 file.WriteAttr(control->Attribute().String(), B_STRING_TYPE, 0, 303 value, strlen(value) + 1); 304 control->Update(); 305 } 306 307 // Write the picture, if any, in the person file content 308 if (fPictureView) { 309 // Trim any previous content 310 file.Seek(0, SEEK_SET); 311 file.SetSize(0); 312 313 BBitmap* picture = fPictureView->Bitmap(); 314 if (picture) { 315 BBitmapStream stream(picture); 316 // Detach *our* bitmap from stream to avoid its deletion 317 // at stream object destruction 318 stream.DetachBitmap(&picture); 319 320 BTranslatorRoster* roster = BTranslatorRoster::Default(); 321 roster->Translate(&stream, NULL, NULL, &file, 322 fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP, 323 fPictureView->SuggestedMIMEType()); 324 325 } 326 327 fPictureView->Update(); 328 } 329 330 file.GetModificationTime(&fLastModificationTime); 331 332 fSaving = false; 333 } 334 335 336 const char* 337 PersonView::AttributeValue(const char* attribute) const 338 { 339 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 340 if (fControls.ItemAt(i)->Attribute() == attribute) 341 return fControls.ItemAt(i)->Text(); 342 } 343 344 return ""; 345 } 346 347 348 void 349 PersonView::SetAttribute(const char* attribute, bool update) 350 { 351 char* value = NULL; 352 attr_info info; 353 BFile* file = NULL; 354 355 if (fRef != NULL) 356 file = new(std::nothrow) BFile(fRef, B_READ_ONLY); 357 358 if (file != NULL && file->GetAttrInfo(attribute, &info) == B_OK) { 359 value = (char*)calloc(info.size, 1); 360 file->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size); 361 } 362 363 SetAttribute(attribute, value, update); 364 365 free(value); 366 delete file; 367 } 368 369 370 void 371 PersonView::SetAttribute(const char* attribute, const char* value, 372 bool update) 373 { 374 if (!LockLooper()) 375 return; 376 377 AttributeTextControl* control = NULL; 378 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 379 if (fControls.ItemAt(i)->Attribute() == attribute) { 380 control = fControls.ItemAt(i); 381 break; 382 } 383 } 384 385 if (control == NULL) 386 return; 387 388 if (update) { 389 control->SetText(value); 390 control->Update(); 391 } else { 392 BTextView* text = control->TextView(); 393 394 int32 start, end; 395 text->GetSelection(&start, &end); 396 if (start != end) { 397 text->Delete(); 398 text->Insert(value); 399 } else if ((end = text->TextLength())) { 400 text->Select(end, end); 401 text->Insert(","); 402 text->Insert(value); 403 text->Select(text->TextLength(), text->TextLength()); 404 } else 405 control->SetText(value); 406 } 407 408 UnlockLooper(); 409 } 410 411 412 void 413 PersonView::UpdatePicture(const entry_ref* ref) 414 { 415 if (fPictureView == NULL) 416 return; 417 418 if (fSaving) 419 return; 420 421 time_t modificationTime = 0; 422 BEntry entry(ref); 423 entry.GetModificationTime(&modificationTime); 424 425 if (entry.InitCheck() == B_OK 426 && modificationTime <= fLastModificationTime) { 427 return; 428 } 429 430 fPictureView->Update(ref); 431 } 432 433 434 bool 435 PersonView::IsTextSelected() const 436 { 437 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 438 BTextView* text = fControls.ItemAt(i)->TextView(); 439 440 int32 start, end; 441 text->GetSelection(&start, &end); 442 if (start != end) 443 return true; 444 } 445 return false; 446 } 447