xref: /haiku/src/preferences/keymap/KeymapWindow.cpp (revision 4720c31bb08f5c6d1c8ddb616463c6fba9b350a1)
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(&currentFamily, &currentStyle);
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