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