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