Files
oboe/src/scenedit/scen.main.cpp

642 lines
17 KiB
C++

#include <cstdio>
#include <string>
#include <memory>
#include <boost/filesystem/operations.hpp>
#include "scen.global.hpp"
#include "scenario.hpp"
#include "render_image.hpp"
#include "tiling.hpp"
#include "scen.graphics.hpp"
#include "scen.actions.hpp"
#include "scen.fileio.hpp"
#include "scen.btnmg.hpp"
#include "sounds.hpp"
#include "scen.townout.hpp"
#include "scen.core.hpp"
#include "scen.keydlgs.hpp"
#include "mathutil.hpp"
#include "fileio.hpp"
#include "scrollbar.hpp"
#include "winutil.hpp"
#include "cursors.hpp"
#include "strdlog.hpp"
#include "choicedlog.hpp"
#include "scen.menus.hpp"
#include "res_image.hpp"
#include "prefs.hpp"
#include "framerate_limiter.hpp"
#include "event_listener.hpp"
#include "drawable_manager.hpp"
/* Globals */
bool All_Done = false;
sf::RenderWindow mainPtr;
sf::View mainView;
cTown* town = nullptr;
bool mouse_button_held = false,editing_town = false;
short cur_viewing_mode = 0;
short cen_x, cen_y;
eScenMode overall_mode = MODE_INTRO_SCREEN;
std::shared_ptr<cScrollbar> right_sbar, pal_sbar;
short mode_count = 0;
cOutdoors* current_terrain;
std::string scenario_temp_dir_name = "ed_scenario";
bool change_made = false, ae_loading = false;
// Numbers of current areas being edited
short cur_town;
location cur_out;
/* Prototypes */
static void init_scened(int, char*[]);
void handle_events();
void handle_one_event(const sf::Event&);
void redraw_everything();
void Mouse_Pressed(const sf::Event&);
void close_program();
void ding();
void init_main_window(sf::RenderWindow&, sf::View&);
sf::FloatRect compute_viewport(const sf::RenderWindow&, float ui_scale);
cScenario scenario;
rectangle right_sbar_rect;
extern rectangle terrain_buttons_rect;
extern void set_up_apple_events(int argc, char* argv[]);
// TODO: these should be members of some global entity instead of being here
std::unordered_map<std::string, std::shared_ptr <iEventListener>> event_listeners;
cDrawableManager drawable_mgr;
//Changed to ISO C specified argument and return type.
int main(int argc, char* argv[]) {
try {
init_scened(argc, argv);
if(ae_loading)
set_up_main_screen();
else {
shut_down_menus(0);
set_up_start_screen();
}
handle_events();
close_program();
return 0;
} catch(std::exception& x) {
showFatalError(x.what());
throw;
} catch(std::string& x) {
showFatalError(x);
throw;
} catch(...) {
showFatalError("An unknown error occurred!");
throw;
}
}
static void init_sbar(std::shared_ptr<cScrollbar>& sbar, const std::string& name, rectangle rect, rectangle events_rect, int pgSz) {
sbar.reset(new cScrollbar(mainPtr));
sbar->setBounds(rect);
sbar->set_wheel_event_rect(events_rect);
sbar->setPageSize(pgSz);
sbar->hide();
drawable_mgr.add_drawable(UI_LAYER_DEFAULT, name, sbar);
event_listeners[name] = std::dynamic_pointer_cast<iEventListener>(sbar);
}
static void init_scrollbars() {
right_sbar_rect.top = RIGHT_AREA_UL_Y - 1;
right_sbar_rect.left = RIGHT_AREA_UL_X + RIGHT_AREA_WIDTH - 1 - 16;
right_sbar_rect.bottom = RIGHT_AREA_UL_Y + RIGHT_AREA_HEIGHT + 1;
right_sbar_rect.right = RIGHT_AREA_UL_X + RIGHT_AREA_WIDTH - 1;
rectangle pal_sbar_rect = terrain_buttons_rect;
pal_sbar_rect.offset(RIGHT_AREA_UL_X,RIGHT_AREA_UL_Y);
pal_sbar_rect.left = pal_sbar_rect.right - 16;
pal_sbar_rect.height() = 17 * 16;
rectangle const right_sbar_event_rect { 5, 287, 405, 577 };
rectangle const pal_sbar_event_rect { 5, 287, 279, 581 };
init_sbar(right_sbar, "right_sbar", right_sbar_rect, right_sbar_event_rect, NRSONPAGE - 1);
init_sbar(pal_sbar, "pal_sbar", pal_sbar_rect, pal_sbar_event_rect, 16);
}
sf::FloatRect compute_viewport(const sf::RenderWindow & mainPtr, float ui_scale) {
// See compute_viewport() in boe.graphics.cpp
const int os_specific_y_offset =
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif
sf::FloatRect viewport;
viewport.top = float(os_specific_y_offset) / mainPtr.getSize().y;
viewport.left = 0;
viewport.width = ui_scale;
viewport.height = ui_scale;
return viewport;
}
void init_main_window (sf::RenderWindow & mainPtr, sf::View & mainView) {
// TODO: things might still be broken when upscaled.
// translate_mouse_coordinates has been applied in some places but more work might be needed.
// In particular, the white area on the right side of the main menu needs fixing.
float ui_scale = get_float_pref("UIScale", 1.0);
const int width = ui_scale * 584;
const int height = ui_scale * 420
#ifndef SFML_SYSTEM_WINDOWS
+ getMenubarHeight()
#endif
;
mainPtr.create(sf::VideoMode(width, height), "Blades of Exile Scenario Editor", sf::Style::Titlebar | sf::Style::Close);
sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
mainPtr.setPosition({static_cast<int>((desktop.width - width) / 2), static_cast<int>((desktop.height - height) / 2)});
// Initialize the view
mainView.setSize(width, height);
mainView.setCenter(width / 2, height / 2);
// Apply the viewport to the view
sf::FloatRect mainPort = compute_viewport(mainPtr, ui_scale);
mainView.setViewport(mainPort);
// Apply view to the main window
mainPtr.setView(mainView);
#ifndef SFML_SYSTEM_MAC // This overrides Dock icon on OSX, which isn't what we want at all
const ImageRsrc& icon = ResMgr::graphics.get("icon", true);
mainPtr.setIcon(icon->getSize().x, icon->getSize().y, icon->copyToImage().getPixelsPtr());
#endif
}
void init_scened(int argc, char* argv[]) {
init_directories(argv[0]);
sync_prefs();
init_main_window(mainPtr, mainView);
init_menubar();
init_shaders();
init_tiling();
init_snd_tool();
#ifdef SFML_SYSTEM_MAC
init_menubar(); // This is called twice because Windows and Mac have different ordering requirements
#endif
mainPtr.clear(sf::Color::Black);
mainPtr.display();
set_cursor(watch_curs);
check_for_intel();
srand(time(nullptr));
cen_x = 18;
cen_y = 18;
init_scrollbars();
init_lb();
init_rb();
Set_up_win();
init_screen_locs();
load_graphics();
cDialog::init();
if(get_bool_pref("ShowStartupLogo", true))
run_startup_g();
set_cursor(sword_curs);
cDialog::defaultBackground = cDialog::BG_LIGHT;
cDialog::doAnimations = true;
set_up_apple_events(argc, argv);
init_fileio();
}
void handle_events() {
sf::Event currentEvent;
cFramerateLimiter fps_limiter;
while(!All_Done) {
while(mainPtr.pollEvent(currentEvent)) handle_one_event(currentEvent);
// Why do we have to set this to false after handling every event?
ae_loading = false;
redraw_everything();
// Prevent the loop from executing too fast.
fps_limiter.frame_finished();
}
}
void handle_one_event(const sf::Event& event) {
// Check if any of the event listeners want this event.
for (auto& listener : event_listeners) {
if(listener.second->handle_event(event)) return;
}
switch(event.type) {
case sf::Event::KeyPressed:
if(!(event.key.*systemKey))
handle_keystroke(event);
break;
case sf::Event::MouseButtonPressed:
Mouse_Pressed(event);
break;
case sf::Event::MouseMoved:
if(mouse_button_held)
handle_action(location { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y})},event);
update_mouse_spot(location { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y})});
break;
case sf::Event::MouseWheelMoved:
handle_scroll(event);
break;
case sf::Event::MouseButtonReleased:
mouse_button_held = false;
break;
case sf::Event::Closed:
if(!save_check("save-before-quit"))
break;
All_Done = true;
break;
default:
break;
}
}
void redraw_everything() {
redraw_screen();
restore_cursor();
}
extern fs::path progDir;
void handle_menu_choice(eMenu item_hit) {
extern cUndoList undo_list;
bool isEdit = false, isHelp = false;
std::string helpDlog;
fs::path file_to_load;
cKey editKey = {true};
switch(item_hit) {
case eMenu::NONE: return;
case eMenu::FILE_OPEN:
if(change_made && !save_check("save-before-load"))
break;
if(false)
case eMenu::FILE_REVERT:
if(change_made && cChoiceDlog("save-before-revert", {"revert", "cancel"}).show() == "cancel")
break;
file_to_load = item_hit == eMenu::FILE_OPEN ? nav_get_scenario() : scenario.scen_file;
if(!file_to_load.empty() && load_scenario(file_to_load, scenario)) {
cur_town = scenario.last_town_edited;
town = scenario.towns[cur_town];
cur_out = scenario.last_out_edited;
current_terrain = scenario.outdoors[cur_out.x][cur_out.y];
overall_mode = MODE_MAIN_SCREEN;
change_made = false;
set_up_main_screen();
} else if(!file_to_load.empty())
set_up_start_screen(); // Failed to load file, dump to start
undo_list.clear();
update_edit_menu();
break;
case eMenu::FILE_SAVE:
save_scenario();
break;
case eMenu::FILE_SAVE_AS:
save_scenario(true);
break;
case eMenu::FILE_NEW:
if(!save_check("save-before-close"))
break;
if(build_scenario()) {
overall_mode = MODE_MAIN_SCREEN;
set_up_main_screen();
}
undo_list.clear();
update_edit_menu();
break;
case eMenu::FILE_CLOSE:
if(!save_check("save-before-close"))
break;
overall_mode = MODE_INTRO_SCREEN;
right_sbar->hide();
pal_sbar->hide();
shut_down_menus(0);
set_up_start_screen();
undo_list.clear();
update_edit_menu();
break;
case eMenu::QUIT: // quit
if(!save_check("save-before-quit"))
break;
All_Done = true;
break;
case eMenu::EDIT_UNDO:
editKey.k = key_undo;
isEdit = true;
break;
case eMenu::EDIT_REDO:
editKey.k = key_redo;
isEdit = true;
break;
case eMenu::EDIT_CUT:
editKey.k = key_cut;
isEdit = true;
break;
case eMenu::EDIT_COPY:
editKey.k = key_copy;
isEdit = true;
break;
case eMenu::EDIT_PASTE:
editKey.k = key_paste;
isEdit = true;
break;
case eMenu::EDIT_DELETE:
editKey.k = key_del;
isEdit = true;
break;
case eMenu::EDIT_SELECT_ALL:
editKey.k = key_selectall;
isEdit = true;
break;
case eMenu::TOWN_CREATE:
if(scenario.towns.size() >= 200) {
showError("You have reached the limit of 200 towns you can have in one scenario.");
return;
}
if(new_town())
set_up_main_screen();
break;
case eMenu::OUT_RESIZE:
if(resize_outdoors())
change_made = true;
if(overall_mode == MODE_MAIN_SCREEN)
set_up_main_screen();
break;
case eMenu::SCEN_DETAILS:
edit_scen_details();
change_made = true;
break;
case eMenu::SCEN_INTRO:
edit_scen_intro();
change_made = true;
break;
case eMenu::TOWN_START:
overall_mode = MODE_SET_TOWN_START;
set_string("Select party starting location.","");
break;
case eMenu::SCEN_SHEETS:
edit_custom_sheets();
change_made = true;
break;
case eMenu::SCEN_PICS:
edit_custom_pics_types();
change_made = true;
break;
case eMenu::SCEN_SNDS:
edit_custom_sounds();
change_made = true;
break;
case eMenu::SCEN_SPECIALS:
right_sbar->setPosition(0);
start_special_editing(0,0);
break;
case eMenu::SCEN_TEXT:
right_sbar->setPosition(0);
start_string_editing(STRS_SCEN,0);
break;
case eMenu::SCEN_JOURNALS:
right_sbar->setPosition(0);
start_string_editing(STRS_JOURNAL,0);
break;
case eMenu::TOWN_IMPORT:
if(cTown* town = pick_import_town()) {
town->reattach(scenario);
delete scenario.towns[cur_town];
scenario.towns[cur_town] = town;
::town = town;
change_made = true;
redraw_screen();
}
break;
case eMenu::OUT_IMPORT:
if(cOutdoors* out = pick_import_out()) {
out->reattach(scenario);
delete scenario.outdoors[cur_out.x][cur_out.y];
scenario.outdoors[cur_out.x][cur_out.y] = out;
current_terrain = out;
change_made = true;
redraw_screen();
}
break;
case eMenu::SCEN_SAVE_ITEM_RECTS:
edit_save_rects();
change_made = true;
break;
case eMenu::TOWN_VARYING:
edit_add_town();
change_made = true;
break;
case eMenu::SCEN_TIMERS:
edit_scenario_events();
change_made = true;
break;
case eMenu::SCEN_ITEM_SHORTCUTS:
edit_item_placement();
break;
case eMenu::TOWN_DELETE:
if(scenario.towns.size() == 1) {
showError("You can't delete the last town in a scenario. All scenarios must have at least 1 town.");
return;
}
if(scenario.towns.size() - 1 == cur_town) {
showError("You can't delete the last town in a scenario while you're working on it. Load a different town, and try this again.");
return;
}
if(scenario.towns.size() - 1 == scenario.which_town_start) {
showError("You can't delete the last town in a scenario while it's the town the party starts the scenario in. Change the parties starting point and try this again.");
return;
}
if(cChoiceDlog("delete-town-confirm", {"okay", "cancel"}).show() == "okay")
delete_last_town();
break;
case eMenu::SCEN_DATA_DUMP:
if(cChoiceDlog("data-dump-confirm", {"okay", "cancel"}).show() == "okay")
start_data_dump();
break;
case eMenu::SCEN_TEXT_DUMP:
if(cChoiceDlog("text-dump-confirm", {"okay", "cancel"}).show() == "okay")
scen_text_dump();
break;
case eMenu::TOWN_DETAILS:
edit_town_details();
change_made = true;
break;
case eMenu::TOWN_WANDERING:
edit_town_wand();
change_made = true;
break;
case eMenu::TOWN_BOUNDARIES:
overall_mode = MODE_SET_TOWN_RECT;
mode_count = 2;
set_string("Set town boundary","Select upper left corner");
break;
case eMenu::FRILL:
frill_up_terrain();
change_made = true;
break;
case eMenu::UNFRILL:
unfrill_terrain();
change_made = true;
break;
case eMenu::TOWN_AREAS:
right_sbar->setPosition(0);
start_string_editing(STRS_TOWN_RECT,0);
break;
case eMenu::TOWN_ITEMS_RANDOM:
if(cChoiceDlog("add-random-items", {"okay", "cancel"}).show() == "cancel")
break;
place_items_in_town();
change_made = true;
break;
case eMenu::TOWN_ITEMS_NOT_PROPERTY:
for(int i = 0; i < town->preset_items.size(); i++)
town->preset_items[i].property = 0;
cChoiceDlog("set-not-owned").show();
draw_terrain();
change_made = true;
break;
case eMenu::TOWN_ITEMS_CLEAR:
if(cChoiceDlog("clear-items-confirm", {"okay", "cancel"}).show() == "cancel")
break;
town->preset_items.clear();
draw_terrain();
change_made = true;
break;
case eMenu::TOWN_SPECIALS:
right_sbar->setPosition(0);
start_special_editing(2,0);
break;
case eMenu::TOWN_TEXT:
right_sbar->setPosition(0);
start_string_editing(STRS_TOWN,0);
break;
case eMenu::TOWN_SIGNS:
right_sbar->setPosition(0);
start_string_editing(STRS_TOWN_SIGN,0);
break;
case eMenu::TOWN_ADVANCED:
edit_advanced_town();
change_made = true;
break;
case eMenu::TOWN_TIMERS:
edit_town_events();
change_made = true;
break;
case eMenu::OUT_DETAILS:
outdoor_details();
change_made = true;
break;
case eMenu::OUT_WANDERING:
edit_out_wand(0);
change_made = true;
break;
case eMenu::OUT_ENCOUNTERS:
edit_out_wand(1);
change_made = true;
break;
case eMenu::OUT_AREAS:
right_sbar->setPosition(0);
start_string_editing(STRS_OUT_RECT,0);
break;
case eMenu::OUT_START:
overall_mode = MODE_SET_OUT_START;
set_string("Select party starting location.","");
break;
case eMenu::OUT_SPECIALS:
right_sbar->setPosition(0);
start_special_editing(1,0);
break;
case eMenu::OUT_TEXT:
right_sbar->setPosition(0);
start_string_editing(STRS_OUT,0);
break;
case eMenu::OUT_SIGNS:
right_sbar->setPosition(0);
start_string_editing(STRS_OUT_SIGN,0);
break;
case eMenu::ABOUT:
helpDlog = "about-scened";
isHelp = true;
break;
case eMenu::HELP_TOC:
if(fs::is_directory(progDir/"doc"))
launchURL("file://" + (progDir/"doc/editor/Contents.html").string());
else launchURL("http://openboe.com/docs/editor/Contents.html");
break;
case eMenu::HELP_START:
helpDlog = "help-editing";
isHelp = true;
break;
case eMenu::HELP_TEST:
helpDlog = "help-testing";
isHelp = true;
break;
case eMenu::HELP_DIST:
helpDlog = "help-distributing";
isHelp = true;
break;
case eMenu::HELP_CONTEST:
helpDlog = "help-contest";
isHelp = true;
break;
}
if(isEdit) {
if(!cDialog::sendInput(editKey)) {
// Handle non-dialog edit operations here.
switch(editKey.k) {
case key_undo: undo_list.undo(); break;
case key_redo: undo_list.redo(); break;
default: break;
}
update_edit_menu();
if(overall_mode == MODE_MAIN_SCREEN)
set_up_main_screen();
redraw_screen();
}
}
if(isHelp)
cChoiceDlog(helpDlog).show();
redraw_screen();
}
void Mouse_Pressed(const sf::Event & event) {
location mousePos { translate_mouse_coordinates({event.mouseButton.x, event.mouseButton.y}) };
handle_action(mousePos,event);
}
void close_program() {
}
// TODO: Remove this function and replace it with beep() or play_sound() everywhere.
void ding() {
beep();
}