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