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/Font.h>
41#include <LibGfx/Bitmap.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 if (pledge("stdio shared_buffer rpath unix cpath fattr", nullptr) < 0) {
132 perror("pledge");
133 return 1;
134 }
135
136 GUI::Application app(argc, argv);
137
138 if (pledge("stdio shared_buffer rpath", nullptr) < 0) {
139 perror("pledge");
140 return 1;
141 }
142
143 if (unveil("/res", "r") < 0) {
144 perror("unveil");
145 return 1;
146 }
147
148 unveil(nullptr, nullptr);
149
150 Optional<Vector<ContentPage>> _pages = parse_welcome_file("/res/welcome.txt");
151 if (!_pages.has_value()) {
152 GUI::MessageBox::show("Could not open Welcome file.", "Welcome", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, nullptr);
153 return 1;
154 }
155 auto pages = _pages.value();
156
157 auto window = GUI::Window::construct();
158 window->set_title("Welcome");
159 Gfx::Rect window_rect { 0, 0, 640, 360 };
160 window_rect.center_within(GUI::Desktop::the().rect());
161 window->set_resizable(true);
162 window->set_rect(window_rect);
163
164 auto background = BackgroundWidget::construct();
165 window->set_main_widget(background);
166 background->set_fill_with_background_color(false);
167 background->set_layout(make<GUI::VerticalBoxLayout>());
168 background->layout()->set_margins({ 16, 8, 16, 8 });
169 background->layout()->set_spacing(8);
170 background->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
171
172 //
173 // header
174 //
175
176 auto header = background->add<GUI::Label>();
177 header->set_font(Gfx::Font::load_from_file("/res/fonts/PebbletonBold11.font"));
178 header->set_text("Welcome to SerenityOS!");
179 header->set_text_alignment(Gfx::TextAlignment::CenterLeft);
180 header->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
181 header->set_preferred_size(0, 30);
182
183 //
184 // main section
185 //
186
187 auto main_section = background->add<GUI::Widget>();
188 main_section->set_layout(make<GUI::HorizontalBoxLayout>());
189 main_section->layout()->set_margins({ 0, 0, 0, 0 });
190 main_section->layout()->set_spacing(8);
191 main_section->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
192
193 auto menu = main_section->add<GUI::Widget>();
194 menu->set_layout(make<GUI::VerticalBoxLayout>());
195 menu->layout()->set_margins({ 0, 0, 0, 0 });
196 menu->layout()->set_spacing(4);
197 menu->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
198 menu->set_preferred_size(100, 0);
199
200 auto stack = main_section->add<GUI::StackWidget>();
201 stack->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
202
203 bool first = true;
204 for (auto& page : pages) {
205 auto content = stack->add<GUI::Widget>();
206 content->set_layout(make<GUI::VerticalBoxLayout>());
207 content->layout()->set_margins({ 0, 0, 0, 0 });
208 content->layout()->set_spacing(8);
209 content->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill);
210
211 auto title_box = content->add<GUI::Widget>();
212 title_box->set_layout(make<GUI::HorizontalBoxLayout>());
213 title_box->layout()->set_spacing(4);
214 title_box->set_preferred_size(0, 16);
215 title_box->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
216
217 if (!page.icon.is_empty()) {
218 auto icon = title_box->add<GUI::Label>();
219 icon->set_icon(Gfx::Bitmap::load_from_file(page.icon));
220 icon->set_preferred_size(16, 16);
221 icon->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
222 }
223
224 auto content_title = title_box->add<GUI::Label>();
225 content_title->set_font(Gfx::Font::default_bold_font());
226 content_title->set_text(page.title);
227 content_title->set_text_alignment(Gfx::TextAlignment::CenterLeft);
228 content_title->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
229 content_title->set_preferred_size(0, 10);
230
231 for (auto& paragraph : page.content) {
232 auto content_text = content->add<TextWidget>();
233 content_text->set_font(Gfx::Font::default_font());
234 content_text->set_text(paragraph);
235 content_text->set_text_alignment(Gfx::TextAlignment::TopLeft);
236 content_text->set_line_height(12);
237 content_text->wrap_and_set_height();
238 }
239
240 auto menu_option = menu->add<UnuncheckableButton>();
241 menu_option->set_font(Gfx::Font::default_font());
242 menu_option->set_text(page.menu_name);
243 menu_option->set_text_alignment(Gfx::TextAlignment::CenterLeft);
244 menu_option->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
245 menu_option->set_preferred_size(0, 20);
246 menu_option->set_checkable(true);
247 menu_option->set_exclusive(true);
248
249 if (first)
250 menu_option->set_checked(true);
251
252 menu_option->on_click = [content = content.ptr(), &stack](auto&) {
253 stack->set_active_widget(content);
254 content->invalidate_layout();
255 };
256
257 first = false;
258 }
259
260 window->show();
261 return app.exec();
262}