1 /* 2 * Copyright 2006-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "AttributeWindow.h" 8 #include "FileTypes.h" 9 #include "FileTypesWindow.h" 10 11 #include <Box.h> 12 #include <Button.h> 13 #include <CheckBox.h> 14 #include <MenuField.h> 15 #include <MenuItem.h> 16 #include <Mime.h> 17 #include <PopUpMenu.h> 18 #include <String.h> 19 #include <TextControl.h> 20 21 #include <ctype.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 27 const uint32 kMsgAttributeUpdated = 'atup'; 28 const uint32 kMsgTypeChosen = 'typc'; 29 const uint32 kMsgDisplayAsChosen = 'dach'; 30 const uint32 kMsgVisibilityChanged = 'vsch'; 31 const uint32 kMsgAlignmentChosen = 'alnc'; 32 const uint32 kMsgAccept = 'acpt'; 33 34 35 static int 36 compare_attributes(const void* _a, const void* _b) 37 { 38 AttributeItem* a = *(AttributeItem **)_a; 39 AttributeItem* b = *(AttributeItem **)_b; 40 41 int compare = strcasecmp(a->PublicName(), b->PublicName()); 42 if (compare != 0) 43 return compare; 44 45 return strcmp(a->Name(), b->Name()); 46 } 47 48 49 static bool 50 compare_display_as(const char* a, const char* b) 51 { 52 bool emptyA = a == NULL || !a[0]; 53 bool emptyB = b == NULL || !b[0]; 54 55 if (emptyA && emptyB) 56 return true; 57 if (emptyA || emptyB) 58 return false; 59 60 const char* end = strstr(a, ": "); 61 int32 lengthA = end ? end - a : strlen(a); 62 end = strstr(b, ": "); 63 int32 lengthB = end ? end - b : strlen(b); 64 65 if (lengthA != lengthB) 66 return false; 67 68 return !strncasecmp(a, b, lengthA); 69 } 70 71 72 static const char* 73 display_as_parameter(const char* special) 74 { 75 const char* parameter = strstr(special, ": "); 76 if (parameter != NULL) 77 return parameter + 2; 78 79 return NULL; 80 } 81 82 83 // #pragma mark - 84 85 86 AttributeWindow::AttributeWindow(FileTypesWindow* target, BMimeType& mimeType, 87 AttributeItem* attributeItem) 88 : BWindow(BRect(100, 100, 350, 200), "Attribute", B_MODAL_WINDOW_LOOK, 89 B_MODAL_SUBSET_WINDOW_FEEL, B_NOT_ZOOMABLE | B_NOT_V_RESIZABLE 90 | B_ASYNCHRONOUS_CONTROLS), 91 fTarget(target), 92 fMimeType(mimeType.Type()) 93 { 94 if (attributeItem != NULL) 95 fAttribute = *attributeItem; 96 97 BRect rect = Bounds(); 98 BView* topView = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW); 99 topView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 100 AddChild(topView); 101 102 rect.InsetBy(8.0f, 8.0f); 103 fPublicNameControl = new BTextControl(rect, "public", "Attribute Name:", 104 fAttribute.PublicName(), NULL, B_FOLLOW_LEFT_RIGHT); 105 fPublicNameControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated)); 106 107 float labelWidth = fPublicNameControl->StringWidth(fPublicNameControl->Label()) + 2.0f; 108 fPublicNameControl->SetDivider(labelWidth); 109 fPublicNameControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 110 111 float width, height; 112 fPublicNameControl->GetPreferredSize(&width, &height); 113 fPublicNameControl->ResizeTo(rect.Width(), height); 114 topView->AddChild(fPublicNameControl); 115 116 rect = fPublicNameControl->Frame(); 117 rect.OffsetBy(0.0f, rect.Height() + 5.0f); 118 fAttributeControl = new BTextControl(rect, "internal", "Internal Name:", 119 fAttribute.Name(), NULL, B_FOLLOW_LEFT_RIGHT); 120 fAttributeControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated)); 121 fAttributeControl->SetDivider(labelWidth); 122 fAttributeControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 123 124 // filter out invalid characters that can't be part of an attribute 125 BTextView* textView = fAttributeControl->TextView(); 126 const char* disallowedCharacters = "/"; 127 for (int32 i = 0; disallowedCharacters[i]; i++) { 128 textView->DisallowChar(disallowedCharacters[i]); 129 } 130 131 topView->AddChild(fAttributeControl); 132 133 fTypeMenu = new BPopUpMenu("type"); 134 BMenuItem* item = NULL; 135 for (int32 i = 0; kTypeMap[i].name != NULL; i++) { 136 BMessage* message = new BMessage(kMsgTypeChosen); 137 message->AddInt32("type", kTypeMap[i].type); 138 139 item = new BMenuItem(kTypeMap[i].name, message); 140 fTypeMenu->AddItem(item); 141 142 if (kTypeMap[i].type == fAttribute.Type()) 143 item->SetMarked(true); 144 } 145 146 rect.OffsetBy(0.0f, rect.Height() + 4.0f); 147 BMenuField* menuField = new BMenuField(rect, "types", 148 "Type:", fTypeMenu); 149 menuField->SetDivider(labelWidth); 150 menuField->SetAlignment(B_ALIGN_RIGHT); 151 menuField->GetPreferredSize(&width, &height); 152 menuField->ResizeTo(rect.Width(), height); 153 topView->AddChild(menuField); 154 155 rect.OffsetBy(0.0f, rect.Height() + 4.0f); 156 rect.bottom = rect.top + fAttributeControl->Bounds().Height() * 2.0f + 18.0f; 157 BBox* box = new BBox(rect, "", B_FOLLOW_LEFT_RIGHT); 158 topView->AddChild(box); 159 160 fVisibleCheckBox = new BCheckBox(rect, "visible", "Visible", 161 new BMessage(kMsgVisibilityChanged)); 162 fVisibleCheckBox->SetValue(fAttribute.Visible()); 163 fVisibleCheckBox->ResizeToPreferred(); 164 box->SetLabel(fVisibleCheckBox); 165 166 labelWidth -= 8.0f; 167 168 BMenu* menu = new BPopUpMenu("display as"); 169 for (int32 i = 0; kDisplayAsMap[i].name != NULL; i++) { 170 BMessage* message = new BMessage(kMsgDisplayAsChosen); 171 if (kDisplayAsMap[i].identifier != NULL) { 172 message->AddString("identifier", kDisplayAsMap[i].identifier); 173 for (int32 j = 0; kDisplayAsMap[i].supported[j]; j++) { 174 message->AddInt32("supports", kDisplayAsMap[i].supported[j]); 175 } 176 } 177 178 item = new BMenuItem(kDisplayAsMap[i].name, message); 179 menu->AddItem(item); 180 181 if (compare_display_as(kDisplayAsMap[i].identifier, fAttribute.DisplayAs())) 182 item->SetMarked(true); 183 } 184 185 rect.OffsetTo(8.0f, fVisibleCheckBox->Bounds().Height()); 186 rect.right -= 18.0f; 187 fDisplayAsMenuField = new BMenuField(rect, "display as", 188 "Display As:", menu); 189 fDisplayAsMenuField->SetDivider(labelWidth); 190 fDisplayAsMenuField->SetAlignment(B_ALIGN_RIGHT); 191 fDisplayAsMenuField->ResizeTo(rect.Width(), height); 192 box->AddChild(fDisplayAsMenuField); 193 194 fEditableCheckBox = new BCheckBox(rect, "editable", "Editable", 195 new BMessage(kMsgAttributeUpdated), B_FOLLOW_RIGHT); 196 fEditableCheckBox->SetValue(fAttribute.Editable()); 197 fEditableCheckBox->ResizeToPreferred(); 198 fEditableCheckBox->MoveTo(rect.right - fEditableCheckBox->Bounds().Width(), 199 rect.top + (fDisplayAsMenuField->Bounds().Height() 200 - fEditableCheckBox->Bounds().Height()) / 2.0f); 201 box->AddChild(fEditableCheckBox); 202 203 rect.OffsetBy(0.0f, menuField->Bounds().Height() + 4.0f); 204 rect.bottom = rect.top + fPublicNameControl->Bounds().Height(); 205 fSpecialControl = new BTextControl(rect, "special", "Special:", 206 display_as_parameter(fAttribute.DisplayAs()), NULL, 207 B_FOLLOW_LEFT_RIGHT); 208 fSpecialControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated)); 209 fSpecialControl->SetDivider(labelWidth); 210 fSpecialControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 211 fSpecialControl->SetEnabled(false); 212 box->AddChild(fSpecialControl); 213 214 char text[64]; 215 snprintf(text, sizeof(text), "%ld", fAttribute.Width()); 216 rect.OffsetBy(0.0f, fSpecialControl->Bounds().Height() + 4.0f); 217 fWidthControl = new BTextControl(rect, "width", "Width:", 218 text, NULL, B_FOLLOW_LEFT_RIGHT); 219 fWidthControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated)); 220 fWidthControl->SetDivider(labelWidth); 221 fWidthControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 222 223 // filter out invalid characters that can't be part of a width 224 textView = fWidthControl->TextView(); 225 for (int32 i = 0; i < 256; i++) { 226 if (!isdigit(i)) 227 textView->DisallowChar(i); 228 } 229 textView->SetMaxBytes(4); 230 231 box->AddChild(fWidthControl); 232 233 const struct alignment_map { 234 int32 alignment; 235 const char* name; 236 } kAlignmentMap[] = { 237 {B_ALIGN_LEFT, "Left"}, 238 {B_ALIGN_RIGHT, "Right"}, 239 {B_ALIGN_CENTER, "Center"}, 240 {0, NULL} 241 }; 242 243 menu = new BPopUpMenu("alignment"); 244 for (int32 i = 0; kAlignmentMap[i].name != NULL; i++) { 245 BMessage* message = new BMessage(kMsgAlignmentChosen); 246 message->AddInt32("alignment", kAlignmentMap[i].alignment); 247 248 item = new BMenuItem(kAlignmentMap[i].name, message); 249 menu->AddItem(item); 250 251 if (kAlignmentMap[i].alignment == fAttribute.Alignment()) 252 item->SetMarked(true); 253 } 254 255 rect.OffsetBy(0.0f, menuField->Bounds().Height() + 1.0f); 256 fAlignmentMenuField = new BMenuField(rect, "alignment", 257 "Alignment:", menu); 258 fAlignmentMenuField->SetDivider(labelWidth); 259 fAlignmentMenuField->SetAlignment(B_ALIGN_RIGHT); 260 fAlignmentMenuField->ResizeTo(rect.Width(), height); 261 box->AddChild(fAlignmentMenuField); 262 box->ResizeBy(0.0f, fAlignmentMenuField->Bounds().Height() * 2.0f 263 + fVisibleCheckBox->Bounds().Height()); 264 265 fAcceptButton = new BButton(rect, "add", item ? "Done" : "Add", 266 new BMessage(kMsgAccept), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 267 fAcceptButton->ResizeToPreferred(); 268 fAcceptButton->MoveTo(Bounds().Width() - 8.0f - fAcceptButton->Bounds().Width(), 269 Bounds().Height() - 8.0f - fAcceptButton->Bounds().Height()); 270 fAcceptButton->SetEnabled(false); 271 topView->AddChild(fAcceptButton); 272 273 BButton* button = new BButton(rect, "cancel", "Cancel", 274 new BMessage(B_QUIT_REQUESTED), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 275 button->ResizeToPreferred(); 276 button->MoveTo(fAcceptButton->Frame().left - 10.0f - button->Bounds().Width(), 277 fAcceptButton->Frame().top); 278 topView->AddChild(button); 279 280 ResizeTo(labelWidth * 4.0f + 24.0f, box->Frame().bottom 281 + button->Bounds().Height() + 20.0f); 282 SetSizeLimits(fEditableCheckBox->Bounds().Width() + button->Bounds().Width() 283 + fAcceptButton->Bounds().Width() + labelWidth + 24.0f, 284 32767.0f, Frame().Height(), Frame().Height()); 285 286 fAcceptButton->MakeDefault(true); 287 fPublicNameControl->MakeFocus(true); 288 289 target->PlaceSubWindow(this); 290 AddToSubset(target); 291 } 292 293 294 AttributeWindow::~AttributeWindow() 295 { 296 } 297 298 299 type_code 300 AttributeWindow::_CurrentType() const 301 { 302 type_code type = B_STRING_TYPE; 303 BMenuItem* item = fTypeMenu->FindMarked(); 304 if (item != NULL && item->Message() != NULL) { 305 int32 value; 306 if (item->Message()->FindInt32("type", &value) == B_OK) 307 type = value; 308 } 309 310 return type; 311 } 312 313 314 BMenuItem* 315 AttributeWindow::_DefaultDisplayAs() const 316 { 317 return fDisplayAsMenuField->Menu()->ItemAt(0); 318 } 319 320 321 void 322 AttributeWindow::_CheckDisplayAs() 323 { 324 // check display as suported types 325 326 type_code currentType = _CurrentType(); 327 328 BMenu* menu = fDisplayAsMenuField->Menu(); 329 for (int32 i = menu->CountItems(); i-- > 0;) { 330 BMenuItem* item = menu->ItemAt(i); 331 bool supported = item == _DefaultDisplayAs(); 332 // the default type is always supported 333 type_code type; 334 for (int32 j = 0; item->Message()->FindInt32("supports", 335 j, (int32*)&type) == B_OK; j++) { 336 if (type == currentType) { 337 supported = true; 338 break; 339 } 340 } 341 342 item->SetEnabled(supported); 343 if (item->IsMarked() && !supported) 344 menu->ItemAt(0)->SetMarked(true); 345 } 346 347 fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked()); 348 } 349 350 351 void 352 AttributeWindow::_CheckAcceptable() 353 { 354 bool enabled = fAttributeControl->Text() != NULL 355 && fAttributeControl->Text()[0] != '\0' 356 && fPublicNameControl->Text() != NULL 357 && fPublicNameControl->Text()[0] != '\0'; 358 359 if (enabled) { 360 // check for equality 361 AttributeItem* item = _NewItemFromCurrent(); 362 enabled = fAttribute != *item; 363 delete item; 364 } 365 366 // Update button 367 368 if (fAcceptButton->IsEnabled() != enabled) 369 fAcceptButton->SetEnabled(enabled); 370 } 371 372 373 AttributeItem* 374 AttributeWindow::_NewItemFromCurrent() 375 { 376 const char* newAttribute = fAttributeControl->Text(); 377 378 type_code type = _CurrentType(); 379 380 int32 alignment = B_ALIGN_LEFT; 381 BMenuItem* item = fAlignmentMenuField->Menu()->FindMarked(); 382 if (item != NULL && item->Message() != NULL) { 383 int32 value; 384 if (item->Message()->FindInt32("alignment", &value) == B_OK) 385 alignment = value; 386 } 387 388 int32 width = atoi(fWidthControl->Text()); 389 if (width < 0) 390 width = 0; 391 392 BString displayAs; 393 item = fDisplayAsMenuField->Menu()->FindMarked(); 394 if (item != NULL) { 395 const char* identifier; 396 if (item->Message()->FindString("identifier", &identifier) == B_OK) { 397 displayAs = identifier; 398 399 if (fSpecialControl->Text() && fSpecialControl->Text()[0]) { 400 displayAs += ": "; 401 displayAs += fSpecialControl->Text(); 402 } 403 } 404 } 405 406 return new AttributeItem(newAttribute, 407 fPublicNameControl->Text(), type, displayAs.String(), alignment, 408 width, fVisibleCheckBox->Value() == B_CONTROL_ON, 409 fEditableCheckBox->Value() == B_CONTROL_ON); 410 } 411 412 413 void 414 AttributeWindow::MessageReceived(BMessage* message) 415 { 416 switch (message->what) { 417 case kMsgAttributeUpdated: 418 case kMsgAlignmentChosen: 419 case kMsgTypeChosen: 420 _CheckDisplayAs(); 421 _CheckAcceptable(); 422 break; 423 424 case kMsgDisplayAsChosen: 425 fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked()); 426 _CheckAcceptable(); 427 break; 428 429 case kMsgVisibilityChanged: 430 { 431 bool enabled = fVisibleCheckBox->Value() != B_CONTROL_OFF; 432 433 fDisplayAsMenuField->SetEnabled(enabled); 434 fWidthControl->SetEnabled(enabled); 435 fAlignmentMenuField->SetEnabled(enabled); 436 fEditableCheckBox->SetEnabled(enabled); 437 438 _CheckDisplayAs(); 439 _CheckAcceptable(); 440 break; 441 } 442 443 case kMsgAccept: 444 { 445 BMessage attributes; 446 status_t status = fMimeType.GetAttrInfo(&attributes); 447 if (status == B_OK) { 448 // replace the entry, and remove any equivalent entries 449 BList list; 450 451 const char* newAttribute = fAttributeControl->Text(); 452 list.AddItem(_NewItemFromCurrent()); 453 454 const char* attribute; 455 for (int32 i = 0; attributes.FindString("attr:name", i, 456 &attribute) == B_OK; i++) { 457 if (!strcmp(fAttribute.Name(), attribute) 458 || !strcmp(newAttribute, attribute)) { 459 // remove this item 460 continue; 461 } 462 463 AttributeItem* item = create_attribute_item(attributes, i); 464 if (item != NULL) 465 list.AddItem(item); 466 } 467 468 list.SortItems(compare_attributes); 469 470 // Copy them to a new message (their memory is still part of the 471 // original BMessage) 472 BMessage newAttributes; 473 for (int32 i = 0; i < list.CountItems(); i++) { 474 AttributeItem* item = (AttributeItem*)list.ItemAt(i); 475 476 newAttributes.AddString("attr:name", item->Name()); 477 newAttributes.AddString("attr:public_name", item->PublicName()); 478 newAttributes.AddInt32("attr:type", (int32)item->Type()); 479 newAttributes.AddString("attr:display_as", item->DisplayAs()); 480 newAttributes.AddInt32("attr:alignment", item->Alignment()); 481 newAttributes.AddInt32("attr:width", item->Width()); 482 newAttributes.AddBool("attr:viewable", item->Visible()); 483 newAttributes.AddBool("attr:editable", item->Editable()); 484 485 delete item; 486 } 487 488 status = fMimeType.SetAttrInfo(&newAttributes); 489 } 490 491 if (status != B_OK) 492 error_alert("Could not change attributes", status); 493 494 PostMessage(B_QUIT_REQUESTED); 495 break; 496 } 497 498 default: 499 BWindow::MessageReceived(message); 500 break; 501 } 502 } 503 504 505 bool 506 AttributeWindow::QuitRequested() 507 { 508 return true; 509 } 510