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