⛏️ index : haiku.git

/*
 * Copyright 2007-2014, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ryan Leavengood
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Bitmap.h>
#include <Catalog.h>
#include <DefaultSettingsView.h>
#include <Font.h>
#include <ObjectList.h>
#include <Picture.h>
#include <Screen.h>
#include <ScreenSaver.h>
#include <String.h>
#include <StringList.h>
#include <TextView.h>
#include <View.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Screensaver Message"


// Double brackets to satisfy a compiler warning
const pattern kCheckered =
	{ { 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0x33, 0x33 } };

// Get a clever message from fortune
BString *get_message()
{
	BString *result = new BString();
	FILE *file = popen("/bin/fortune", "r");
	if (file) {
		char buf[512];
		int bytesRead;
		while (!feof(file)) {
			bytesRead = fread(buf, 1, 512, file);
			result->Append(buf, bytesRead);
		}
		pclose(file);
	}

	// Just in case
	if (result->Length() <= 0) {
		result->Append(B_TRANSLATE("Insert clever anecdote or phrase here!"));
	}

	return result;
}


int
get_lines(BString *message, BStringList& list, int *longestLine)
{
	BString copy(*message);
	// Convert tabs to 4 spaces
	copy.ReplaceAll("\t", "    ");
	if (copy.Split("\n", false, list)) {
		int maxLength = 0;
		int32 count = list.CountStrings();
		for (int32 i = 0; i < count; i++) {
			int32 length = list.StringAt(i).Length();
			if (length  > maxLength) {
				maxLength = length;
				*longestLine = i;
			}
		}
		return count;
	}
	return 0;
}


// Inspired by the classic BeOS screensaver, of course.
// Thanks to Jon Watte for writing the original.
class Message : public BScreenSaver
{
	public:
					Message(BMessage *archive, image_id);
					~Message();
		void		Draw(BView *view, int32 frame);
		void		StartConfig(BView *view);
		status_t	StartSaver(BView *view, bool preview);

	private:
		struct font_family_wrapper {
			font_family val;
		};
		BObjectList<font_family_wrapper>	fFontFamilies;
		float						fScaleFactor;
		bool						fPreview;
};


BScreenSaver *instantiate_screen_saver(BMessage *msg, image_id image)
{
	return new Message(msg, image);
}


Message::Message(BMessage *archive, image_id id)
 :	BScreenSaver(archive, id)
{
}


Message::~Message()
{
	for (int32 i = 0; i < fFontFamilies.CountItems(); i++) {
		if (fFontFamilies.ItemAt(i))
			delete fFontFamilies.ItemAt(i);
	}
}


void
Message::StartConfig(BView *view)
{
	BPrivate::BuildDefaultSettingsView(view, "Message",
		B_TRANSLATE("by Ryan Leavengood"));
}


status_t
Message::StartSaver(BView *view, bool preview)
{
	fPreview = preview;
	// Scale factor is based on the system I developed this on, in
	// other words other factors below depend on this.
	fScaleFactor = view->Bounds().Height() / 1024;

	// Get font families
	int numFamilies = count_font_families();
	for (int32 i = 0; i < numFamilies; i++) {
		font_family_wrapper* family = new font_family_wrapper;
		uint32 flags;
		if (get_font_family(i, &(family->val), &flags) == B_OK
			&& (flags & B_IS_FIXED) == 0) {
			// Do not add fixed fonts
			fFontFamilies.AddItem(family);
		} else
			delete family;
	}

	// Seed the random number generator
	srand((int)system_time());

	// Set tick size to 30,000,000 microseconds = 30 seconds
	SetTickSize(30000000);

	return B_OK;
}


void
Message::Draw(BView *view, int32 frame)
{
	if (view == NULL || view->Window() == NULL || !view->Window()->IsLocked())
		return;

	BScreen screen(view->Window());
	if (!screen.IsValid())
		return;

	// Double-buffered drawing
	BBitmap buffer(view->Bounds(), screen.ColorSpace(), true);
	if (buffer.InitCheck() != B_OK)
		return;

	BView offscreen(view->Bounds(), NULL, 0, 0);
	buffer.AddChild(&offscreen);
	buffer.Lock();

	// Set up the colors
	rgb_color base_color = {(uint8)(rand() % 25), (uint8)(rand() % 25),
		(uint8)(rand() % 25)};
	offscreen.SetHighColor(base_color);
	offscreen.SetLowColor(tint_color(base_color, 0.815F));
	offscreen.FillRect(offscreen.Bounds(), kCheckered);
	rgb_color colors[8] = {
		tint_color(base_color, B_LIGHTEN_1_TINT),
		tint_color(base_color, 0.795F),
		tint_color(base_color, 0.851F),
		tint_color(base_color, 0.926F),
		tint_color(base_color, 1.05F),
		tint_color(base_color, B_DARKEN_1_TINT),
		tint_color(base_color, B_DARKEN_2_TINT),
		tint_color(base_color, B_DARKEN_3_TINT),
	};

	offscreen.SetDrawingMode(B_OP_OVER);

	// Set the basic font parameters, including random font family
	BFont font;
	offscreen.GetFont(&font);
	font.SetFace(B_BOLD_FACE);
	font.SetFamilyAndStyle(
		fFontFamilies.ItemAt(rand() % fFontFamilies.CountItems())->val, NULL);
	offscreen.SetFont(&font);

	// Get the message
	BString *message = get_message();
	BString *origMessage = new BString();
	message->CopyInto(*origMessage, 0, message->Length());
	// Replace newlines and tabs with spaces
	message->ReplaceSet("\n\t", ' ');

	int height = (int) offscreen.Bounds().Height();
	int width = (int) offscreen.Bounds().Width();

	// From 14 to 22 iterations
	int32 iterations = (rand() % 8) + 14;
	for (int32 i = 0; i < iterations; i++) {
		// Randomly set font size and shear
		BFont font;
		offscreen.GetFont(&font);
		float fontSize = ((rand() % 320) + 42) * fScaleFactor;
		font.SetSize(fontSize);
		// Set the shear off 90 about 1/2 of the time
		if (rand() % 2 == 1)
			font.SetShear((float) ((rand() % 135) + (rand() % 45)));
		else
			font.SetShear(90.0);
		offscreen.SetFont(&font);

		// Randomly set drawing location
		int x = (rand() % width) - (rand() % width/((rand() % 8)+1));
		int y = rand() % height;

		// Draw new text
		offscreen.SetHighColor(colors[rand() % 8]);
		int strLength = message->Length();
		// See how wide this string is with the current font
		float strWidth = offscreen.StringWidth(message->String());
		int drawingLength = (int) (strLength * (width / strWidth));
		int start = 0;
		if (drawingLength >= strLength)
			drawingLength = strLength;
		else
			start = rand() % (strLength - drawingLength);
		char *toDraw = new char[drawingLength+1];
		strncpy(toDraw, message->String()+start, drawingLength);
		toDraw[drawingLength] = 0;
		offscreen.DrawString(toDraw, BPoint(x, y));
		delete[] toDraw;
	}

	// Now draw the full message in a nice translucent box, but only
	// if this isn't preview mode
	if (!fPreview) {
		BFont font(be_fixed_font);
		font.SetSize(14.0);
		offscreen.SetFont(&font);
		font_height fontHeight;
		font.GetHeight(&fontHeight);
		float lineHeight = fontHeight.ascent + fontHeight.descent
			+ fontHeight.leading;

		BStringList lines;
		int longestLine = 0;
		int32 count = get_lines(origMessage, lines, &longestLine);

		float stringWidth =
			font.StringWidth(lines.StringAt(longestLine).String());
		BRect box(0, 0, stringWidth + 20, (lineHeight * count) + 20);
		box.OffsetTo((width - box.Width()) / 2, height - box.Height() - 40);

		offscreen.SetDrawingMode(B_OP_ALPHA);
		base_color.alpha = 128;
		offscreen.SetHighColor(base_color);
		offscreen.FillRoundRect(box, 8, 8);
		offscreen.SetHighColor(205, 205, 205);
		BPoint start = box.LeftTop();
		start.x += 10;
		start.y += 10 + fontHeight.ascent + fontHeight.leading;
		for (int i = 0; i < count; i++) {
			offscreen.DrawString(lines.StringAt(i).String(), start);
			start.y += lineHeight;
		}
	}

	delete origMessage;
	delete message;

	offscreen.Sync();
	buffer.Unlock();
	view->DrawBitmap(&buffer);
	buffer.RemoveChild(&offscreen);
}