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