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