/* Copyright (C) 2006 - 2016 by Joerg Hinrichs wesnoth playlevel Copyright (C) 2003 by David White Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ #include "controller_base.hpp" #include "show_dialog.hpp" //gui::in_dialog #include "display.hpp" #include "events.hpp" #include "game_preferences.hpp" #include "hotkey/command_executor.hpp" #include "hotkey/hotkey_command.hpp" #include "log.hpp" #include "map/map.hpp" #include "mouse_handler_base.hpp" #include "scripting/plugins/context.hpp" #include "soundsource.hpp" static lg::log_domain log_display("display"); #define ERR_DP LOG_STREAM(err, log_display) controller_base::controller_base( const config& game_config, CVideo& /*video*/) : game_config_(game_config) , key_() , scrolling_(false) , scroll_up_(false) , scroll_down_(false) , scroll_left_(false) , scroll_right_(false) , joystick_manager_() { } controller_base::~controller_base() { } void controller_base::handle_event(const SDL_Event& event) { if(gui::in_dialog()) { return; } static const hotkey::hotkey_command& quit_hotkey = hotkey::hotkey_command::get_command_by_command(hotkey::HOTKEY_QUIT_GAME); switch(event.type) { case SDL_KEYDOWN: // Detect key press events, unless there something that has keyboard focus // in which case the key press events should go only to it. if(have_keyboard_focus()) { if(event.key.keysym.sym == SDLK_ESCAPE) { hotkey::execute_command(quit_hotkey, get_hotkey_command_executor()); break; } process_keydown_event(event); hotkey::key_event(event, get_hotkey_command_executor()); process_keyup_event(event); } else { process_focus_keydown_event(event); } break; case SDL_KEYUP: process_keyup_event(event); hotkey::key_event(event, get_hotkey_command_executor()); break; case SDL_JOYBUTTONDOWN: process_keydown_event(event); hotkey::jbutton_event(event, get_hotkey_command_executor()); break; case SDL_JOYHATMOTION: process_keydown_event(event); hotkey::jhat_event(event, get_hotkey_command_executor()); break; case SDL_MOUSEMOTION: // Ignore old mouse motion events in the event queue SDL_Event new_event; if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) { while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {}; get_mouse_handler_base().mouse_motion_event(new_event.motion, is_browsing()); } else { get_mouse_handler_base().mouse_motion_event(event.motion, is_browsing()); } break; case SDL_MOUSEBUTTONDOWN: process_keydown_event(event); get_mouse_handler_base().mouse_press(event.button, is_browsing()); if (get_mouse_handler_base().get_show_menu()){ show_menu(get_display().get_theme().context_menu()->items(),event.button.x,event.button.y,true, get_display()); } hotkey::mbutton_event(event, get_hotkey_command_executor()); break; case SDL_MOUSEBUTTONUP: get_mouse_handler_base().mouse_press(event.button, is_browsing()); if (get_mouse_handler_base().get_show_menu()){ show_menu(get_display().get_theme().context_menu()->items(),event.button.x,event.button.y,true, get_display()); } break; case SDL_MOUSEWHEEL: get_mouse_handler_base().mouse_wheel(event.wheel.x, event.wheel.y, is_browsing()); break; default: break; } } bool controller_base::have_keyboard_focus() { return true; } void controller_base::process_focus_keydown_event(const SDL_Event& /*event*/) { //no action by default } void controller_base::process_keydown_event(const SDL_Event& /*event*/) { //no action by default } void controller_base::process_keyup_event(const SDL_Event& /*event*/) { //no action by default } bool controller_base::handle_scroll(int mousex, int mousey, int mouse_flags, double x_axis, double y_axis) { bool mouse_in_window = (CVideo::get_singleton().window_state() & SDL_APPMOUSEFOCUS) != 0 || preferences::get("scroll_when_mouse_outside", true); int scroll_speed = preferences::scroll_speed(); int dx = 0, dy = 0; int scroll_threshold = (preferences::mouse_scroll_enabled()) ? preferences::mouse_scroll_threshold() : 0; for (const theme::menu& m : get_display().get_theme().menus()) { if (sdl::point_in_rect(mousex, mousey, m.get_location())) { scroll_threshold = 0; } } // apply keyboard scrolling dy -= scroll_up_ * scroll_speed; dy += scroll_down_ * scroll_speed; dx -= scroll_left_ * scroll_speed; dx += scroll_right_ * scroll_speed; // scroll if mouse is placed near the edge of the screen if (mouse_in_window) { if (mousey < scroll_threshold) { dy -= scroll_speed; } if (mousey > get_display().h() - scroll_threshold) { dy += scroll_speed; } if (mousex < scroll_threshold) { dx -= scroll_speed; } if (mousex > get_display().w() - scroll_threshold) { dx += scroll_speed; } } // scroll with middle-mouse if enabled if ((mouse_flags & SDL_BUTTON_MMASK) != 0 && preferences::middle_click_scrolls()) { const SDL_Point original_loc = get_mouse_handler_base().get_scroll_start(); if (get_mouse_handler_base().scroll_started()) { const SDL_Rect& rect = get_display().map_outside_area(); if (sdl::point_in_rect(mousex, mousey,rect) && get_mouse_handler_base().scroll_started()) { // Scroll speed is proportional from the distance from the first // middle click and scrolling speed preference. const double speed = 0.04 * sqrt(static_cast(scroll_speed)); const double snap_dist = 16; // Snap to horizontal/vertical scrolling const double x_diff = (mousex - original_loc.x); const double y_diff = (mousey - original_loc.y); if (std::fabs(x_diff) > snap_dist || std::fabs(y_diff) <= snap_dist) dx += speed * x_diff; if (std::fabs(y_diff) > snap_dist || std::fabs(x_diff) <= snap_dist) dy += speed * y_diff; } } else { // Event may fire mouse down out of order with respect to initial click get_mouse_handler_base().set_scroll_start(mousex, mousey); } } // scroll with joystick dx += round_double( x_axis * scroll_speed); dy += round_double( y_axis * scroll_speed); return get_display().scroll(dx, dy); } void controller_base::play_slice(bool is_delay_enabled) { CKey key; if (plugins_context *l = get_plugins_context()) { l->play_slice(); } events::pump(); events::raise_process_event(); events::raise_draw_event(); // Update sound sources before scrolling if (soundsource::manager *l = get_soundsource_man()) { l->update(); } const theme::menu* const m = get_display().menu_pressed(); if(m != nullptr) { const SDL_Rect& menu_loc = m->location(get_display().screen_area()); show_menu(m->items(),menu_loc.x+1,menu_loc.y + menu_loc.h + 1,false, get_display()); return; } const theme::action* const a = get_display().action_pressed(); if(a != nullptr) { const SDL_Rect& action_loc = a->location(get_display().screen_area()); execute_action(a->items(), action_loc.x+1, action_loc.y + action_loc.h + 1,false); return; } auto str_vec = additional_actions_pressed(); if (!str_vec.empty()) { execute_action(str_vec, 0, 0, false); return; } bool was_scrolling = scrolling_; std::pair values = joystick_manager_.get_scroll_axis_pair(); const double joystickx = values.first; const double joysticky = values.second; int mousex, mousey; Uint8 mouse_flags = SDL_GetMouseState(&mousex, &mousey); /* TODO fendrin enable after an axis choosing mechanism is implemented std::pair values = joystick_manager_.get_mouse_axis_pair(); mousex += values.first * 10; mousey += values.second * 10; SDL_WarpMouse(mousex, mousey); */ scrolling_ = handle_scroll(mousex, mousey, mouse_flags, joystickx, joysticky); map_location highlighted_hex = get_display().mouseover_hex(); /* TODO fendrin enable when the relative cursor movement is implemented well enough const map_location& selected_hex = get_display().selected_hex(); if (selected_hex != map_location::null_location()) { if (joystick_manager_.next_highlighted_hex(highlighted_hex, selected_hex)) { get_mouse_handler_base().mouse_motion(0,0, true, true, highlighted_hex); get_display().scroll_to_tile(highlighted_hex, display::ONSCREEN_WARP, false, true); scrolling_ = true; } } else */ if (joystick_manager_.update_highlighted_hex(highlighted_hex) && get_display().get_map().on_board(highlighted_hex)) { get_mouse_handler_base().mouse_motion(0,0, true, true, highlighted_hex); get_display().scroll_to_tile(highlighted_hex, display::ONSCREEN_WARP, false, true); scrolling_ = true; } get_display().draw(); // be nice when window is not visible // NOTE should be handled by display instead, to only disable drawing if (is_delay_enabled && (CVideo::get_singleton().window_state() & SDL_APPACTIVE) == 0) { CVideo::delay(200); } if (!scrolling_ && was_scrolling) { // scrolling ended, update the cursor and the brightened hex get_mouse_handler_base().mouse_update(is_browsing(), highlighted_hex); } } void controller_base::show_menu(const std::vector& items_arg, int xloc, int yloc, bool context_menu, display& disp) { hotkey::command_executor * cmd_exec = get_hotkey_command_executor(); if (!cmd_exec) { return; } std::vector items = items_arg; std::vector::iterator i = items.begin(); while(i != items.end()) { const hotkey::hotkey_command& command = hotkey::get_hotkey_command(*i); if(!cmd_exec->can_execute_command(command) || (context_menu && !in_context_menu(command.id))) { i = items.erase(i); continue; } ++i; } if(items.empty()) return; cmd_exec->show_menu(items, xloc, yloc, context_menu, disp); } void controller_base::execute_action(const std::vector& items_arg, int xloc, int yloc, bool context_menu) { hotkey::command_executor * cmd_exec = get_hotkey_command_executor(); if (!cmd_exec) { return; } std::vector items; for (const std::string& item : items_arg) { const hotkey::hotkey_command& command = hotkey::get_hotkey_command(item); if(cmd_exec->can_execute_command(command)) items.push_back(item); } if(items.empty()) return; cmd_exec->execute_action(items, xloc, yloc, context_menu, get_display()); } bool controller_base::in_context_menu(hotkey::HOTKEY_COMMAND /*command*/) const { return true; } const config& controller_base::get_theme(const config& game_config, std::string theme_name) { if (theme_name.empty()) theme_name = preferences::theme(); if (const config &c = game_config.find_child("theme", "id", theme_name)) return c; ERR_DP << "Theme '" << theme_name << "' not found. Trying the default theme." << std::endl; if (const config &c = game_config.find_child("theme", "id", "Default")) return c; ERR_DP << "Default theme not found." << std::endl; static config empty; return empty; } void controller_base::set_scroll_up(bool on) { scroll_up_ = on; } void controller_base::set_scroll_down(bool on) { scroll_down_ = on; } void controller_base::set_scroll_left(bool on) { scroll_left_ = on; } void controller_base::set_scroll_right(bool on) { scroll_right_ = on; }