Serenity Operating System
at hosted 524 lines 18 kB view raw
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 "VBForm.h" 28#include "VBProperty.h" 29#include "VBWidget.h" 30#include "VBWidgetRegistry.h" 31#include <AK/JsonArray.h> 32#include <AK/JsonObject.h> 33#include <AK/StringBuilder.h> 34#include <LibCore/File.h> 35#include <LibGUI/Action.h> 36#include <LibGUI/BoxLayout.h> 37#include <LibGUI/Menu.h> 38#include <LibGUI/MessageBox.h> 39#include <LibGUI/Painter.h> 40 41static VBForm* s_current; 42VBForm* VBForm::current() 43{ 44 return s_current; 45} 46 47VBForm::VBForm(const String& name) 48 : m_name(name) 49{ 50 s_current = this; 51 set_fill_with_background_color(true); 52 set_greedy_for_hits(true); 53 54 m_context_menu = GUI::Menu::construct(); 55 m_context_menu->add_action(GUI::CommonActions::make_move_to_front_action([this](auto&) { 56 if (auto* widget = single_selected_widget()) 57 widget->gwidget()->move_to_front(); 58 })); 59 m_context_menu->add_action(GUI::CommonActions::make_move_to_back_action([this](auto&) { 60 if (auto* widget = single_selected_widget()) 61 widget->gwidget()->move_to_back(); 62 })); 63 m_context_menu->add_separator(); 64 m_context_menu->add_action(GUI::Action::create("Lay out horizontally", Gfx::Bitmap::load_from_file("/res/icons/16x16/layout-horizontally.png"), [this](auto&) { 65 if (auto* widget = single_selected_widget()) { 66 dbg() << "Giving " << *widget->gwidget() << " a horizontal box layout"; 67 widget->gwidget()->set_layout<GUI::HorizontalBoxLayout>(); 68 } 69 })); 70 m_context_menu->add_action(GUI::Action::create("Lay out vertically", Gfx::Bitmap::load_from_file("/res/icons/16x16/layout-vertically.png"), [this](auto&) { 71 if (auto* widget = single_selected_widget()) { 72 dbg() << "Giving " << *widget->gwidget() << " a vertical box layout"; 73 widget->gwidget()->set_layout<GUI::VerticalBoxLayout>(); 74 } 75 })); 76 m_context_menu->add_separator(); 77 m_context_menu->add_action(GUI::CommonActions::make_delete_action([this](auto&) { 78 delete_selected_widgets(); 79 })); 80} 81 82void VBForm::context_menu_event(GUI::ContextMenuEvent& event) 83{ 84 m_context_menu->popup(event.screen_position()); 85} 86 87void VBForm::insert_widget(VBWidgetType type) 88{ 89 auto* insertion_parent = single_selected_widget(); 90 auto widget = VBWidget::create(type, *this, insertion_parent); 91 Gfx::Point insertion_position = m_next_insertion_position; 92 if (insertion_parent) 93 insertion_position.move_by(insertion_parent->gwidget()->window_relative_rect().location()); 94 widget->set_rect({ insertion_position, { m_grid_size * 10 + 1, m_grid_size * 5 + 1 } }); 95 m_next_insertion_position.move_by(m_grid_size, m_grid_size); 96 m_widgets.append(move(widget)); 97} 98 99VBForm::~VBForm() 100{ 101} 102 103void VBForm::paint_event(GUI::PaintEvent& event) 104{ 105 GUI::Painter painter(*this); 106 painter.add_clip_rect(event.rect()); 107 108 for (int y = 0; y < height(); y += m_grid_size) { 109 for (int x = 0; x < width(); x += m_grid_size) { 110 painter.set_pixel({ x, y }, Color::from_rgb(0x404040)); 111 } 112 } 113} 114 115void VBForm::second_paint_event(GUI::PaintEvent& event) 116{ 117 GUI::Painter painter(*this); 118 painter.add_clip_rect(event.rect()); 119 120 for (auto& widget : m_widgets) { 121 if (widget.is_selected()) { 122 for_each_direction([&](auto direction) { 123 bool in_layout = widget.is_in_layout(); 124 auto grabber_rect = widget.grabber_rect(direction); 125 painter.fill_rect(grabber_rect, in_layout ? Color::White : Color::Black); 126 if (in_layout) 127 painter.draw_rect(grabber_rect, Color::Black); 128 }); 129 } 130 } 131} 132 133bool VBForm::is_selected(const VBWidget& widget) const 134{ 135 // FIXME: Fix HashTable and remove this const_cast. 136 return m_selected_widgets.contains(const_cast<VBWidget*>(&widget)); 137} 138 139VBWidget* VBForm::widget_at(const Gfx::Point& position) 140{ 141 auto result = hit_test(position, GUI::Widget::ShouldRespectGreediness::No); 142 if (!result.widget) 143 return nullptr; 144 auto* gwidget = result.widget; 145 while (gwidget) { 146 if (auto* widget = m_gwidget_map.get(gwidget).value_or(nullptr)) 147 return widget; 148 gwidget = gwidget->parent_widget(); 149 } 150 return nullptr; 151} 152 153void VBForm::grabber_mousedown_event(GUI::MouseEvent& event, Direction grabber) 154{ 155 m_transform_event_origin = event.position(); 156 for_each_selected_widget([](auto& widget) { widget.capture_transform_origin_rect(); }); 157 m_resize_direction = grabber; 158} 159 160void VBForm::keydown_event(GUI::KeyEvent& event) 161{ 162 if (event.key() == KeyCode::Key_Delete) { 163 delete_selected_widgets(); 164 return; 165 } 166 if (event.key() == KeyCode::Key_Tab) { 167 if (m_widgets.is_empty()) 168 return; 169 if (m_selected_widgets.is_empty()) { 170 set_single_selected_widget(&m_widgets.first()); 171 update(); 172 return; 173 } 174 size_t selected_widget_index = 0; 175 for (; selected_widget_index < m_widgets.size(); ++selected_widget_index) { 176 if (&m_widgets[selected_widget_index] == *m_selected_widgets.begin()) 177 break; 178 } 179 ++selected_widget_index; 180 if (selected_widget_index == m_widgets.size()) 181 selected_widget_index = 0; 182 set_single_selected_widget(&m_widgets[selected_widget_index]); 183 update(); 184 return; 185 } 186 if (!m_selected_widgets.is_empty()) { 187 switch (event.key()) { 188 case KeyCode::Key_Up: 189 update(); 190 for_each_selected_widget([this](auto& widget) { 191 if (widget.is_in_layout()) 192 return; 193 widget.gwidget()->move_by(0, -m_grid_size); 194 }); 195 break; 196 case KeyCode::Key_Down: 197 update(); 198 for_each_selected_widget([this](auto& widget) { 199 if (widget.is_in_layout()) 200 return; 201 widget.gwidget()->move_by(0, m_grid_size); 202 }); 203 break; 204 case KeyCode::Key_Left: 205 update(); 206 for_each_selected_widget([this](auto& widget) { 207 if (widget.is_in_layout()) 208 return; 209 widget.gwidget()->move_by(-m_grid_size, 0); 210 }); 211 break; 212 case KeyCode::Key_Right: 213 update(); 214 for_each_selected_widget([this](auto& widget) { 215 if (widget.is_in_layout()) 216 return; 217 widget.gwidget()->move_by(m_grid_size, 0); 218 }); 219 break; 220 } 221 return; 222 } 223} 224 225void VBForm::set_single_selected_widget(VBWidget* widget) 226{ 227 if (!widget) { 228 if (!m_selected_widgets.is_empty()) { 229 m_selected_widgets.clear(); 230 on_widget_selected(nullptr); 231 update(); 232 } 233 return; 234 } 235 m_selected_widgets.clear(); 236 m_selected_widgets.set(widget); 237 on_widget_selected(m_selected_widgets.size() == 1 ? widget : nullptr); 238 update(); 239} 240 241void VBForm::add_to_selection(VBWidget& widget) 242{ 243 m_selected_widgets.set(&widget); 244 update(); 245} 246 247void VBForm::remove_from_selection(VBWidget& widget) 248{ 249 m_selected_widgets.remove(&widget); 250 update(); 251} 252 253void VBForm::mousedown_event(GUI::MouseEvent& event) 254{ 255 if (m_resize_direction == Direction::None) { 256 bool hit_grabber = false; 257 for_each_selected_widget([&](auto& widget) { 258 if (widget.is_in_layout()) 259 return; 260 auto grabber = widget.grabber_at(event.position()); 261 if (grabber != Direction::None) { 262 hit_grabber = true; 263 return grabber_mousedown_event(event, grabber); 264 } 265 }); 266 if (hit_grabber) 267 return; 268 } 269 auto* widget = widget_at(event.position()); 270 if (!widget) { 271 set_single_selected_widget(nullptr); 272 return; 273 } 274 if (event.button() == GUI::MouseButton::Left || event.button() == GUI::MouseButton::Right) { 275 m_transform_event_origin = event.position(); 276 if (event.modifiers() == Mod_Ctrl) 277 remove_from_selection(*widget); 278 else if (event.modifiers() == Mod_Shift) 279 add_to_selection(*widget); 280 else if (!m_selected_widgets.contains(widget)) 281 set_single_selected_widget(widget); 282 for_each_selected_widget([](auto& widget) { widget.capture_transform_origin_rect(); }); 283 on_widget_selected(single_selected_widget()); 284 } 285} 286 287void VBForm::mousemove_event(GUI::MouseEvent& event) 288{ 289 if (event.buttons() & GUI::MouseButton::Left) { 290 if (m_resize_direction == Direction::None) { 291 update(); 292 auto delta = event.position() - m_transform_event_origin; 293 for_each_selected_widget([&](auto& widget) { 294 if (widget.is_in_layout()) 295 return; 296 auto new_rect = widget.transform_origin_rect().translated(delta); 297 new_rect.set_x(new_rect.x() - (new_rect.x() % m_grid_size)); 298 new_rect.set_y(new_rect.y() - (new_rect.y() % m_grid_size)); 299 widget.set_rect(new_rect); 300 }); 301 return; 302 } 303 int diff_x = event.x() - m_transform_event_origin.x(); 304 int diff_y = event.y() - m_transform_event_origin.y(); 305 306 int change_x = 0; 307 int change_y = 0; 308 int change_w = 0; 309 int change_h = 0; 310 311 switch (m_resize_direction) { 312 case Direction::DownRight: 313 change_w = diff_x; 314 change_h = diff_y; 315 break; 316 case Direction::Right: 317 change_w = diff_x; 318 break; 319 case Direction::UpRight: 320 change_w = diff_x; 321 change_y = diff_y; 322 change_h = -diff_y; 323 break; 324 case Direction::Up: 325 change_y = diff_y; 326 change_h = -diff_y; 327 break; 328 case Direction::UpLeft: 329 change_x = diff_x; 330 change_w = -diff_x; 331 change_y = diff_y; 332 change_h = -diff_y; 333 break; 334 case Direction::Left: 335 change_x = diff_x; 336 change_w = -diff_x; 337 break; 338 case Direction::DownLeft: 339 change_x = diff_x; 340 change_w = -diff_x; 341 change_h = diff_y; 342 break; 343 case Direction::Down: 344 change_h = diff_y; 345 break; 346 default: 347 ASSERT_NOT_REACHED(); 348 } 349 350 update(); 351 for_each_selected_widget([&](auto& widget) { 352 if (widget.is_in_layout()) 353 return; 354 auto new_rect = widget.transform_origin_rect(); 355 Gfx::Size minimum_size { 5, 5 }; 356 new_rect.set_x(new_rect.x() + change_x); 357 new_rect.set_y(new_rect.y() + change_y); 358 new_rect.set_width(max(minimum_size.width(), new_rect.width() + change_w)); 359 new_rect.set_height(max(minimum_size.height(), new_rect.height() + change_h)); 360 new_rect.set_x(new_rect.x() - (new_rect.x() % m_grid_size)); 361 new_rect.set_y(new_rect.y() - (new_rect.y() % m_grid_size)); 362 new_rect.set_width(new_rect.width() - (new_rect.width() % m_grid_size) + 1); 363 new_rect.set_height(new_rect.height() - (new_rect.height() % m_grid_size) + 1); 364 widget.set_rect(new_rect); 365 }); 366 367 set_cursor_type_from_grabber(m_resize_direction); 368 } else { 369 for (auto& widget : m_selected_widgets) { 370 if (widget->is_in_layout()) 371 continue; 372 auto grabber_at = widget->grabber_at(event.position()); 373 set_cursor_type_from_grabber(grabber_at); 374 if (grabber_at != Direction::None) 375 break; 376 } 377 } 378} 379 380void VBForm::load_from_file(const String& path) 381{ 382 auto file = Core::File::construct(path); 383 if (!file->open(Core::IODevice::ReadOnly)) { 384 GUI::MessageBox::show(String::format("Could not open '%s' for reading", path.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 385 return; 386 } 387 388 auto file_contents = file->read_all(); 389 auto form_json = JsonValue::from_string(file_contents); 390 391 if (!form_json.is_object()) { 392 GUI::MessageBox::show(String::format("Could not parse '%s'", path.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 393 return; 394 } 395 396 m_name = form_json.as_object().get("name").to_string(); 397 auto widgets = form_json.as_object().get("widgets").as_array(); 398 399 widgets.for_each([&](const JsonValue& widget_value) { 400 auto& widget_object = widget_value.as_object(); 401 auto widget_class = widget_object.get("class").as_string(); 402 auto widget_type = widget_type_from_class_name(widget_class); 403 // FIXME: Construct VBWidget within the right parent.. 404 auto vbwidget = VBWidget::create(widget_type, *this, nullptr); 405 widget_object.for_each_member([&](auto& property_name, const JsonValue& property_value) { 406 (void)property_name; 407 (void)property_value; 408 VBProperty& property = vbwidget->property(property_name); 409 dbgprintf("Set property %s.%s to '%s'\n", widget_class.characters(), property_name.characters(), property_value.to_string().characters()); 410 property.set_value(property_value); 411 }); 412 m_widgets.append(vbwidget); 413 }); 414} 415 416void VBForm::write_to_file(const String& path) 417{ 418 auto file = Core::File::construct(path); 419 if (!file->open(Core::IODevice::WriteOnly)) { 420 GUI::MessageBox::show(String::format("Could not open '%s' for writing", path.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 421 return; 422 } 423 424 JsonObject form_object; 425 form_object.set("name", m_name); 426 JsonArray widget_array; 427 for (auto& widget : m_widgets) { 428 JsonObject widget_object; 429 widget.for_each_property([&](auto& property) { 430 if (property.value().is_bool()) 431 widget_object.set(property.name(), property.value().to_bool()); 432 else if (property.value().is_i32()) 433 widget_object.set(property.name(), property.value().to_i32()); 434 else if (property.value().is_i64()) 435 widget_object.set(property.name(), property.value().to_i64()); 436 else 437 widget_object.set(property.name(), property.value().to_string()); 438 }); 439 widget_array.append(widget_object); 440 } 441 form_object.set("widgets", widget_array); 442 file->write(form_object.to_string()); 443} 444 445void VBForm::dump() 446{ 447 dbgprintf("[Form]\n"); 448 dbgprintf("Name=%s\n", m_name.characters()); 449 dbgprintf("\n"); 450 int i = 0; 451 for (auto& widget : m_widgets) { 452 dbgprintf("[Widget %d]\n", i++); 453 widget.for_each_property([](auto& property) { 454 dbgprintf("%s=%s\n", property.name().characters(), property.value().to_string().characters()); 455 }); 456 dbgprintf("\n"); 457 } 458} 459 460void VBForm::mouseup_event(GUI::MouseEvent& event) 461{ 462 if (event.button() == GUI::MouseButton::Left) { 463 m_transform_event_origin = {}; 464 m_resize_direction = Direction::None; 465 } 466} 467 468void VBForm::delete_selected_widgets() 469{ 470 Vector<VBWidget*> to_delete; 471 for_each_selected_widget([&](auto& widget) { 472 to_delete.append(&widget); 473 }); 474 if (to_delete.is_empty()) 475 return; 476 for (auto& widget : to_delete) 477 m_widgets.remove_first_matching([&widget](auto& entry) { return entry == widget; }); 478 on_widget_selected(single_selected_widget()); 479 update(); 480} 481 482template<typename Callback> 483void VBForm::for_each_selected_widget(Callback callback) 484{ 485 for (auto& widget : m_selected_widgets) 486 callback(*widget); 487} 488 489void VBForm::set_cursor_type_from_grabber(Direction grabber) 490{ 491 if (grabber == m_mouse_direction_type) 492 return; 493 494 switch (grabber) { 495 case Direction::Up: 496 case Direction::Down: 497 window()->set_override_cursor(GUI::StandardCursor::ResizeVertical); 498 break; 499 case Direction::Left: 500 case Direction::Right: 501 window()->set_override_cursor(GUI::StandardCursor::ResizeHorizontal); 502 break; 503 case Direction::UpLeft: 504 case Direction::DownRight: 505 window()->set_override_cursor(GUI::StandardCursor::ResizeDiagonalTLBR); 506 break; 507 case Direction::UpRight: 508 case Direction::DownLeft: 509 window()->set_override_cursor(GUI::StandardCursor::ResizeDiagonalBLTR); 510 break; 511 case Direction::None: 512 window()->set_override_cursor(GUI::StandardCursor::None); 513 break; 514 } 515 516 m_mouse_direction_type = grabber; 517} 518 519VBWidget* VBForm::single_selected_widget() 520{ 521 if (m_selected_widgets.size() != 1) 522 return nullptr; 523 return *m_selected_widgets.begin(); 524}