xref: /haiku/src/preferences/appearance/FontSelectionView.cpp (revision 7b3e89c0944ae1efa9a8fc66c7303874b7a344b2)
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 
43 static const float kMinSize = 8.0;
44 static const float kMaxSize = 72.0;
45 
46 static 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
53 extern void _set_system_font_(const char *which, font_family family,
54 	font_style style, float size);
55 extern 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
61 void
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)
71 status_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 
84 FontSelectionView::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 
163 FontSelectionView::~FontSelectionView()
164 {
165 #ifndef INSTANT_UPDATE
166 	_UpdateSystemFont();
167 #endif
168 }
169 
170 
171 void
172 FontSelectionView::SetTarget(BHandler* messageTarget)
173 {
174 	fMessageTarget = messageTarget;
175 	fFontSizeSpinner->SetTarget(messageTarget);
176 }
177 
178 
179 void
180 FontSelectionView::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 
246 BView*
247 FontSelectionView::GetPreviewBox() const
248 {
249 	return fPreviewBox;
250 }
251 
252 
253 BView*
254 FontSelectionView::GetFontSizeSpinner() const
255 {
256 	return fFontSizeSpinner;
257 }
258 
259 
260 BLayoutItem*
261 FontSelectionView::CreateFontsLabelLayoutItem() const
262 {
263 	return fFontsMenuField->CreateLabelLayoutItem();
264 }
265 
266 
267 BLayoutItem*
268 FontSelectionView::CreateFontsMenuBarLayoutItem() const
269 {
270 	return fFontsMenuField->CreateMenuBarLayoutItem();
271 }
272 
273 
274 void
275 FontSelectionView::_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 
294 void
295 FontSelectionView::_SelectCurrentSize()
296 {
297 	fFontSizeSpinner->SetValue((int32)fCurrentFont.Size());
298 }
299 
300 
301 void
302 FontSelectionView::_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 
314 void
315 FontSelectionView::_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 
336 void
337 FontSelectionView::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 
371 void
372 FontSelectionView::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 
387 bool
388 FontSelectionView::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 
418 bool
419 FontSelectionView::IsRevertable()
420 {
421 	return fCurrentFont != fSavedFont;
422 }
423 
424 
425 void
426 FontSelectionView::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