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