603 lines
14 KiB
C++
603 lines
14 KiB
C++
/*
|
|
* Copyright 2009-2015, Axel Dörfler, axeld@pinc-software.de.
|
|
* Copyright 2011, Philippe Saint-Pierre, stpere@gmail.com.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include "CharacterWindow.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <Application.h>
|
|
#include <Button.h>
|
|
#include <Catalog.h>
|
|
#include <File.h>
|
|
#include <FindDirectory.h>
|
|
#include <Font.h>
|
|
#include <LayoutBuilder.h>
|
|
#include <ListView.h>
|
|
#include <Menu.h>
|
|
#include <MenuBar.h>
|
|
#include <MenuItem.h>
|
|
#include <MessageFilter.h>
|
|
#include <Path.h>
|
|
#include <Roster.h>
|
|
#include <ScrollView.h>
|
|
#include <Slider.h>
|
|
#include <StringView.h>
|
|
#include <TextControl.h>
|
|
#include <UnicodeChar.h>
|
|
|
|
#include "CharacterView.h"
|
|
#include "UnicodeBlockView.h"
|
|
|
|
|
|
#undef B_TRANSLATION_CONTEXT
|
|
#define B_TRANSLATION_CONTEXT "CharacterWindow"
|
|
|
|
|
|
static const uint32 kMsgUnicodeBlockSelected = 'unbs';
|
|
static const uint32 kMsgCharacterChanged = 'chch';
|
|
static const uint32 kMsgFontSelected = 'fnts';
|
|
static const uint32 kMsgFontSizeChanged = 'fsch';
|
|
static const uint32 kMsgPrivateBlocks = 'prbl';
|
|
static const uint32 kMsgContainedBlocks = 'cnbl';
|
|
static const uint32 kMsgFilterChanged = 'fltr';
|
|
static const uint32 kMsgClearFilter = 'clrf';
|
|
|
|
static const int32 kMinFontSize = 10;
|
|
static const int32 kMaxFontSize = 72;
|
|
|
|
|
|
class FontSizeSlider : public BSlider {
|
|
public:
|
|
FontSizeSlider(const char* name, const char* label, BMessage* message,
|
|
int32 min, int32 max)
|
|
: BSlider(name, label, NULL, min, max, B_HORIZONTAL)
|
|
{
|
|
SetModificationMessage(message);
|
|
}
|
|
|
|
protected:
|
|
const char* UpdateText() const
|
|
{
|
|
snprintf(fText, sizeof(fText), "%" B_PRId32 "pt", Value());
|
|
return fText;
|
|
}
|
|
|
|
private:
|
|
mutable char fText[32];
|
|
};
|
|
|
|
|
|
class RedirectUpAndDownFilter : public BMessageFilter {
|
|
public:
|
|
RedirectUpAndDownFilter(BHandler* target)
|
|
: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_KEY_DOWN),
|
|
fTarget(target)
|
|
{
|
|
}
|
|
|
|
virtual filter_result Filter(BMessage* message, BHandler** _target)
|
|
{
|
|
const char* bytes;
|
|
if (message->FindString("bytes", &bytes) != B_OK)
|
|
return B_DISPATCH_MESSAGE;
|
|
|
|
if (bytes[0] == B_UP_ARROW
|
|
|| bytes[0] == B_DOWN_ARROW)
|
|
*_target = fTarget;
|
|
|
|
return B_DISPATCH_MESSAGE;
|
|
}
|
|
|
|
private:
|
|
BHandler* fTarget;
|
|
};
|
|
|
|
|
|
class EscapeMessageFilter : public BMessageFilter {
|
|
public:
|
|
EscapeMessageFilter(uint32 command)
|
|
: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_KEY_DOWN),
|
|
fCommand(command)
|
|
{
|
|
}
|
|
|
|
virtual filter_result Filter(BMessage* message, BHandler** /*_target*/)
|
|
{
|
|
const char* bytes;
|
|
if (message->what != B_KEY_DOWN
|
|
|| message->FindString("bytes", &bytes) != B_OK
|
|
|| bytes[0] != B_ESCAPE)
|
|
return B_DISPATCH_MESSAGE;
|
|
|
|
Looper()->PostMessage(fCommand);
|
|
return B_SKIP_MESSAGE;
|
|
}
|
|
|
|
private:
|
|
uint32 fCommand;
|
|
};
|
|
|
|
|
|
CharacterWindow::CharacterWindow()
|
|
:
|
|
BWindow(BRect(100, 100, 700, 550), B_TRANSLATE_SYSTEM_NAME("CharacterMap"),
|
|
B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE
|
|
| B_AUTO_UPDATE_SIZE_LIMITS)
|
|
{
|
|
BMessage settings;
|
|
_LoadSettings(settings);
|
|
|
|
BRect frame;
|
|
if (settings.FindRect("window frame", &frame) == B_OK) {
|
|
MoveTo(frame.LeftTop());
|
|
ResizeTo(frame.Width(), frame.Height());
|
|
} else {
|
|
float scaling = be_plain_font->Size() / 12.0f;
|
|
ResizeTo(Frame().Width() * scaling, Frame().Height() * scaling);
|
|
CenterOnScreen();
|
|
}
|
|
|
|
// create GUI
|
|
BMenuBar* menuBar = new BMenuBar("menu");
|
|
|
|
fFilterControl = new BTextControl(B_TRANSLATE("Filter:"), NULL, NULL);
|
|
fFilterControl->SetModificationMessage(new BMessage(kMsgFilterChanged));
|
|
|
|
BButton* clearButton = new BButton("clear", B_TRANSLATE("Clear"),
|
|
new BMessage(kMsgClearFilter));
|
|
|
|
fUnicodeBlockView = new UnicodeBlockView("unicodeBlocks");
|
|
fUnicodeBlockView->SetSelectionMessage(
|
|
new BMessage(kMsgUnicodeBlockSelected));
|
|
|
|
BScrollView* unicodeScroller = new BScrollView("unicodeScroller",
|
|
fUnicodeBlockView, 0, false, true);
|
|
|
|
fCharacterView = new CharacterView("characters");
|
|
fCharacterView->SetTarget(this, kMsgCharacterChanged);
|
|
|
|
fGlyphView = new BStringView("glyph", "");
|
|
fGlyphView->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
|
|
fGlyphView->PreferredSize().Height()));
|
|
|
|
// TODO: have a context object shared by CharacterView/UnicodeBlockView
|
|
bool show;
|
|
if (settings.FindBool("show private blocks", &show) == B_OK) {
|
|
fCharacterView->ShowPrivateBlocks(show);
|
|
fUnicodeBlockView->ShowPrivateBlocks(show);
|
|
}
|
|
if (settings.FindBool("show contained blocks only", &show) == B_OK) {
|
|
fCharacterView->ShowContainedBlocksOnly(show);
|
|
fUnicodeBlockView->ShowContainedBlocksOnly(show);
|
|
}
|
|
|
|
const char* family;
|
|
const char* style;
|
|
BString displayName;
|
|
|
|
if (settings.FindString("font family", &family) == B_OK
|
|
&& settings.FindString("font style", &style) == B_OK) {
|
|
_SetFont(family, style);
|
|
displayName << family << " " << style;
|
|
} else {
|
|
font_family currentFontFamily;
|
|
font_style currentFontStyle;
|
|
fCharacterView->CharacterFont().GetFamilyAndStyle(¤tFontFamily,
|
|
¤tFontStyle);
|
|
displayName << currentFontFamily << " " << currentFontStyle;
|
|
}
|
|
|
|
int32 fontSize;
|
|
if (settings.FindInt32("font size", &fontSize) == B_OK) {
|
|
BFont font = fCharacterView->CharacterFont();
|
|
if (fontSize < kMinFontSize)
|
|
fontSize = kMinFontSize;
|
|
else if (fontSize > kMaxFontSize)
|
|
fontSize = kMaxFontSize;
|
|
font.SetSize(fontSize);
|
|
|
|
fCharacterView->SetCharacterFont(font);
|
|
fUnicodeBlockView->SetCharacterFont(font);
|
|
} else
|
|
fontSize = (int32)fCharacterView->CharacterFont().Size();
|
|
|
|
BScrollView* characterScroller = new BScrollView("characterScroller",
|
|
fCharacterView, 0, false, true);
|
|
|
|
fFontSizeSlider = new FontSizeSlider("fontSizeSlider",
|
|
displayName,
|
|
new BMessage(kMsgFontSizeChanged), kMinFontSize, kMaxFontSize);
|
|
fFontSizeSlider->SetValue(fontSize);
|
|
|
|
fCodeView = new BStringView("code", "-");
|
|
fCodeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
|
|
fCodeView->PreferredSize().Height()));
|
|
|
|
// Set minimum width for character pane to prevent UI
|
|
// from jumping when longer code strings are displayed.
|
|
// use 'w' character for sizing as it's likely the widest
|
|
// character for a Latin font. 40 characters is a little
|
|
// wider than needed so hopefully this covers other
|
|
// non-Latin fonts that may be wider.
|
|
BFont viewFont;
|
|
fCodeView->GetFont(&viewFont);
|
|
fCharacterView->SetExplicitMinSize(BSize(viewFont.StringWidth("w") * 40,
|
|
B_SIZE_UNSET));
|
|
|
|
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
|
|
.Add(menuBar)
|
|
.AddGroup(B_HORIZONTAL)
|
|
.SetInsets(B_USE_WINDOW_SPACING)
|
|
.AddGroup(B_VERTICAL)
|
|
.AddGroup(B_HORIZONTAL)
|
|
.Add(fFilterControl)
|
|
.Add(clearButton)
|
|
.End()
|
|
.Add(unicodeScroller)
|
|
.End()
|
|
.AddGroup(B_VERTICAL)
|
|
.Add(characterScroller)
|
|
.Add(fFontSizeSlider)
|
|
.AddGroup(B_HORIZONTAL)
|
|
.Add(fGlyphView)
|
|
.Add(fCodeView);
|
|
|
|
// Add menu
|
|
|
|
// "File" menu
|
|
BMenu* menu = new BMenu(B_TRANSLATE("File"));
|
|
BMenuItem* item;
|
|
|
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
|
|
new BMessage(B_QUIT_REQUESTED), 'Q'));
|
|
menu->SetTargetForItems(this);
|
|
menuBar->AddItem(menu);
|
|
|
|
menu = new BMenu(B_TRANSLATE("View"));
|
|
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show private blocks"),
|
|
new BMessage(kMsgPrivateBlocks)));
|
|
item->SetMarked(fCharacterView->IsShowingPrivateBlocks());
|
|
|
|
menu->AddItem(item = new BMenuItem(
|
|
B_TRANSLATE("Only show blocks contained in font"),
|
|
new BMessage(kMsgContainedBlocks)));
|
|
item->SetMarked(fCharacterView->IsShowingContainedBlocksOnly());
|
|
menuBar->AddItem(menu);
|
|
|
|
fFontMenu = _CreateFontMenu();
|
|
menuBar->AddItem(fFontMenu);
|
|
|
|
AddCommonFilter(new EscapeMessageFilter(kMsgClearFilter));
|
|
AddCommonFilter(new RedirectUpAndDownFilter(fUnicodeBlockView));
|
|
|
|
// TODO: why is this needed?
|
|
fUnicodeBlockView->SetTarget(this);
|
|
|
|
fFilterControl->MakeFocus();
|
|
|
|
fUnicodeBlockView->SelectBlockForCharacter(0);
|
|
}
|
|
|
|
|
|
CharacterWindow::~CharacterWindow()
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
CharacterWindow::MessageReceived(BMessage* message)
|
|
{
|
|
if (message->WasDropped()) {
|
|
const char* text;
|
|
ssize_t size;
|
|
uint32 c;
|
|
if (message->FindInt32("character", (int32*)&c) == B_OK) {
|
|
fCharacterView->ScrollToCharacter(c);
|
|
return;
|
|
} else if (message->FindData("text/plain", B_MIME_TYPE,
|
|
(const void**)&text, &size) == B_OK) {
|
|
fCharacterView->ScrollToCharacter(BUnicodeChar::FromUTF8(text));
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (message->what) {
|
|
case B_COPY:
|
|
PostMessage(message, fCharacterView);
|
|
break;
|
|
|
|
case kMsgUnicodeBlockSelected:
|
|
{
|
|
int32 index;
|
|
if (message->FindInt32("index", &index) != B_OK
|
|
|| index < 0)
|
|
break;
|
|
|
|
BlockListItem* item
|
|
= static_cast<BlockListItem*>(fUnicodeBlockView->ItemAt(index));
|
|
fCharacterView->ScrollToBlock(item->BlockIndex());
|
|
|
|
fFilterControl->MakeFocus();
|
|
break;
|
|
}
|
|
|
|
case kMsgCharacterChanged:
|
|
{
|
|
uint32 character;
|
|
if (message->FindInt32("character", (int32*)&character) != B_OK)
|
|
break;
|
|
|
|
char utf8[16];
|
|
CharacterView::UnicodeToUTF8(character, utf8, sizeof(utf8));
|
|
|
|
char utf8Hex[32];
|
|
CharacterView::UnicodeToUTF8Hex(character, utf8Hex,
|
|
sizeof(utf8Hex));
|
|
|
|
char text[128];
|
|
snprintf(text, sizeof(text), " %s: %#" B_PRIx32 " (%" B_PRId32 "), UTF-8: %s",
|
|
B_TRANSLATE("Code"), character, character, utf8Hex);
|
|
|
|
char glyph[20];
|
|
snprintf(glyph, sizeof(glyph), "'%s'", utf8);
|
|
|
|
fGlyphView->SetText(glyph);
|
|
fCodeView->SetText(text);
|
|
|
|
fUnicodeBlockView->SelectBlockForCharacter(character);
|
|
break;
|
|
}
|
|
|
|
case kMsgFontSelected:
|
|
{
|
|
BMenuItem* item;
|
|
|
|
if (message->FindPointer("source", (void**)&item) != B_OK)
|
|
break;
|
|
|
|
fSelectedFontItem->SetMarked(false);
|
|
|
|
// If it's the family menu, just select the first style
|
|
if (item->Submenu() != NULL) {
|
|
item->SetMarked(true);
|
|
item = item->Submenu()->ItemAt(0);
|
|
}
|
|
|
|
if (item != NULL) {
|
|
item->SetMarked(true);
|
|
fSelectedFontItem = item;
|
|
|
|
_SetFont(item->Menu()->Name(), item->Label());
|
|
|
|
BString displayName;
|
|
displayName << item->Menu()->Name() << " " << item->Label();
|
|
|
|
fFontSizeSlider->SetLabel(displayName);
|
|
|
|
item = item->Menu()->Superitem();
|
|
item->SetMarked(true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kMsgFontSizeChanged:
|
|
{
|
|
int32 size = fFontSizeSlider->Value();
|
|
if (size < kMinFontSize)
|
|
size = kMinFontSize;
|
|
else if (size > kMaxFontSize)
|
|
size = kMaxFontSize;
|
|
|
|
BFont font = fCharacterView->CharacterFont();
|
|
font.SetSize(size);
|
|
fCharacterView->SetCharacterFont(font);
|
|
fUnicodeBlockView->SetCharacterFont(font);
|
|
break;
|
|
}
|
|
|
|
case kMsgPrivateBlocks:
|
|
{
|
|
BMenuItem* item;
|
|
if (message->FindPointer("source", (void**)&item) != B_OK
|
|
|| item == NULL)
|
|
break;
|
|
|
|
item->SetMarked(!item->IsMarked());
|
|
|
|
fCharacterView->ShowPrivateBlocks(item->IsMarked());
|
|
fUnicodeBlockView->ShowPrivateBlocks(item->IsMarked());
|
|
break;
|
|
}
|
|
|
|
case kMsgContainedBlocks:
|
|
{
|
|
BMenuItem* item;
|
|
if (message->FindPointer("source", (void**)&item) != B_OK
|
|
|| item == NULL)
|
|
break;
|
|
|
|
item->SetMarked(!item->IsMarked());
|
|
|
|
fCharacterView->ShowContainedBlocksOnly(item->IsMarked());
|
|
fUnicodeBlockView->ShowContainedBlocksOnly(item->IsMarked());
|
|
break;
|
|
}
|
|
|
|
case kMsgFilterChanged:
|
|
fUnicodeBlockView->SetFilter(fFilterControl->Text());
|
|
fUnicodeBlockView->Select(0);
|
|
break;
|
|
|
|
case kMsgClearFilter:
|
|
fFilterControl->SetText("");
|
|
fFilterControl->MakeFocus();
|
|
break;
|
|
|
|
default:
|
|
BWindow::MessageReceived(message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterWindow::QuitRequested()
|
|
{
|
|
_SaveSettings();
|
|
be_app->PostMessage(B_QUIT_REQUESTED);
|
|
return true;
|
|
}
|
|
|
|
|
|
status_t
|
|
CharacterWindow::_OpenSettings(BFile& file, uint32 mode)
|
|
{
|
|
BPath path;
|
|
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
|
|
return B_ERROR;
|
|
|
|
path.Append("CharacterMap settings");
|
|
|
|
return file.SetTo(path.Path(), mode);
|
|
}
|
|
|
|
|
|
status_t
|
|
CharacterWindow::_LoadSettings(BMessage& settings)
|
|
{
|
|
BFile file;
|
|
status_t status = _OpenSettings(file, B_READ_ONLY);
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
return settings.Unflatten(&file);
|
|
}
|
|
|
|
|
|
status_t
|
|
CharacterWindow::_SaveSettings()
|
|
{
|
|
BFile file;
|
|
status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE
|
|
| B_ERASE_FILE);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
BMessage settings('chrm');
|
|
status = settings.AddRect("window frame", Frame());
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
if (status == B_OK) {
|
|
status = settings.AddBool("show private blocks",
|
|
fCharacterView->IsShowingPrivateBlocks());
|
|
}
|
|
if (status == B_OK) {
|
|
status = settings.AddBool("show contained blocks only",
|
|
fCharacterView->IsShowingContainedBlocksOnly());
|
|
}
|
|
|
|
if (status == B_OK) {
|
|
BFont font = fCharacterView->CharacterFont();
|
|
status = settings.AddInt32("font size", font.Size());
|
|
|
|
font_family family;
|
|
font_style style;
|
|
if (status == B_OK)
|
|
font.GetFamilyAndStyle(&family, &style);
|
|
if (status == B_OK)
|
|
status = settings.AddString("font family", family);
|
|
if (status == B_OK)
|
|
status = settings.AddString("font style", style);
|
|
}
|
|
|
|
if (status == B_OK)
|
|
status = settings.Flatten(&file);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterWindow::_SetFont(const char* family, const char* style)
|
|
{
|
|
BFont font = fCharacterView->CharacterFont();
|
|
font.SetFamilyAndStyle(family, style);
|
|
|
|
fCharacterView->SetCharacterFont(font);
|
|
fUnicodeBlockView->SetCharacterFont(font);
|
|
fGlyphView->SetFont(&font, B_FONT_FAMILY_AND_STYLE);
|
|
}
|
|
|
|
|
|
BMenu*
|
|
CharacterWindow::_CreateFontMenu()
|
|
{
|
|
BMenu* menu = new BMenu(B_TRANSLATE("Font"));
|
|
_UpdateFontMenu(menu);
|
|
|
|
return menu;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterWindow::_UpdateFontMenu(BMenu* menu)
|
|
{
|
|
BMenuItem* item;
|
|
|
|
while (menu->CountItems() > 0) {
|
|
item = menu->RemoveItem(static_cast<int32>(0));
|
|
delete(item);
|
|
}
|
|
|
|
font_family currentFamily;
|
|
font_style currentStyle;
|
|
fCharacterView->CharacterFont().GetFamilyAndStyle(¤tFamily,
|
|
¤tStyle);
|
|
|
|
int32 numFamilies = count_font_families();
|
|
|
|
menu->SetRadioMode(true);
|
|
|
|
for (int32 i = 0; i < numFamilies; i++) {
|
|
font_family family;
|
|
if (get_font_family(i, &family) == B_OK) {
|
|
BMenu* subMenu = new BMenu(family);
|
|
menu->AddItem(new BMenuItem(subMenu,
|
|
new BMessage(kMsgFontSelected)));
|
|
|
|
int numStyles = count_font_styles(family);
|
|
for (int32 j = 0; j < numStyles; j++) {
|
|
font_style style;
|
|
uint32 flags;
|
|
if (get_font_style(family, j, &style, &flags) == B_OK) {
|
|
item = new BMenuItem(style, new BMessage(kMsgFontSelected));
|
|
subMenu->AddItem(item);
|
|
|
|
if (!strcmp(family, currentFamily)
|
|
&& !strcmp(style, currentStyle)) {
|
|
fSelectedFontItem = item;
|
|
item->SetMarked(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
item = menu->FindItem(currentFamily);
|
|
item->SetMarked(true);
|
|
}
|
|
|
|
|
|
void
|
|
CharacterWindow::MenusBeginning()
|
|
{
|
|
if (update_font_families(false) == true)
|
|
_UpdateFontMenu(fFontMenu);
|
|
}
|