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