1 /* 2 * Copyright 1999-2009 Jeremy Friesner 3 * Copyright 2009-2010 Haiku, Inc. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Jeremy Friesner 8 */ 9 10 #include "ShortcutsSpec.h" 11 12 #include <ctype.h> 13 #include <stdio.h> 14 15 #include <Beep.h> 16 #include <Catalog.h> 17 #include <Directory.h> 18 #include <Locale.h> 19 #include <NodeInfo.h> 20 #include <Path.h> 21 #include <Region.h> 22 #include <Window.h> 23 24 #include "ColumnListView.h" 25 26 #include "BitFieldTesters.h" 27 #include "Colors.h" 28 #include "CommandActuators.h" 29 #include "MetaKeyStateMap.h" 30 #include "ParseCommandLine.h" 31 32 33 #define CLASS "ShortcutsSpec : " 34 35 #undef B_TRANSLATION_CONTEXT 36 #define B_TRANSLATION_CONTEXT "ShortcutsSpec" 37 38 const float _height = 20.0f; 39 40 static MetaKeyStateMap sMetaMaps[ShortcutsSpec::NUM_META_COLUMNS]; 41 42 static bool sFontCached = false; 43 static BFont sViewFont; 44 static float sFontHeight; 45 static BBitmap* sActuatorBitmaps[2]; 46 47 const char* ShortcutsSpec::sShiftName; 48 const char* ShortcutsSpec::sControlName; 49 const char* ShortcutsSpec::sOptionName; 50 const char* ShortcutsSpec::sCommandName; 51 52 53 #define ICON_BITMAP_RECT BRect(0.0f, 0.0f, 15.0f, 15.0f) 54 #define ICON_BITMAP_SPACE B_RGBA32 55 56 57 // Returns the (pos)'th char in the string, or '\0' if (pos) if off the end of 58 // the string 59 static char 60 GetLetterAt(const char* str, int pos) 61 { 62 for (int i = 0; i < pos; i++) { 63 if (str[i] == '\0') 64 return '\0'; 65 } 66 return str[pos]; 67 } 68 69 70 // Setup the states in a standard manner for a pair of meta-keys. 71 static void 72 SetupStandardMap(MetaKeyStateMap& map, const char* name, uint32 both, 73 uint32 left, uint32 right) 74 { 75 map.SetInfo(name); 76 77 // In this state, neither key may be pressed. 78 map.AddState("(None)", new HasBitsFieldTester(0, both)); 79 80 // Here, either may be pressed. (Remember both is NOT a 2-bit chord, it's 81 // another bit entirely) 82 map.AddState("Either", new HasBitsFieldTester(both)); 83 84 // Here, only the left may be pressed 85 map.AddState("Left", new HasBitsFieldTester(left, right)); 86 87 // Here, only the right may be pressed 88 map.AddState("Right", new HasBitsFieldTester(right, left)); 89 90 // Here, both must be pressed. 91 map.AddState("Both", new HasBitsFieldTester(left | right)); 92 } 93 94 95 MetaKeyStateMap& 96 GetNthKeyMap(int which) 97 { 98 return sMetaMaps[which]; 99 } 100 101 102 static BBitmap* 103 MakeActuatorBitmap(bool lit) 104 { 105 BBitmap* map = new BBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE, true); 106 const rgb_color yellow = {255, 255, 0}; 107 const rgb_color red = {200, 200, 200}; 108 const rgb_color black = {0, 0, 0}; 109 const BPoint points[10] = { 110 BPoint(8, 0), BPoint(9.8, 5.8), BPoint(16, 5.8), 111 BPoint(11, 9.0), BPoint(13, 16), BPoint(8, 11), 112 BPoint(3, 16), BPoint(5, 9.0), BPoint(0, 5.8), 113 BPoint(6.2, 5.8) }; 114 115 BView* view = new BView(BRect(0, 0, 16, 16), NULL, B_FOLLOW_ALL_SIDES, 0L); 116 map->AddChild(view); 117 map->Lock(); 118 view->SetHighColor(B_TRANSPARENT_32_BIT); 119 view->FillRect(ICON_BITMAP_RECT); 120 view->SetHighColor(lit ? yellow : red); 121 view->FillPolygon(points, 10); 122 view->SetHighColor(black); 123 view->StrokePolygon(points, 10); 124 map->Unlock(); 125 map->RemoveChild(view); 126 delete view; 127 return map; 128 } 129 130 131 /*static*/ void 132 ShortcutsSpec::InitializeMetaMaps() 133 { 134 static bool metaMapsInitialized = false; 135 if (metaMapsInitialized) 136 return; 137 metaMapsInitialized = true; 138 139 _InitModifierNames(); 140 141 SetupStandardMap(sMetaMaps[ShortcutsSpec::SHIFT_COLUMN_INDEX], sShiftName, 142 B_SHIFT_KEY, B_LEFT_SHIFT_KEY, B_RIGHT_SHIFT_KEY); 143 144 SetupStandardMap(sMetaMaps[ShortcutsSpec::CONTROL_COLUMN_INDEX], 145 sControlName, B_CONTROL_KEY, B_LEFT_CONTROL_KEY, B_RIGHT_CONTROL_KEY); 146 147 SetupStandardMap(sMetaMaps[ShortcutsSpec::COMMAND_COLUMN_INDEX], 148 sCommandName, B_COMMAND_KEY, B_LEFT_COMMAND_KEY, B_RIGHT_COMMAND_KEY); 149 150 SetupStandardMap(sMetaMaps[ShortcutsSpec::OPTION_COLUMN_INDEX], sOptionName 151 , B_OPTION_KEY, B_LEFT_OPTION_KEY, B_RIGHT_OPTION_KEY); 152 153 sActuatorBitmaps[0] = MakeActuatorBitmap(false); 154 sActuatorBitmaps[1] = MakeActuatorBitmap(true); 155 } 156 157 158 ShortcutsSpec::ShortcutsSpec(const char* cmd) 159 : 160 CLVListItem(0, false, false, _height), 161 fCommand(NULL), 162 fTextOffset(0), 163 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), 164 fLastBitmapName(NULL), 165 fBitmapValid(false), 166 fKey(0), 167 fCursorPtsValid(false) 168 { 169 for (int i = 0; i < NUM_META_COLUMNS; i++) 170 fMetaCellStateIndex[i] = 0; 171 SetCommand(cmd); 172 } 173 174 175 ShortcutsSpec::ShortcutsSpec(const ShortcutsSpec& from) 176 : 177 CLVListItem(0, false, false, _height), 178 fCommand(NULL), 179 fTextOffset(from.fTextOffset), 180 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), 181 fLastBitmapName(NULL), 182 fBitmapValid(false), 183 fKey(from.fKey), 184 fCursorPtsValid(false) 185 { 186 for (int i = 0; i < NUM_META_COLUMNS; i++) 187 fMetaCellStateIndex[i] = from.fMetaCellStateIndex[i]; 188 189 SetCommand(from.fCommand); 190 SetSelectedColumn(from.GetSelectedColumn()); 191 } 192 193 194 ShortcutsSpec::ShortcutsSpec(BMessage* from) 195 : 196 CLVListItem(0, false, false, _height), 197 fCommand(NULL), 198 fTextOffset(0), 199 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE), 200 fLastBitmapName(NULL), 201 fBitmapValid(false), 202 fCursorPtsValid(false) 203 { 204 const char* temp; 205 if (from->FindString("command", &temp) != B_NO_ERROR) { 206 printf(CLASS); 207 printf(" Error, no command string in archive BMessage!\n"); 208 temp = ""; 209 } 210 211 SetCommand(temp); 212 213 if (from->FindInt32("key", (int32*) &fKey) != B_NO_ERROR) { 214 printf(CLASS); 215 printf(" Error, no key int32 in archive BMessage!\n"); 216 } 217 218 for (int i = 0; i < NUM_META_COLUMNS; i++) 219 if (from->FindInt32("mcidx", i, (int32*)&fMetaCellStateIndex[i]) 220 != B_NO_ERROR) { 221 printf(CLASS); 222 printf(" Error, no modifiers int32 in archive BMessage!\n"); 223 } 224 } 225 226 227 void 228 ShortcutsSpec::SetCommand(const char* command) 229 { 230 delete[] fCommand; 231 // out with the old (if any)... 232 fCommandLen = strlen(command) + 1; 233 fCommandNul = fCommandLen - 1; 234 fCommand = new char[fCommandLen]; 235 strcpy(fCommand, command); 236 _UpdateIconBitmap(); 237 } 238 239 240 const char* 241 ShortcutsSpec::GetColumnName(int i) 242 { 243 return sMetaMaps[i].GetName(); 244 } 245 246 247 status_t 248 ShortcutsSpec::Archive(BMessage* into, bool deep) const 249 { 250 status_t ret = BArchivable::Archive(into, deep); 251 if (ret != B_NO_ERROR) 252 return ret; 253 254 into->AddString("class", "ShortcutsSpec"); 255 256 // These fields are for our prefs panel's benefit only 257 into->AddString("command", fCommand); 258 into->AddInt32("key", fKey); 259 260 // Assemble a BitFieldTester for the input_server add-on to use... 261 MinMatchFieldTester test(NUM_META_COLUMNS, false); 262 for (int i = 0; i < NUM_META_COLUMNS; i++) { 263 // for easy parsing by prefs applet on load-in 264 into->AddInt32("mcidx", fMetaCellStateIndex[i]); 265 test.AddSlave(sMetaMaps[i].GetNthStateTester(fMetaCellStateIndex[i])); 266 } 267 268 BMessage testerMsg; 269 ret = test.Archive(&testerMsg); 270 if (ret != B_NO_ERROR) 271 return ret; 272 273 into->AddMessage("modtester", &testerMsg); 274 275 // And also create a CommandActuator for the input_server add-on to execute 276 CommandActuator* act = CreateCommandActuator(fCommand); 277 BMessage actMsg; 278 ret = act->Archive(&actMsg); 279 if (ret != B_NO_ERROR) 280 return ret; 281 delete act; 282 283 into->AddMessage("act", &actMsg); 284 return ret; 285 } 286 287 288 static bool IsValidActuatorName(const char* c); 289 static bool 290 IsValidActuatorName(const char* c) 291 { 292 return (strcmp(c, B_TRANSLATE("InsertString")) == 0 293 || strcmp(c, B_TRANSLATE("MoveMouse")) == 0 294 || strcmp(c, B_TRANSLATE("MoveMouseTo")) == 0 295 || strcmp(c, B_TRANSLATE("MouseButton")) == 0 296 || strcmp(c, B_TRANSLATE("LaunchHandler")) == 0 297 || strcmp(c, B_TRANSLATE("Multi")) == 0 298 || strcmp(c, B_TRANSLATE("MouseDown")) == 0 299 || strcmp(c, B_TRANSLATE("MouseUp")) == 0 300 || strcmp(c, B_TRANSLATE("SendMessage")) == 0 301 || strcmp(c, B_TRANSLATE("Beep")) == 0); 302 } 303 304 305 BArchivable* 306 ShortcutsSpec::Instantiate(BMessage* from) 307 { 308 bool validateOK = false; 309 if (validate_instantiation(from, "ShortcutsSpec")) 310 validateOK = true; 311 else // test the old one. 312 if (validate_instantiation(from, "SpicyKeysSpec")) 313 validateOK = true; 314 315 if (!validateOK) 316 return NULL; 317 318 return new ShortcutsSpec(from); 319 } 320 321 322 ShortcutsSpec::~ShortcutsSpec() 323 { 324 delete[] fCommand; 325 delete[] fLastBitmapName; 326 } 327 328 329 void 330 ShortcutsSpec::_CacheViewFont(BView* owner) 331 { 332 if (sFontCached == false) { 333 sFontCached = true; 334 owner->GetFont(&sViewFont); 335 font_height fh; 336 sViewFont.GetHeight(&fh); 337 sFontHeight = fh.ascent - fh.descent; 338 } 339 } 340 341 342 void 343 ShortcutsSpec::DrawItemColumn(BView* owner, BRect item_column_rect, 344 int32 column_index, bool columnSelected, bool complete) 345 { 346 const float STRING_COLUMN_LEFT_MARGIN = 25.0f; 347 // 16 for the icon, +9 empty 348 349 rgb_color color; 350 bool selected = IsSelected(); 351 if (selected) 352 color = columnSelected ? BeBackgroundGrey : BeListSelectGrey; 353 else 354 color = BeInactiveControlGrey; 355 owner->SetLowColor(color); 356 owner->SetDrawingMode(B_OP_COPY); 357 owner->SetHighColor(color); 358 owner->FillRect(item_column_rect); 359 360 const char* text = GetCellText(column_index); 361 362 if (text == NULL) 363 return; 364 365 _CacheViewFont(owner); 366 // Ensure that sViewFont is configured before using it to calculate 367 // widths. The lack of this call was causing the initial display of 368 // columns to be incorrect, with a "jump" as all the columns correct 369 // themselves upon the first column resize. 370 371 float textWidth = sViewFont.StringWidth(text); 372 BPoint point; 373 rgb_color lowColor = color; 374 375 if (column_index == STRING_COLUMN_INDEX) { 376 // left justified 377 point.Set(item_column_rect.left + STRING_COLUMN_LEFT_MARGIN, 378 item_column_rect.top + fTextOffset); 379 380 item_column_rect.left = point.x; 381 // keep text from drawing into icon area 382 383 // scroll if too wide 384 float rectWidth = item_column_rect.Width() - STRING_COLUMN_LEFT_MARGIN; 385 float extra = textWidth - rectWidth; 386 if (extra > 0.0f) 387 point.x -= extra; 388 } else { 389 if ((column_index < NUM_META_COLUMNS) && (text[0] == '(')) 390 return; // don't draw for this ... 391 392 if ((column_index <= NUM_META_COLUMNS) && (text[0] == '\0')) 393 return; // don't draw for this ... 394 395 // centered 396 point.Set((item_column_rect.left + item_column_rect.right) / 2.0, 397 item_column_rect.top + fTextOffset); 398 _CacheViewFont(owner); 399 point.x -= textWidth / 2.0f; 400 } 401 402 BRegion Region; 403 Region.Include(item_column_rect); 404 owner->ConstrainClippingRegion(&Region); 405 if (column_index != STRING_COLUMN_INDEX) { 406 const float KEY_MARGIN = 3.0f; 407 const float CORNER_RADIUS = 3.0f; 408 _CacheViewFont(owner); 409 410 // How about I draw a nice "key" background for this one? 411 BRect textRect(point.x - KEY_MARGIN, (point.y - sFontHeight) - KEY_MARGIN, 412 point.x + textWidth + KEY_MARGIN - 2.0f, point.y + KEY_MARGIN); 413 414 if (column_index == KEY_COLUMN_INDEX) 415 lowColor = ReallyLightPurple; 416 else 417 lowColor = LightYellow; 418 419 owner->SetHighColor(lowColor); 420 owner->FillRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS); 421 owner->SetHighColor(Black); 422 owner->StrokeRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS); 423 } 424 425 owner->SetHighColor(Black); 426 owner->SetLowColor(lowColor); 427 owner->DrawString(text, point); 428 // with a cursor at the end if highlighted 429 if (column_index == STRING_COLUMN_INDEX) { 430 // Draw cursor 431 if ((columnSelected) && (selected)) { 432 point.x += textWidth; 433 point.y += (fTextOffset / 4.0f); 434 435 BPoint pt2 = point; 436 pt2.y -= fTextOffset; 437 owner->StrokeLine(point, pt2); 438 439 fCursorPt1 = point; 440 fCursorPt2 = pt2; 441 fCursorPtsValid = true; 442 } 443 444 BRegion bitmapRegion; 445 item_column_rect.left -= (STRING_COLUMN_LEFT_MARGIN - 4.0f); 446 item_column_rect.right = item_column_rect.left + 16.0f; 447 item_column_rect.top += 3.0f; 448 item_column_rect.bottom = item_column_rect.top + 16.0f; 449 450 bitmapRegion.Include(item_column_rect); 451 owner->ConstrainClippingRegion(&bitmapRegion); 452 owner->SetDrawingMode(B_OP_ALPHA); 453 454 if ((fCommand != NULL) && (fCommand[0] == '*')) 455 owner->DrawBitmap(sActuatorBitmaps[fBitmapValid ? 1 : 0], 456 ICON_BITMAP_RECT, item_column_rect); 457 else 458 // Draw icon, if any 459 if (fBitmapValid) 460 owner->DrawBitmap(&fBitmap, ICON_BITMAP_RECT, 461 item_column_rect); 462 } 463 464 owner->SetDrawingMode(B_OP_COPY); 465 owner->ConstrainClippingRegion(NULL); 466 } 467 468 469 void 470 ShortcutsSpec::Update(BView* owner, const BFont* font) 471 { 472 CLVListItem::Update(owner, font); 473 font_height FontAttributes; 474 be_plain_font->GetHeight(&FontAttributes); 475 float fontHeight = ceil(FontAttributes.ascent) + 476 ceil(FontAttributes.descent); 477 fTextOffset = ceil(FontAttributes.ascent) + (Height() - fontHeight) / 2.0; 478 } 479 480 481 const char* 482 ShortcutsSpec::GetCellText(int whichColumn) const 483 { 484 const char* temp = ""; // default 485 switch(whichColumn) { 486 case KEY_COLUMN_INDEX: 487 { 488 if ((fKey > 0) && (fKey <= 0xFF)) { 489 temp = GetKeyName(fKey); 490 if (temp == NULL) 491 temp = ""; 492 } else if (fKey > 0xFF) { 493 sprintf(fScratch, "#%" B_PRIx32, fKey); 494 return fScratch; 495 } 496 break; 497 } 498 499 case STRING_COLUMN_INDEX: 500 temp = fCommand; 501 break; 502 503 default: 504 if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) 505 temp = sMetaMaps[whichColumn].GetNthStateDesc( 506 fMetaCellStateIndex[whichColumn]); 507 break; 508 } 509 return temp; 510 } 511 512 513 bool 514 ShortcutsSpec::ProcessColumnMouseClick(int whichColumn) 515 { 516 if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) { 517 // same as hitting space for these columns: cycle entry 518 const char temp = B_SPACE; 519 520 // 3rd arg isn't correct but it isn't read for this case anyway 521 return ProcessColumnKeyStroke(whichColumn, &temp, 0); 522 } 523 return false; 524 } 525 526 527 bool 528 ShortcutsSpec::ProcessColumnTextString(int whichColumn, const char* string) 529 { 530 switch(whichColumn) { 531 case STRING_COLUMN_INDEX: 532 SetCommand(string); 533 return true; 534 break; 535 536 case KEY_COLUMN_INDEX: 537 { 538 fKey = FindKeyCode(string); 539 return true; 540 break; 541 } 542 543 default: 544 return ProcessColumnKeyStroke(whichColumn, string, 0); 545 } 546 } 547 548 549 bool 550 ShortcutsSpec::_AttemptTabCompletion() 551 { 552 bool result = false; 553 554 int32 argc; 555 char** argv = ParseArgvFromString(fCommand, argc); 556 if (argc > 0) { 557 // Try to complete the path partially expressed in the last argument! 558 char* arg = argv[argc - 1]; 559 char* fileFragment = strrchr(arg, '/'); 560 if (fileFragment != NULL) { 561 const char* directoryName = (fileFragment == arg) ? "/" : arg; 562 *fileFragment = '\0'; 563 fileFragment++; 564 int fragmentLength = strlen(fileFragment); 565 566 BDirectory dir(directoryName); 567 if (dir.InitCheck() == B_NO_ERROR) { 568 BEntry nextEnt; 569 BPath nextPath; 570 BList matchList; 571 int maxEntryLen = 0; 572 573 // Read in all the files in the directory whose names start 574 // with our fragment. 575 while (dir.GetNextEntry(&nextEnt) == B_NO_ERROR) { 576 if (nextEnt.GetPath(&nextPath) == B_NO_ERROR) { 577 char* filePath = strrchr(nextPath.Path(), '/') + 1; 578 if (strncmp(filePath, fileFragment, fragmentLength) == 0) { 579 int len = strlen(filePath); 580 if (len > maxEntryLen) 581 maxEntryLen = len; 582 char* newStr = new char[len + 1]; 583 strcpy(newStr, filePath); 584 matchList.AddItem(newStr); 585 } 586 } 587 } 588 589 // Now slowly extend our keyword to its full length, counting 590 // numbers of matches at each step. If the match list length 591 // is 1, we can use that whole entry. If it's greater than one, 592 // we can use just the match length. 593 int matchLen = matchList.CountItems(); 594 if (matchLen > 0) { 595 int i; 596 BString result(fileFragment); 597 for (i = fragmentLength; i < maxEntryLen; i++) { 598 // See if all the matching entries have the same letter 599 // in the next position... if so, we can go farther. 600 char commonLetter = '\0'; 601 for (int j = 0; j < matchLen; j++) { 602 char nextLetter = GetLetterAt( 603 (char*)matchList.ItemAt(j), i); 604 if (commonLetter == '\0') 605 commonLetter = nextLetter; 606 607 if ((commonLetter != '\0') 608 && (commonLetter != nextLetter)) { 609 commonLetter = '\0';// failed; 610 beep(); 611 break; 612 } 613 } 614 if (commonLetter == '\0') 615 break; 616 else 617 result.Append(commonLetter, 1); 618 } 619 620 // free all the strings we allocated 621 for (int k = 0; k < matchLen; k++) 622 delete [] ((char*)matchList.ItemAt(k)); 623 624 DoStandardEscapes(result); 625 626 BString wholeLine; 627 for (int l = 0; l < argc - 1; l++) { 628 wholeLine += argv[l]; 629 wholeLine += " "; 630 } 631 632 BString file(directoryName); 633 DoStandardEscapes(file); 634 635 if (directoryName[strlen(directoryName) - 1] != '/') 636 file += "/"; 637 638 file += result; 639 640 // Remove any trailing slash... 641 const char* fileStr = file.String(); 642 if (fileStr[strlen(fileStr) - 1] == '/') 643 file.RemoveLast("/"); 644 645 // and re-append it iff the file is a dir. 646 BDirectory testFileAsDir(file.String()); 647 if ((strcmp(file.String(), "/") != 0) 648 && (testFileAsDir.InitCheck() == B_NO_ERROR)) 649 file.Append("/"); 650 651 wholeLine += file; 652 653 SetCommand(wholeLine.String()); 654 result = true; 655 } 656 } 657 *(fileFragment - 1) = '/'; 658 } 659 } 660 FreeArgv(argv); 661 662 return result; 663 } 664 665 666 bool 667 ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn, const char* bytes, 668 int32 key) 669 { 670 bool result = false; 671 672 switch(whichColumn) { 673 case KEY_COLUMN_INDEX: 674 if (key > -1) { 675 if ((int32)fKey != key) { 676 fKey = key; 677 result = true; 678 } 679 } 680 break; 681 682 case STRING_COLUMN_INDEX: 683 { 684 switch(bytes[0]) { 685 case B_BACKSPACE: 686 case B_DELETE: 687 if (fCommandNul > 0) { 688 // trim a char off the string 689 fCommand[fCommandNul - 1] = '\0'; 690 fCommandNul--; // note new nul position 691 result = true; 692 _UpdateIconBitmap(); 693 } 694 break; 695 696 case B_TAB: 697 if (_AttemptTabCompletion()) { 698 _UpdateIconBitmap(); 699 result = true; 700 } else 701 beep(); 702 break; 703 704 default: 705 { 706 uint32 newCharLen = strlen(bytes); 707 if ((newCharLen > 0) && (bytes[0] >= ' ')) { 708 bool reAllocString = false; 709 // Make sure we have enough room in our command string 710 // to add these chars... 711 while (fCommandLen - fCommandNul <= newCharLen) { 712 reAllocString = true; 713 // enough for a while... 714 fCommandLen = (fCommandLen + 10) * 2; 715 } 716 717 if (reAllocString) { 718 char* temp = new char[fCommandLen]; 719 strcpy(temp, fCommand); 720 delete [] fCommand; 721 fCommand = temp; 722 // fCommandNul is still valid since it's an offset 723 // and the string length is the same for now 724 } 725 726 // Here we should be guaranteed enough room. 727 strncat(fCommand, bytes, fCommandLen); 728 fCommandNul += newCharLen; 729 result = true; 730 _UpdateIconBitmap(); 731 } 732 } 733 } 734 break; 735 } 736 737 default: 738 if (whichColumn < 0 || whichColumn >= NUM_META_COLUMNS) 739 break; 740 741 MetaKeyStateMap * map = &sMetaMaps[whichColumn]; 742 int curState = fMetaCellStateIndex[whichColumn]; 743 int origState = curState; 744 int numStates = map->GetNumStates(); 745 746 switch(bytes[0]) { 747 case B_RETURN: 748 // cycle to the previous state 749 curState = (curState + numStates - 1) % numStates; 750 break; 751 752 case B_SPACE: 753 // cycle to the next state 754 curState = (curState + 1) % numStates; 755 break; 756 757 default: 758 { 759 // Go to the state starting with the given letter, if 760 // any 761 char letter = bytes[0]; 762 if (islower(letter)) 763 letter = toupper(letter); // convert to upper case 764 765 if ((letter == B_BACKSPACE) || (letter == B_DELETE)) 766 letter = '('; 767 // so space bar will blank out an entry 768 769 for (int i = 0; i < numStates; i++) { 770 const char* desc = map->GetNthStateDesc(i); 771 772 if (desc) { 773 if (desc[0] == letter) { 774 curState = i; 775 break; 776 } 777 } else { 778 puts(B_TRANSLATE( 779 "Error, NULL state description?")); 780 } 781 } 782 } 783 } 784 fMetaCellStateIndex[whichColumn] = curState; 785 786 if (curState != origState) 787 result = true; 788 } 789 790 return result; 791 } 792 793 794 int 795 ShortcutsSpec::CLVListItemCompare(const CLVListItem* firstItem, 796 const CLVListItem* secondItem, int32 keyColumn) 797 { 798 ShortcutsSpec* left = (ShortcutsSpec*) firstItem; 799 ShortcutsSpec* right = (ShortcutsSpec*) secondItem; 800 801 int result = strcmp(left->GetCellText(keyColumn), 802 right->GetCellText(keyColumn)); 803 804 return result > 0 ? 1 : (result == 0 ? 0 : -1); 805 } 806 807 808 void 809 ShortcutsSpec::Pulse(BView* owner) 810 { 811 if ((fCursorPtsValid)&&(owner->Window()->IsActive())) { 812 rgb_color prevColor = owner->HighColor(); 813 rgb_color backgroundColor = (GetSelectedColumn() == 814 STRING_COLUMN_INDEX) ? BeBackgroundGrey : BeListSelectGrey; 815 rgb_color barColor = ((GetSelectedColumn() == STRING_COLUMN_INDEX) 816 && ((system_time() % 1000000) > 500000)) ? Black : backgroundColor; 817 owner->SetHighColor(barColor); 818 owner->StrokeLine(fCursorPt1, fCursorPt2); 819 owner->SetHighColor(prevColor); 820 } 821 } 822 823 824 void 825 ShortcutsSpec::_UpdateIconBitmap() 826 { 827 BString firstWord = ParseArgvZeroFromString(fCommand); 828 829 // we only need to change if the first word has changed... 830 if (fLastBitmapName == NULL || firstWord.Length() == 0 831 || firstWord.Compare(fLastBitmapName)) { 832 if (firstWord.ByteAt(0) == '*') 833 fBitmapValid = IsValidActuatorName(&firstWord.String()[1]); 834 else { 835 fBitmapValid = false; 836 // default until we prove otherwise 837 838 if (firstWord.Length() > 0) { 839 delete [] fLastBitmapName; 840 fLastBitmapName = new char[firstWord.Length() + 1]; 841 strcpy(fLastBitmapName, firstWord.String()); 842 843 BEntry progEntry(fLastBitmapName, true); 844 if ((progEntry.InitCheck() == B_NO_ERROR) 845 && (progEntry.Exists())) { 846 BNode progNode(&progEntry); 847 if (progNode.InitCheck() == B_NO_ERROR) { 848 BNodeInfo progNodeInfo(&progNode); 849 if ((progNodeInfo.InitCheck() == B_NO_ERROR) 850 && (progNodeInfo.GetTrackerIcon(&fBitmap, B_MINI_ICON) 851 == B_NO_ERROR)) { 852 fBitmapValid = fBitmap.IsValid(); 853 } 854 } 855 } 856 } 857 } 858 } 859 } 860 861 862 /*static*/ void 863 ShortcutsSpec::_InitModifierNames() 864 { 865 sShiftName = B_TRANSLATE_COMMENT("Shift", 866 "Name for modifier on keyboard"); 867 sControlName = B_TRANSLATE_COMMENT("Control", 868 "Name for modifier on keyboard"); 869 // TODO: Wrapping in __INTEL__ define probably won't work to extract catkeys? 870 #if __INTEL__ 871 sOptionName = B_TRANSLATE_COMMENT("Option", 872 "Name for modifier on keyboard"); 873 sCommandName = B_TRANSLATE_COMMENT("Alt", 874 "Name for modifier on keyboard"); 875 #else 876 sOptionName = B_TRANSLATE_COMMENT("Option", 877 "Name for modifier on keyboard"); 878 sCommandName = B_TRANSLATE_COMMENT("Command", 879 "Name for modifier on keyboard"); 880 #endif 881 } 882