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