CellTypeDialog.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "CellTypeDialog.h"
  7. #include "Cell.h"
  8. #include "Spreadsheet.h"
  9. #include <AK/StringBuilder.h>
  10. #include <Applications/Spreadsheet/CondFormattingGML.h>
  11. #include <Applications/Spreadsheet/CondFormattingViewGML.h>
  12. #include <LibGUI/BoxLayout.h>
  13. #include <LibGUI/Button.h>
  14. #include <LibGUI/CheckBox.h>
  15. #include <LibGUI/ColorInput.h>
  16. #include <LibGUI/ComboBox.h>
  17. #include <LibGUI/ItemListModel.h>
  18. #include <LibGUI/Label.h>
  19. #include <LibGUI/ListView.h>
  20. #include <LibGUI/SpinBox.h>
  21. #include <LibGUI/TabWidget.h>
  22. #include <LibGUI/TextEditor.h>
  23. #include <LibGUI/Widget.h>
  24. #include <LibGfx/Font/FontDatabase.h>
  25. #include <LibJS/SyntaxHighlighter.h>
  26. REGISTER_WIDGET(Spreadsheet, ConditionsView);
  27. namespace Spreadsheet {
  28. CellTypeDialog::CellTypeDialog(Vector<Position> const& positions, Sheet& sheet, GUI::Window* parent)
  29. : GUI::Dialog(parent)
  30. {
  31. VERIFY(!positions.is_empty());
  32. StringBuilder builder;
  33. if (positions.size() == 1)
  34. builder.appendff("Format cell {}", positions.first().to_cell_identifier(sheet));
  35. else
  36. builder.appendff("Format {} cells", positions.size());
  37. set_title(builder.string_view());
  38. set_icon(parent->icon());
  39. resize(285, 360);
  40. auto main_widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
  41. main_widget->set_layout<GUI::VerticalBoxLayout>(4);
  42. main_widget->set_fill_with_background_color(true);
  43. auto& tab_widget = main_widget->add<GUI::TabWidget>();
  44. setup_tabs(tab_widget, positions, sheet);
  45. auto& buttonbox = main_widget->add<GUI::Widget>();
  46. buttonbox.set_shrink_to_fit(true);
  47. buttonbox.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins {}, 10);
  48. buttonbox.add_spacer().release_value_but_fixme_should_propagate_errors();
  49. auto& ok_button = buttonbox.add<GUI::Button>(String::from_utf8_short_string("OK"sv));
  50. ok_button.set_fixed_width(80);
  51. ok_button.on_click = [&](auto) { done(ExecResult::OK); };
  52. }
  53. Vector<DeprecatedString> const g_horizontal_alignments { "Left", "Center", "Right" };
  54. Vector<DeprecatedString> const g_vertical_alignments { "Top", "Center", "Bottom" };
  55. Vector<DeprecatedString> g_types;
  56. constexpr static CellTypeDialog::VerticalAlignment vertical_alignment_from(Gfx::TextAlignment alignment)
  57. {
  58. switch (alignment) {
  59. case Gfx::TextAlignment::CenterRight:
  60. case Gfx::TextAlignment::CenterLeft:
  61. case Gfx::TextAlignment::Center:
  62. return CellTypeDialog::VerticalAlignment::Center;
  63. case Gfx::TextAlignment::TopCenter:
  64. case Gfx::TextAlignment::TopRight:
  65. case Gfx::TextAlignment::TopLeft:
  66. return CellTypeDialog::VerticalAlignment::Top;
  67. case Gfx::TextAlignment::BottomCenter:
  68. case Gfx::TextAlignment::BottomLeft:
  69. case Gfx::TextAlignment::BottomRight:
  70. return CellTypeDialog::VerticalAlignment::Bottom;
  71. }
  72. return CellTypeDialog::VerticalAlignment::Center;
  73. }
  74. constexpr static CellTypeDialog::HorizontalAlignment horizontal_alignment_from(Gfx::TextAlignment alignment)
  75. {
  76. switch (alignment) {
  77. case Gfx::TextAlignment::BottomCenter:
  78. case Gfx::TextAlignment::Center:
  79. case Gfx::TextAlignment::TopCenter:
  80. return CellTypeDialog::HorizontalAlignment::Center;
  81. case Gfx::TextAlignment::TopRight:
  82. case Gfx::TextAlignment::CenterRight:
  83. case Gfx::TextAlignment::BottomRight:
  84. return CellTypeDialog::HorizontalAlignment::Right;
  85. case Gfx::TextAlignment::TopLeft:
  86. case Gfx::TextAlignment::CenterLeft:
  87. case Gfx::TextAlignment::BottomLeft:
  88. return CellTypeDialog::HorizontalAlignment::Left;
  89. }
  90. return CellTypeDialog::HorizontalAlignment::Right;
  91. }
  92. void CellTypeDialog::setup_tabs(GUI::TabWidget& tabs, Vector<Position> const& positions, Sheet& sheet)
  93. {
  94. g_types.clear();
  95. for (auto& type_name : CellType::names())
  96. g_types.append(type_name);
  97. Vector<Cell&> cells;
  98. for (auto& position : positions) {
  99. if (auto cell = sheet.at(position))
  100. cells.append(*cell);
  101. }
  102. if (cells.size() == 1) {
  103. auto& cell = cells.first();
  104. m_format = cell.type_metadata().format;
  105. m_length = cell.type_metadata().length;
  106. m_type = &cell.type();
  107. m_vertical_alignment = vertical_alignment_from(cell.type_metadata().alignment);
  108. m_horizontal_alignment = horizontal_alignment_from(cell.type_metadata().alignment);
  109. m_static_format = cell.type_metadata().static_format;
  110. m_conditional_formats = cell.conditional_formats();
  111. }
  112. auto& type_tab = tabs.add_tab<GUI::Widget>("Type");
  113. type_tab.set_layout<GUI::HorizontalBoxLayout>(4);
  114. {
  115. auto& left_side = type_tab.add<GUI::Widget>();
  116. left_side.set_layout<GUI::VerticalBoxLayout>();
  117. auto& right_side = type_tab.add<GUI::Widget>();
  118. right_side.set_layout<GUI::VerticalBoxLayout>();
  119. right_side.set_fixed_width(170);
  120. auto& type_list = left_side.add<GUI::ListView>();
  121. type_list.set_model(*GUI::ItemListModel<DeprecatedString>::create(g_types));
  122. type_list.set_should_hide_unnecessary_scrollbars(true);
  123. type_list.on_selection_change = [&] {
  124. const auto& index = type_list.selection().first();
  125. if (!index.is_valid()) {
  126. m_type = nullptr;
  127. return;
  128. }
  129. m_type = CellType::get_by_name(g_types.at(index.row()));
  130. if (auto* editor = right_side.find_descendant_of_type_named<GUI::TextEditor>("format_editor"))
  131. editor->set_tooltip(m_type->metadata_hint(MetadataName::Format));
  132. };
  133. {
  134. auto& checkbox = right_side.add<GUI::CheckBox>(String::from_utf8("Override max length"sv).release_value_but_fixme_should_propagate_errors());
  135. auto& spinbox = right_side.add<GUI::SpinBox>();
  136. checkbox.set_checked(m_length != -1);
  137. spinbox.set_min(0);
  138. spinbox.set_enabled(m_length != -1);
  139. if (m_length > -1)
  140. spinbox.set_value(m_length);
  141. checkbox.on_checked = [&](auto checked) {
  142. spinbox.set_enabled(checked);
  143. if (!checked) {
  144. m_length = -1;
  145. spinbox.set_value(0);
  146. }
  147. };
  148. spinbox.on_change = [&](auto value) {
  149. m_length = value;
  150. };
  151. }
  152. {
  153. auto& checkbox = right_side.add<GUI::CheckBox>(String::from_utf8("Override display format"sv).release_value_but_fixme_should_propagate_errors());
  154. auto& editor = right_side.add<GUI::TextEditor>();
  155. checkbox.set_checked(!m_format.is_empty());
  156. editor.set_name("format_editor");
  157. editor.set_should_hide_unnecessary_scrollbars(true);
  158. editor.set_enabled(!m_format.is_empty());
  159. editor.set_text(m_format);
  160. checkbox.on_checked = [&](auto checked) {
  161. editor.set_enabled(checked);
  162. if (!checked)
  163. m_format = DeprecatedString::empty();
  164. editor.set_text(m_format);
  165. };
  166. editor.on_change = [&] {
  167. m_format = editor.text();
  168. };
  169. }
  170. }
  171. auto& alignment_tab = tabs.add_tab<GUI::Widget>("Alignment");
  172. alignment_tab.set_layout<GUI::VerticalBoxLayout>(4);
  173. {
  174. // FIXME: Frame?
  175. // Horizontal alignment
  176. {
  177. auto& horizontal_alignment_selection_container = alignment_tab.add<GUI::Widget>();
  178. horizontal_alignment_selection_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 4, 0, 0 });
  179. horizontal_alignment_selection_container.set_fixed_height(22);
  180. auto& horizontal_alignment_label = horizontal_alignment_selection_container.add<GUI::Label>();
  181. horizontal_alignment_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  182. horizontal_alignment_label.set_text("Horizontal text alignment");
  183. auto& horizontal_combobox = alignment_tab.add<GUI::ComboBox>();
  184. horizontal_combobox.set_only_allow_values_from_model(true);
  185. horizontal_combobox.set_model(*GUI::ItemListModel<DeprecatedString>::create(g_horizontal_alignments));
  186. horizontal_combobox.set_selected_index((int)m_horizontal_alignment);
  187. horizontal_combobox.on_change = [&](auto&, const GUI::ModelIndex& index) {
  188. switch (index.row()) {
  189. case 0:
  190. m_horizontal_alignment = HorizontalAlignment::Left;
  191. break;
  192. case 1:
  193. m_horizontal_alignment = HorizontalAlignment::Center;
  194. break;
  195. case 2:
  196. m_horizontal_alignment = HorizontalAlignment::Right;
  197. break;
  198. default:
  199. VERIFY_NOT_REACHED();
  200. }
  201. };
  202. }
  203. // Vertical alignment
  204. {
  205. auto& vertical_alignment_container = alignment_tab.add<GUI::Widget>();
  206. vertical_alignment_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 4, 0, 0 });
  207. vertical_alignment_container.set_fixed_height(22);
  208. auto& vertical_alignment_label = vertical_alignment_container.add<GUI::Label>();
  209. vertical_alignment_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  210. vertical_alignment_label.set_text("Vertical text alignment");
  211. auto& vertical_combobox = alignment_tab.add<GUI::ComboBox>();
  212. vertical_combobox.set_only_allow_values_from_model(true);
  213. vertical_combobox.set_model(*GUI::ItemListModel<DeprecatedString>::create(g_vertical_alignments));
  214. vertical_combobox.set_selected_index((int)m_vertical_alignment);
  215. vertical_combobox.on_change = [&](auto&, const GUI::ModelIndex& index) {
  216. switch (index.row()) {
  217. case 0:
  218. m_vertical_alignment = VerticalAlignment::Top;
  219. break;
  220. case 1:
  221. m_vertical_alignment = VerticalAlignment::Center;
  222. break;
  223. case 2:
  224. m_vertical_alignment = VerticalAlignment::Bottom;
  225. break;
  226. default:
  227. VERIFY_NOT_REACHED();
  228. }
  229. };
  230. }
  231. }
  232. auto& colors_tab = tabs.add_tab<GUI::Widget>("Color");
  233. colors_tab.set_layout<GUI::VerticalBoxLayout>(4);
  234. {
  235. // Static formatting
  236. {
  237. auto& static_formatting_container = colors_tab.add<GUI::Widget>();
  238. static_formatting_container.set_layout<GUI::VerticalBoxLayout>();
  239. // Foreground
  240. {
  241. // FIXME: Somehow allow unsetting these.
  242. auto& foreground_container = static_formatting_container.add<GUI::Widget>();
  243. foreground_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 4, 0, 0 });
  244. foreground_container.set_preferred_height(GUI::SpecialDimension::Fit);
  245. auto& foreground_label = foreground_container.add<GUI::Label>();
  246. foreground_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  247. foreground_label.set_text("Static foreground color");
  248. auto& foreground_selector = foreground_container.add<GUI::ColorInput>();
  249. if (m_static_format.foreground_color.has_value())
  250. foreground_selector.set_color(m_static_format.foreground_color.value());
  251. foreground_selector.on_change = [&]() {
  252. m_static_format.foreground_color = foreground_selector.color();
  253. };
  254. }
  255. // Background
  256. {
  257. // FIXME: Somehow allow unsetting these.
  258. auto& background_container = static_formatting_container.add<GUI::Widget>();
  259. background_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 4, 0, 0 });
  260. background_container.set_preferred_height(GUI::SpecialDimension::Fit);
  261. auto& background_label = background_container.add<GUI::Label>();
  262. background_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
  263. background_label.set_text("Static background color");
  264. auto& background_selector = background_container.add<GUI::ColorInput>();
  265. if (m_static_format.background_color.has_value())
  266. background_selector.set_color(m_static_format.background_color.value());
  267. background_selector.on_change = [&]() {
  268. m_static_format.background_color = background_selector.color();
  269. };
  270. }
  271. }
  272. }
  273. auto& conditional_fmt_tab = tabs.add_tab<GUI::Widget>("Conditional format");
  274. conditional_fmt_tab.load_from_gml(cond_fmt_gml).release_value_but_fixme_should_propagate_errors();
  275. {
  276. auto& view = *conditional_fmt_tab.find_descendant_of_type_named<Spreadsheet::ConditionsView>("conditions_view");
  277. view.set_formats(&m_conditional_formats);
  278. auto& add_button = *conditional_fmt_tab.find_descendant_of_type_named<GUI::Button>("add_button");
  279. add_button.on_click = [&](auto) {
  280. view.add_format();
  281. };
  282. // FIXME: Disable this when empty.
  283. auto& remove_button = *conditional_fmt_tab.find_descendant_of_type_named<GUI::Button>("remove_button");
  284. remove_button.on_click = [&](auto) {
  285. view.remove_top();
  286. };
  287. }
  288. }
  289. CellTypeMetadata CellTypeDialog::metadata() const
  290. {
  291. CellTypeMetadata metadata;
  292. metadata.format = m_format;
  293. metadata.length = m_length;
  294. metadata.static_format = m_static_format;
  295. switch (m_vertical_alignment) {
  296. case VerticalAlignment::Top:
  297. switch (m_horizontal_alignment) {
  298. case HorizontalAlignment::Left:
  299. metadata.alignment = Gfx::TextAlignment::TopLeft;
  300. break;
  301. case HorizontalAlignment::Center:
  302. metadata.alignment = Gfx::TextAlignment::TopCenter;
  303. break;
  304. case HorizontalAlignment::Right:
  305. metadata.alignment = Gfx::TextAlignment::TopRight;
  306. break;
  307. }
  308. break;
  309. case VerticalAlignment::Center:
  310. switch (m_horizontal_alignment) {
  311. case HorizontalAlignment::Left:
  312. metadata.alignment = Gfx::TextAlignment::CenterLeft;
  313. break;
  314. case HorizontalAlignment::Center:
  315. metadata.alignment = Gfx::TextAlignment::Center;
  316. break;
  317. case HorizontalAlignment::Right:
  318. metadata.alignment = Gfx::TextAlignment::CenterRight;
  319. break;
  320. }
  321. break;
  322. case VerticalAlignment::Bottom:
  323. switch (m_horizontal_alignment) {
  324. case HorizontalAlignment::Left:
  325. metadata.alignment = Gfx::TextAlignment::CenterLeft; // BottomLeft?
  326. break;
  327. case HorizontalAlignment::Center:
  328. metadata.alignment = Gfx::TextAlignment::Center;
  329. break;
  330. case HorizontalAlignment::Right:
  331. metadata.alignment = Gfx::TextAlignment::BottomRight;
  332. break;
  333. }
  334. break;
  335. }
  336. return metadata;
  337. }
  338. ConditionView::ConditionView(ConditionalFormat& fmt)
  339. : m_format(fmt)
  340. {
  341. load_from_gml(cond_fmt_view_gml).release_value_but_fixme_should_propagate_errors();
  342. auto& fg_input = *find_descendant_of_type_named<GUI::ColorInput>("foreground_input");
  343. auto& bg_input = *find_descendant_of_type_named<GUI::ColorInput>("background_input");
  344. auto& formula_editor = *find_descendant_of_type_named<GUI::TextEditor>("formula_editor");
  345. if (m_format.foreground_color.has_value())
  346. fg_input.set_color(m_format.foreground_color.value());
  347. if (m_format.background_color.has_value())
  348. bg_input.set_color(m_format.background_color.value());
  349. formula_editor.set_text(m_format.condition);
  350. // FIXME: Allow unsetting these.
  351. fg_input.on_change = [&] {
  352. m_format.foreground_color = fg_input.color();
  353. };
  354. bg_input.on_change = [&] {
  355. m_format.background_color = bg_input.color();
  356. };
  357. formula_editor.set_syntax_highlighter(make<JS::SyntaxHighlighter>());
  358. formula_editor.set_should_hide_unnecessary_scrollbars(true);
  359. formula_editor.on_change = [&] {
  360. m_format.condition = formula_editor.text();
  361. };
  362. }
  363. ConditionView::~ConditionView()
  364. {
  365. }
  366. ConditionsView::ConditionsView()
  367. {
  368. set_layout<GUI::VerticalBoxLayout>(6, 4);
  369. }
  370. void ConditionsView::set_formats(Vector<ConditionalFormat>* formats)
  371. {
  372. VERIFY(!m_formats);
  373. m_formats = formats;
  374. for (auto& entry : *m_formats)
  375. m_widgets.append(add<ConditionView>(entry));
  376. }
  377. void ConditionsView::add_format()
  378. {
  379. VERIFY(m_formats);
  380. m_formats->empend();
  381. auto& last = m_formats->last();
  382. m_widgets.append(add<ConditionView>(last));
  383. update();
  384. }
  385. void ConditionsView::remove_top()
  386. {
  387. VERIFY(m_formats);
  388. if (m_formats->is_empty())
  389. return;
  390. m_formats->take_last();
  391. m_widgets.take_last()->remove_from_parent();
  392. update();
  393. }
  394. ConditionsView::~ConditionsView()
  395. {
  396. }
  397. }