xref: /haiku/src/add-ons/screen_savers/message/Message.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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
33 const pattern kCheckered =
34 	{ { 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33 } };
35 
36 // Get a clever message from fortune
37 BString *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 
60 int
61 get_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.
84 class 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 
103 BScreenSaver *instantiate_screen_saver(BMessage *msg, image_id image)
104 {
105 	return new Message(msg, image);
106 }
107 
108 
109 Message::Message(BMessage *archive, image_id id)
110  :	BScreenSaver(archive, id)
111 {
112 }
113 
114 
115 Message::~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 
124 void
125 Message::StartConfig(BView *view)
126 {
127 	BPrivate::BuildDefaultSettingsView(view, "Message",
128 		B_TRANSLATE("by Ryan Leavengood"));
129 }
130 
131 
132 status_t
133 Message::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 
163 void
164 Message::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