Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/Optional.h>
28#include <AK/String.h>
29#include <AK/StringBuilder.h>
30#include <AK/Vector.h>
31#include <LibCore/File.h>
32#include <LibGUI/Application.h>
33#include <LibGUI/BoxLayout.h>
34#include <LibGUI/Button.h>
35#include <LibGUI/Desktop.h>
36#include <LibGUI/Label.h>
37#include <LibGUI/MessageBox.h>
38#include <LibGUI/StackWidget.h>
39#include <LibGUI/Window.h>
40#include <LibGfx/Bitmap.h>
41#include <LibGfx/Font.h>
42#include <stdio.h>
43#include <unistd.h>
44
45#include "BackgroundWidget.h"
46#include "TextWidget.h"
47#include "UnuncheckableButton.h"
48
49struct ContentPage {
50 String menu_name;
51 String title;
52 String icon = String::empty();
53 Vector<String> content;
54};
55
56Optional<Vector<ContentPage>> parse_welcome_file(const String& path)
57{
58 const auto error = Optional<Vector<ContentPage>>();
59 auto file = Core::File::construct(path);
60
61 if (!file->open(Core::IODevice::ReadOnly))
62 return error;
63
64 Vector<ContentPage> pages;
65 StringBuilder current_output_line;
66 bool started = false;
67 ContentPage current;
68 while (true) {
69 auto buffer = file->read_line(4096);
70 if (buffer.is_null()) {
71 if (file->error()) {
72 file->close();
73 return error;
74 }
75
76 break;
77 }
78
79 auto line = String((char*)buffer.data());
80 if (line.length() > 1)
81 line = line.substring(0, line.length() - 1); // remove newline
82 switch (line[0]) {
83 case '*':
84 dbg() << "menu_item line:\t" << line;
85 if (started)
86 pages.append(current);
87 else
88 started = true;
89
90 current = {};
91 current.menu_name = line.substring(2, line.length() - 2);
92 break;
93 case '$':
94 dbg() << "icon line: \t" << line;
95 current.icon = line.substring(2, line.length() - 2);
96 break;
97 case '>':
98 dbg() << "title line:\t" << line;
99 current.title = line.substring(2, line.length() - 2);
100 break;
101 case '\n':
102 dbg() << "newline";
103
104 if (!current_output_line.to_string().is_empty())
105 current.content.append(current_output_line.to_string());
106 current_output_line.clear();
107 break;
108 case '#':
109 dbg() << "comment line:\t" << line;
110 break;
111 default:
112 dbg() << "content line:\t" << line;
113 if (current_output_line.length() != 0)
114 current_output_line.append(' ');
115 current_output_line.append(line);
116 break;
117 }
118 }
119
120 if (started) {
121 current.content.append(current_output_line.to_string());
122 pages.append(current);
123 }
124
125 file->close();
126 return pages;
127}
128
129int main(int argc, char** argv)
130{
131#ifdef __serenity__
132 if (pledge("stdio shared_buffer rpath unix cpath fattr", nullptr) < 0) {
133 perror("pledge");
134 return 1;
135 }
136#endif
137
138 GUI::Application app(argc, argv);
139
140#ifdef __serenity__
141 if (pledge("stdio shared_buffer rpath", nullptr) < 0) {
142 perror("pledge");
143 return 1;
144 }
145#endif
146
147 if (unveil("/res", "r") < 0) {
148 perror("unveil");
149 return 1;
150 }
151
152 unveil(nullptr, nullptr);
153
154 Optional<Vector<ContentPage>> _pages = parse_welcome_file("/res/welcome.txt");
155 if (!_pages.has_value()) {
156 GUI::MessageBox::show("Could not open Welcome file.", "Welcome", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, nullptr);
157 return 1;
158 }
159 auto pages = _pages.value();
160
161 auto window = GUI::Window::construct();
162 window->set_title("Welcome");
163 Gfx::Rect window_rect { 0, 0, 640, 360 };
164 window_rect.center_within(GUI::Desktop::the().rect());
165 window->set_resizable(true);
166 window->set_rect(window_rect);
167
168 auto& background = window->set_main_widget<BackgroundWidget>();
169 background.set_fill_with_background_color(false);
170 background.set_layout<GUI::VerticalBoxLayout>();
171 background.layout()->set_margins({ 16, 8, 16, 8 });
172 background.layout()->set_spacing(8);
173 background.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
174
175 //
176 // header
177 //
178
179 auto& header = background.add<GUI::Label>();
180 header.set_font(Gfx::Font::load_from_file("/res/fonts/PebbletonBold11.font"));
181 header.set_text("Welcome to SerenityOS!");
182 header.set_text_alignment(Gfx::TextAlignment::CenterLeft);
183 header.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
184 header.set_preferred_size(0, 30);
185
186 //
187 // main section
188 //
189
190 auto& main_section = background.add<GUI::Widget>();
191 main_section.set_layout<GUI::HorizontalBoxLayout>();
192 main_section.layout()->set_margins({ 0, 0, 0, 0 });
193 main_section.layout()->set_spacing(8);
194 main_section.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
195
196 auto& menu = main_section.add<GUI::Widget>();
197 menu.set_layout<GUI::VerticalBoxLayout>();
198 menu.layout()->set_margins({ 0, 0, 0, 0 });
199 menu.layout()->set_spacing(4);
200 menu.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
201 menu.set_preferred_size(100, 0);
202
203 auto& stack = main_section.add<GUI::StackWidget>();
204 stack.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
205
206 bool first = true;
207 for (auto& page : pages) {
208 auto& content = stack.add<GUI::Widget>();
209 content.set_layout<GUI::VerticalBoxLayout>();
210 content.layout()->set_margins({ 0, 0, 0, 0 });
211 content.layout()->set_spacing(8);
212 content.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
213
214 auto& title_box = content.add<GUI::Widget>();
215 title_box.set_layout<GUI::HorizontalBoxLayout>();
216 title_box.layout()->set_spacing(4);
217 title_box.set_preferred_size(0, 16);
218 title_box.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
219
220 if (!page.icon.is_empty()) {
221 auto& icon = title_box.add<GUI::Label>();
222 icon.set_icon(Gfx::Bitmap::load_from_file(page.icon));
223 icon.set_preferred_size(16, 16);
224 icon.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
225 }
226
227 auto& content_title = title_box.add<GUI::Label>();
228 content_title.set_font(Gfx::Font::default_bold_font());
229 content_title.set_text(page.title);
230 content_title.set_text_alignment(Gfx::TextAlignment::CenterLeft);
231 content_title.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
232 content_title.set_preferred_size(0, 10);
233
234 for (auto& paragraph : page.content) {
235 auto& content_text = content.add<TextWidget>();
236 content_text.set_font(Gfx::Font::default_font());
237 content_text.set_text(paragraph);
238 content_text.set_text_alignment(Gfx::TextAlignment::TopLeft);
239 content_text.set_line_height(12);
240 content_text.wrap_and_set_height();
241 }
242
243 auto& menu_option = menu.add<UnuncheckableButton>();
244 menu_option.set_font(Gfx::Font::default_font());
245 menu_option.set_text(page.menu_name);
246 menu_option.set_text_alignment(Gfx::TextAlignment::CenterLeft);
247 menu_option.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
248 menu_option.set_preferred_size(0, 20);
249 menu_option.set_checkable(true);
250 menu_option.set_exclusive(true);
251
252 if (first)
253 menu_option.set_checked(true);
254
255 menu_option.on_click = [content = &content, &stack] {
256 stack.set_active_widget(content);
257 content->invalidate_layout();
258 };
259
260 first = false;
261 }
262
263 window->show();
264 return app.exec();
265}