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