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 <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 TR_CONTEXT 34 #define TR_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 = 3.0f; 106 //if (be_control_look) 107 //padding = be_control_look->DefaultItemSpacing(); 108 109 if (attributeItem != NULL) 110 fAttribute = *attributeItem; 111 112 fPublicNameControl = new BTextControl(B_TRANSLATE("Attribute name:"), 113 fAttribute.PublicName(), NULL); 114 fPublicNameControl->SetModificationMessage( 115 new BMessage(kMsgAttributeUpdated)); 116 fPublicNameControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 117 118 fAttributeControl = new BTextControl(B_TRANSLATE("Internal name:"), 119 fAttribute.Name(), NULL); 120 fAttributeControl->SetModificationMessage( 121 new BMessage(kMsgAttributeUpdated)); 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 fTypeMenu = new BPopUpMenu("type"); 132 BMenuItem* item = NULL; 133 for (int32 i = 0; kTypeMap[i].name != NULL; i++) { 134 BMessage* message = new BMessage(kMsgTypeChosen); 135 message->AddInt32("type", kTypeMap[i].type); 136 137 item = new BMenuItem(kTypeMap[i].name, message); 138 fTypeMenu->AddItem(item); 139 140 if (kTypeMap[i].type == fAttribute.Type()) 141 item->SetMarked(true); 142 } 143 144 BMenuField* typeMenuField = new BMenuField("types" , B_TRANSLATE("Type:"), 145 fTypeMenu); 146 typeMenuField->SetAlignment(B_ALIGN_RIGHT); 147 148 fVisibleCheckBox = new BCheckBox("visible", B_TRANSLATE("Visible"), 149 new BMessage(kMsgVisibilityChanged)); 150 fVisibleCheckBox->SetValue(fAttribute.Visible()); 151 152 BMenu* menu = new BPopUpMenu("display as"); 153 for (int32 i = 0; kDisplayAsMap[i].name != NULL; i++) { 154 BMessage* message = new BMessage(kMsgDisplayAsChosen); 155 if (kDisplayAsMap[i].identifier != NULL) { 156 message->AddString("identifier", kDisplayAsMap[i].identifier); 157 for (int32 j = 0; kDisplayAsMap[i].supported[j]; j++) { 158 message->AddInt32("supports", kDisplayAsMap[i].supported[j]); 159 } 160 } 161 162 item = new BMenuItem(kDisplayAsMap[i].name, message); 163 menu->AddItem(item); 164 165 if (compare_display_as(kDisplayAsMap[i].identifier, fAttribute.DisplayAs())) 166 item->SetMarked(true); 167 } 168 169 fDisplayAsMenuField = new BMenuField("display as", 170 B_TRANSLATE_COMMENT("Display as:", 171 "Tracker offers different display modes for attributes."), menu); 172 fDisplayAsMenuField->SetAlignment(B_ALIGN_RIGHT); 173 174 fEditableCheckBox = new BCheckBox("editable", 175 B_TRANSLATE_COMMENT("Editable", 176 "If Tracker allows to edit this attribute."), 177 new BMessage(kMsgAttributeUpdated)); 178 fEditableCheckBox->SetValue(fAttribute.Editable()); 179 180 fSpecialControl = new BTextControl(B_TRANSLATE("Special:"), 181 display_as_parameter(fAttribute.DisplayAs()), NULL); 182 fSpecialControl->SetModificationMessage( 183 new BMessage(kMsgAttributeUpdated)); 184 fSpecialControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 185 fSpecialControl->SetEnabled(false); 186 187 char text[64]; 188 snprintf(text, sizeof(text), "%ld", fAttribute.Width()); 189 fWidthControl = new BTextControl(B_TRANSLATE_COMMENT("Width:", 190 "Default column width in Tracker for this attribute."), 191 text, NULL); 192 fWidthControl->SetModificationMessage( 193 new BMessage(kMsgAttributeUpdated)); 194 fWidthControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); 195 196 // filter out invalid characters that can't be part of a width 197 textView = fWidthControl->TextView(); 198 for (int32 i = 0; i < 256; i++) { 199 if (!isdigit(i)) 200 textView->DisallowChar(i); 201 } 202 textView->SetMaxBytes(4); 203 204 const struct alignment_map { 205 int32 alignment; 206 const char* name; 207 } kAlignmentMap[] = { 208 {B_ALIGN_LEFT, B_TRANSLATE_COMMENT("Left", 209 "Attribute column alignment in Tracker")}, 210 {B_ALIGN_RIGHT, B_TRANSLATE_COMMENT("Right", 211 "Attribute column alignment in Tracker")}, 212 {B_ALIGN_CENTER, B_TRANSLATE_COMMENT("Center", 213 "Attribute column alignment in Tracker")}, 214 {0, NULL} 215 }; 216 217 menu = new BPopUpMenu("alignment"); 218 for (int32 i = 0; kAlignmentMap[i].name != NULL; i++) { 219 BMessage* message = new BMessage(kMsgAlignmentChosen); 220 message->AddInt32("alignment", kAlignmentMap[i].alignment); 221 222 item = new BMenuItem(kAlignmentMap[i].name, message); 223 menu->AddItem(item); 224 225 if (kAlignmentMap[i].alignment == fAttribute.Alignment()) 226 item->SetMarked(true); 227 } 228 229 fAlignmentMenuField = new BMenuField("alignment", 230 B_TRANSLATE("Alignment:"), menu); 231 fAlignmentMenuField->SetAlignment(B_ALIGN_RIGHT); 232 233 fAcceptButton = new BButton("add", item 234 ? B_TRANSLATE("Done") : B_TRANSLATE("Add"), 235 new BMessage(kMsgAccept)); 236 fAcceptButton->SetEnabled(false); 237 238 BButton* cancelButton = new BButton("cancel", B_TRANSLATE("Cancel"), 239 new BMessage(B_QUIT_REQUESTED)); 240 241 BBox* visibleBox; 242 SetLayout(new BGroupLayout(B_VERTICAL)); 243 AddChild(BGroupLayoutBuilder(B_VERTICAL, padding) 244 .SetInsets(padding, padding, padding, padding) 245 .Add(BGridLayoutBuilder(padding, padding) 246 .Add(fPublicNameControl->CreateLabelLayoutItem(), 0, 0) 247 .Add(fPublicNameControl->CreateTextViewLayoutItem(), 1, 0) 248 .Add(fAttributeControl->CreateLabelLayoutItem(), 0, 1) 249 .Add(fAttributeControl->CreateTextViewLayoutItem(), 1, 1) 250 .Add(typeMenuField->CreateLabelLayoutItem(), 0, 2) 251 .Add(typeMenuField->CreateMenuBarLayoutItem(), 1, 2) 252 ) 253 .Add(visibleBox = new BBox(B_FANCY_BORDER, 254 BGridLayoutBuilder(padding, padding) 255 .Add(fDisplayAsMenuField->CreateLabelLayoutItem(), 0, 0) 256 .Add(fDisplayAsMenuField->CreateMenuBarLayoutItem(), 1, 0) 257 .Add(fEditableCheckBox, 3, 0) 258 .Add(fSpecialControl->CreateLabelLayoutItem(), 0, 1) 259 .Add(fSpecialControl->CreateTextViewLayoutItem(), 1, 1, 3) 260 .Add(fWidthControl->CreateLabelLayoutItem(), 0, 2) 261 .Add(fWidthControl->CreateTextViewLayoutItem(), 1, 2, 3) 262 .Add(fAlignmentMenuField->CreateLabelLayoutItem(), 0, 3) 263 .Add(fAlignmentMenuField->CreateMenuBarLayoutItem(), 1, 3, 3) 264 .SetInsets(padding, padding, padding, padding) 265 )) 266 .Add(BGroupLayoutBuilder(B_HORIZONTAL, padding) 267 .Add(BSpaceLayoutItem::CreateGlue()) 268 .Add(BSpaceLayoutItem::CreateGlue()) 269 .Add(cancelButton) 270 .Add(fAcceptButton) 271 ) 272 ); 273 visibleBox->SetLabel(fVisibleCheckBox); 274 275 fAcceptButton->MakeDefault(true); 276 fPublicNameControl->MakeFocus(true); 277 278 target->PlaceSubWindow(this); 279 AddToSubset(target); 280 } 281 282 283 AttributeWindow::~AttributeWindow() 284 { 285 } 286 287 288 type_code 289 AttributeWindow::_CurrentType() const 290 { 291 type_code type = B_STRING_TYPE; 292 BMenuItem* item = fTypeMenu->FindMarked(); 293 if (item != NULL && item->Message() != NULL) { 294 int32 value; 295 if (item->Message()->FindInt32("type", &value) == B_OK) 296 type = value; 297 } 298 299 return type; 300 } 301 302 303 BMenuItem* 304 AttributeWindow::_DefaultDisplayAs() const 305 { 306 return fDisplayAsMenuField->Menu()->ItemAt(0); 307 } 308 309 310 void 311 AttributeWindow::_CheckDisplayAs() 312 { 313 // check display as suported types 314 315 type_code currentType = _CurrentType(); 316 317 BMenu* menu = fDisplayAsMenuField->Menu(); 318 for (int32 i = menu->CountItems(); i-- > 0;) { 319 BMenuItem* item = menu->ItemAt(i); 320 bool supported = item == _DefaultDisplayAs(); 321 // the default type is always supported 322 type_code type; 323 for (int32 j = 0; item->Message()->FindInt32("supports", 324 j, (int32*)&type) == B_OK; j++) { 325 if (type == currentType) { 326 supported = true; 327 break; 328 } 329 } 330 331 item->SetEnabled(supported); 332 if (item->IsMarked() && !supported) 333 menu->ItemAt(0)->SetMarked(true); 334 } 335 336 fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked()); 337 } 338 339 340 void 341 AttributeWindow::_CheckAcceptable() 342 { 343 bool enabled = fAttributeControl->Text() != NULL 344 && fAttributeControl->Text()[0] != '\0' 345 && fPublicNameControl->Text() != NULL 346 && fPublicNameControl->Text()[0] != '\0'; 347 348 if (enabled) { 349 // check for equality 350 AttributeItem* item = _NewItemFromCurrent(); 351 enabled = fAttribute != *item; 352 delete item; 353 } 354 355 // Update button 356 357 if (fAcceptButton->IsEnabled() != enabled) 358 fAcceptButton->SetEnabled(enabled); 359 } 360 361 362 AttributeItem* 363 AttributeWindow::_NewItemFromCurrent() 364 { 365 const char* newAttribute = fAttributeControl->Text(); 366 367 type_code type = _CurrentType(); 368 369 int32 alignment = B_ALIGN_LEFT; 370 BMenuItem* item = fAlignmentMenuField->Menu()->FindMarked(); 371 if (item != NULL && item->Message() != NULL) { 372 int32 value; 373 if (item->Message()->FindInt32("alignment", &value) == B_OK) 374 alignment = value; 375 } 376 377 int32 width = atoi(fWidthControl->Text()); 378 if (width < 0) 379 width = 0; 380 381 BString displayAs; 382 item = fDisplayAsMenuField->Menu()->FindMarked(); 383 if (item != NULL) { 384 const char* identifier; 385 if (item->Message()->FindString("identifier", &identifier) == B_OK) { 386 displayAs = identifier; 387 388 if (fSpecialControl->Text() && fSpecialControl->Text()[0]) { 389 displayAs += ": "; 390 displayAs += fSpecialControl->Text(); 391 } 392 } 393 } 394 395 return new AttributeItem(newAttribute, 396 fPublicNameControl->Text(), type, displayAs.String(), alignment, 397 width, fVisibleCheckBox->Value() == B_CONTROL_ON, 398 fEditableCheckBox->Value() == B_CONTROL_ON); 399 } 400 401 402 void 403 AttributeWindow::MessageReceived(BMessage* message) 404 { 405 switch (message->what) { 406 case kMsgAttributeUpdated: 407 case kMsgAlignmentChosen: 408 case kMsgTypeChosen: 409 _CheckDisplayAs(); 410 _CheckAcceptable(); 411 break; 412 413 case kMsgDisplayAsChosen: 414 fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked()); 415 _CheckAcceptable(); 416 break; 417 418 case kMsgVisibilityChanged: 419 { 420 bool enabled = fVisibleCheckBox->Value() != B_CONTROL_OFF; 421 422 fDisplayAsMenuField->SetEnabled(enabled); 423 fWidthControl->SetEnabled(enabled); 424 fAlignmentMenuField->SetEnabled(enabled); 425 fEditableCheckBox->SetEnabled(enabled); 426 427 _CheckDisplayAs(); 428 _CheckAcceptable(); 429 break; 430 } 431 432 case kMsgAccept: 433 { 434 BMessage attributes; 435 status_t status = fMimeType.GetAttrInfo(&attributes); 436 if (status == B_OK) { 437 // replace the entry, and remove any equivalent entries 438 BList list; 439 440 const char* newAttribute = fAttributeControl->Text(); 441 list.AddItem(_NewItemFromCurrent()); 442 443 const char* attribute; 444 for (int32 i = 0; attributes.FindString("attr:name", i, 445 &attribute) == B_OK; i++) { 446 if (!strcmp(fAttribute.Name(), attribute) 447 || !strcmp(newAttribute, attribute)) { 448 // remove this item 449 continue; 450 } 451 452 AttributeItem* item = create_attribute_item(attributes, i); 453 if (item != NULL) 454 list.AddItem(item); 455 } 456 457 list.SortItems(compare_attributes); 458 459 // Copy them to a new message (their memory is still part of the 460 // original BMessage) 461 BMessage newAttributes; 462 for (int32 i = 0; i < list.CountItems(); i++) { 463 AttributeItem* item = (AttributeItem*)list.ItemAt(i); 464 465 newAttributes.AddString("attr:name", item->Name()); 466 newAttributes.AddString("attr:public_name", item->PublicName()); 467 newAttributes.AddInt32("attr:type", (int32)item->Type()); 468 newAttributes.AddString("attr:display_as", item->DisplayAs()); 469 newAttributes.AddInt32("attr:alignment", item->Alignment()); 470 newAttributes.AddInt32("attr:width", item->Width()); 471 newAttributes.AddBool("attr:viewable", item->Visible()); 472 newAttributes.AddBool("attr:editable", item->Editable()); 473 474 delete item; 475 } 476 477 status = fMimeType.SetAttrInfo(&newAttributes); 478 } 479 480 if (status != B_OK) 481 error_alert(B_TRANSLATE("Could not change attributes"), 482 status); 483 484 PostMessage(B_QUIT_REQUESTED); 485 break; 486 } 487 488 default: 489 BWindow::MessageReceived(message); 490 break; 491 } 492 } 493 494 495 bool 496 AttributeWindow::QuitRequested() 497 { 498 return true; 499 } 500