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