// // Snap action code file for the Fast Light Tool Kit (FLTK). // // Copyright 2023 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // https://www.fltk.org/COPYING.php // // Please see the following page on how to report bugs and issues: // // https://www.fltk.org/bugs.php // #include "Fd_Snap_Action.h" #include "Fl_Group_Type.h" #include "alignment_panel.h" #include "file.h" #include #include #include #include #include #include // TODO: warning if the user wants to change builtin layouts // TODO: move panel to global settings panel (move load & save to main pulldown, or to toolbox?) // INFO: how about a small tool box for quick preset selection and diabeling of individual snaps? void select_layout_suite_cb(Fl_Widget *, void *user_data); int Fd_Snap_Action::eex = 0; int Fd_Snap_Action::eey = 0; static Fd_Layout_Preset fltk_app = { 15, 15, 15, 15, 0, 0, // window: l, r, t, b, gx, gy 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy 25, 25, // tabs: t, b 20, 10, 4, // widget_x: min, inc, gap 20, 4, 8, // widget_y: min, inc, gap 0, 14, 0, 14 // labelfont/size, textfont/size }; static Fd_Layout_Preset fltk_dlg = { 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy 20, 20, // tabs: t, b 20, 10, 5, // widget_x: min, inc, gap 20, 5, 5, // widget_y: min, inc, gap 0, 11, 0, 11 // labelfont/size, textfont/size }; static Fd_Layout_Preset fltk_tool = { 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy 18, 18, // tabs: t, b 16, 8, 2, // widget_x: min, inc, gap 16, 4, 2, // widget_y: min, inc, gap 0, 10, 0, 10 // labelfont/size, textfont/size }; static Fd_Layout_Preset grid_app = { 12, 12, 12, 12, 12, 12, // window: l, r, t, b, gx, gy 12, 12, 12, 12, 12, 12, // group: l, r, t, b, gx, gy 24, 24, // tabs: t, b 12, 6, 6, // widget_x: min, inc, gap 12, 6, 6, // widget_y: min, inc, gap 0, 14, 0, 14 // labelfont/size, textfont/size }; static Fd_Layout_Preset grid_dlg = { 10, 10, 10, 10, 10, 10, // window: l, r, t, b, gx, gy 10, 10, 10, 10, 10, 10, // group: l, r, t, b, gx, gy 20, 20, // tabs: t, b 10, 5, 5, // widget_x: min, inc, gap 10, 5, 5, // widget_y: min, inc, gap 0, 12, 0, 12 // labelfont/size, textfont/size }; static Fd_Layout_Preset grid_tool = { 8, 8, 8, 8, 8, 8, // window: l, r, t, b, gx, gy 8, 8, 8, 8, 8, 8, // group: l, r, t, b, gx, gy 16, 16, // tabs: t, b 8, 4, 4, // widget_x: min, inc, gap 8, 4, 4, // widget_y: min, inc, gap 0, 10, 0, 10 // labelfont/size, textfont/size }; static Fd_Layout_Suite static_suite_list[] = { { (char*)"FLTK", (char*)"@fd_beaker FLTK", { &fltk_app, &fltk_dlg, &fltk_tool }, FD_STORE_INTERNAL }, { (char*)"Grid", (char*)"@fd_beaker Grid", { &grid_app, &grid_dlg, &grid_tool }, FD_STORE_INTERNAL } }; Fl_Menu_Item main_layout_submenu_[] = { { static_suite_list[0].menu_label, 0, select_layout_suite_cb, (void*)0, FL_MENU_RADIO|FL_MENU_VALUE }, { static_suite_list[1].menu_label, 0, select_layout_suite_cb, (void*)1, FL_MENU_RADIO }, { NULL } }; static Fl_Menu_Item static_choice_menu[] = { { static_suite_list[0].menu_label }, { static_suite_list[1].menu_label }, { NULL } }; Fd_Layout_Preset *layout = &fltk_app; Fd_Layout_List g_layout_list; // ---- Callbacks ------------------------------------------------------ MARK: - void layout_suite_marker(Fl_Widget *, void *) { assert(0); // intentionally left empty } void select_layout_suite_cb(Fl_Widget *, void *user_data) { int index = (int)(fl_intptr_t)user_data; assert(index >= 0); assert(index < g_layout_list.list_size_); g_layout_list.current_suite(index); g_layout_list.update_dialogs(); } void select_layout_preset_cb(Fl_Widget *, void *user_data) { int index = (int)(fl_intptr_t)user_data; assert(index >= 0); assert(index < 3); g_layout_list.current_preset(index); g_layout_list.update_dialogs(); } void edit_layout_preset_cb(Fl_Button *w, long user_data) { int index = (int)w->argument(); assert(index >= 0); assert(index < 3); if (user_data == (long)(fl_intptr_t)LOAD) { w->value(g_layout_list.current_preset() == index); } else { g_layout_list.current_preset(index); g_layout_list.update_dialogs(); } } // ---- Fd_Layout_Suite ------------------------------------------------ MARK: - void Fd_Layout_Preset::write(Fl_Preferences &prefs) { assert(this); Fl_Preferences p_win(prefs, "Window"); p_win.set("left_margin", left_window_margin); p_win.set("right_margin", right_window_margin); p_win.set("top_margin", top_window_margin); p_win.set("bottom_margin", bottom_window_margin); p_win.set("grid_x", window_grid_x); p_win.set("grid_y", window_grid_y); Fl_Preferences p_grp(prefs, "Group"); p_grp.set("left_margin", left_group_margin); p_grp.set("right_margin", right_group_margin); p_grp.set("top_margin", top_group_margin); p_grp.set("bottom_margin", bottom_group_margin); p_grp.set("grid_x", group_grid_x); p_grp.set("grid_y", group_grid_y); Fl_Preferences p_tbs(prefs, "Tabs"); p_tbs.set("top_margin", top_tabs_margin); p_tbs.set("bottom_margin", bottom_tabs_margin); Fl_Preferences p_wgt(prefs, "Widget"); p_wgt.set("min_w", widget_min_w); p_wgt.set("inc_w", widget_inc_w); p_wgt.set("gap_x", widget_gap_x); p_wgt.set("min_h", widget_min_h); p_wgt.set("inc_h", widget_inc_h); p_wgt.set("gap_y", widget_gap_y); Fl_Preferences p_lyt(prefs, "Layout"); p_lyt.set("labelfont", labelfont); p_lyt.set("labelsize", labelsize); p_lyt.set("textfont", textfont); p_lyt.set("textsize", textsize); } void Fd_Layout_Preset::read(Fl_Preferences &prefs) { assert(this); Fl_Preferences p_win(prefs, "Window"); p_win.get("left_margin", left_window_margin, 15); p_win.get("right_margin", right_window_margin, 15); p_win.get("top_margin", top_window_margin, 15); p_win.get("bottom_margin", bottom_window_margin, 15); p_win.get("grid_x", window_grid_x, 0); p_win.get("grid_y", window_grid_y, 0); Fl_Preferences p_grp(prefs, "Group"); p_grp.get("left_margin", left_group_margin, 10); p_grp.get("right_margin", right_group_margin, 10); p_grp.get("top_margin", top_group_margin, 10); p_grp.get("bottom_margin", bottom_group_margin, 10); p_grp.get("grid_x", group_grid_x, 0); p_grp.get("grid_y", group_grid_y, 0); Fl_Preferences p_tbs(prefs, "Tabs"); p_tbs.get("top_margin", top_tabs_margin, 25); p_tbs.get("bottom_margin", bottom_tabs_margin, 25); Fl_Preferences p_wgt(prefs, "Widget"); p_wgt.get("min_w", widget_min_w, 20); p_wgt.get("inc_w", widget_inc_w, 10); p_wgt.get("gap_x", widget_gap_x, 4); p_wgt.get("min_h", widget_min_h, 20); p_wgt.get("inc_h", widget_inc_h, 4); p_wgt.get("gap_y", widget_gap_y, 8); Fl_Preferences p_lyt(prefs, "Layout"); p_lyt.get("labelfont", labelfont, 0); p_lyt.get("labelsize", labelsize, 14); p_lyt.get("textfont", textfont, 0); p_lyt.get("textsize", textsize, 14); } void Fd_Layout_Preset::write(Fd_Project_Writer *out) { out->write_string(" preset { 1\n"); // preset format version out->write_string(" %d %d %d %d %d %d\n", left_window_margin, right_window_margin, top_window_margin, bottom_window_margin, window_grid_x, window_grid_y); out->write_string(" %d %d %d %d %d %d\n", left_group_margin, right_group_margin, top_group_margin, bottom_group_margin, group_grid_x, group_grid_y); out->write_string(" %d %d\n", top_tabs_margin, bottom_tabs_margin); out->write_string(" %d %d %d %d %d %d\n", widget_min_w, widget_inc_w, widget_gap_x, widget_min_h, widget_inc_h, widget_gap_y); out->write_string(" %d %d %d %d\n", labelfont, labelsize, textfont, textsize); out->write_string(" }\n"); // preset format version } void Fd_Layout_Preset::read(Fd_Project_Reader *in) { const char *key; key = in->read_word(1); if (key && !strcmp(key, "{")) { for (;;) { key = in->read_word(); if (!key) return; if (key[0] == '}') break; int ver = atoi(key); if (ver == 0) { continue; } else if (ver == 1) { left_window_margin = in->read_int(); right_window_margin = in->read_int(); top_window_margin = in->read_int(); bottom_window_margin = in->read_int(); window_grid_x = in->read_int(); window_grid_y = in->read_int(); left_group_margin = in->read_int(); right_group_margin = in->read_int(); top_group_margin = in->read_int(); bottom_group_margin = in->read_int(); group_grid_x = in->read_int(); group_grid_y = in->read_int(); top_tabs_margin = in->read_int(); bottom_tabs_margin = in->read_int(); widget_min_w = in->read_int(); widget_inc_w = in->read_int(); widget_gap_x = in->read_int(); widget_min_h = in->read_int(); widget_inc_h = in->read_int(); widget_gap_y = in->read_int(); labelfont = in->read_int(); labelsize = in->read_int(); textfont = in->read_int(); textsize = in->read_int(); } else { // skip unknown chunks for (;;) { key = in->read_word(1); if (key && (key[0] == '}')) return; } } } } else { // format error } } // ---- Fd_Layout_Suite ------------------------------------------------ MARK: - void Fd_Layout_Suite::write(Fl_Preferences &prefs) { assert(this); assert(name_); prefs.set("name", name_); for (int i = 0; i < 3; ++i) { Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); assert(layout[i]); layout[i]->write(prefs_preset); } } void Fd_Layout_Suite::read(Fl_Preferences &prefs) { assert(this); for (int i = 0; i < 3; ++i) { Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); assert(layout[i]); layout[i]->read(prefs_preset); } } void Fd_Layout_Suite::write(Fd_Project_Writer *out) { out->write_string(" suite {\n"); out->write_string(" name "); out->write_word(name_); out->write_string("\n"); for (int i = 0; i < 3; ++i) { layout[i]->write(out); } out->write_string(" }\n"); } void Fd_Layout_Suite::read(Fd_Project_Reader *in) { const char *key; key = in->read_word(1); if (key && !strcmp(key, "{")) { int ix = 0; for (;;) { key = in->read_word(); if (!key) return; if (!strcmp(key, "name")) { name(in->read_word()); } else if (!strcmp(key, "preset")) { if (ix >= 3) return; // file format error layout[ix++]->read(in); } else if (!strcmp(key, "}")) { break; } else { in->read_word(); // unknown key, ignore, hopefully a key-value pair } } } else { // file format error } } void Fd_Layout_Suite::update_label() { Fl_String sym; switch (storage_) { case FD_STORE_INTERNAL: sym.assign("@fd_beaker "); break; case FD_STORE_USER: sym.assign("@fd_user "); break; case FD_STORE_PROJECT: sym.assign("@fd_project "); break; case FD_STORE_FILE: sym.assign("@fd_file "); break; } sym.append(name_); if (menu_label) ::free(menu_label); menu_label = fl_strdup(sym.c_str()); g_layout_list.update_menu_labels(); } void Fd_Layout_Suite::name(const char *n) { if (name_) ::free(name_); if (n) name_ = fl_strdup(n); else name_ = NULL; update_label(); } void Fd_Layout_Suite::init() { name_ = NULL; menu_label = NULL; layout[0] = layout[1] = layout[2] = NULL; storage_ = 0; } Fd_Layout_Suite::~Fd_Layout_Suite() { if (storage_ == FD_STORE_INTERNAL) return; if (name_) ::free(name_); for (int i = 0; i < 3; ++i) { delete layout[i]; } } // ---- Fd_Layout_List ------------------------------------------------- MARK: - static void fd_beaker(Fl_Color c) { fl_color(221); fl_begin_polygon(); fl_vertex(-0.6, 0.2); fl_vertex(-0.9, 0.8); fl_vertex(-0.8, 0.9); fl_vertex( 0.8, 0.9); fl_vertex( 0.9, 0.8); fl_vertex( 0.6, 0.2); fl_end_polygon(); fl_color(c); fl_begin_line(); fl_vertex(-0.3, -0.9); fl_vertex(-0.2, -0.8); fl_vertex(-0.2, -0.2); fl_vertex(-0.9, 0.8); fl_vertex(-0.8, 0.9); fl_vertex( 0.8, 0.9); fl_vertex( 0.9, 0.8); fl_vertex( 0.2, -0.2); fl_vertex( 0.2, -0.8); fl_vertex( 0.3, -0.9); fl_end_line(); } static void fd_user(Fl_Color c) { fl_color(245); fl_begin_complex_polygon(); fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); fl_end_complex_polygon(); fl_color(c); fl_begin_line(); fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); fl_end_line(); } static void fd_project(Fl_Color c) { Fl_Color fc = FL_LIGHT2; fl_color(fc); fl_begin_complex_polygon(); fl_vertex(-0.7, -1.0); fl_vertex(0.1, -1.0); fl_vertex(0.1, -0.4); fl_vertex(0.7, -0.4); fl_vertex(0.7, 1.0); fl_vertex(-0.7, 1.0); fl_end_complex_polygon(); fl_color(fl_lighter(fc)); fl_begin_polygon(); fl_vertex(0.1, -1.0); fl_vertex(0.1, -0.4); fl_vertex(0.7, -0.4); fl_end_polygon(); fl_color(fl_darker(c)); fl_begin_loop(); fl_vertex(-0.7, -1.0); fl_vertex(0.1, -1.0); fl_vertex(0.1, -0.4); fl_vertex(0.7, -0.4); fl_vertex(0.7, 1.0); fl_vertex(-0.7, 1.0); fl_end_loop(); fl_begin_line(); fl_vertex(0.1, -1.0); fl_vertex(0.7, -0.4); fl_end_line(); } void fd_file(Fl_Color c) { Fl_Color fl = FL_LIGHT2; Fl_Color fc = FL_DARK3; fl_color(fc); fl_begin_polygon(); // case fl_vertex(-0.9, -1.0); fl_vertex(0.9, -1.0); fl_vertex(1.0, -0.9); fl_vertex(1.0, 0.9); fl_vertex(0.9, 1.0); fl_vertex(-0.9, 1.0); fl_vertex(-1.0, 0.9); fl_vertex(-1.0, -0.9); fl_end_polygon(); fl_color(fl_lighter(fl)); fl_begin_polygon(); fl_vertex(-0.7, -1.0); // slider fl_vertex(0.7, -1.0); fl_vertex(0.7, -0.4); fl_vertex(-0.7, -0.4); fl_end_polygon(); fl_begin_polygon(); // label fl_vertex(-0.7, 0.0); fl_vertex(0.7, 0.0); fl_vertex(0.7, 1.0); fl_vertex(-0.7, 1.0); fl_end_polygon(); fl_color(fc); fl_begin_polygon(); fl_vertex(-0.5, -0.9); // slot fl_vertex(-0.3, -0.9); fl_vertex(-0.3, -0.5); fl_vertex(-0.5, -0.5); fl_end_polygon(); fl_color(fl_darker(c)); fl_begin_loop(); fl_vertex(-0.9, -1.0); fl_vertex(0.9, -1.0); fl_vertex(1.0, -0.9); fl_vertex(1.0, 0.9); fl_vertex(0.9, 1.0); fl_vertex(-0.9, 1.0); fl_vertex(-1.0, 0.9); fl_vertex(-1.0, -0.9); fl_end_loop(); } Fd_Layout_List::Fd_Layout_List() : main_menu_(main_layout_submenu_), choice_menu_(static_choice_menu), list_(static_suite_list), list_size_(2), list_capacity_(2), list_is_static_(true), current_suite_(0), current_preset_(0), filename_(NULL) { fl_add_symbol("fd_beaker", fd_beaker, 1); fl_add_symbol("fd_user", fd_user, 1); fl_add_symbol("fd_project", fd_project, 1); fl_add_symbol("fd_file", fd_file, 1); } Fd_Layout_List::~Fd_Layout_List() { assert(this); if (!list_is_static_) { ::free(main_menu_); ::free(choice_menu_); for (int i = 0; i < list_size_; i++) { Fd_Layout_Suite &suite = list_[i]; if (suite.storage_ != FD_STORE_INTERNAL) suite.~Fd_Layout_Suite(); } ::free(list_); } if (filename_) ::free(filename_); } void Fd_Layout_List::update_dialogs() { static Fl_Menu_Item *preset_menu = NULL; if (!preset_menu) { preset_menu = (Fl_Menu_Item*)main_menubar->find_item(select_layout_preset_cb); assert(preset_menu); } assert(this); assert(current_suite_ >= 0 ); assert(current_suite_ < list_size_); assert(current_preset_ >= 0 ); assert(current_preset_ < 3); layout = list_[current_suite_].layout[current_preset_]; assert(layout); if (w_settings_layout_tab) { w_settings_layout_tab->do_callback(w_settings_layout_tab, LOAD); layout_choice->redraw(); } preset_menu[current_preset_].setonly(preset_menu); main_menu_[current_suite_].setonly(main_menu_); } void Fd_Layout_List::update_menu_labels() { for (int i=0; iwrite_string("\nsnap {\n ver 1\n"); out->write_string(" current_suite "); out->write_word(list_[current_suite()].name_); out->write_string("\n"); out->write_string(" current_preset %d\n", current_preset()); for (int i=0; iwrite_string("}"); } void Fd_Layout_List::read(Fd_Project_Reader *in) { const char *key; key = in->read_word(1); if (key && !strcmp(key, "{")) { Fl_String cs; int cp = 0; for (;;) { key = in->read_word(); if (!key) return; if (!strcmp(key, "ver")) { in->read_int(); } else if (!strcmp(key, "current_suite")) { cs = in->read_word(); } else if (!strcmp(key, "current_preset")) { cp = in->read_int(); } else if (!strcmp(key, "suite")) { int n = add(in->filename_name()); list_[n].read(in); list_[n].storage(FD_STORE_PROJECT); } else if (!strcmp(key, "}")) { break; } else { in->read_word(); // unknown key, ignore, hopefully a key-value pair } } current_suite(cs); current_preset(cp); update_dialogs(); } else { // old style "snap" is followed by an integer. Ignore. } } void Fd_Layout_List::current_suite(int ix) { assert(ix >= 0); assert(ix < list_size_); current_suite_ = ix; layout = list_[current_suite_].layout[current_preset_]; } void Fd_Layout_List::current_suite(Fl_String arg_name) { if (arg_name.empty()) return; for (int i = 0; i < list_size_; ++i) { Fd_Layout_Suite &suite = list_[i]; if (suite.name_ && (strcmp(suite.name_, arg_name.c_str()) == 0)) { current_suite(i); break; } } } void Fd_Layout_List::current_preset(int ix) { assert(ix >= 0); assert(ix < 3); current_preset_ = ix; layout = list_[current_suite_].layout[current_preset_]; } /** Allocate enough space for n entries in the list. */ void Fd_Layout_List::capacity(int n) { static Fl_Menu_Item *suite_menu = NULL; if (!suite_menu) suite_menu = (Fl_Menu_Item*)main_menubar->find_item(layout_suite_marker); int old_n = list_size_; int i; Fd_Layout_Suite *new_list = (Fd_Layout_Suite*)::calloc(n, sizeof(Fd_Layout_Suite)); for (i = 0; i < old_n; i++) new_list[i] = list_[i]; if (!list_is_static_) ::free(list_); list_ = new_list; Fl_Menu_Item *new_main_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); for (i = 0; i < old_n; i++) new_main_menu[i] = main_menu_[i]; if (!list_is_static_) ::free(main_menu_); main_menu_ = new_main_menu; suite_menu->user_data(main_menu_); Fl_Menu_Item *new_choice_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); for (i = 0; i < old_n; i++) new_choice_menu[i] = choice_menu_[i]; if (!list_is_static_) ::free(choice_menu_); choice_menu_ = new_choice_menu; layout_choice->menu(choice_menu_); list_capacity_ = n; list_is_static_ = false; } /** Clone the currently selected suite and append it to the list. Selectes the new layout and updates the UI. */ int Fd_Layout_List::add(const char *name) { if (list_size_ == list_capacity_) { capacity(list_capacity_ * 2); } int n = list_size_; Fd_Layout_Suite &old_suite = list_[current_suite_]; Fd_Layout_Suite &new_suite = list_[n]; new_suite.init(); new_suite.name(name); for (int i=0; i<3; ++i) { new_suite.layout[i] = new Fd_Layout_Preset; ::memcpy(new_suite.layout[i], old_suite.layout[i], sizeof(Fd_Layout_Preset)); } int new_storage = old_suite.storage_; if (new_storage == FD_STORE_INTERNAL) new_storage = FD_STORE_USER; new_suite.storage(new_storage); main_menu_[n].label(new_suite.menu_label); main_menu_[n].callback(main_menu_[0].callback()); main_menu_[n].argument(n); main_menu_[n].flags = main_menu_[0].flags; choice_menu_[n].label(new_suite.menu_label); list_size_++; current_suite(n); return n; } void Fd_Layout_List::rename(const char *name) { int n = current_suite(); list_[n].name(name); main_menu_[n].label(list_[n].menu_label); choice_menu_[n].label(list_[n].menu_label); } void Fd_Layout_List::remove(int ix) { int tail = list_size_-ix-1; if (tail) { for (int i = ix; i < list_size_-1; i++) list_[i] = list_[i+1]; } ::memmove(main_menu_+ix, main_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); ::memmove(choice_menu_+ix, choice_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); list_size_--; if (current_suite() >= list_size_) current_suite(list_size_ - 1); } void Fd_Layout_List::remove_all(int storage) { for (int i=list_size_-1; i>=0; --i) { if (list_[i].storage_ == storage) remove(i); } } // ---- Helper --------------------------------------------------------- MARK: - static void draw_h_arrow(int, int, int); static void draw_v_arrow(int x, int y1, int y2); static void draw_left_brace(const Fl_Widget *w); static void draw_right_brace(const Fl_Widget *w); static void draw_top_brace(const Fl_Widget *w); static void draw_bottom_brace(const Fl_Widget *w); static void draw_grid(int x, int y, int dx, int dy); static void draw_width(int x, int y, int r, Fl_Align a); static void draw_height(int x, int y, int b, Fl_Align a); static int nearest(int x, int left, int grid, int right=0x7fff) { int grid_x = ((x-left+grid/2)/grid)*grid+left; if (grid_x < left+grid/2) return left; // left+grid/2; if (grid_x > right-grid/2) return right; // right-grid/2; return grid_x; } static bool in_window(Fd_Snap_Data &d) { return (d.wgt && d.wgt->parent == d.win); } static bool in_group(Fd_Snap_Data &d) { return (d.wgt && d.wgt->parent && d.wgt->parent->is_group() && d.wgt->parent != d.win); } static bool in_tabs(Fd_Snap_Data &d) { return (d.wgt && d.wgt->parent && d.wgt->parent->is_tabs()); } static Fl_Group *parent(Fd_Snap_Data &d) { return (d.wgt->o->parent()); } // ---- Fd_Snap_Action ------------------------------------------------- MARK: - /** \class Fd_Snap_Action */ int Fd_Snap_Action::check_x_(Fd_Snap_Data &d, int x_ref, int x_snap) { int dd = x_ref + d.dx - x_snap; int d2 = abs(dd); if (d2 > d.x_dist) return 1; dx = d.dx_out = d.dx - dd; ex = d.ex_out = x_snap; if (d2 == d.x_dist) return 0; d.x_dist = d2; return -1; } int Fd_Snap_Action::check_y_(Fd_Snap_Data &d, int y_ref, int y_snap) { int dd = y_ref + d.dy - y_snap; int d2 = abs(dd); if (d2 > d.y_dist) return 1; dy = d.dy_out = d.dy - dd; ey = d.ey_out = y_snap; if (d2 == d.y_dist) return 0; d.y_dist = d2; return -1; } void Fd_Snap_Action::check_x_y_(Fd_Snap_Data &d, int x_ref, int x_snap, int y_ref, int y_snap) { int ddx = x_ref + d.dx - x_snap; int d2x = abs(ddx); int ddy = y_ref + d.dy - y_snap; int d2y = abs(ddy); if ((d2x <= d.x_dist) && (d2y <= d.y_dist)) { dx = d.dx_out = d.dx - ddx; ex = d.ex_out = x_snap; d.x_dist = d2x; dy = d.dy_out = d.dy - ddy; ey = d.ey_out = y_snap; d.y_dist = d2y; } } bool Fd_Snap_Action::matches(Fd_Snap_Data &d) { switch (type) { case 1: return (d.drag & mask) && (eex == ex) && (d.dx == dx); case 2: return (d.drag & mask) && (eey == ey) && (d.dy == dy); case 3: return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); } return false; } void Fd_Snap_Action::check_all(Fd_Snap_Data &data) { for (int i=0; list[i]; i++) { if (list[i]->mask & data.drag) list[i]->check(data); } eex = data.ex_out; eey = data.ey_out; } void Fd_Snap_Action::draw_all(Fd_Snap_Data &data) { for (int i=0; list[i]; i++) { if (list[i]->matches(data)) list[i]->draw(data); } } /** Return a sensible step size for resizing a widget. */ void Fd_Snap_Action::get_resize_stepsize(int &x_step, int &y_step) { if ((layout->widget_inc_w > 1) && (layout->widget_inc_h > 1)) { x_step = layout->widget_inc_w; y_step = layout->widget_inc_h; } else if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { x_step = layout->group_grid_x; y_step = layout->group_grid_y; } else { x_step = layout->window_grid_x; y_step = layout->window_grid_y; } } /** Return a sensible step size for moving a widget. */ void Fd_Snap_Action::get_move_stepsize(int &x_step, int &y_step) { if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { x_step = layout->group_grid_x; y_step = layout->group_grid_y; } else if ((layout->window_grid_x > 1) && (layout->window_grid_y > 1)) { x_step = layout->window_grid_x; y_step = layout->window_grid_y; } else { x_step = layout->widget_gap_x; y_step = layout->widget_gap_y; } } // ---- snapping prototypes -------------------------------------------- MARK: - class Fd_Snap_Left : public Fd_Snap_Action { public: Fd_Snap_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } }; class Fd_Snap_Right : public Fd_Snap_Action { public: Fd_Snap_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } }; class Fd_Snap_Top : public Fd_Snap_Action { public: Fd_Snap_Top() { type = 2; mask = FD_TOP|FD_DRAG; } }; class Fd_Snap_Bottom : public Fd_Snap_Action { public: Fd_Snap_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } }; // ---- window snapping ------------------------------------------------ MARK: - class Fd_Snap_Left_Window_Edge : public Fd_Snap_Left { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_x_(d, d.bx, 0); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_left_brace(d.win->o); }; }; Fd_Snap_Left_Window_Edge snap_left_window_edge; class Fd_Snap_Right_Window_Edge : public Fd_Snap_Right { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_x_(d, d.br, d.win->o->w()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_right_brace(d.win->o); }; }; Fd_Snap_Right_Window_Edge snap_right_window_edge; class Fd_Snap_Top_Window_Edge : public Fd_Snap_Top { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_y_(d, d.by, 0); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_top_brace(d.win->o); }; }; Fd_Snap_Top_Window_Edge snap_top_window_edge; class Fd_Snap_Bottom_Window_Edge : public Fd_Snap_Bottom { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_y_(d, d.bt, d.win->o->h()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_bottom_brace(d.win->o); }; }; Fd_Snap_Bottom_Window_Edge snap_bottom_window_edge; class Fd_Snap_Left_Window_Margin : public Fd_Snap_Left { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_window(d)) check_x_(d, d.bx, layout->left_window_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_h_arrow(d.bx, (d.by+d.bt)/2, 0); }; }; Fd_Snap_Left_Window_Margin snap_left_window_margin; class Fd_Snap_Right_Window_Margin : public Fd_Snap_Right { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_window(d)) check_x_(d, d.br, d.win->o->w()-layout->right_window_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_h_arrow(d.br, (d.by+d.bt)/2, d.win->o->w()-1); }; }; Fd_Snap_Right_Window_Margin snap_right_window_margin; class Fd_Snap_Top_Window_Margin : public Fd_Snap_Top { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_window(d)) check_y_(d, d.by, layout->top_window_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_v_arrow((d.bx+d.br)/2, d.by, 0); }; }; Fd_Snap_Top_Window_Margin snap_top_window_margin; class Fd_Snap_Bottom_Window_Margin : public Fd_Snap_Bottom { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_window(d)) check_y_(d, d.bt, d.win->o->h()-layout->bottom_window_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_v_arrow((d.bx+d.br)/2, d.bt, d.win->o->h()-1); }; }; Fd_Snap_Bottom_Window_Margin snap_bottom_window_margin; // ---- group snapping ------------------------------------------------- MARK: - class Fd_Snap_Left_Group_Edge : public Fd_Snap_Left { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_x_(d, d.bx, parent(d)->x()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_left_brace(parent(d)); }; }; Fd_Snap_Left_Group_Edge snap_left_group_edge; class Fd_Snap_Right_Group_Edge : public Fd_Snap_Right { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_x_(d, d.br, parent(d)->x() + parent(d)->w()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_right_brace(parent(d)); }; }; Fd_Snap_Right_Group_Edge snap_right_group_edge; class Fd_Snap_Top_Group_Edge : public Fd_Snap_Top { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_y_(d, d.by, parent(d)->y()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_top_brace(parent(d)); }; }; Fd_Snap_Top_Group_Edge snap_top_group_edge; class Fd_Snap_Bottom_Group_Edge : public Fd_Snap_Bottom { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_y_(d, d.bt, parent(d)->y() + parent(d)->h()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_bottom_brace(parent(d)); }; }; Fd_Snap_Bottom_Group_Edge snap_bottom_group_edge; class Fd_Snap_Left_Group_Margin : public Fd_Snap_Left { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_x_(d, d.bx, parent(d)->x() + layout->left_group_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_left_brace(parent(d)); draw_h_arrow(d.bx, (d.by+d.bt)/2, parent(d)->x()); }; }; Fd_Snap_Left_Group_Margin snap_left_group_margin; class Fd_Snap_Right_Group_Margin : public Fd_Snap_Right { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d)) check_x_(d, d.br, parent(d)->x()+parent(d)->w()-layout->right_group_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_right_brace(parent(d)); draw_h_arrow(d.br, (d.by+d.bt)/2, parent(d)->x()+parent(d)->w()-1); }; }; Fd_Snap_Right_Group_Margin snap_right_group_margin; class Fd_Snap_Top_Group_Margin : public Fd_Snap_Top { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d) && !in_tabs(d)) check_y_(d, d.by, parent(d)->y()+layout->top_group_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_top_brace(parent(d)); draw_v_arrow((d.bx+d.br)/2, d.by, parent(d)->y()); }; }; Fd_Snap_Top_Group_Margin snap_top_group_margin; class Fd_Snap_Bottom_Group_Margin : public Fd_Snap_Bottom { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_group(d) && !in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-layout->bottom_group_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_bottom_brace(parent(d)); draw_v_arrow((d.bx+d.br)/2, d.bt, parent(d)->y()+parent(d)->h()-1); }; }; Fd_Snap_Bottom_Group_Margin snap_bottom_group_margin; // ----- tabs snapping ------------------------------------------------- MARK: - class Fd_Snap_Top_Tabs_Margin : public Fd_Snap_Top_Group_Margin { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_tabs(d)) check_y_(d, d.by, parent(d)->y()+layout->top_tabs_margin); } }; Fd_Snap_Top_Tabs_Margin snap_top_tabs_margin; class Fd_Snap_Bottom_Tabs_Margin : public Fd_Snap_Bottom_Group_Margin { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-layout->bottom_tabs_margin); } }; Fd_Snap_Bottom_Tabs_Margin snap_bottom_tabs_margin; // ----- grid snapping ------------------------------------------------- MARK: - class Fd_Snap_Grid : public Fd_Snap_Action { protected: int nearest_x, nearest_y; public: Fd_Snap_Grid() { type = 3; mask = FD_LEFT|FD_TOP|FD_DRAG; } void check_grid(Fd_Snap_Data &d, int left, int grid_x, int right, int top, int grid_y, int bottom) { if ((grid_x <= 1) || (grid_y <= 1)) return; int suggested_x = d.bx + d.dx; nearest_x = nearest(suggested_x, left, grid_x, right); int suggested_y = d.by + d.dy; nearest_y = nearest(suggested_y, top, grid_y, bottom); if (d.drag == FD_LEFT) check_x_(d, d.bx, nearest_x); else if (d.drag == FD_TOP) check_y_(d, d.by, nearest_y); else check_x_y_(d, d.bx, nearest_x, d.by, nearest_y); } bool matches(Fd_Snap_Data &d) FL_OVERRIDE { if (d.drag == FD_LEFT) return (eex == ex); if (d.drag == FD_TOP) return (eey == ey) && (d.dx == dx); return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); } }; class Fd_Snap_Window_Grid : public Fd_Snap_Grid { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (in_window(d)) check_grid(d, layout->left_window_margin, layout->window_grid_x, d.win->o->w()-layout->right_window_margin, layout->top_window_margin, layout->window_grid_y, d.win->o->h()-layout->bottom_window_margin); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_grid(nearest_x, nearest_y, layout->window_grid_x, layout->window_grid_y); }; }; Fd_Snap_Window_Grid snap_window_grid; class Fd_Snap_Group_Grid : public Fd_Snap_Grid { public: void check(Fd_Snap_Data &d) FL_OVERRIDE { if (in_group(d)) { clr(); Fl_Widget *g = parent(d); check_grid(d, g->x()+layout->left_group_margin, layout->group_grid_x, g->x()+g->w()-layout->right_group_margin, g->y()+layout->top_group_margin, layout->group_grid_y, g->y()+g->h()-layout->bottom_group_margin); } } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_grid(nearest_x, nearest_y, layout->group_grid_x, layout->group_grid_y); }; }; Fd_Snap_Group_Grid snap_group_grid; // ----- sibling snapping ---------------------------------------------- MARK: - class Fd_Snap_Sibling : public Fd_Snap_Action { protected: Fl_Widget *best_match; public: Fd_Snap_Sibling() : best_match(NULL) { } virtual int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) = 0; void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); best_match = NULL; if (!d.wgt) return; if (!d.wgt->parent->is_group()) return; int dsib_min = 1024; Fl_Group_Type *gt = (Fl_Group_Type*)d.wgt->parent; Fl_Group *g = (Fl_Group*)gt->o; Fl_Widget *w = d.wgt->o; for (int i=0; ichildren(); i++) { Fl_Widget *c = g->child(i); if (c == w) continue; int sret = sibling_check(d, c); if (sret < 1) { int dsib; if (type==1) dsib = abs( ((d.by+d.bt)/2+d.dy) - (c->y()+c->h()/2) ); else dsib = abs( ((d.bx+d.br)/2+d.dx) - (c->x()+c->w()/2) ); if (sret == -1 || (dsib < dsib_min)) { dsib_min = dsib; best_match = c; } } } } }; class Fd_Snap_Siblings_Left_Same : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Left_Same() { type = 1; mask = FD_LEFT|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return check_x_(d, d.bx, s->x()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_left_brace(best_match); }; }; Fd_Snap_Siblings_Left_Same snap_siblings_left_same; class Fd_Snap_Siblings_Left : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return fd_min(check_x_(d, d.bx, s->x()+s->w()), check_x_(d, d.bx, s->x()+s->w()+layout->widget_gap_x) ); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_right_brace(best_match); }; }; Fd_Snap_Siblings_Left snap_siblings_left; class Fd_Snap_Siblings_Right_Same : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Right_Same() { type = 1; mask = FD_RIGHT|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return check_x_(d, d.br, s->x()+s->w()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_right_brace(best_match); }; }; Fd_Snap_Siblings_Right_Same snap_siblings_right_same; class Fd_Snap_Siblings_Right : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return fd_min(check_x_(d, d.br, s->x()), check_x_(d, d.br, s->x()-layout->widget_gap_x)); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_left_brace(best_match); }; }; Fd_Snap_Siblings_Right snap_siblings_right; class Fd_Snap_Siblings_Top_Same : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Top_Same() { type = 2; mask = FD_TOP|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return check_y_(d, d.by, s->y()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_top_brace(best_match); }; }; Fd_Snap_Siblings_Top_Same snap_siblings_top_same; class Fd_Snap_Siblings_Top : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Top() { type = 2; mask = FD_TOP|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return fd_min(check_y_(d, d.by, s->y()+s->h()), check_y_(d, d.by, s->y()+s->h()+layout->widget_gap_y)); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_bottom_brace(best_match); }; }; Fd_Snap_Siblings_Top snap_siblings_top; class Fd_Snap_Siblings_Bottom_Same : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Bottom_Same() { type = 2; mask = FD_BOTTOM|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return check_y_(d, d.bt, s->y()+s->h()); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_bottom_brace(best_match); }; }; Fd_Snap_Siblings_Bottom_Same snap_siblings_bottom_same; class Fd_Snap_Siblings_Bottom : public Fd_Snap_Sibling { public: Fd_Snap_Siblings_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { return fd_min(check_y_(d, d.bt, s->y()), check_y_(d, d.bt, s->y()-layout->widget_gap_y)); } void draw(Fd_Snap_Data &d) FL_OVERRIDE { if (best_match) draw_top_brace(best_match); }; }; Fd_Snap_Siblings_Bottom snap_siblings_bottom; // ------ widget snapping ---------------------------------------------- MARK: - class Fd_Snap_Widget_Ideal_Width : public Fd_Snap_Action { public: Fd_Snap_Widget_Ideal_Width() { type = 1; mask = FD_LEFT|FD_RIGHT; } void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (!d.wgt) return; int iw = 15, ih = 15; d.wgt->ideal_size(iw, ih); if (d.drag == FD_RIGHT) { check_x_(d, d.br, d.bx+iw); iw = layout->widget_min_w; if (iw > 0) iw = nearest(d.br-d.bx+d.dx, layout->widget_min_w, layout->widget_inc_w); check_x_(d, d.br, d.bx+iw); } else { check_x_(d, d.bx, d.br-iw); iw = layout->widget_min_w; if (iw > 0) iw = nearest(d.br-d.bx-d.dx, layout->widget_min_w, layout->widget_inc_w); check_x_(d, d.bx, d.br-iw); } } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_width(d.bx, d.bt+7, d.br, 0); }; }; Fd_Snap_Widget_Ideal_Width snap_widget_ideal_width; class Fd_Snap_Widget_Ideal_Height : public Fd_Snap_Action { public: Fd_Snap_Widget_Ideal_Height() { type = 2; mask = FD_TOP|FD_BOTTOM; } void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); if (!d.wgt) return; int iw, ih; d.wgt->ideal_size(iw, ih); if (d.drag == FD_BOTTOM) { check_y_(d, d.bt, d.by+ih); ih = layout->widget_min_h; if (ih > 0) ih = nearest(d.bt-d.by+d.dy, layout->widget_min_h, layout->widget_inc_h); check_y_(d, d.bt, d.by+ih); } else { check_y_(d, d.by, d.bt-ih); ih = layout->widget_min_h; if (ih > 0) ih = nearest(d.bt-d.by-d.dy, layout->widget_min_h, layout->widget_inc_h); check_y_(d, d.by, d.bt-ih); } } void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_height(d.br+7, d.by, d.bt, 0); }; }; Fd_Snap_Widget_Ideal_Height snap_widget_ideal_height; // ---- snap actions list ---------------------------------------------- MARK: - Fd_Snap_Action *Fd_Snap_Action::list[] = { &snap_left_window_edge, &snap_right_window_edge, &snap_top_window_edge, &snap_bottom_window_edge, &snap_left_window_margin, &snap_right_window_margin, &snap_top_window_margin, &snap_bottom_window_margin, &snap_window_grid, &snap_group_grid, &snap_left_group_edge, &snap_right_group_edge, &snap_top_group_edge, &snap_bottom_group_edge, &snap_left_group_margin, &snap_right_group_margin, &snap_top_group_margin, &snap_bottom_group_margin, &snap_top_tabs_margin, &snap_bottom_tabs_margin, &snap_siblings_left_same, &snap_siblings_left, &snap_siblings_right_same, &snap_siblings_right, &snap_siblings_top_same, &snap_siblings_top, &snap_siblings_bottom_same, &snap_siblings_bottom, &snap_widget_ideal_width, &snap_widget_ideal_height, NULL }; // ---- draw alignment marks ------------------------------------------- MARK: - static void draw_v_arrow(int x, int y1, int y2) { int dy = (y1>y2) ? -1 : 1 ; fl_yxline(x, y1, y2); fl_xyline(x-4, y2, x+4); fl_line(x-2, y2-dy*5, x, y2-dy); fl_line(x+2, y2-dy*5, x, y2-dy); } static void draw_h_arrow(int x1, int y, int x2) { int dx = (x1>x2) ? -1 : 1 ; fl_xyline(x1, y, x2); fl_yxline(x2, y-4, y+4); fl_line(x2-dx*5, y-2, x2-dx, y); fl_line(x2-dx*5, y+2, x2-dx, y); } static void draw_top_brace(const Fl_Widget *w) { int x = w->as_window() ? 0 : w->x(); int y = w->as_window() ? 0 : w->y(); fl_yxline(x, y-2, y+6); fl_yxline(x+w->w()-1, y-2, y+6); fl_xyline(x-2, y, x+w->w()+1); } static void draw_left_brace(const Fl_Widget *w) { int x = w->as_window() ? 0 : w->x(); int y = w->as_window() ? 0 : w->y(); fl_xyline(x-2, y, x+6); fl_xyline(x-2, y+w->h()-1, x+6); fl_yxline(x, y-2, y+w->h()+1); } static void draw_right_brace(const Fl_Widget *w) { int x = w->as_window() ? w->w() - 1 : w->x() + w->w() - 1; int y = w->as_window() ? 0 : w->y(); fl_xyline(x-6, y, x+2); fl_xyline(x-6, y+w->h()-1, x+2); fl_yxline(x, y-2, y+w->h()+1); } static void draw_bottom_brace(const Fl_Widget *w) { int x = w->as_window() ? 0 : w->x(); int y = w->as_window() ? w->h() - 1 : w->y() + w->h() - 1; fl_yxline(x, y-6, y+2); fl_yxline(x+w->w()-1, y-6, y+2); fl_xyline(x-2, y, x+w->w()+1); } static void draw_height(int x, int y, int b, Fl_Align a) { char buf[16]; int h = b - y; sprintf(buf, "%d", h); fl_font(FL_HELVETICA, 9); int lw = (int)fl_width(buf); int lx; b --; if (h < 30) { // Move height to the side... if (a == FL_ALIGN_LEFT) lx = x - lw - 2; else lx = x + 2; fl_yxline(x, y, b); } else { // Put height inside the arrows... if (a == FL_ALIGN_LEFT) lx = x - lw + 2; else lx = x - lw / 2; fl_yxline(x, y, y + (h - 11) / 2); fl_yxline(x, y + (h + 11) / 2, b); } // Draw the height... fl_draw(buf, lx, y + (h + 7) / 2); // Draw the arrowheads... fl_line(x-2, y+5, x, y+1, x+2, y+5); fl_line(x-2, b-5, x, b-1, x+2, b-5); // Draw the end lines... fl_xyline(x - 4, y, x + 4); fl_xyline(x - 4, b, x + 4); } static void draw_width(int x, int y, int r, Fl_Align a) { char buf[16]; int w = r-x; sprintf(buf, "%d", w); fl_font(FL_HELVETICA, 9); int lw = (int)fl_width(buf); int ly = y + 4; r--; if (lw > (w - 20)) { // Move width above/below the arrows... if (a == FL_ALIGN_TOP) ly -= 10; else ly += 10; fl_xyline(x, y, r); } else { // Put width inside the arrows... fl_xyline(x, y, x + (w - lw - 2) / 2); fl_xyline(x + (w + lw + 2) / 2, y, r); } // Draw the width... fl_draw(buf, x + (w - lw) / 2, ly-2); // Draw the arrowheads... fl_line(x+5, y-2, x+1, y, x+5, y+2); fl_line(r-5, y-2, r-1, y, r-5, y+2); // Draw the end lines... fl_yxline(x, y - 4, y + 4); fl_yxline(r, y - 4, y + 4); } static void draw_grid(int x, int y, int dx, int dy) { int dx2 = 1, dy2 = 1; const int n = 2; for (int i=-n; i<=n; i++) { for (int j=-n; j<=n; j++) { if (abs(i)+abs(j) < 4) { int xx = x + i*dx , yy = y + j*dy; fl_xyline(xx-dx2, yy, xx+dx2); fl_yxline(xx, yy-dy2, yy+dy2); } } } }