xref: /haiku/src/apps/charactermap/CharacterWindow.cpp (revision 372a66634410cf0450e426716c14ad42d40c0da4)
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, 10)
207 			.SetInsets(10)
208 			.AddGroup(B_VERTICAL, 10)
209 				.AddGroup(B_HORIZONTAL, 10)
210 					.Add(fFilterControl)
211 					.Add(clearButton)
212 				.End()
213 				.Add(unicodeScroller)
214 			.End()
215 			.AddGroup(B_VERTICAL, 10)
216 				.Add(characterScroller)
217 				.Add(fFontSizeSlider)
218 				.AddGroup(B_HORIZONTAL, 0)
219 					.Add(fGlyphView)
220 					.Add(fCodeView);
221 
222 	// Add menu
223 
224 	// "File" menu
225 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
226 	BMenuItem* item;
227 
228 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
229 		new BMessage(B_QUIT_REQUESTED), 'Q'));
230 	menu->SetTargetForItems(this);
231 	menuBar->AddItem(menu);
232 
233 	menu = new BMenu(B_TRANSLATE("View"));
234 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show private blocks"),
235 		new BMessage(kMsgPrivateBlocks)));
236 	item->SetMarked(fCharacterView->IsShowingPrivateBlocks());
237 // TODO: this feature is not yet supported by Haiku!
238 #if 0
239 	menu->AddItem(item = new BMenuItem("Only show blocks contained in font",
240 		new BMessage(kMsgContainedBlocks)));
241 	item->SetMarked(fCharacterView->IsShowingContainedBlocksOnly());
242 #endif
243 	menuBar->AddItem(menu);
244 
245 	menuBar->AddItem(_CreateFontMenu());
246 
247 	AddCommonFilter(new EscapeMessageFilter(kMsgClearFilter));
248 	AddCommonFilter(new RedirectUpAndDownFilter(fUnicodeBlockView));
249 
250 	// TODO: why is this needed?
251 	fUnicodeBlockView->SetTarget(this);
252 
253 	fFilterControl->MakeFocus();
254 }
255 
256 
257 CharacterWindow::~CharacterWindow()
258 {
259 }
260 
261 
262 void
263 CharacterWindow::MessageReceived(BMessage* message)
264 {
265 	if (message->WasDropped()) {
266 		const char* text;
267 		ssize_t size;
268 		uint32 c;
269 		if (message->FindInt32("character", (int32*)&c) == B_OK) {
270 			fCharacterView->ScrollToCharacter(c);
271 			return;
272 		} else if (message->FindData("text/plain", B_MIME_TYPE,
273 				(const void**)&text, &size) == B_OK) {
274 			fCharacterView->ScrollToCharacter(BUnicodeChar::FromUTF8(text));
275 			return;
276 		}
277 	}
278 
279 	switch (message->what) {
280 		case B_COPY:
281 			PostMessage(message, fCharacterView);
282 			break;
283 
284 		case kMsgUnicodeBlockSelected:
285 		{
286 			int32 index;
287 			if (message->FindInt32("index", &index) != B_OK
288 				|| index < 0)
289 				break;
290 
291 			BlockListItem* item
292 				= static_cast<BlockListItem*>(fUnicodeBlockView->ItemAt(index));
293 			fCharacterView->ScrollToBlock(item->BlockIndex());
294 
295 			fFilterControl->MakeFocus();
296 			break;
297 		}
298 
299 		case kMsgCharacterChanged:
300 		{
301 			uint32 character;
302 			if (message->FindInt32("character", (int32*)&character) != B_OK)
303 				break;
304 
305 			char utf8[16];
306 			CharacterView::UnicodeToUTF8(character, utf8, sizeof(utf8));
307 
308 			char utf8Hex[32];
309 			CharacterView::UnicodeToUTF8Hex(character, utf8Hex,
310 				sizeof(utf8Hex));
311 
312 			char text[128];
313 			snprintf(text, sizeof(text), " %s: %#" B_PRIx32 " (%" B_PRId32 "), UTF-8: %s",
314 				B_TRANSLATE("Code"), character, character, utf8Hex);
315 
316 			char glyph[20];
317 			snprintf(glyph, sizeof(glyph), "'%s'", utf8);
318 
319 			fGlyphView->SetText(glyph);
320 			fCodeView->SetText(text);
321 			break;
322 		}
323 
324 		case kMsgFontSelected:
325 		{
326 			BMenuItem* item;
327 
328 			if (message->FindPointer("source", (void**)&item) != B_OK)
329 				break;
330 
331 			fSelectedFontItem->SetMarked(false);
332 
333 			// If it's the family menu, just select the first style
334 			if (item->Submenu() != NULL) {
335 				item->SetMarked(true);
336 				item = item->Submenu()->ItemAt(0);
337 			}
338 
339 			if (item != NULL) {
340 				item->SetMarked(true);
341 				fSelectedFontItem = item;
342 
343 				_SetFont(item->Menu()->Name(), item->Label());
344 				item = item->Menu()->Superitem();
345 				item->SetMarked(true);
346 			}
347 			break;
348 		}
349 
350 		case kMsgFontSizeChanged:
351 		{
352 			int32 size = fFontSizeSlider->Value();
353 			if (size < kMinFontSize)
354 				size = kMinFontSize;
355 			else if (size > kMaxFontSize)
356 				size = kMaxFontSize;
357 
358 			BFont font = fCharacterView->CharacterFont();
359 			font.SetSize(size);
360 			fCharacterView->SetCharacterFont(font);
361 			break;
362 		}
363 
364 		case kMsgPrivateBlocks:
365 		{
366 			BMenuItem* item;
367 			if (message->FindPointer("source", (void**)&item) != B_OK
368 				|| item == NULL)
369 				break;
370 
371 			item->SetMarked(!item->IsMarked());
372 
373 			fCharacterView->ShowPrivateBlocks(item->IsMarked());
374 			fUnicodeBlockView->ShowPrivateBlocks(item->IsMarked());
375 			break;
376 		}
377 
378 		case kMsgContainedBlocks:
379 		{
380 			BMenuItem* item;
381 			if (message->FindPointer("source", (void**)&item) != B_OK
382 				|| item == NULL)
383 				break;
384 
385 			item->SetMarked(!item->IsMarked());
386 
387 			fCharacterView->ShowContainedBlocksOnly(item->IsMarked());
388 			fUnicodeBlockView->ShowContainedBlocksOnly(item->IsMarked());
389 			break;
390 		}
391 
392 		case kMsgFilterChanged:
393 			fUnicodeBlockView->SetFilter(fFilterControl->Text());
394 			fUnicodeBlockView->Select(0);
395 			break;
396 
397 		case kMsgClearFilter:
398 			fFilterControl->SetText("");
399 			fFilterControl->MakeFocus();
400 			break;
401 
402 		default:
403 			BWindow::MessageReceived(message);
404 			break;
405 	}
406 }
407 
408 
409 bool
410 CharacterWindow::QuitRequested()
411 {
412 	_SaveSettings();
413 	be_app->PostMessage(B_QUIT_REQUESTED);
414 	return true;
415 }
416 
417 
418 status_t
419 CharacterWindow::_OpenSettings(BFile& file, uint32 mode)
420 {
421 	BPath path;
422 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
423 		return B_ERROR;
424 
425 	path.Append("CharacterMap settings");
426 
427 	return file.SetTo(path.Path(), mode);
428 }
429 
430 
431 status_t
432 CharacterWindow::_LoadSettings(BMessage& settings)
433 {
434 	BFile file;
435 	status_t status = _OpenSettings(file, B_READ_ONLY);
436 	if (status < B_OK)
437 		return status;
438 
439 	return settings.Unflatten(&file);
440 }
441 
442 
443 status_t
444 CharacterWindow::_SaveSettings()
445 {
446 	BFile file;
447 	status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
448 		| B_ERASE_FILE);
449 	if (status < B_OK)
450 		return status;
451 
452 	BMessage settings('chrm');
453 	status = settings.AddRect("window frame", Frame());
454 	if (status != B_OK)
455 		return status;
456 
457 	if (status == B_OK) {
458 		status = settings.AddBool("show private blocks",
459 			fCharacterView->IsShowingPrivateBlocks());
460 	}
461 	if (status == B_OK) {
462 		status = settings.AddBool("show contained blocks only",
463 			fCharacterView->IsShowingContainedBlocksOnly());
464 	}
465 
466 	if (status == B_OK) {
467 		BFont font = fCharacterView->CharacterFont();
468 		status = settings.AddInt32("font size", font.Size());
469 
470 		font_family family;
471 		font_style style;
472 		if (status == B_OK)
473 			font.GetFamilyAndStyle(&family, &style);
474 		if (status == B_OK)
475 			status = settings.AddString("font family", family);
476 		if (status == B_OK)
477 			status = settings.AddString("font style", style);
478 	}
479 
480 	if (status == B_OK)
481 		status = settings.Flatten(&file);
482 
483 	return status;
484 }
485 
486 
487 void
488 CharacterWindow::_SetFont(const char* family, const char* style)
489 {
490 	BFont font = fCharacterView->CharacterFont();
491 	font.SetFamilyAndStyle(family, style);
492 
493 	fCharacterView->SetCharacterFont(font);
494 	fGlyphView->SetFont(&font, B_FONT_FAMILY_AND_STYLE);
495 }
496 
497 
498 BMenu*
499 CharacterWindow::_CreateFontMenu()
500 {
501 	BMenu* menu = new BMenu(B_TRANSLATE("Font"));
502 	BMenuItem* item;
503 
504 	font_family currentFamily;
505 	font_style currentStyle;
506 	fCharacterView->CharacterFont().GetFamilyAndStyle(&currentFamily,
507 		&currentStyle);
508 
509 	int32 numFamilies = count_font_families();
510 
511 	menu->SetRadioMode(true);
512 
513 	for (int32 i = 0; i < numFamilies; i++) {
514 		font_family family;
515 		if (get_font_family(i, &family) == B_OK) {
516 			BMenu* subMenu = new BMenu(family);
517 			menu->AddItem(new BMenuItem(subMenu,
518 				new BMessage(kMsgFontSelected)));
519 
520 			int numStyles = count_font_styles(family);
521 			for (int32 j = 0; j < numStyles; j++) {
522 				font_style style;
523 				uint32 flags;
524 				if (get_font_style(family, j, &style, &flags) == B_OK) {
525 					item = new BMenuItem(style, new BMessage(kMsgFontSelected));
526 					subMenu->AddItem(item);
527 
528 					if (!strcmp(family, currentFamily)
529 						&& !strcmp(style, currentStyle)) {
530 						fSelectedFontItem = item;
531 						item->SetMarked(true);
532 					}
533 				}
534 			}
535 		}
536 	}
537 
538 	item = menu->FindItem(currentFamily);
539 	item->SetMarked(true);
540 
541 	return menu;
542 }
543