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