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