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