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