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