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 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 288 CharacterWindow::~CharacterWindow() 289 { 290 } 291 292 293 void 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 450 CharacterWindow::QuitRequested() 451 { 452 _SaveSettings(); 453 be_app->PostMessage(B_QUIT_REQUESTED); 454 return true; 455 } 456 457 458 status_t 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 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 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 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* 540 CharacterWindow::_CreateFontMenu() 541 { 542 BMenu* menu = new BMenu(B_TRANSLATE("Font")); 543 _UpdateFontMenu(menu); 544 545 return menu; 546 } 547 548 549 void 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 599 CharacterWindow::MenusBeginning() 600 { 601 if (update_font_families(false) == true) 602 _UpdateFontMenu(fFontMenu); 603 } 604