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