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