1/*
2 * Copyright 2001-2010, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Mark Hogben
7 *		DarkWyrm <bpmagic@columbus.rr.com>
8 *		Axel D��rfler, axeld@pinc-software.de
9 *		Philippe Saint-Pierre, stpere@gmail.com
10 *		Stephan A��mus <superstippi@gmx.de>
11 */
12
13#include "FontSelectionView.h"
14
15#include "private/interface/FontPrivate.h"
16
17#include <Box.h>
18#include <Catalog.h>
19#include <Locale.h>
20#include <Looper.h>
21#include <MenuField.h>
22#include <MenuItem.h>
23#include <PopUpMenu.h>
24#include <String.h>
25#include <StringView.h>
26#include <LayoutItem.h>
27#include <GroupLayoutBuilder.h>
28
29#include <stdio.h>
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "Font Selection view"
33
34
35static const float kMinSize = 8.0;
36static const float kMaxSize = 18.0;
37
38static const int32 kMsgSetFamily = 'fmly';
39static const int32 kMsgSetStyle = 'styl';
40static const int32 kMsgSetSize = 'size';
41
42
43//	#pragma mark -
44
45
46FontSelectionView::FontSelectionView(const char* name, const char* label,
47		bool separateStyles, const BFont* currentFont)
48	:
49	BHandler(name),
50	fMessage(NULL),
51	fTarget(NULL)
52{
53	if (currentFont == NULL)
54		fCurrentFont = _DefaultFont();
55	else
56		fCurrentFont = *currentFont;
57
58	fSavedFont = fCurrentFont;
59
60	fSizesMenu = new BPopUpMenu("size menu");
61	fFontsMenu = new BPopUpMenu("font menu");
62
63	// font menu
64	fFontsMenuField = new BMenuField("fonts", label, fFontsMenu, B_WILL_DRAW);
65	fFontsMenuField->SetAlignment(B_ALIGN_RIGHT);
66
67	// styles menu, if desired
68	if (separateStyles) {
69		fStylesMenu = new BPopUpMenu("styles menu");
70		fStylesMenuField = new BMenuField("styles", B_TRANSLATE("Style:"),
71			fStylesMenu, B_WILL_DRAW);
72	} else {
73		fStylesMenu = NULL;
74		fStylesMenuField = NULL;
75	}
76
77	// size menu
78	fSizesMenuField = new BMenuField("size", B_TRANSLATE("Size:"), fSizesMenu,
79		B_WILL_DRAW);
80	fSizesMenuField->SetAlignment(B_ALIGN_RIGHT);
81
82	// preview
83	fPreviewText = new BStringView("preview text",
84		B_TRANSLATE_COMMENT("The quick brown fox jumps over the lazy dog.",
85		"Don't translate this literally ! Use a phrase showing all "
86		"chars from A to Z."));
87
88	fPreviewText->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
89		B_SIZE_UNLIMITED));
90	fPreviewText->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.65);
91
92	fPreviewBox = new BBox("preview box", B_WILL_DRAW | B_FRAME_EVENTS);
93	fPreviewBox->AddChild(BGroupLayoutBuilder(B_VERTICAL, B_USE_HALF_ITEM_SPACING)
94		.Add(fPreviewText)
95		.SetInsets(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING,
96			B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING)
97		.TopView()
98	);
99	_UpdateFontPreview();
100}
101
102
103FontSelectionView::~FontSelectionView()
104{
105	// Some controls may not have been attached...
106	if (!fPreviewText->Window())
107		delete fPreviewText;
108	if (!fSizesMenuField->Window())
109		delete fSizesMenuField;
110	if (fStylesMenuField && !fStylesMenuField->Window())
111		delete fStylesMenuField;
112	if (!fFontsMenuField->Window())
113		delete fFontsMenuField;
114
115	delete fMessage;
116}
117
118
119void
120FontSelectionView::AttachedToLooper()
121{
122	_BuildSizesMenu();
123	UpdateFontsMenu();
124}
125
126
127void
128FontSelectionView::MessageReceived(BMessage* message)
129{
130	switch (message->what) {
131		case kMsgSetSize:
132		{
133			int32 size;
134			if (message->FindInt32("size", &size) != B_OK
135				|| size == fCurrentFont.Size())
136				break;
137
138			fCurrentFont.SetSize(size);
139			_UpdateFontPreview();
140			_Invoke();
141			break;
142		}
143
144		case kMsgSetFamily:
145		{
146			const char* family;
147			if (message->FindString("family", &family) != B_OK)
148				break;
149
150			font_style style;
151			fCurrentFont.GetFamilyAndStyle(NULL, &style);
152
153			BMenuItem* familyItem = fFontsMenu->FindItem(family);
154			if (familyItem != NULL) {
155				_SelectCurrentFont(false);
156
157				BMenuItem* styleItem;
158				if (fStylesMenuField != NULL)
159					styleItem = fStylesMenuField->Menu()->FindMarked();
160				else {
161					styleItem = familyItem->Submenu()->FindItem(style);
162					if (styleItem == NULL)
163						styleItem = familyItem->Submenu()->ItemAt(0);
164				}
165
166				if (styleItem != NULL) {
167					styleItem->SetMarked(true);
168					fCurrentFont.SetFamilyAndStyle(family, styleItem->Label());
169					_UpdateFontPreview();
170				}
171				if (fStylesMenuField != NULL)
172					_AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu());
173			}
174
175			_Invoke();
176			break;
177		}
178
179		case kMsgSetStyle:
180		{
181			const char* family;
182			const char* style;
183			if (message->FindString("family", &family) != B_OK
184				|| message->FindString("style", &style) != B_OK)
185				break;
186
187			BMenuItem *familyItem = fFontsMenu->FindItem(family);
188			if (!familyItem)
189				break;
190
191			_SelectCurrentFont(false);
192			familyItem->SetMarked(true);
193
194			fCurrentFont.SetFamilyAndStyle(family, style);
195			_UpdateFontPreview();
196			_Invoke();
197			break;
198		}
199
200		default:
201			BHandler::MessageReceived(message);
202	}
203}
204
205
206void
207FontSelectionView::SetMessage(BMessage* message)
208{
209	delete fMessage;
210	fMessage = message;
211}
212
213
214void
215FontSelectionView::SetTarget(BHandler* target)
216{
217	fTarget = target;
218}
219
220
221// #pragma mark -
222
223
224void
225FontSelectionView::SetFont(const BFont& font, float size)
226{
227	BFont resizedFont(font);
228	resizedFont.SetSize(size);
229	SetFont(resizedFont);
230}
231
232
233void
234FontSelectionView::SetFont(const BFont& font)
235{
236	if (font == fCurrentFont && font == fSavedFont)
237		return;
238
239	_SelectCurrentFont(false);
240	fSavedFont = fCurrentFont = font;
241	_UpdateFontPreview();
242
243	_SelectCurrentFont(true);
244	_SelectCurrentSize(true);
245}
246
247
248void
249FontSelectionView::SetSize(float size)
250{
251	SetFont(fCurrentFont, size);
252}
253
254
255const BFont&
256FontSelectionView::Font() const
257{
258	return fCurrentFont;
259}
260
261
262void
263FontSelectionView::SetDefaults()
264{
265	BFont defaultFont = _DefaultFont();
266	if (defaultFont == fCurrentFont)
267		return;
268
269	_SelectCurrentFont(false);
270
271	fCurrentFont = defaultFont;
272	_UpdateFontPreview();
273
274	_SelectCurrentFont(true);
275	_SelectCurrentSize(true);
276}
277
278
279void
280FontSelectionView::Revert()
281{
282	if (!IsRevertable())
283		return;
284
285	_SelectCurrentFont(false);
286
287	fCurrentFont = fSavedFont;
288	_UpdateFontPreview();
289
290	_SelectCurrentFont(true);
291	_SelectCurrentSize(true);
292}
293
294
295bool
296FontSelectionView::IsDefaultable()
297{
298	return fCurrentFont != _DefaultFont();
299}
300
301
302bool
303FontSelectionView::IsRevertable()
304{
305	return fCurrentFont != fSavedFont;
306}
307
308
309void
310FontSelectionView::UpdateFontsMenu()
311{
312	int32 numFamilies = count_font_families();
313
314	fFontsMenu->RemoveItems(0, fFontsMenu->CountItems(), true);
315
316	BFont font = fCurrentFont;
317
318	font_family currentFamily;
319	font_style currentStyle;
320	font.GetFamilyAndStyle(&currentFamily, &currentStyle);
321
322	for (int32 i = 0; i < numFamilies; i++) {
323		font_family family;
324		uint32 flags;
325		if (get_font_family(i, &family, &flags) != B_OK)
326			continue;
327
328		// if we're setting the fixed font, we only want to show fixed and
329		// full-and-half-fixed fonts
330		if (strcmp(Name(), "fixed") == 0
331			&& (flags & (B_IS_FIXED | B_PRIVATE_FONT_IS_FULL_AND_HALF_FIXED)) == 0) {
332			continue;
333		}
334
335		font.SetFamilyAndFace(family, B_REGULAR_FACE);
336
337		BMessage* message = new BMessage(kMsgSetFamily);
338		message->AddString("family", family);
339		message->AddString("name", Name());
340
341		BMenuItem* familyItem;
342		if (fStylesMenuField != NULL) {
343			familyItem = new BMenuItem(family, message);
344		} else {
345			// Each family item has a submenu with all styles for that font.
346			BMenu* stylesMenu = new BMenu(family);
347			_AddStylesToMenu(font, stylesMenu);
348			familyItem = new BMenuItem(stylesMenu, message);
349		}
350
351		familyItem->SetMarked(strcmp(family, currentFamily) == 0);
352		fFontsMenu->AddItem(familyItem);
353		familyItem->SetTarget(this);
354	}
355
356	// Separate styles menu for only the current font.
357	if (fStylesMenuField != NULL)
358		_AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu());
359}
360
361
362// #pragma mark - private
363
364
365BLayoutItem*
366FontSelectionView::CreateSizesLabelLayoutItem()
367{
368	return fSizesMenuField->CreateLabelLayoutItem();
369}
370
371
372BLayoutItem*
373FontSelectionView::CreateSizesMenuBarLayoutItem()
374{
375	return fSizesMenuField->CreateMenuBarLayoutItem();
376}
377
378
379BLayoutItem*
380FontSelectionView::CreateFontsLabelLayoutItem()
381{
382	return fFontsMenuField->CreateLabelLayoutItem();
383}
384
385
386BLayoutItem*
387FontSelectionView::CreateFontsMenuBarLayoutItem()
388{
389	return fFontsMenuField->CreateMenuBarLayoutItem();
390}
391
392
393BLayoutItem*
394FontSelectionView::CreateStylesLabelLayoutItem()
395{
396	if (fStylesMenuField)
397		return fStylesMenuField->CreateLabelLayoutItem();
398	return NULL;
399}
400
401
402BLayoutItem*
403FontSelectionView::CreateStylesMenuBarLayoutItem()
404{
405	if (fStylesMenuField)
406		return fStylesMenuField->CreateMenuBarLayoutItem();
407	return NULL;
408}
409
410
411BView*
412FontSelectionView::PreviewBox() const
413{
414	return fPreviewBox;
415}
416
417
418// #pragma mark - private
419
420
421void
422FontSelectionView::_Invoke()
423{
424	if (fTarget != NULL && fTarget->Looper() != NULL && fMessage != NULL) {
425		BMessage message(*fMessage);
426		fTarget->Looper()->PostMessage(&message, fTarget);
427	}
428}
429
430
431BFont
432FontSelectionView::_DefaultFont() const
433{
434	if (strcmp(Name(), "bold") == 0)
435		return *be_bold_font;
436	if (strcmp(Name(), "fixed") == 0)
437		return *be_fixed_font;
438	else
439		return *be_plain_font;
440}
441
442
443void
444FontSelectionView::_SelectCurrentFont(bool select)
445{
446	font_family family;
447	font_style style;
448	fCurrentFont.GetFamilyAndStyle(&family, &style);
449
450	BMenuItem *item = fFontsMenu->FindItem(family);
451	if (item != NULL) {
452		item->SetMarked(select);
453
454		if (item->Submenu() != NULL) {
455			item = item->Submenu()->FindItem(style);
456			if (item != NULL)
457				item->SetMarked(select);
458		}
459	}
460}
461
462
463void
464FontSelectionView::_SelectCurrentSize(bool select)
465{
466	char label[16];
467	snprintf(label, sizeof(label), "%" B_PRId32, (int32)fCurrentFont.Size());
468
469	BMenuItem* item = fSizesMenu->FindItem(label);
470	if (item != NULL)
471		item->SetMarked(select);
472}
473
474
475void
476FontSelectionView::_UpdateFontPreview()
477{
478	fPreviewText->SetFont(&fCurrentFont);
479}
480
481
482void
483FontSelectionView::_BuildSizesMenu()
484{
485	const int32 sizes[] = {7, 8, 9, 10, 11, 12, 13, 14, 18, 21, 24, 0};
486
487	// build size menu
488	for (int32 i = 0; sizes[i]; i++) {
489		int32 size = sizes[i];
490		if (size < kMinSize || size > kMaxSize)
491			continue;
492
493		char label[32];
494		snprintf(label, sizeof(label), "%" B_PRId32, size);
495
496		BMessage* message = new BMessage(kMsgSetSize);
497		message->AddInt32("size", size);
498		message->AddString("name", Name());
499
500		BMenuItem* item = new BMenuItem(label, message);
501		if (size == fCurrentFont.Size())
502			item->SetMarked(true);
503
504		fSizesMenu->AddItem(item);
505		item->SetTarget(this);
506	}
507}
508
509
510void
511FontSelectionView::_AddStylesToMenu(const BFont& font, BMenu* stylesMenu) const
512{
513	stylesMenu->RemoveItems(0, stylesMenu->CountItems(), true);
514	stylesMenu->SetRadioMode(true);
515
516	font_family family;
517	font_style style;
518	font.GetFamilyAndStyle(&family, &style);
519	BString currentStyle(style);
520
521	int32 numStyles = count_font_styles(family);
522
523	for (int32 j = 0; j < numStyles; j++) {
524		if (get_font_style(family, j, &style) != B_OK)
525			continue;
526
527		BMessage* message = new BMessage(kMsgSetStyle);
528		message->AddString("family", (char*)family);
529		message->AddString("style", (char*)style);
530
531		BMenuItem* item = new BMenuItem(style, message);
532		item->SetMarked(currentStyle == style);
533
534		stylesMenu->AddItem(item);
535		item->SetTarget(this);
536	}
537}
538
539