1/*
2 * Copyright 2007-2014, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ryan Leavengood
7 */
8
9
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13
14#include <Bitmap.h>
15#include <Catalog.h>
16#include <DefaultSettingsView.h>
17#include <Font.h>
18#include <ObjectList.h>
19#include <Picture.h>
20#include <Screen.h>
21#include <ScreenSaver.h>
22#include <String.h>
23#include <StringList.h>
24#include <TextView.h>
25#include <View.h>
26
27
28#undef B_TRANSLATION_CONTEXT
29#define B_TRANSLATION_CONTEXT "Screensaver Message"
30
31
32// Double brackets to satisfy a compiler warning
33const pattern kCheckered =
34	{ { 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33 } };
35
36// Get a clever message from fortune
37BString *get_message()
38{
39	BString *result = new BString();
40	FILE *file = popen("/bin/fortune", "r");
41	if (file) {
42		char buf[512];
43		int bytesRead;
44		while (!feof(file)) {
45			bytesRead = fread(buf, 1, 512, file);
46			result->Append(buf, bytesRead);
47		}
48		pclose(file);
49	}
50
51	// Just in case
52	if (result->Length() <= 0) {
53		result->Append(B_TRANSLATE("Insert clever anecdote or phrase here!"));
54	}
55
56	return result;
57}
58
59
60int
61get_lines(BString *message, BStringList& list, int *longestLine)
62{
63	BString copy(*message);
64	// Convert tabs to 4 spaces
65	copy.ReplaceAll("\t", "    ");
66	if (copy.Split("\n", false, list)) {
67		int maxLength = 0;
68		int32 count = list.CountStrings();
69		for (int32 i = 0; i < count; i++) {
70			int32 length = list.StringAt(i).Length();
71			if (length  > maxLength) {
72				maxLength = length;
73				*longestLine = i;
74			}
75		}
76		return count;
77	}
78	return 0;
79}
80
81
82// Inspired by the classic BeOS screensaver, of course.
83// Thanks to Jon Watte for writing the original.
84class Message : public BScreenSaver
85{
86	public:
87					Message(BMessage *archive, image_id);
88					~Message();
89		void		Draw(BView *view, int32 frame);
90		void		StartConfig(BView *view);
91		status_t	StartSaver(BView *view, bool preview);
92
93	private:
94		struct font_family_wrapper {
95			font_family val;
96		};
97		BObjectList<font_family_wrapper>	fFontFamilies;
98		float						fScaleFactor;
99		bool						fPreview;
100};
101
102
103BScreenSaver *instantiate_screen_saver(BMessage *msg, image_id image)
104{
105	return new Message(msg, image);
106}
107
108
109Message::Message(BMessage *archive, image_id id)
110 :	BScreenSaver(archive, id)
111{
112}
113
114
115Message::~Message()
116{
117	for (int32 i = 0; i < fFontFamilies.CountItems(); i++) {
118		if (fFontFamilies.ItemAt(i))
119			delete fFontFamilies.ItemAt(i);
120	}
121}
122
123
124void
125Message::StartConfig(BView *view)
126{
127	BPrivate::BuildDefaultSettingsView(view, "Message",
128		B_TRANSLATE("by Ryan Leavengood"));
129}
130
131
132status_t
133Message::StartSaver(BView *view, bool preview)
134{
135	fPreview = preview;
136	// Scale factor is based on the system I developed this on, in
137	// other words other factors below depend on this.
138	fScaleFactor = view->Bounds().Height() / 1024;
139
140	// Get font families
141	int numFamilies = count_font_families();
142	for (int32 i = 0; i < numFamilies; i++) {
143		font_family_wrapper* family = new font_family_wrapper;
144		uint32 flags;
145		if (get_font_family(i, &(family->val), &flags) == B_OK
146			&& (flags & B_IS_FIXED) == 0) {
147			// Do not add fixed fonts
148			fFontFamilies.AddItem(family);
149		} else
150			delete family;
151	}
152
153	// Seed the random number generator
154	srand((int)system_time());
155
156	// Set tick size to 30,000,000 microseconds = 30 seconds
157	SetTickSize(30000000);
158
159	return B_OK;
160}
161
162
163void
164Message::Draw(BView *view, int32 frame)
165{
166	if (view == NULL || view->Window() == NULL || !view->Window()->IsLocked())
167		return;
168
169	BScreen screen(view->Window());
170	if (!screen.IsValid())
171		return;
172
173	// Double-buffered drawing
174	BBitmap buffer(view->Bounds(), screen.ColorSpace(), true);
175	if (buffer.InitCheck() != B_OK)
176		return;
177
178	BView offscreen(view->Bounds(), NULL, 0, 0);
179	buffer.AddChild(&offscreen);
180	buffer.Lock();
181
182	// Set up the colors
183	rgb_color base_color = {(uint8)(rand() % 25), (uint8)(rand() % 25),
184		(uint8)(rand() % 25)};
185	offscreen.SetHighColor(base_color);
186	offscreen.SetLowColor(tint_color(base_color, 0.815F));
187	offscreen.FillRect(offscreen.Bounds(), kCheckered);
188	rgb_color colors[8] = {
189		tint_color(base_color, B_LIGHTEN_1_TINT),
190		tint_color(base_color, 0.795F),
191		tint_color(base_color, 0.851F),
192		tint_color(base_color, 0.926F),
193		tint_color(base_color, 1.05F),
194		tint_color(base_color, B_DARKEN_1_TINT),
195		tint_color(base_color, B_DARKEN_2_TINT),
196		tint_color(base_color, B_DARKEN_3_TINT),
197	};
198
199	offscreen.SetDrawingMode(B_OP_OVER);
200
201	// Set the basic font parameters, including random font family
202	BFont font;
203	offscreen.GetFont(&font);
204	font.SetFace(B_BOLD_FACE);
205	font.SetFamilyAndStyle(
206		fFontFamilies.ItemAt(rand() % fFontFamilies.CountItems())->val, NULL);
207	offscreen.SetFont(&font);
208
209	// Get the message
210	BString *message = get_message();
211	BString *origMessage = new BString();
212	message->CopyInto(*origMessage, 0, message->Length());
213	// Replace newlines and tabs with spaces
214	message->ReplaceSet("\n\t", ' ');
215
216	int height = (int) offscreen.Bounds().Height();
217	int width = (int) offscreen.Bounds().Width();
218
219	// From 14 to 22 iterations
220	int32 iterations = (rand() % 8) + 14;
221	for (int32 i = 0; i < iterations; i++) {
222		// Randomly set font size and shear
223		BFont font;
224		offscreen.GetFont(&font);
225		float fontSize = ((rand() % 320) + 42) * fScaleFactor;
226		font.SetSize(fontSize);
227		// Set the shear off 90 about 1/2 of the time
228		if (rand() % 2 == 1)
229			font.SetShear((float) ((rand() % 135) + (rand() % 45)));
230		else
231			font.SetShear(90.0);
232		offscreen.SetFont(&font);
233
234		// Randomly set drawing location
235		int x = (rand() % width) - (rand() % width/((rand() % 8)+1));
236		int y = rand() % height;
237
238		// Draw new text
239		offscreen.SetHighColor(colors[rand() % 8]);
240		int strLength = message->Length();
241		// See how wide this string is with the current font
242		float strWidth = offscreen.StringWidth(message->String());
243		int drawingLength = (int) (strLength * (width / strWidth));
244		int start = 0;
245		if (drawingLength >= strLength)
246			drawingLength = strLength;
247		else
248			start = rand() % (strLength - drawingLength);
249		char *toDraw = new char[drawingLength+1];
250		strncpy(toDraw, message->String()+start, drawingLength);
251		toDraw[drawingLength] = 0;
252		offscreen.DrawString(toDraw, BPoint(x, y));
253		delete[] toDraw;
254	}
255
256	// Now draw the full message in a nice translucent box, but only
257	// if this isn't preview mode
258	if (!fPreview) {
259		BFont font(be_fixed_font);
260		font.SetSize(14.0);
261		offscreen.SetFont(&font);
262		font_height fontHeight;
263		font.GetHeight(&fontHeight);
264		float lineHeight = fontHeight.ascent + fontHeight.descent
265			+ fontHeight.leading;
266
267		BStringList lines;
268		int longestLine = 0;
269		int32 count = get_lines(origMessage, lines, &longestLine);
270
271		float stringWidth =
272			font.StringWidth(lines.StringAt(longestLine).String());
273		BRect box(0, 0, stringWidth + 20, (lineHeight * count) + 20);
274		box.OffsetTo((width - box.Width()) / 2, height - box.Height() - 40);
275
276		offscreen.SetDrawingMode(B_OP_ALPHA);
277		base_color.alpha = 128;
278		offscreen.SetHighColor(base_color);
279		offscreen.FillRoundRect(box, 8, 8);
280		offscreen.SetHighColor(205, 205, 205);
281		BPoint start = box.LeftTop();
282		start.x += 10;
283		start.y += 10 + fontHeight.ascent + fontHeight.leading;
284		for (int i = 0; i < count; i++) {
285			offscreen.DrawString(lines.StringAt(i).String(), start);
286			start.y += lineHeight;
287		}
288	}
289
290	delete origMessage;
291	delete message;
292
293	offscreen.Sync();
294	buffer.Unlock();
295	view->DrawBitmap(&buffer);
296	buffer.RemoveChild(&offscreen);
297}
298