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 if (ref) 59 fFile = new BFile(ref, O_RDWR); 60 else 61 fFile = NULL; 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 (fFile) 75 fFile->GetModificationTime(&fLastModificationTime); 76 } 77 78 79 PersonView::~PersonView() 80 { 81 delete fFile; 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 > 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 delete fFile; 273 fFile = new BFile(ref, B_READ_WRITE); 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 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 fFile->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 fFile->Seek(0, SEEK_SET); 311 fFile->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, fFile, 322 fPictureView->SuggestedType(), B_TRANSLATOR_BITMAP, 323 fPictureView->SuggestedMIMEType()); 324 325 } 326 327 fPictureView->Update(); 328 } 329 330 fFile->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 if (fFile != NULL && fFile->GetAttrInfo(attribute, &info) == B_OK) { 354 value = (char*)calloc(info.size, 1); 355 fFile->ReadAttr(attribute, B_STRING_TYPE, 0, value, info.size); 356 } 357 358 SetAttribute(attribute, value, update); 359 360 free(value); 361 } 362 363 364 void 365 PersonView::SetAttribute(const char* attribute, const char* value, 366 bool update) 367 { 368 if (!LockLooper()) 369 return; 370 371 AttributeTextControl* control = NULL; 372 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 373 if (fControls.ItemAt(i)->Attribute() == attribute) { 374 control = fControls.ItemAt(i); 375 break; 376 } 377 } 378 379 if (control == NULL) 380 return; 381 382 if (update) { 383 control->SetText(value); 384 control->Update(); 385 } else { 386 BTextView* text = control->TextView(); 387 388 int32 start, end; 389 text->GetSelection(&start, &end); 390 if (start != end) { 391 text->Delete(); 392 text->Insert(value); 393 } else if ((end = text->TextLength())) { 394 text->Select(end, end); 395 text->Insert(","); 396 text->Insert(value); 397 text->Select(text->TextLength(), text->TextLength()); 398 } else 399 control->SetText(value); 400 } 401 402 UnlockLooper(); 403 } 404 405 406 void 407 PersonView::UpdatePicture(const entry_ref* ref) 408 { 409 if (fPictureView == NULL) 410 return; 411 412 if (fSaving) 413 return; 414 415 time_t modificationTime = 0; 416 BEntry entry(ref); 417 entry.GetModificationTime(&modificationTime); 418 419 if (entry.InitCheck() == B_OK 420 && modificationTime <= fLastModificationTime) { 421 return; 422 } 423 424 fPictureView->Update(ref); 425 } 426 427 428 bool 429 PersonView::IsTextSelected() const 430 { 431 for (int32 i = fControls.CountItems() - 1; i >= 0; i--) { 432 BTextView* text = fControls.ItemAt(i)->TextView(); 433 434 int32 start, end; 435 text->GetSelection(&start, &end); 436 if (start != end) 437 return true; 438 } 439 return false; 440 } 441