xref: /haiku/src/apps/charactermap/CharacterWindow.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
1 /*
2  * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2011, Philippe Saint-Pierre, stpere@gmail.com.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "CharacterWindow.h"
9 
10 #include <stdio.h>
11 #include <string.h>
12 
13 #include <Application.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <File.h>
17 #include <FindDirectory.h>
18 #include <LayoutBuilder.h>
19 #include <ListView.h>
20 #include <Menu.h>
21 #include <MenuBar.h>
22 #include <MenuItem.h>
23 #include <MessageFilter.h>
24 #include <Path.h>
25 #include <Roster.h>
26 #include <ScrollView.h>
27 #include <Slider.h>
28 #include <StringView.h>
29 #include <TextControl.h>
30 #include <UnicodeChar.h>
31 
32 #include "CharacterView.h"
33 #include "UnicodeBlockView.h"
34 
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "CharacterWindow"
37 
38 static const uint32 kMsgUnicodeBlockSelected = 'unbs';
39 static const uint32 kMsgCharacterChanged = 'chch';
40 static const uint32 kMsgFontSelected = 'fnts';
41 static const uint32 kMsgFontSizeChanged = 'fsch';
42 static const uint32 kMsgPrivateBlocks = 'prbl';
43 static const uint32 kMsgContainedBlocks = 'cnbl';
44 static const uint32 kMsgFilterChanged = 'fltr';
45 static const uint32 kMsgClearFilter = 'clrf';
46 
47 static const int32 kMinFontSize = 10;
48 static const int32 kMaxFontSize = 72;
49 
50 
51 class FontSizeSlider : public BSlider {
52 public:
53 	FontSizeSlider(const char* name, const char* label, BMessage* message,
54 			int32 min, int32 max)
55 		: BSlider(name, label, NULL, min, max, B_HORIZONTAL)
56 	{
57 		SetModificationMessage(message);
58 	}
59 
60 protected:
61 	const char* UpdateText() const
62 	{
63 		snprintf(fText, sizeof(fText), "%" B_PRId32 "pt", Value());
64 		return fText;
65 	}
66 
67 private:
68 	mutable char	fText[32];
69 };
70 
71 
72 class RedirectUpAndDownFilter : public BMessageFilter {
73 public:
74 	RedirectUpAndDownFilter(BHandler* target)
75 		: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_KEY_DOWN),
76 		fTarget(target)
77 	{
78 	}
79 
80 	virtual filter_result Filter(BMessage* message, BHandler** _target)
81 	{
82 		const char* bytes;
83 		if (message->FindString("bytes", &bytes) != B_OK)
84 			return B_DISPATCH_MESSAGE;
85 
86 		if (bytes[0] == B_UP_ARROW
87 			|| bytes[0] == B_DOWN_ARROW)
88 			*_target = fTarget;
89 
90 		return B_DISPATCH_MESSAGE;
91 	}
92 
93 private:
94 	BHandler*	fTarget;
95 };
96 
97 
98 class EscapeMessageFilter : public BMessageFilter {
99 public:
100 	EscapeMessageFilter(uint32 command)
101 		: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_KEY_DOWN),
102 		fCommand(command)
103 	{
104 	}
105 
106 	virtual filter_result Filter(BMessage* message, BHandler** /*_target*/)
107 	{
108 		const char* bytes;
109 		if (message->what != B_KEY_DOWN
110 			|| message->FindString("bytes", &bytes) != B_OK
111 			|| bytes[0] != B_ESCAPE)
112 			return B_DISPATCH_MESSAGE;
113 
114 		Looper()->PostMessage(fCommand);
115 		return B_SKIP_MESSAGE;
116 	}
117 
118 private:
119 	uint32	fCommand;
120 };
121 
122 
123 CharacterWindow::CharacterWindow()
124 	:
125 	BWindow(BRect(100, 100, 700, 550), B_TRANSLATE_SYSTEM_NAME("CharacterMap"),
126 		B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE
127 		| B_AUTO_UPDATE_SIZE_LIMITS)
128 {
129 	BMessage settings;
130 	_LoadSettings(settings);
131 
132 	BRect frame;
133 	if (settings.FindRect("window frame", &frame) == B_OK) {
134 		MoveTo(frame.LeftTop());
135 		ResizeTo(frame.Width(), frame.Height());
136 	}
137 
138 	// create GUI
139 	BMenuBar* menuBar = new BMenuBar("menu");
140 
141 	fFilterControl = new BTextControl(B_TRANSLATE("Filter:"), NULL, NULL);
142 	fFilterControl->SetModificationMessage(new BMessage(kMsgFilterChanged));
143 
144 	BButton* clearButton = new BButton("clear", B_TRANSLATE("Clear"),
145 		new BMessage(kMsgClearFilter));
146 
147 	fUnicodeBlockView = new UnicodeBlockView("unicodeBlocks");
148 	fUnicodeBlockView->SetSelectionMessage(
149 		new BMessage(kMsgUnicodeBlockSelected));
150 
151 	BScrollView* unicodeScroller = new BScrollView("unicodeScroller",
152 		fUnicodeBlockView, 0, false, true);
153 
154 	fCharacterView = new CharacterView("characters");
155 	fCharacterView->SetTarget(this, kMsgCharacterChanged);
156 
157 	fGlyphView = new BStringView("glyph", "");
158 	fGlyphView->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
159 		fGlyphView->PreferredSize().Height()));
160 
161 	// TODO: have a context object shared by CharacterView/UnicodeBlockView
162 	bool show;
163 	if (settings.FindBool("show private blocks", &show) == B_OK) {
164 		fCharacterView->ShowPrivateBlocks(show);
165 		fUnicodeBlockView->ShowPrivateBlocks(show);
166 	}
167 	if (settings.FindBool("show contained blocks only", &show) == B_OK) {
168 		fCharacterView->ShowContainedBlocksOnly(show);
169 		fUnicodeBlockView->ShowPrivateBlocks(show);
170 	}
171 
172 	const char* family;
173 	const char* style;
174 	if (settings.FindString("font family", &family) == B_OK
175 		&& settings.FindString("font style", &style) == B_OK) {
176 		_SetFont(family, style);
177 	}
178 
179 	int32 fontSize;
180 	if (settings.FindInt32("font size", &fontSize) == B_OK) {
181 		BFont font = fCharacterView->CharacterFont();
182 		if (fontSize < kMinFontSize)
183 			fontSize = kMinFontSize;
184 		else if (fontSize > kMaxFontSize)
185 			fontSize = kMaxFontSize;
186 		font.SetSize(fontSize);
187 
188 		fCharacterView->SetCharacterFont(font);
189 	} else
190 		fontSize = (int32)fCharacterView->CharacterFont().Size();
191 
192 	BScrollView* characterScroller = new BScrollView("characterScroller",
193 		fCharacterView, 0, false, true);
194 
195 	fFontSizeSlider = new FontSizeSlider("fontSizeSlider",
196 		B_TRANSLATE("Font size:"),
197 		new BMessage(kMsgFontSizeChanged), kMinFontSize, kMaxFontSize);
198 	fFontSizeSlider->SetValue(fontSize);
199 
200 	fCodeView = new BStringView("code", "-");
201 	fCodeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
202 		fCodeView->PreferredSize().Height()));
203 
204 	BLayoutBuilder::Group<>(this, B_VERTICAL)
205 		.Add(menuBar)
206 		.AddGroup(B_HORIZONTAL)
207 			.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
208 				B_USE_DEFAULT_SPACING)
209 			.AddGroup(B_VERTICAL)
210 				.AddGroup(B_HORIZONTAL)
211 					.Add(fFilterControl)
212 					.Add(clearButton)
213 				.End()
214 				.Add(unicodeScroller)
215 			.End()
216 			.AddGroup(B_VERTICAL)
217 				.Add(characterScroller)
218 				.Add(fFontSizeSlider)
219 				.AddGroup(B_HORIZONTAL)
220 					.Add(fGlyphView)
221 					.Add(fCodeView);
222 
223 	// Add menu
224 
225 	// "File" menu
226 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
227 	BMenuItem* item;
228 
229 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
230 		new BMessage(B_QUIT_REQUESTED), 'Q'));
231 	menu->SetTargetForItems(this);
232 	menuBar->AddItem(menu);
233 
234 	menu = new BMenu(B_TRANSLATE("View"));
235 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show private blocks"),
236 		new BMessage(kMsgPrivateBlocks)));
237 	item->SetMarked(fCharacterView->IsShowingPrivateBlocks());
238 // TODO: this feature is not yet supported by Haiku!
239 #if 0
240 	menu->AddItem(item = new BMenuItem("Only show blocks contained in font",
241 		new BMessage(kMsgContainedBlocks)));
242 	item->SetMarked(fCharacterView->IsShowingContainedBlocksOnly());
243 #endif
244 	menuBar->AddItem(menu);
245 
246 	menuBar->AddItem(_CreateFontMenu());
247 
248 	AddCommonFilter(new EscapeMessageFilter(kMsgClearFilter));
249 	AddCommonFilter(new RedirectUpAndDownFilter(fUnicodeBlockView));
250 
251 	// TODO: why is this needed?
252 	fUnicodeBlockView->SetTarget(this);
253 
254 	fFilterControl->MakeFocus();
255 }
256 
257 
258 CharacterWindow::~CharacterWindow()
259 {
260 }
261 
262 
263 void
264 CharacterWindow::MessageReceived(BMessage* message)
265 {
266 	if (message->WasDropped()) {
267 		const char* text;
268 		ssize_t size;
269 		uint32 c;
270 		if (message->FindInt32("character", (int32*)&c) == B_OK) {
271 			fCharacterView->ScrollToCharacter(c);
272 			return;
273 		} else if (message->FindData("text/plain", B_MIME_TYPE,
274 				(const void**)&text, &size) == B_OK) {
275 			fCharacterView->ScrollToCharacter(BUnicodeChar::FromUTF8(text));
276 			return;
277 		}
278 	}
279 
280 	switch (message->what) {
281 		case B_COPY:
282 			PostMessage(message, fCharacterView);
283 			break;
284 
285 		case kMsgUnicodeBlockSelected:
286 		{
287 			int32 index;
288 			if (message->FindInt32("index", &index) != B_OK
289 				|| index < 0)
290 				break;
291 
292 			BlockListItem* item
293 				= static_cast<BlockListItem*>(fUnicodeBlockView->ItemAt(index));
294 			fCharacterView->ScrollToBlock(item->BlockIndex());
295 
296 			fFilterControl->MakeFocus();
297 			break;
298 		}
299 
300 		case kMsgCharacterChanged:
301 		{
302 			uint32 character;
303 			if (message->FindInt32("character", (int32*)&character) != B_OK)
304 				break;
305 
306 			char utf8[16];
307 			CharacterView::UnicodeToUTF8(character, utf8, sizeof(utf8));
308 
309 			char utf8Hex[32];
310 			CharacterView::UnicodeToUTF8Hex(character, utf8Hex,
311 				sizeof(utf8Hex));
312 
313 			char text[128];
314 			snprintf(text, sizeof(text), " %s: %#" B_PRIx32 " (%" B_PRId32 "), UTF-8: %s",
315 				B_TRANSLATE("Code"), character, character, utf8Hex);
316 
317 			char glyph[20];
318 			snprintf(glyph, sizeof(glyph), "'%s'", utf8);
319 
320 			fGlyphView->SetText(glyph);
321 			fCodeView->SetText(text);
322 			break;
323 		}
324 
325 		case kMsgFontSelected:
326 		{
327 			BMenuItem* item;
328 
329 			if (message->FindPointer("source", (void**)&item) != B_OK)
330 				break;
331 
332 			fSelectedFontItem->SetMarked(false);
333 
334 			// If it's the family menu, just select the first style
335 			if (item->Submenu() != NULL) {
336 				item->SetMarked(true);
337 				item = item->Submenu()->ItemAt(0);
338 			}
339 
340 			if (item != NULL) {
341 				item->SetMarked(true);
342 				fSelectedFontItem = item;
343 
344 				_SetFont(item->Menu()->Name(), item->Label());
345 				item = item->Menu()->Superitem();
346 				item->SetMarked(true);
347 			}
348 			break;
349 		}
350 
351 		case kMsgFontSizeChanged:
352 		{
353 			int32 size = fFontSizeSlider->Value();
354 			if (size < kMinFontSize)
355 				size = kMinFontSize;
356 			else if (size > kMaxFontSize)
357 				size = kMaxFontSize;
358 
359 			BFont font = fCharacterView->CharacterFont();
360 			font.SetSize(size);
361 			fCharacterView->SetCharacterFont(font);
362 			break;
363 		}
364 
365 		case kMsgPrivateBlocks:
366 		{
367 			BMenuItem* item;
368 			if (message->FindPointer("source", (void**)&item) != B_OK
369 				|| item == NULL)
370 				break;
371 
372 			item->SetMarked(!item->IsMarked());
373 
374 			fCharacterView->ShowPrivateBlocks(item->IsMarked());
375 			fUnicodeBlockView->ShowPrivateBlocks(item->IsMarked());
376 			break;
377 		}
378 
379 		case kMsgContainedBlocks:
380 		{
381 			BMenuItem* item;
382 			if (message->FindPointer("source", (void**)&item) != B_OK
383 				|| item == NULL)
384 				break;
385 
386 			item->SetMarked(!item->IsMarked());
387 
388 			fCharacterView->ShowContainedBlocksOnly(item->IsMarked());
389 			fUnicodeBlockView->ShowContainedBlocksOnly(item->IsMarked());
390 			break;
391 		}
392 
393 		case kMsgFilterChanged:
394 			fUnicodeBlockView->SetFilter(fFilterControl->Text());
395 			fUnicodeBlockView->Select(0);
396 			break;
397 
398 		case kMsgClearFilter:
399 			fFilterControl->SetText("");
400 			fFilterControl->MakeFocus();
401 			break;
402 
403 		default:
404 			BWindow::MessageReceived(message);
405 			break;
406 	}
407 }
408 
409 
410 bool
411 CharacterWindow::QuitRequested()
412 {
413 	_SaveSettings();
414 	be_app->PostMessage(B_QUIT_REQUESTED);
415 	return true;
416 }
417 
418 
419 status_t
420 CharacterWindow::_OpenSettings(BFile& file, uint32 mode)
421 {
422 	BPath path;
423 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
424 		return B_ERROR;
425 
426 	path.Append("CharacterMap settings");
427 
428 	return file.SetTo(path.Path(), mode);
429 }
430 
431 
432 status_t
433 CharacterWindow::_LoadSettings(BMessage& settings)
434 {
435 	BFile file;
436 	status_t status = _OpenSettings(file, B_READ_ONLY);
437 	if (status < B_OK)
438 		return status;
439 
440 	return settings.Unflatten(&file);
441 }
442 
443 
444 status_t
445 CharacterWindow::_SaveSettings()
446 {
447 	BFile file;
448 	status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
449 		| B_ERASE_FILE);
450 	if (status < B_OK)
451 		return status;
452 
453 	BMessage settings('chrm');
454 	status = settings.AddRect("window frame", Frame());
455 	if (status != B_OK)
456 		return status;
457 
458 	if (status == B_OK) {
459 		status = settings.AddBool("show private blocks",
460 			fCharacterView->IsShowingPrivateBlocks());
461 	}
462 	if (status == B_OK) {
463 		status = settings.AddBool("show contained blocks only",
464 			fCharacterView->IsShowingContainedBlocksOnly());
465 	}
466 
467 	if (status == B_OK) {
468 		BFont font = fCharacterView->CharacterFont();
469 		status = settings.AddInt32("font size", font.Size());
470 
471 		font_family family;
472 		font_style style;
473 		if (status == B_OK)
474 			font.GetFamilyAndStyle(&family, &style);
475 		if (status == B_OK)
476 			status = settings.AddString("font family", family);
477 		if (status == B_OK)
478 			status = settings.AddString("font style", style);
479 	}
480 
481 	if (status == B_OK)
482 		status = settings.Flatten(&file);
483 
484 	return status;
485 }
486 
487 
488 void
489 CharacterWindow::_SetFont(const char* family, const char* style)
490 {
491 	BFont font = fCharacterView->CharacterFont();
492 	font.SetFamilyAndStyle(family, style);
493 
494 	fCharacterView->SetCharacterFont(font);
495 	fGlyphView->SetFont(&font, B_FONT_FAMILY_AND_STYLE);
496 }
497 
498 
499 BMenu*
500 CharacterWindow::_CreateFontMenu()
501 {
502 	BMenu* menu = new BMenu(B_TRANSLATE("Font"));
503 	BMenuItem* item;
504 
505 	font_family currentFamily;
506 	font_style currentStyle;
507 	fCharacterView->CharacterFont().GetFamilyAndStyle(&currentFamily,
508 		&currentStyle);
509 
510 	int32 numFamilies = count_font_families();
511 
512 	menu->SetRadioMode(true);
513 
514 	for (int32 i = 0; i < numFamilies; i++) {
515 		font_family family;
516 		if (get_font_family(i, &family) == B_OK) {
517 			BMenu* subMenu = new BMenu(family);
518 			menu->AddItem(new BMenuItem(subMenu,
519 				new BMessage(kMsgFontSelected)));
520 
521 			int numStyles = count_font_styles(family);
522 			for (int32 j = 0; j < numStyles; j++) {
523 				font_style style;
524 				uint32 flags;
525 				if (get_font_style(family, j, &style, &flags) == B_OK) {
526 					item = new BMenuItem(style, new BMessage(kMsgFontSelected));
527 					subMenu->AddItem(item);
528 
529 					if (!strcmp(family, currentFamily)
530 						&& !strcmp(style, currentStyle)) {
531 						fSelectedFontItem = item;
532 						item->SetMarked(true);
533 					}
534 				}
535 			}
536 		}
537 	}
538 
539 	item = menu->FindItem(currentFamily);
540 	item->SetMarked(true);
541 
542 	return menu;
543 }
544