1 /* 2 * Copyright 2004-2009 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Sandor Vroemisse 7 * Jérôme Duval 8 * Alexandre Deckner, alex@zappotek.com 9 * Axel Dörfler, axeld@pinc-software.de. 10 */ 11 12 #include "KeymapWindow.h" 13 14 #include <string.h> 15 16 #include <Alert.h> 17 #include <Button.h> 18 #include <Directory.h> 19 #include <FindDirectory.h> 20 #include <GroupLayoutBuilder.h> 21 #include <ListView.h> 22 #include <MenuBar.h> 23 #include <MenuItem.h> 24 #include <Path.h> 25 #include <Screen.h> 26 #include <ScrollView.h> 27 #include <StringView.h> 28 #include <TextControl.h> 29 30 #include "KeyboardLayoutView.h" 31 #include "KeymapApplication.h" 32 #include "KeymapListItem.h" 33 #include "KeymapMessageFilter.h" 34 35 36 static const uint32 kMsgMenuFileOpen = 'mMFO'; 37 static const uint32 kMsgMenuFileSave = 'mMFS'; 38 static const uint32 kMsgMenuFileSaveAs = 'mMFA'; 39 40 static const uint32 kChangeKeyboardLayout = 'cKyL'; 41 42 static const uint32 kMsgMenuFontChanged = 'mMFC'; 43 44 static const uint32 kMsgSystemMapSelected = 'SmST'; 45 static const uint32 kMsgUserMapSelected = 'UmST'; 46 47 static const uint32 kMsgUseKeymap = 'UkyM'; 48 static const uint32 kMsgRevertKeymap = 'Rvrt'; 49 static const uint32 kMsgKeymapUpdated = 'upkM'; 50 51 52 KeymapWindow::KeymapWindow() 53 : BWindow(BRect(80, 50, 880, 380), "Keymap", B_TITLED_WINDOW, 54 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 55 fFirstTime(true) 56 { 57 SetLayout(new BGroupLayout(B_VERTICAL)); 58 59 fKeyboardLayoutView = new KeyboardLayoutView("layout"); 60 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 61 62 fTextControl = new BTextControl("Sample and Clipboard:", "", NULL); 63 64 fUseButton = new BButton("useButton", "Use", new BMessage(kMsgUseKeymap)); 65 fRevertButton = new BButton("revertButton", "Revert", 66 new BMessage(kMsgRevertKeymap)); 67 68 // controls pane 69 AddChild(BGroupLayoutBuilder(B_VERTICAL) 70 .Add(_CreateMenu()) 71 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10) 72 .Add(_CreateMapLists(), 0.25) 73 .Add(BGroupLayoutBuilder(B_VERTICAL, 10) 74 .Add(fKeyboardLayoutView) 75 //.Add(new BStringView("text label", "Sample and Clipboard:")) 76 .Add(fTextControl) 77 .AddGlue(0.0) 78 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10) 79 .AddGlue(0.0) 80 .Add(fUseButton) 81 .Add(fRevertButton))) 82 .SetInsets(10, 10, 10, 10))); 83 84 fKeyboardLayoutView->SetTarget(fTextControl->TextView()); 85 fTextControl->MakeFocus(); 86 fTextControl->TextView()->AddFilter(new KeymapMessageFilter( 87 B_PROGRAMMED_DELIVERY, B_ANY_SOURCE, &fCurrentMap)); 88 89 _UpdateButtons(); 90 91 fCurrentMap.SetTarget(this, new BMessage(kMsgKeymapUpdated)); 92 93 // Make sure the user keymap directory exists 94 BPath path; 95 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 96 path.Append("Keymap"); 97 98 entry_ref ref; 99 get_ref_for_path(path.Path(), &ref); 100 101 BDirectory userKeymapsDir(&ref); 102 if (userKeymapsDir.InitCheck() != B_OK) { 103 create_directory(path.Path(), S_IRWXU | S_IRWXG | S_IRWXO); 104 } 105 106 BMessenger messenger(this); 107 fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, &ref, 108 B_FILE_NODE, false, NULL); 109 fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, &ref, 110 B_FILE_NODE, false, NULL); 111 112 BScreen screen(this); 113 114 float width = Frame().Width(); 115 float height = Frame().Height(); 116 117 // Make sure we can fit on screen 118 if (screen.Frame().Width() < Frame().Width()) 119 width = screen.Frame().Width(); 120 if (screen.Frame().Height() < Frame().Height()) 121 height = screen.Frame().Height(); 122 123 // See if we can use a larger default size 124 if (screen.Frame().Width() > 1200) { 125 width = 900; 126 height = 400; 127 } 128 129 // TODO: store and restore position and size! 130 ResizeTo(width, height); 131 MoveTo(BAlert::AlertPosition(width, height)); 132 133 // TODO: this might be a bug in the interface kit, but scrolling to 134 // selection does not correctly work unless the window is shown. 135 Show(); 136 Lock(); 137 138 // Try and find the current map name in the two list views (if the name 139 // was read at all) 140 _SelectCurrentMap(); 141 142 KeymapListItem* current 143 = static_cast<KeymapListItem*>(fUserListView->FirstItem()); 144 145 fCurrentMap.Load(current->EntryRef()); 146 fPreviousMap.Load(current->EntryRef()); 147 fAppliedMap.Load(current->EntryRef()); 148 149 Unlock(); 150 } 151 152 153 KeymapWindow::~KeymapWindow(void) 154 { 155 delete fOpenPanel; 156 delete fSavePanel; 157 } 158 159 160 bool 161 KeymapWindow::QuitRequested() 162 { 163 be_app->PostMessage(B_QUIT_REQUESTED); 164 return true; 165 } 166 167 168 void 169 KeymapWindow::MessageReceived(BMessage* message) 170 { 171 switch (message->what) { 172 case B_SIMPLE_DATA: 173 case B_REFS_RECEIVED: 174 { 175 entry_ref ref; 176 int32 i = 0; 177 while (message->FindRef("refs", i++, &ref) == B_OK) { 178 fCurrentMap.Load(ref); 179 } 180 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 181 fSystemListView->DeselectAll(); 182 fUserListView->DeselectAll(); 183 break; 184 } 185 186 case B_MIME_DATA: 187 break; 188 189 case B_SAVE_REQUESTED: 190 { 191 entry_ref ref; 192 const char *name; 193 if (message->FindRef("directory", &ref) == B_OK 194 && message->FindString("name", &name) == B_OK) { 195 BDirectory directory(&ref); 196 BEntry entry(&directory, name); 197 entry.GetRef(&ref); 198 fCurrentMap.Save(ref); 199 200 _FillUserMaps(); 201 } 202 break; 203 } 204 205 case kMsgMenuFileOpen: 206 fOpenPanel->Show(); 207 break; 208 case kMsgMenuFileSave: 209 break; 210 case kMsgMenuFileSaveAs: 211 fSavePanel->Show(); 212 break; 213 214 case kChangeKeyboardLayout: 215 { 216 entry_ref ref; 217 if (message->FindRef("ref", &ref) == B_OK 218 && fKeyboardLayoutView->GetKeyboardLayout()->Load(ref) 219 == B_OK) { 220 fKeyboardLayoutView->SetKeyboardLayout( 221 fKeyboardLayoutView->GetKeyboardLayout()); 222 } else { 223 fKeyboardLayoutView->GetKeyboardLayout()->SetDefault(); 224 fLayoutMenu->ItemAt(0)->SetMarked(true); 225 } 226 227 fKeyboardLayoutView->SetKeyboardLayout( 228 fKeyboardLayoutView->GetKeyboardLayout()); 229 break; 230 } 231 232 case kMsgMenuFontChanged: 233 { 234 BMenuItem *item = fFontMenu->FindMarked(); 235 if (item != NULL) { 236 BFont font; 237 font.SetFamilyAndStyle(item->Label(), NULL); 238 fKeyboardLayoutView->SetFont(font); 239 fTextControl->TextView()->SetFontAndColor(&font); 240 } 241 break; 242 } 243 244 case kMsgSystemMapSelected: 245 case kMsgUserMapSelected: 246 { 247 BListView* listView; 248 BListView* otherListView; 249 250 if (message->what == kMsgSystemMapSelected) { 251 fUserListView->DeselectAll(); 252 listView = fSystemListView; 253 otherListView = fUserListView; 254 } else { 255 listView = fUserListView; 256 otherListView = fSystemListView; 257 } 258 259 int32 index = listView->CurrentSelection(); 260 if (index < 0) 261 break; 262 263 // Deselect item in other BListView 264 otherListView->DeselectAll(); 265 266 if (index == 0 && listView == fUserListView) { 267 // we can safely ignore the "(Current)" item 268 break; 269 } 270 271 KeymapListItem* item 272 = static_cast<KeymapListItem*>(listView->ItemAt(index)); 273 if (item != NULL) { 274 if (!fFirstTime) 275 fCurrentMap.Load(item->EntryRef()); 276 else 277 fFirstTime = false; 278 279 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 280 _UpdateButtons(); 281 } 282 break; 283 } 284 285 case kMsgUseKeymap: 286 _UseKeymap(); 287 _UpdateButtons(); 288 break; 289 case kMsgRevertKeymap: 290 _RevertKeymap(); 291 _UpdateButtons(); 292 break; 293 294 case kMsgKeymapUpdated: 295 _UpdateButtons(); 296 fSystemListView->DeselectAll(); 297 fUserListView->Select(0L); 298 break; 299 300 default: 301 BWindow::MessageReceived(message); 302 break; 303 } 304 } 305 306 307 BMenuBar* 308 KeymapWindow::_CreateMenu() 309 { 310 BMenuBar* menuBar = new BMenuBar(Bounds(), "menubar"); 311 BMenuItem* item; 312 313 // Create the File menu 314 BMenu* menu = new BMenu("File"); 315 menu->AddItem(new BMenuItem("Open" B_UTF8_ELLIPSIS, 316 new BMessage(kMsgMenuFileOpen), 'O')); 317 menu->AddSeparatorItem(); 318 item = new BMenuItem("Save", new BMessage(kMsgMenuFileSave), 'S'); 319 item->SetEnabled(false); 320 menu->AddItem(item); 321 menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, 322 new BMessage(kMsgMenuFileSaveAs))); 323 menu->AddSeparatorItem(); 324 menu->AddItem(new BMenuItem("Quit", 325 new BMessage(B_QUIT_REQUESTED), 'Q')); 326 menuBar->AddItem(menu); 327 328 // Create keyboard layout menu 329 fLayoutMenu = new BMenu("Layout"); 330 fLayoutMenu->SetRadioMode(true); 331 fLayoutMenu->AddItem(item = new BMenuItem( 332 fKeyboardLayoutView->GetKeyboardLayout()->Name(), 333 new BMessage(kChangeKeyboardLayout))); 334 item->SetMarked(true); 335 336 _AddKeyboardLayouts(fLayoutMenu); 337 menuBar->AddItem(fLayoutMenu); 338 339 // Create the Font menu 340 fFontMenu = new BMenu("Font"); 341 fFontMenu->SetRadioMode(true); 342 int32 numFamilies = count_font_families(); 343 font_family family, currentFamily; 344 font_style currentStyle; 345 uint32 flags; 346 347 be_plain_font->GetFamilyAndStyle(¤tFamily, ¤tStyle); 348 349 for (int32 i = 0; i < numFamilies; i++) { 350 if (get_font_family(i, &family, &flags) == B_OK) { 351 BMenuItem *item = 352 new BMenuItem(family, new BMessage(kMsgMenuFontChanged)); 353 fFontMenu->AddItem(item); 354 355 if (!strcmp(family, currentFamily)) 356 item->SetMarked(true); 357 } 358 } 359 menuBar->AddItem(fFontMenu); 360 361 return menuBar; 362 } 363 364 365 BView* 366 KeymapWindow::_CreateMapLists() 367 { 368 // The System list 369 fSystemListView = new BListView("systemList"); 370 fSystemListView->SetSelectionMessage(new BMessage(kMsgSystemMapSelected)); 371 372 BScrollView* systemScroller = new BScrollView("systemScrollList", 373 fSystemListView, 0, false, true); 374 375 // The User list 376 fUserListView = new BListView("userList"); 377 fUserListView->SetSelectionMessage(new BMessage(kMsgUserMapSelected)); 378 BScrollView* userScroller = new BScrollView("userScrollList", 379 fUserListView, 0, false, true); 380 381 // Saved keymaps 382 383 _FillSystemMaps(); 384 _FillUserMaps(); 385 386 _SetListViewSize(fSystemListView); 387 _SetListViewSize(fUserListView); 388 389 return BGroupLayoutBuilder(B_VERTICAL) 390 .Add(new BStringView("system", "System:")) 391 .Add(systemScroller, 3) 392 .Add(new BStringView("user", "User:")) 393 .Add(userScroller); 394 } 395 396 397 void 398 KeymapWindow::_AddKeyboardLayouts(BMenu* menu) 399 { 400 directory_which dataDirectories[] = { 401 B_USER_DATA_DIRECTORY, 402 B_COMMON_DATA_DIRECTORY, 403 B_BEOS_DATA_DIRECTORY 404 }; 405 406 for (uint32 i = 0; 407 i < sizeof(dataDirectories) / sizeof(dataDirectories[0]); i++) { 408 BPath path; 409 if (find_directory(dataDirectories[i], &path) != B_OK) 410 continue; 411 412 path.Append("KeyboardLayouts"); 413 414 BDirectory directory; 415 if (directory.SetTo(path.Path()) == B_OK) { 416 entry_ref ref; 417 while (directory.GetNextRef(&ref) == B_OK) { 418 if (menu->FindItem(ref.name) != NULL) 419 continue; 420 421 BMessage* message = new BMessage(kChangeKeyboardLayout); 422 message->AddRef("ref", &ref); 423 424 menu->AddItem(new BMenuItem(ref.name, message)); 425 } 426 } 427 } 428 } 429 430 431 void 432 KeymapWindow::_UpdateButtons() 433 { 434 fUseButton->SetEnabled(!fCurrentMap.Equals(fAppliedMap)); 435 fRevertButton->SetEnabled(!fCurrentMap.Equals(fPreviousMap)); 436 } 437 438 439 //! Saves previous map to the "Key_map" file. 440 void 441 KeymapWindow::_RevertKeymap() 442 { 443 entry_ref ref; 444 _GetCurrentKeymap(ref); 445 446 status_t status = fPreviousMap.Save(ref); 447 if (status != B_OK) { 448 printf("error when saving keymap: %s", strerror(status)); 449 return; 450 } 451 452 fPreviousMap.Use(); 453 fAppliedMap.Load(ref); 454 455 // TODO: add = operator 456 fCurrentMap.Load(ref); 457 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 458 459 fCurrentMapName = _GetActiveKeymapName(); 460 _SelectCurrentMap(); 461 } 462 463 464 void 465 KeymapWindow::_UseKeymap() 466 { 467 entry_ref ref; 468 _GetCurrentKeymap(ref); 469 470 status_t status = fCurrentMap.Save(ref); 471 if (status != B_OK) { 472 printf("error when saving : %s", strerror(status)); 473 return; 474 } 475 476 fCurrentMap.Use(); 477 fAppliedMap.Load(ref); 478 479 fCurrentMapName = _GetActiveKeymapName(); 480 } 481 482 483 void 484 KeymapWindow::_FillSystemMaps() 485 { 486 BListItem *item; 487 while ((item = fSystemListView->RemoveItem(static_cast<int32>(0)))) 488 delete item; 489 490 BPath path; 491 if (find_directory(B_BEOS_ETC_DIRECTORY, &path) != B_OK) 492 return; 493 494 path.Append("Keymap"); 495 496 BDirectory directory; 497 entry_ref ref; 498 499 if (directory.SetTo(path.Path()) == B_OK) { 500 while (directory.GetNextRef(&ref) == B_OK) { 501 fSystemListView->AddItem(new KeymapListItem(ref)); 502 } 503 } 504 } 505 506 507 void 508 KeymapWindow::_FillUserMaps() 509 { 510 BListItem* item; 511 while ((item = fUserListView->RemoveItem(static_cast<int32>(0)))) 512 delete item; 513 514 entry_ref ref; 515 _GetCurrentKeymap(ref); 516 517 fUserListView->AddItem(new KeymapListItem(ref, "(Current)")); 518 519 fCurrentMapName = _GetActiveKeymapName(); 520 521 BPath path; 522 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 523 return; 524 525 path.Append("Keymap"); 526 527 BDirectory directory; 528 if (directory.SetTo(path.Path()) == B_OK) { 529 while (directory.GetNextRef(&ref) == B_OK) { 530 fUserListView->AddItem(new KeymapListItem(ref)); 531 } 532 } 533 } 534 535 536 void 537 KeymapWindow::_SetListViewSize(BListView* listView) 538 { 539 float minWidth = 0; 540 for (int32 i = 0; i < listView->CountItems(); i++) { 541 BStringItem* item = (BStringItem*)listView->ItemAt(i); 542 float width = listView->StringWidth(item->Text()); 543 if (width > minWidth) 544 minWidth = width; 545 } 546 547 listView->SetExplicitMinSize(BSize(minWidth + 8, 32)); 548 } 549 550 551 status_t 552 KeymapWindow::_GetCurrentKeymap(entry_ref& ref) 553 { 554 BPath path; 555 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 556 return B_ERROR; 557 558 path.Append("Key_map"); 559 560 return get_ref_for_path(path.Path(), &ref); 561 } 562 563 564 BString 565 KeymapWindow::_GetActiveKeymapName() 566 { 567 BString mapName = "(Current)"; // safe default 568 569 entry_ref ref; 570 _GetCurrentKeymap(ref); 571 572 BNode node(&ref); 573 574 if (node.InitCheck() == B_OK) 575 node.ReadAttrString("keymap:name", &mapName); 576 577 return mapName; 578 } 579 580 581 bool 582 KeymapWindow::_SelectCurrentMap(BListView* view) 583 { 584 if (fCurrentMapName.Length() <= 0) 585 return false; 586 587 for (int32 i = 0; i < view->CountItems(); i++) { 588 BStringItem* current = dynamic_cast<BStringItem *>(view->ItemAt(i)); 589 if (current != NULL && fCurrentMapName == current->Text()) { 590 view->Select(i); 591 view->ScrollToSelection(); 592 return true; 593 } 594 } 595 596 return false; 597 } 598 599 600 void 601 KeymapWindow::_SelectCurrentMap() 602 { 603 if (!_SelectCurrentMap(fSystemListView) 604 && !_SelectCurrentMap(fUserListView)) { 605 // Select the "(Current)" entry if no name matches 606 fUserListView->Select(0L); 607 fFirstTime = false; 608 } 609 } 610