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