1/*
2 * Copyright 2001-2022 Haiku, Inc. All rights reserved.
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 *		John Scipione, jscipione@gmail.com
12 */
13
14
15#include "FontSelectionView.h"
16
17#include <Box.h>
18#include <Catalog.h>
19#include <ControlLook.h>
20#include <GroupLayoutBuilder.h>
21#include <LayoutItem.h>
22#include <Locale.h>
23#include <MenuField.h>
24#include <MenuItem.h>
25#include <PopUpMenu.h>
26#include <String.h>
27#include <TextView.h>
28#include <Spinner.h>
29
30#include <FontPrivate.h>
31
32#include <stdio.h>
33
34
35#undef B_TRANSLATION_CONTEXT
36#define B_TRANSLATION_CONTEXT "Font Selection view"
37
38
39#define INSTANT_UPDATE
40	// if defined, the system font will be updated immediately, and not
41	// only on exit
42
43static const float kMinSize = 8.0;
44static const float kMaxSize = 72.0;
45
46static const char* kPreviewText = B_TRANSLATE_COMMENT(
47	"The quick brown fox jumps over the lazy dog.",
48	"Don't translate this literally ! Use a phrase showing all chars "
49	"from A to Z.");
50
51
52// private font API
53extern void _set_system_font_(const char *which, font_family family,
54	font_style style, float size);
55extern status_t _get_system_default_font_(const char* which,
56	font_family family, font_style style, float* _size);
57
58
59#ifdef B_BEOS_VERSION_DANO
60// this call only exists under R5
61void
62_set_system_font_(const char *which, font_family family,
63	font_style style, float size)
64{
65	puts("you don't have _set_system_font_()");
66}
67#endif
68
69#if !defined(HAIKU_TARGET_PLATFORM_HAIKU) && !defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST)
70// this call only exists under Haiku (and the test environment)
71status_t
72_get_system_default_font_(const char* which, font_family family,
73	font_style style, float* _size)
74{
75	puts("you don't have _get_system_default_font_()");
76	return B_ERROR;
77}
78#endif
79
80
81//	#pragma mark -
82
83
84FontSelectionView::FontSelectionView(const char* name,
85	const char* label, const BFont* currentFont)
86	:
87	BView(name, B_WILL_DRAW),
88	fMessageTarget(this)
89{
90	if (currentFont == NULL) {
91		if (!strcmp(Name(), "plain"))
92			fCurrentFont = *be_plain_font;
93		else if (!strcmp(Name(), "bold"))
94			fCurrentFont = *be_bold_font;
95		else if (!strcmp(Name(), "fixed"))
96			fCurrentFont = *be_fixed_font;
97		else if (!strcmp(Name(), "menu")) {
98			menu_info info;
99			get_menu_info(&info);
100
101			fCurrentFont.SetFamilyAndStyle(info.f_family, info.f_style);
102			fCurrentFont.SetSize(info.font_size);
103		}
104	} else
105		fCurrentFont = *currentFont;
106
107	fSavedFont = fCurrentFont;
108
109	fFontsMenu = new BPopUpMenu("font menu");
110
111	// font menu
112	fFontsMenuField = new BMenuField("fonts", label, fFontsMenu);
113	fFontsMenuField->SetAlignment(B_ALIGN_RIGHT);
114
115	// font size
116	BMessage* fontSizeMessage = new BMessage(kMsgSetSize);
117	fontSizeMessage->AddString("name", Name());
118
119	fFontSizeSpinner = new BSpinner("font size", B_TRANSLATE("Size:"),
120		fontSizeMessage);
121
122	fFontSizeSpinner->SetRange(kMinSize, kMaxSize);
123	fFontSizeSpinner->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
124		B_SIZE_UNSET));
125
126	// preview
127	// A string view would be enough if only it handled word-wrap.
128	fPreviewTextView = new BTextView("preview text");
129	fPreviewTextView->SetFontAndColor(&fCurrentFont);
130	fPreviewTextView->SetText(kPreviewText);
131	fPreviewTextView->MakeResizable(false);
132	fPreviewTextView->SetWordWrap(true);
133	fPreviewTextView->MakeEditable(false);
134	fPreviewTextView->MakeSelectable(false);
135	fPreviewTextView->SetInsets(0, 0, 0, 0);
136	fPreviewTextView->SetViewUIColor(ViewUIColor());
137	fPreviewTextView->SetLowUIColor(LowUIColor());
138	fPreviewTextView->SetHighUIColor(HighUIColor());
139
140	// determine initial line count using fCurrentFont
141	fPreviewTextWidth = be_control_look->DefaultLabelSpacing() * 58.0f;
142	float lineCount = ceilf(fCurrentFont.StringWidth(kPreviewText)
143		/ fPreviewTextWidth);
144	fPreviewTextView->SetExplicitSize(BSize(fPreviewTextWidth,
145		fPreviewTextView->LineHeight(0) * lineCount));
146
147	// box around preview
148	fPreviewBox = new BBox("preview box", B_WILL_DRAW | B_FRAME_EVENTS);
149	fPreviewBox->AddChild(BGroupLayoutBuilder(B_VERTICAL)
150		.AddGroup(B_HORIZONTAL, 0)
151			.Add(fPreviewTextView)
152			.AddGlue()
153			.End()
154		.SetInsets(B_USE_SMALL_SPACING, B_USE_SMALL_SPACING,
155			B_USE_SMALL_SPACING, B_USE_SMALL_SPACING)
156		.TopView()
157	);
158
159	_SelectCurrentSize();
160}
161
162
163FontSelectionView::~FontSelectionView()
164{
165#ifndef INSTANT_UPDATE
166	_UpdateSystemFont();
167#endif
168}
169
170
171void
172FontSelectionView::SetTarget(BHandler* messageTarget)
173{
174	fMessageTarget = messageTarget;
175	fFontSizeSpinner->SetTarget(messageTarget);
176}
177
178
179void
180FontSelectionView::MessageReceived(BMessage* msg)
181{
182	switch (msg->what) {
183		case kMsgSetSize:
184		{
185			int32 size = fFontSizeSpinner->Value();
186			if (size == fCurrentFont.Size())
187				break;
188
189			fCurrentFont.SetSize(size);
190			_UpdateFontPreview();
191			break;
192		}
193
194		case kMsgSetFamily:
195		{
196			const char* family;
197			if (msg->FindString("family", &family) != B_OK)
198				break;
199
200			font_style style;
201			fCurrentFont.GetFamilyAndStyle(NULL, &style);
202
203			BMenuItem* familyItem = fFontsMenu->FindItem(family);
204			if (familyItem != NULL) {
205				_SelectCurrentFont(false);
206
207				BMenuItem* item = familyItem->Submenu()->FindItem(style);
208				if (item == NULL)
209					item = familyItem->Submenu()->ItemAt(0);
210
211				if (item != NULL) {
212					item->SetMarked(true);
213					fCurrentFont.SetFamilyAndStyle(family, item->Label());
214					_UpdateFontPreview();
215				}
216			}
217			break;
218		}
219
220		case kMsgSetStyle:
221		{
222			const char* family;
223			const char* style;
224			if (msg->FindString("family", &family) != B_OK
225				|| msg->FindString("style", &style) != B_OK)
226				break;
227
228			BMenuItem *familyItem = fFontsMenu->FindItem(family);
229			if (!familyItem)
230				break;
231
232			_SelectCurrentFont(false);
233			familyItem->SetMarked(true);
234
235			fCurrentFont.SetFamilyAndStyle(family, style);
236			_UpdateFontPreview();
237			break;
238		}
239
240		default:
241			BView::MessageReceived(msg);
242	}
243}
244
245
246BView*
247FontSelectionView::GetPreviewBox() const
248{
249	return fPreviewBox;
250}
251
252
253BView*
254FontSelectionView::GetFontSizeSpinner() const
255{
256	return fFontSizeSpinner;
257}
258
259
260BLayoutItem*
261FontSelectionView::CreateFontsLabelLayoutItem() const
262{
263	return fFontsMenuField->CreateLabelLayoutItem();
264}
265
266
267BLayoutItem*
268FontSelectionView::CreateFontsMenuBarLayoutItem() const
269{
270	return fFontsMenuField->CreateMenuBarLayoutItem();
271}
272
273
274void
275FontSelectionView::_SelectCurrentFont(bool select)
276{
277	font_family family;
278	font_style style;
279	fCurrentFont.GetFamilyAndStyle(&family, &style);
280
281	BMenuItem *item = fFontsMenu->FindItem(family);
282	if (item != NULL) {
283		item->SetMarked(select);
284
285		if (item->Submenu() != NULL) {
286			item = item->Submenu()->FindItem(style);
287			if (item != NULL)
288				item->SetMarked(select);
289		}
290	}
291}
292
293
294void
295FontSelectionView::_SelectCurrentSize()
296{
297	fFontSizeSpinner->SetValue((int32)fCurrentFont.Size());
298}
299
300
301void
302FontSelectionView::_UpdateFontPreview()
303{
304#ifdef INSTANT_UPDATE
305	_UpdateSystemFont();
306#endif
307
308	fPreviewTextView->SetFontAndColor(&fCurrentFont);
309	fPreviewTextView->SetExplicitSize(BSize(fPreviewTextWidth,
310		fPreviewTextView->LineHeight(0) * fPreviewTextView->CountLines()));
311}
312
313
314void
315FontSelectionView::_UpdateSystemFont()
316{
317	font_family family;
318	font_style style;
319	fCurrentFont.GetFamilyAndStyle(&family, &style);
320
321	if (strcmp(Name(), "menu") == 0) {
322		// The menu font is not handled as a system font
323		menu_info info;
324		get_menu_info(&info);
325
326		strlcpy(info.f_family, (const char*)family, B_FONT_FAMILY_LENGTH);
327		strlcpy(info.f_style, (const char*)style, B_FONT_STYLE_LENGTH);
328		info.font_size = fCurrentFont.Size();
329
330		set_menu_info(&info);
331	} else
332		_set_system_font_(Name(), family, style, fCurrentFont.Size());
333}
334
335
336void
337FontSelectionView::SetDefaults()
338{
339	font_family family;
340	font_style style;
341	float size;
342	const char* fontName;
343
344	if (strcmp(Name(), "menu") == 0)
345		fontName = "plain";
346	else
347		fontName = Name();
348
349	if (_get_system_default_font_(fontName, family, style, &size) != B_OK) {
350		Revert();
351		return;
352	}
353
354	BFont defaultFont;
355	defaultFont.SetFamilyAndStyle(family, style);
356	defaultFont.SetSize(size);
357
358	if (defaultFont == fCurrentFont)
359		return;
360
361	_SelectCurrentFont(false);
362
363	fCurrentFont = defaultFont;
364	_UpdateFontPreview();
365
366	_SelectCurrentFont(true);
367	_SelectCurrentSize();
368}
369
370
371void
372FontSelectionView::Revert()
373{
374	if (!IsRevertable())
375		return;
376
377	_SelectCurrentFont(false);
378
379	fCurrentFont = fSavedFont;
380	_UpdateFontPreview();
381
382	_SelectCurrentFont(true);
383	_SelectCurrentSize();
384}
385
386
387bool
388FontSelectionView::IsDefaultable()
389{
390	font_family defaultFamily;
391	font_style defaultStyle;
392	float defaultSize;
393	const char* fontName;
394
395	if (strcmp(Name(), "menu") == 0)
396		fontName = "plain";
397	else
398		fontName = Name();
399
400	if (_get_system_default_font_(fontName, defaultFamily, defaultStyle,
401		&defaultSize) != B_OK) {
402		return false;
403	}
404
405	font_family currentFamily;
406	font_style currentStyle;
407	float currentSize;
408
409	fCurrentFont.GetFamilyAndStyle(&currentFamily, &currentStyle);
410	currentSize = fCurrentFont.Size();
411
412	return strcmp(currentFamily, defaultFamily) != 0
413		|| strcmp(currentStyle, defaultStyle) != 0
414		|| currentSize != defaultSize;
415}
416
417
418bool
419FontSelectionView::IsRevertable()
420{
421	return fCurrentFont != fSavedFont;
422}
423
424
425void
426FontSelectionView::UpdateFontsMenu()
427{
428	int32 numFamilies = count_font_families();
429
430	fFontsMenu->RemoveItems(0, fFontsMenu->CountItems(), true);
431	BFont font;
432	fFontsMenu->GetFont(&font);
433
434	font_family currentFamily;
435	font_style currentStyle;
436	fCurrentFont.GetFamilyAndStyle(&currentFamily, &currentStyle);
437
438	for (int32 i = 0; i < numFamilies; i++) {
439		font_family family;
440		uint32 flags;
441		if (get_font_family(i, &family, &flags) != B_OK)
442			continue;
443
444		// if we're setting the fixed font, we only want to show fixed and
445		// full-and-half-fixed fonts
446		if (strcmp(Name(), "fixed") == 0
447			&& (flags
448				& (B_IS_FIXED | B_PRIVATE_FONT_IS_FULL_AND_HALF_FIXED)) == 0) {
449			continue;
450		}
451
452		BMenu* stylesMenu = new BMenu(family);
453		stylesMenu->SetRadioMode(true);
454		stylesMenu->SetFont(&font);
455
456		BMessage* message = new BMessage(kMsgSetFamily);
457		message->AddString("family", family);
458		message->AddString("name", Name());
459
460		BMenuItem* familyItem = new BMenuItem(stylesMenu, message);
461		fFontsMenu->AddItem(familyItem);
462
463		int32 numStyles = count_font_styles(family);
464
465		for (int32 j = 0; j < numStyles; j++) {
466			font_style style;
467			if (get_font_style(family, j, &style, &flags) != B_OK)
468				continue;
469
470			message = new BMessage(kMsgSetStyle);
471			message->AddString("family", (char*)family);
472			message->AddString("style", (char*)style);
473			message->AddString("name", Name());
474
475			BMenuItem* item = new BMenuItem(style, message);
476
477			if (!strcmp(style, currentStyle)
478				&& !strcmp(family, currentFamily)) {
479				item->SetMarked(true);
480				familyItem->SetMarked(true);
481			}
482			stylesMenu->AddItem(item);
483		}
484
485		stylesMenu->SetTargetForItems(fMessageTarget);
486	}
487
488	fFontsMenu->SetTargetForItems(fMessageTarget);
489}
490