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