finalcut/src/fapplication.cpp

1417 lines
41 KiB
C++

/***********************************************************************
* fapplication.cpp - Manages the application events *
* *
* This file is part of the FINAL CUT widget toolkit *
* *
* Copyright 2013-2021 Markus Gans *
* *
* FINAL CUT is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation; either version 3 of *
* the License, or (at your option) any later version. *
* *
* FINAL CUT is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this program. If not, see *
* <http://www.gnu.org/licenses/>. *
***********************************************************************/
#include <chrono>
#include <fstream>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <thread>
#include "final/fapplication.h"
#include "final/fevent.h"
#include "final/flog.h"
#include "final/flogger.h"
#include "final/fmenu.h"
#include "final/fmenubar.h"
#include "final/fmessagebox.h"
#include "final/fmouse.h"
#include "final/fstartoptions.h"
#include "final/fstatusbar.h"
#include "final/ftermdata.h"
#include "final/ftermios.h"
#include "final/fwidgetcolors.h"
#include "final/fwindow.h"
namespace finalcut
{
namespace internal
{
struct var
{
static FApplication* app_object; // Global application object
static bool exit_loop; // Flag to exit the local event loop
};
FApplication* var::app_object {nullptr};
bool var::exit_loop {false};
} // namespace internal
// Static attributes
FWidget* FWidget::main_widget {nullptr}; // main application widget
FWidget* FWidget::active_window {nullptr}; // the active window
FWidget* FWidget::focus_widget {nullptr}; // has keyboard input focus
FWidget* FWidget::clicked_widget {nullptr}; // is focused by click
FWidget* FWidget::open_menu {nullptr}; // currently open menu
FWidget* FWidget::move_size_widget {nullptr}; // move/size by keyboard
FWidget* FApplication::keyboard_widget {nullptr}; // has the keyboard focus
int FApplication::loop_level {0}; // event loop level
int FApplication::quit_code {EXIT_SUCCESS};
bool FApplication::quit_now {false};
uInt64 FApplication::next_event_wait {5000}; // 5 ms (200 Hz)
struct timeval FApplication::time_last_event {};
//----------------------------------------------------------------------
// class FApplication
//----------------------------------------------------------------------
// constructors and destructor
//----------------------------------------------------------------------
FApplication::FApplication (const int& arg_c, char* arg_v[])
: FWidget{processParameters(Args(arg_v, arg_v + arg_c))}
, app_args{arg_v, arg_v + arg_c}
{
if ( quit_now )
return;
if ( internal::var::app_object )
{
auto& fterm_data = FTerm::getFTermData();
fterm_data.setExitMessage("FApplication: There should be "
"only one application object");
FApplication::exit(EXIT_FAILURE);
return;
}
// First define the application object
internal::var::app_object = this;
init();
}
//----------------------------------------------------------------------
FApplication::~FApplication() // destructor
{
internal::var::app_object = nullptr;
if ( eventInQueue() )
event_queue.clear();
destroyLog();
}
// public methods of FApplication
//----------------------------------------------------------------------
FApplication* FApplication::getApplicationObject()
{
return internal::var::app_object;
}
//----------------------------------------------------------------------
FWidget* FApplication::getKeyboardWidget()
{
return keyboard_widget;
}
//----------------------------------------------------------------------
FApplication::FLogPtr& FApplication::getLog()
{
// Global logger object
static auto logger_ptr = new FLogPtr();
if ( logger_ptr && logger_ptr->get() == nullptr )
{
*logger_ptr = std::make_shared<FLogger>();
// Set the logger as rdbuf of clog
std::clog.rdbuf(logger_ptr->get());
}
return *logger_ptr;
}
//----------------------------------------------------------------------
void FApplication::setLog (const FLogPtr& log)
{
FLogPtr& logger = getLog();
logger.reset();
logger = log;
// Set the logger as rdbuf of clog
std::clog.rdbuf(logger.get());
}
//----------------------------------------------------------------------
bool FApplication::isQuit()
{
return internal::var::app_object ? quit_now : true;
}
//----------------------------------------------------------------------
int FApplication::exec() // run
{
if ( quit_now )
{
quit_now = false;
return quit_code;
}
quit_code = EXIT_SUCCESS;
enterLoop();
return quit_code;
}
//----------------------------------------------------------------------
int FApplication::enterLoop() // event loop
{
loop_level++;
quit_now = false;
const bool old_app_exit_loop = internal::var::exit_loop;
internal::var::exit_loop = false;
while ( ! (quit_now || internal::var::exit_loop) )
processNextEvent();
internal::var::exit_loop = old_app_exit_loop;
loop_level--;
return 0;
}
//----------------------------------------------------------------------
void FApplication::exitLoop() const
{
internal::var::exit_loop = true;
}
//----------------------------------------------------------------------
void FApplication::exit (int retcode)
{
quit_now = true;
quit_code = retcode;
}
//----------------------------------------------------------------------
void FApplication::quit() const
{
FApplication::exit(0);
}
//----------------------------------------------------------------------
bool FApplication::sendEvent (FObject* receiver, FEvent* event )
{
if ( quit_now || internal::var::exit_loop || ! (bool(receiver) && bool(event)) )
return false;
if ( ! isEventProcessable (receiver, event) )
return false;
// Sends the event event directly to receiver
bool ret = receiver->event(event);
event->send = true;
return ret;
}
//----------------------------------------------------------------------
void FApplication::queueEvent (FObject* receiver, FEvent* event)
{
if ( ! (bool(receiver) && bool(event)) )
return;
// queue this event
event->queued = true;
event_queue.emplace_back (receiver, event);
}
//----------------------------------------------------------------------
void FApplication::sendQueuedEvents()
{
while ( eventInQueue() )
{
const EventPair& event_pair = event_queue.front();
event_pair.second->queued = false;
sendEvent(event_pair.first, event_pair.second);
event_queue.pop_front();
}
}
//----------------------------------------------------------------------
bool FApplication::eventInQueue() const
{
if ( internal::var::app_object )
return ( ! event_queue.empty() );
else
return false;
}
//----------------------------------------------------------------------
bool FApplication::removeQueuedEvent (const FObject* receiver)
{
if ( ! eventInQueue() )
return false;
if ( ! receiver )
return false;
bool retval{false};
auto iter = event_queue.begin();
while ( iter != event_queue.end() )
{
if ( iter->first == receiver )
{
iter = event_queue.erase(iter);
retval = true;
}
else
++iter;
}
return retval;
}
//----------------------------------------------------------------------
void FApplication::initTerminal()
{
if ( ! isQuit() )
FWidget::initTerminal();
}
//----------------------------------------------------------------------
void FApplication::setDefaultTheme()
{
if ( FTerm::getMaxColor() < 16 ) // for 8 color mode
{
if ( getStartOptions().color_change )
FTerm::setColorPaletteTheme<default8ColorPalette>();
setColorTheme<default8ColorTheme>();
}
else
{
if ( getStartOptions().color_change )
FTerm::setColorPaletteTheme<default16ColorPalette>();
setColorTheme<default16ColorTheme>();
}
}
//----------------------------------------------------------------------
void FApplication::setDarkTheme()
{
if ( getStartOptions().color_change )
FTerm::setColorPaletteTheme<default16DarkColorPalette>();
if ( FTerm::getMaxColor() < 16 ) // for 8 color mode
setColorTheme<default8ColorDarkTheme>();
else
setColorTheme<default16ColorDarkTheme>();
}
//----------------------------------------------------------------------
void FApplication::setLogFile (const FString& filename)
{
auto& log_stream = getStartOptions().logfile_stream;
log_stream.open(filename.c_str(), std::ofstream::out);
if ( log_stream.is_open() )
{
// Get the global logger object
FLog& log = *FApplication::getLog();
log.setOutputStream(log_stream);
log.enableTimestamp();
log.setLineEnding (FLog::LineEnding::LF);
}
else
{
auto& fterm_data = FTerm::getFTermData();
fterm_data.setExitMessage ( "Could not open log file \""
+ filename + "\"" );
exit(EXIT_FAILURE);
}
}
//----------------------------------------------------------------------
void FApplication::setKeyboardWidget (FWidget* widget)
{
keyboard_widget = widget;
}
//----------------------------------------------------------------------
void FApplication::closeConfirmationDialog (FWidget* w, FCloseEvent* ev)
{
internal::var::app_object->unsetMoveSizeMode();
const FMessageBox::ButtonType ret = \
FMessageBox::info ( w, "Quit"
, "Do you really want\n"
"to quit the program ?"
, FMessageBox::ButtonType::Yes
, FMessageBox::ButtonType::No );
if ( ret == FMessageBox::ButtonType::Yes )
ev->accept();
else
{
ev->ignore();
// Status bar restore after closing the FMessageBox
if ( getStatusBar() )
getStatusBar()->drawMessage();
}
}
// protected methods of FApplication
//----------------------------------------------------------------------
void FApplication::processExternalUserEvent()
{
// This method can be overloaded and replaced by own code
}
// private methods of FApplication
//----------------------------------------------------------------------
void FApplication::init()
{
// FApplication cannot have a second child widget
setMaxChildren(1);
// Initialize the last event time
time_last_event.tv_sec = 0;
time_last_event.tv_usec = 0;
// Initialize keyboard
auto& keyboard = FTerm::getFKeyboard();
auto cmd1 = [this] () { this->keyPressed(); };
auto cmd2 = [this] () { this->keyReleased(); };
auto cmd3 = [this] () { this->escapeKeyPressed(); };
auto cmd4 = [this] () { this->mouseTracking(); };
FKeyboardCommand key_cmd1 (cmd1);
FKeyboardCommand key_cmd2 (cmd2);
FKeyboardCommand key_cmd3 (cmd3);
FKeyboardCommand key_cmd4 (cmd4);
keyboard.setPressCommand (key_cmd1);
keyboard.setReleaseCommand (key_cmd2);
keyboard.setEscPressedCommand (key_cmd3);
keyboard.setMouseTrackingCommand (key_cmd4);
// Set the keyboard keypress timeout
keyboard.setKeypressTimeout (key_timeout);
// Initialize mouse control
auto& mouse = FTerm::getFMouseControl();
auto cmd = [this] (const FMouseData& md) { this->mouseEvent(md); };
FMouseCommand mouse_cmd (cmd);
mouse.setEventCommand (mouse_cmd);
// Set stdin number for a gpm-mouse
mouse.setStdinNo (FTermios::getStdIn());
// Set the default double click interval
mouse.setDblclickInterval (dblclick_interval);
// Initialize logging
if ( ! getStartOptions().logfile_stream.is_open() )
getLog()->setLineEnding(FLog::LineEnding::CRLF);
}
//----------------------------------------------------------------------
void FApplication::setTerminalEncoding (const FString& enc_str)
{
const FString& enc = enc_str.toLower();
if ( enc.includes("utf8") )
getStartOptions().encoding = Encoding::UTF8;
else if ( enc.includes("vt100") )
getStartOptions().encoding = Encoding::VT100;
else if ( enc.includes("pc") )
getStartOptions().encoding = Encoding::PC;
else if ( enc.includes("ascii") )
getStartOptions().encoding = Encoding::ASCII;
else if ( enc.includes("help") )
showParameterUsage();
else
{
auto& fterm_data = FTerm::getFTermData();
fterm_data.setExitMessage ( "Unknown encoding \"" + enc_str
+ "\"\n(Valid encodings are utf8, "
+ "vt100, pc and ascii)" );
exit(EXIT_FAILURE);
}
}
//----------------------------------------------------------------------
inline void FApplication::setLongOptions (std::vector<CmdOption>& long_options)
{
long_options =
{
{"encoding", required_argument, nullptr, 'e' },
{"log-file", required_argument, nullptr, 'l' },
{"no-mouse", no_argument, nullptr, 'm' },
{"no-optimized-cursor", no_argument, nullptr, 'o' },
{"no-terminal-detection", no_argument, nullptr, 'd' },
{"no-terminal-data-request", no_argument, nullptr, 'r' },
{"no-color-change", no_argument, nullptr, 'c' },
{"no-sgr-optimizer", no_argument, nullptr, 's' },
{"vgafont", no_argument, nullptr, 'v' },
{"newfont", no_argument, nullptr, 'n' },
{"dark-theme", no_argument, nullptr, 't' },
#if defined(__FreeBSD__) || defined(__DragonFly__)
{"no-esc-for-alt-meta", no_argument, nullptr, 'E' },
{"no-cursorstyle-change", no_argument, nullptr, 'C' },
#elif defined(__NetBSD__) || defined(__OpenBSD__)
{"no-esc-for-alt-meta", no_argument, nullptr, 'E' },
#endif
{nullptr, 0, nullptr, 0 }
};
}
//----------------------------------------------------------------------
inline void FApplication::setCmdOptionsMap (CmdMap& cmd_map)
{
auto enc = [] (const FString& s) { FApplication::setTerminalEncoding(s); };
auto log = [] (const FString& s) { FApplication::setLogFile(s); };
auto opt = &FApplication::getStartOptions;
// --encoding
cmd_map['e'] = [enc] (const char* arg) { enc(FString(arg)); };
// --log-file
cmd_map['l'] = [log] (const char* arg) { log(FString(arg)); };
// --no-mouse
cmd_map['m'] = [opt] (const char*) { opt().mouse_support = false; };
// --no-optimized-cursor
cmd_map['o'] = [opt] (const char*) { opt().cursor_optimisation = false; };
// --no-terminal-detection
cmd_map['d'] = [opt] (const char*) { opt().terminal_detection = false; };
// --no-terminal-data-request
cmd_map['r'] = [opt] (const char*) { opt().terminal_data_request = false; };
// --no-color-change
cmd_map['c'] = [opt] (const char*) { opt().color_change = false; };
// --no-sgr-optimizer
cmd_map['s'] = [opt] (const char*) { opt().sgr_optimizer = false; };
// --vgafont
cmd_map['v'] = [opt] (const char*) { opt().vgafont = true; };
// --newfont
cmd_map['n'] = [opt] (const char*) { opt().newfont = true; };
// --dark-theme
cmd_map['t'] = [opt] (const char*) { opt().dark_theme = true; };
#if defined(__FreeBSD__) || defined(__DragonFly__)
// --no-esc-for-alt-meta
cmd_map['E'] = [opt] (const char*) { opt().meta_sends_escape = false; };
// --no-cursorstyle-change
cmd_map['C'] = [opt] (const char*) { opt().change_cursorstyle = false; };
#elif defined(__NetBSD__) || defined(__OpenBSD__)
// --no-esc-for-alt-meta
cmd_map['E'] = [opt] (const char*) { opt().meta_sends_escape = false; };
#endif
}
//----------------------------------------------------------------------
void FApplication::cmdOptions (const Args& args)
{
// Interpret the command line options
CmdMap cmd_map{};
setCmdOptionsMap(cmd_map);
auto argc = args.size();
std::vector<const char*> argv(argc);
std::transform ( args.begin()
, args.end()
, argv.begin()
, [] (const std::string& str)
{
return str.data();
}
);
while ( true )
{
opterr = 0;
int idx{0};
std::vector<CmdOption> long_options{};
setLongOptions(long_options);
auto p = reinterpret_cast<const struct option*>(long_options.data());
auto argv_data = const_cast<char* const*>(argv.data());
const int opt = getopt_long (int(argc), argv_data, "", p, &idx);
if ( opt == -1 )
break;
const auto& entry = cmd_map[opt];
if ( entry )
entry(optarg);
}
cmd_map.clear();
}
//----------------------------------------------------------------------
inline FStartOptions& FApplication::getStartOptions()
{
return FStartOptions::getFStartOptions();
}
//----------------------------------------------------------------------
void FApplication::showParameterUsage()
{
std::cout \
<< "Generic options:\n"
<< " -h, --help "
<< " Display this help and exit\n"
<< "\n"
<< "FINAL CUT options:\n"
<< " --encoding=<MODE> "
<< " Sets the character encoding mode\n"
<< " "
<< " {utf8, vt100, pc, ascii}\n"
<< " --log-file=<FILE> "
<< " Writes log output to FILE\n"
<< " --no-mouse "
<< " Disable mouse support\n"
<< " --no-optimized-cursor "
<< " Disable cursor optimization\n"
<< " --no-terminal-detection "
<< " Disable terminal detection\n"
<< " --no-terminal-data-request"
<< " Do not determine terminal font and title\n"
<< " --no-color-change "
<< " Do not redefine the color palette\n"
<< " --no-sgr-optimizer "
<< " Do not optimize SGR sequences\n"
<< " --vgafont "
<< " Set the standard vga 8x16 font\n"
<< " --newfont "
<< " Enables the graphical font\n"
<< " --dark-theme "
<< " Enables the dark theme\n"
#if defined(__FreeBSD__) || defined(__DragonFly__)
<< "\n"
<< "FreeBSD console options:\n"
<< " --no-esc-for-alt-meta "
<< " Do not send a ESC prefix for the alt/meta key\n"
<< " --no-cursorstyle-change "
<< " Do not change the current cursor style\n"
#elif defined(__NetBSD__) || defined(__OpenBSD__)
<< "\n"
<< "NetBSD/OpenBSD console options:\n"
<< " --no-esc-for-alt-meta "
<< " Do not send a ESC prefix for the alt/meta key\n"
#endif
<< std::endl; // newline character + flushes the output stream
}
//----------------------------------------------------------------------
inline void FApplication::destroyLog()
{
// Reset the rdbuf of clog
std::clog.rdbuf(default_clog_rdbuf);
// Delete the logger
const FLogPtr* logger = &(getLog());
delete logger;
}
//----------------------------------------------------------------------
inline void FApplication::findKeyboardWidget() const
{
// Find the widget that has the keyboard focus
FWidget* widget{nullptr};
auto focus = getFocusWidget();
auto move_size = getMoveSizeWidget();
if ( focus )
{
if ( move_size )
widget = move_size;
else
widget = focus;
}
else
{
widget = getMainWidget();
if ( widget && widget->numOfChildren() >= 1 )
widget->focusFirstChild();
}
keyboard_widget = widget;
}
//----------------------------------------------------------------------
inline bool FApplication::isKeyPressed() const
{
auto& mouse = FTerm::getFMouseControl();
auto& keyboard = FTerm::getFKeyboard();
if ( mouse.isGpmMouseEnabled() )
return mouse.getGpmKeyPressed(keyboard.hasUnprocessedInput());
return (keyboard.isKeyPressed() || keyboard.hasPendingInput());
}
//----------------------------------------------------------------------
void FApplication::keyPressed()
{
performKeyboardAction();
}
//----------------------------------------------------------------------
void FApplication::keyReleased() const
{
sendKeyUpEvent (keyboard_widget);
}
//----------------------------------------------------------------------
void FApplication::escapeKeyPressed() const
{
sendEscapeKeyPressEvent();
}
//----------------------------------------------------------------------
void FApplication::mouseTracking() const
{
performMouseAction();
}
//----------------------------------------------------------------------
inline void FApplication::performKeyboardAction()
{
const auto& keyboard = FTerm::getFKeyboard();
if ( keyboard.getKey() == FKey::Ctrl_l ) // Ctrl-L (redraw the screen)
{
redraw();
}
else
{
const bool acceptKeyDown = sendKeyDownEvent (keyboard_widget);
const bool acceptKeyPress = sendKeyPressEvent (keyboard_widget);
if ( ! (acceptKeyDown || acceptKeyPress) )
sendKeyboardAccelerator();
}
}
//----------------------------------------------------------------------
inline void FApplication::performMouseAction() const
{
auto& mouse = FTerm::getFMouseControl();
auto& keyboard = FTerm::getFKeyboard();
const auto key = keyboard.getKey();
auto& buffer = keyboard.getKeyBuffer();
if ( key == FKey::X11mouse )
{
mouse.setRawData (FMouse::MouseType::X11, buffer);
}
else if ( key == FKey::Extended_mouse )
{
mouse.setRawData (FMouse::MouseType::Sgr, buffer);
}
else if ( key == FKey::Urxvt_mouse )
{
mouse.setRawData (FMouse::MouseType::Urxvt, buffer);
}
keyboard.hasUnprocessedInput() = mouse.hasUnprocessedInput();
queuingMouseInput();
}
//----------------------------------------------------------------------
void FApplication::mouseEvent (const FMouseData& md)
{
determineClickedWidget (md);
if ( FWidget::getClickedWidget() )
{
unsetMoveSizeMode();
closeDropDown (md);
unselectMenubarItems (md);
sendMouseEvent (md);
}
}
//----------------------------------------------------------------------
inline void FApplication::sendEscapeKeyPressEvent() const
{
// Send an escape key press event
FKeyEvent k_press_ev (Event::KeyPress, FKey::Escape);
sendEvent (keyboard_widget, &k_press_ev);
}
//----------------------------------------------------------------------
inline bool FApplication::sendKeyDownEvent (FWidget* widget) const
{
// Send key down event
const auto& keyboard = FTerm::getFKeyboard();
FKeyEvent k_down_ev (Event::KeyDown, keyboard.getKey());
sendEvent (widget, &k_down_ev);
return k_down_ev.isAccepted();
}
//----------------------------------------------------------------------
inline bool FApplication::sendKeyPressEvent (FWidget* widget) const
{
// Send key press event
const auto& keyboard = FTerm::getFKeyboard();
FKeyEvent k_press_ev (Event::KeyPress, keyboard.getKey());
sendEvent (widget, &k_press_ev);
return k_press_ev.isAccepted();
}
//----------------------------------------------------------------------
inline bool FApplication::sendKeyUpEvent (FWidget* widget) const
{
// Send key up event
const auto& keyboard = FTerm::getFKeyboard();
FKeyEvent k_up_ev (Event::KeyUp, keyboard.getKey());
sendEvent (widget, &k_up_ev);
return k_up_ev.isAccepted();
}
//----------------------------------------------------------------------
inline void FApplication::sendKeyboardAccelerator()
{
if ( FWidget::getOpenMenu() )
return;
// Switch to a specific dialog with Meta + 1..9
bool accpt = processDialogSwitchAccelerator();
// Windows keyboard accelerator
if ( ! accpt )
{
auto window = static_cast<const FWidget*>(getActiveWindow());
if ( window )
accpt = processAccelerator(*window);
}
// Global keyboard accelerator
if ( ! accpt )
{
auto root_widget = getRootWidget();
if ( root_widget )
processAccelerator(*root_widget);
}
}
//----------------------------------------------------------------------
inline bool FApplication::hasDataInQueue() const
{
const auto& keyboard = FTerm::getFKeyboard();
const auto& mouse = FTerm::getFMouseControl();
if ( keyboard.hasDataInQueue()
|| mouse.hasDataInQueue()
|| FTerm::hasChangedTermSize() )
return true;
return false;
}
//----------------------------------------------------------------------
void FApplication::queuingKeyboardInput() const
{
if ( quit_now
|| internal::var::exit_loop
|| FTerm::hasChangedTermSize() )
return;
findKeyboardWidget();
auto& keyboard = FTerm::getFKeyboard();
keyboard.escapeKeyHandling(); // special case: Esc key
keyboard.clearKeyBufferOnTimeout();
if ( isKeyPressed() )
keyboard.fetchKeyCode();
}
//----------------------------------------------------------------------
void FApplication::queuingMouseInput() const
{
auto& mouse = FTerm::getFMouseControl();
if ( quit_now
|| internal::var::exit_loop
|| ! mouse.hasData()
|| FTerm::hasChangedTermSize() )
return;
auto& keyboard = FTerm::getFKeyboard();
struct timeval* time_keypressed = keyboard.getKeyPressedTime();
mouse.processEvent (time_keypressed);
keyboard.hasUnprocessedInput() = mouse.hasUnprocessedInput();
mouse.clearEvent();
}
//----------------------------------------------------------------------
void FApplication::processKeyboardEvent() const
{
if ( quit_now
|| internal::var::exit_loop
|| FTerm::hasChangedTermSize() )
return;
auto& keyboard = FTerm::getFKeyboard();
keyboard.processQueuedInput();
}
//----------------------------------------------------------------------
void FApplication::processMouseEvent() const
{
if ( quit_now
|| internal::var::exit_loop
|| FTerm::hasChangedTermSize() )
return;
FTerm::getFMouseControl().processQueuedInput();
}
//----------------------------------------------------------------------
bool FApplication::processDialogSwitchAccelerator() const
{
const auto& keyboard = FTerm::getFKeyboard();
if ( keyboard.getKey() >= FKey::Meta_1
&& keyboard.getKey() <= FKey::Meta_9 )
{
const FKey key = keyboard.getKey();
const auto n = std::size_t(key - FKey::Meta_0);
const std::size_t s = getDialogList()->size();
if ( s > 0 && s >= n )
{
// unset the move/size mode
auto move_size = getMoveSizeWidget();
if ( move_size )
{
auto w = move_size;
setMoveSizeWidget(nullptr);
w->redraw();
}
FAccelEvent a_ev (Event::Accelerator, getFocusWidget());
sendEvent (getDialogList()->at(n - 1), &a_ev);
return true;
}
}
return false;
}
//----------------------------------------------------------------------
bool FApplication::processAccelerator (const FWidget& widget) const
{
if ( widget.getAcceleratorList().empty() )
return false;
for (auto&& item : widget.getAcceleratorList())
{
if ( item.key == FTerm::getFKeyboard().getKey() )
{
// unset the move/size mode
auto move_size = getMoveSizeWidget();
if ( move_size )
{
setMoveSizeWidget(nullptr);
move_size->redraw();
}
FAccelEvent a_ev (Event::Accelerator, getFocusWidget());
sendEvent (item.object, &a_ev);
return a_ev.isAccepted();
}
if ( quit_now || internal::var::exit_loop )
break;
}
return false;
}
//----------------------------------------------------------------------
void FApplication::determineClickedWidget (const FMouseData& md)
{
clicked_widget = FWidget::getClickedWidget();
if ( clicked_widget )
return; // The clicked widget was already found
if ( ! md.isLeftButtonPressed()
&& ! md.isLeftButtonDoubleClick()
&& ! md.isRightButtonPressed()
&& ! md.isMiddleButtonPressed()
&& ! md.isWheelUp()
&& ! md.isWheelDown() )
return;
const auto& mouse_position = md.getPos();
// Determine the window object on the current click position
auto window = FWindow::getWindowWidgetAt (mouse_position);
if ( window )
{
// Determine the widget at the current click position
auto child = window->childWidgetAt(mouse_position);
clicked_widget = ( child != nullptr ) ? child : window;
setClickedWidget (clicked_widget);
}
}
//----------------------------------------------------------------------
void FApplication::unsetMoveSizeMode() const
{
// Unset the move/size mode
auto& move_size = getMoveSizeWidget();
if ( move_size )
{
FWidget* w{nullptr};
std::swap(w, move_size); // Clear move_size_widget
w->redraw();
}
}
//----------------------------------------------------------------------
void FApplication::closeDropDown (const FMouseData& md) const
{
// Close the open menu
if ( md.isMoved() )
return;
const auto& mouse_position = md.getPos();
finalcut::closeDropDown (this, mouse_position);
}
//----------------------------------------------------------------------
void FApplication::unselectMenubarItems (const FMouseData& md) const
{
// Unselect the menu bar items
const auto& openmenu = FWidget::getOpenMenu();
auto menu_bar = FWidget::getMenuBar();
if ( openmenu || md.isMoved() )
return;
if ( ! (menu_bar && menu_bar->hasSelectedItem()) )
return;
const auto& mouse_position = md.getPos();
if ( ! menu_bar->getTermGeometry().contains(mouse_position) )
{
if ( FWidget::getStatusBar() )
FWidget::getStatusBar()->clearMessage();
menu_bar->resetMenu();
menu_bar->redraw();
// No widget was been clicked
if ( ! FWidget::getClickedWidget() )
FWindow::switchToPrevWindow(this);
if ( FWidget::getStatusBar() )
FWidget::getStatusBar()->drawMessage();
}
}
//----------------------------------------------------------------------
void FApplication::sendMouseEvent (const FMouseData& md) const
{
const auto& mouse_position = md.getPos();
MouseButton key_state{MouseButton::None};
if ( md.isShiftKeyPressed() )
key_state |= MouseButton::Shift;
if ( md.isControlKeyPressed() )
key_state |= MouseButton::Control;
if ( md.isMetaKeyPressed() )
key_state |= MouseButton::Meta;
const auto& widgetMousePos = clicked_widget->termToWidgetPos(mouse_position);
if ( md.isMoved() )
{
sendMouseMoveEvent (md, widgetMousePos, mouse_position, key_state);
}
else
{
sendMouseLeftClickEvent (md, widgetMousePos, mouse_position, key_state);
sendMouseRightClickEvent (md, widgetMousePos, mouse_position, key_state);
sendMouseMiddleClickEvent (md, widgetMousePos, mouse_position, key_state);
}
sendWheelEvent (md, widgetMousePos, mouse_position);
}
//----------------------------------------------------------------------
void FApplication::sendMouseMoveEvent ( const FMouseData& md
, const FPoint& widgetMousePos
, const FPoint& mouse_position
, MouseButton key_state ) const
{
if ( md.isLeftButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseMove
, widgetMousePos
, mouse_position
, MouseButton::Left | key_state );
sendEvent (clicked_widget, &m_down_ev);
}
if ( md.isRightButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseMove
, widgetMousePos
, mouse_position
, MouseButton::Right | key_state );
sendEvent (clicked_widget, &m_down_ev);
}
if ( md.isMiddleButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseMove
, widgetMousePos
, mouse_position
, MouseButton::Middle | key_state );
sendEvent (clicked_widget, &m_down_ev);
}
}
//----------------------------------------------------------------------
void FApplication::sendMouseLeftClickEvent ( const FMouseData& md
, const FPoint& widgetMousePos
, const FPoint& mouse_position
, MouseButton key_state ) const
{
if ( md.isLeftButtonDoubleClick() )
{
FMouseEvent m_dblclick_ev ( Event::MouseDoubleClick
, widgetMousePos
, mouse_position
, MouseButton::Left | key_state );
sendEvent (clicked_widget, &m_dblclick_ev);
}
else if ( md.isLeftButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseDown
, widgetMousePos
, mouse_position
, MouseButton::Left | key_state );
sendEvent (clicked_widget, &m_down_ev);
}
else if ( md.isLeftButtonReleased() )
{
FMouseEvent m_up_ev ( Event::MouseUp
, widgetMousePos
, mouse_position
, MouseButton::Left | key_state );
auto released_widget = clicked_widget;
if ( ! md.isRightButtonPressed()
&& ! md.isMiddleButtonPressed() )
setClickedWidget(nullptr);
sendEvent (released_widget, &m_up_ev);
}
}
//----------------------------------------------------------------------
void FApplication::sendMouseRightClickEvent ( const FMouseData& md
, const FPoint& widgetMousePos
, const FPoint& mouse_position
, MouseButton key_state ) const
{
if ( md.isRightButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseDown
, widgetMousePos
, mouse_position
, MouseButton::Right | key_state );
sendEvent (clicked_widget, &m_down_ev);
}
else if ( md.isRightButtonReleased() )
{
FMouseEvent m_up_ev ( Event::MouseUp
, widgetMousePos
, mouse_position
, MouseButton::Right | key_state );
auto released_widget = clicked_widget;
if ( ! md.isLeftButtonPressed()
&& ! md.isMiddleButtonPressed() )
setClickedWidget(nullptr);
sendEvent (released_widget, &m_up_ev);
}
}
//----------------------------------------------------------------------
void FApplication::sendMouseMiddleClickEvent ( const FMouseData& md
, const FPoint& widgetMousePos
, const FPoint& mouse_position
, MouseButton key_state ) const
{
if ( md.isMiddleButtonPressed() )
{
FMouseEvent m_down_ev ( Event::MouseDown
, widgetMousePos
, mouse_position
, MouseButton::Middle | key_state );
sendEvent (clicked_widget, &m_down_ev);
// gnome-terminal sends no released on middle click
if ( FTerm::isGnomeTerminal() )
setClickedWidget(nullptr);
}
else if ( md.isMiddleButtonReleased() )
{
FMouseEvent m_up_ev ( Event::MouseUp
, widgetMousePos
, mouse_position
, MouseButton::Middle | key_state );
auto released_widget = clicked_widget;
if ( ! md.isLeftButtonPressed()
&& ! md.isRightButtonPressed() )
{
setClickedWidget(nullptr);
}
sendEvent (released_widget, &m_up_ev);
}
}
//----------------------------------------------------------------------
void FApplication::sendWheelEvent ( const FMouseData& md
, const FPoint& widgetMousePos
, const FPoint& mouse_position ) const
{
if ( md.isWheelUp() )
{
FWheelEvent wheel_ev ( Event::MouseWheel
, widgetMousePos
, mouse_position
, MouseWheel::Up );
auto scroll_over_widget = clicked_widget;
setClickedWidget(nullptr);
sendEvent(scroll_over_widget, &wheel_ev);
}
if ( md.isWheelDown() )
{
FWheelEvent wheel_ev ( Event::MouseWheel
, widgetMousePos
, mouse_position
, MouseWheel::Down );
auto scroll_over_widget = clicked_widget;
setClickedWidget(nullptr);
sendEvent (scroll_over_widget, &wheel_ev);
}
}
//----------------------------------------------------------------------
FWidget* FApplication::processParameters (const Args& args)
{
if ( args.size() > 1 && (args[1] == "--help" || args[1] == "-h") )
{
showParameterUsage();
FApplication::exit(EXIT_SUCCESS);
}
cmdOptions (args);
return nullptr;
}
//----------------------------------------------------------------------
void FApplication::processResizeEvent() const
{
if ( ! FTerm::hasChangedTermSize() ) // A SIGWINCH signal was received
return;
FTerm::detectTermSize(); // Detect and save the current terminal size
auto& mouse = FTerm::getFMouseControl();
mouse.setMaxWidth (uInt16(getDesktopWidth()));
mouse.setMaxHeight (uInt16(getDesktopHeight()));
FResizeEvent r_ev(Event::Resize);
sendEvent(internal::var::app_object, &r_ev);
if ( r_ev.isAccepted() )
FTerm::changeTermSizeFinished();
}
//----------------------------------------------------------------------
void FApplication::processCloseWidget()
{
if ( ! getWidgetCloseList() || getWidgetCloseList()->empty() )
return;
setTerminalUpdates (FVTerm::TerminalUpdate::Stop);
auto iter = getWidgetCloseList()->begin();
while ( iter != getWidgetCloseList()->end() && *iter )
{
delete *iter;
++iter;
}
getWidgetCloseList()->clear();
setTerminalUpdates (FVTerm::TerminalUpdate::Start);
}
//----------------------------------------------------------------------
void FApplication::processLogger() const
{
// Synchronizing the stream buffer with the logging output
auto logger = getLog();
if ( ! logger->str().empty() )
logger->pubsync();
logger->flush();
}
//----------------------------------------------------------------------
bool FApplication::processNextEvent()
{
uInt num_events{0};
bool is_timeout = isNextEventTimeout();
if ( is_timeout || hasDataInQueue() )
{
FObject::getCurrentTime (&time_last_event);
queuingKeyboardInput();
queuingMouseInput();
processKeyboardEvent();
processMouseEvent();
processResizeEvent();
processCloseWidget();
processTerminalUpdate(); // after terminal changes
flush();
processLogger();
}
processExternalUserEvent();
if ( is_timeout )
{
sendQueuedEvents();
num_events += processTimerEvent();
}
return ( num_events > 0 );
}
//----------------------------------------------------------------------
void FApplication::performTimerAction (FObject* receiver, FEvent* event)
{
sendEvent (receiver, event);
}
//----------------------------------------------------------------------
bool FApplication::isEventProcessable ( FObject* receiver
, const FEvent* event )
{
if ( ! receiver->isWidget() ) // No restrictions for non-widgets
return true;
auto widget = static_cast<FWidget*>(receiver);
if ( getModalDialogCounter() > 0 )
{
const FWidget* window;
if ( widget->isWindowWidget() )
window = widget;
else
window = FWindow::getWindowWidget(widget);
// block events for widgets in non modal windows
if ( window
&& ! window->getFlags().modal
&& ! window->isMenuWidget() )
{
constexpr std::array<const Event, 13> blocked_events
{{
Event::KeyPress,
Event::KeyUp,
Event::KeyDown,
Event::MouseDown,
Event::MouseUp,
Event::MouseDoubleClick,
Event::MouseWheel,
Event::MouseMove,
Event::FocusIn,
Event::FocusOut,
Event::ChildFocusIn,
Event::ChildFocusOut,
Event::Accelerator
}};
if ( std::any_of( blocked_events.cbegin()
, blocked_events.cend()
, [&event](const Event& ev)
{
return ev == event->getType();
} ) )
{
return false;
}
}
}
// Throw away mouse events for disabled widgets
if ( event->getType() >= Event::MouseDown
&& event->getType() <= Event::MouseMove
&& ! widget->isEnabled() )
return false;
return true;
}
//----------------------------------------------------------------------
bool FApplication::isNextEventTimeout()
{
return FObject::isTimeout (&time_last_event, next_event_wait);
}
// FLog non-member operators
//----------------------------------------------------------------------
std::ostream& operator << (std::ostream& outstr, FLog::LogLevel l)
{
*FApplication::getLog() << l;
return outstr;
}
} // namespace finalcut