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