finalcut/src/fcombobox.cpp

682 lines
17 KiB
C++

/***********************************************************************
* fcombobox.cpp - Widget FComboBox *
* *
* This file is part of the FINAL CUT widget toolkit *
* *
* Copyright 2019-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 <memory>
#include "final/fapplication.h"
#include "final/fcolorpair.h"
#include "final/fcombobox.h"
#include "final/fevent.h"
#include "final/flabel.h"
#include "final/flineedit.h"
#include "final/flistbox.h"
#include "final/fmouse.h"
#include "final/fpoint.h"
#include "final/fsize.h"
#include "final/fstatusbar.h"
#include "final/fwidgetcolors.h"
namespace finalcut
{
//----------------------------------------------------------------------
// class FDropDownListBox
//----------------------------------------------------------------------
// constructor and destructor
//----------------------------------------------------------------------
FDropDownListBox::FDropDownListBox (FWidget* parent)
: FWindow{parent}
{
init();
}
//----------------------------------------------------------------------
FDropDownListBox::~FDropDownListBox() // destructor
{
if ( FApplication::isQuit() )
return;
FWindow* parent_win{nullptr};
if ( const auto& parent = getParentWidget() )
parent_win = getWindowWidget(parent);
if ( parent_win )
setActiveWindow (parent_win);
else
switchToPrevWindow(this);
}
//----------------------------------------------------------------------
void FDropDownListBox::setGeometry ( const FPoint& pos, const FSize& size
, bool adjust )
{
FWindow::setGeometry (pos, size, adjust);
if ( FTerm::isNewFont() )
{
FSize new_size{size};
new_size.scaleBy(-1, 0);
list.setGeometry (FPoint{2, 1}, new_size, adjust);
}
else
list.setGeometry (FPoint{1, 1}, size, adjust);
}
//----------------------------------------------------------------------
void FDropDownListBox::show()
{
if ( ! isVisible() )
return;
FWindow::show();
}
//----------------------------------------------------------------------
void FDropDownListBox::hide()
{
if ( ! isVisible() )
return;
FWindow::hide();
setOpenMenu(nullptr);
const auto& t_geometry = getTermGeometryWithShadow();
restoreVTerm (t_geometry);
}
// private methods of FDropDownListBox
//----------------------------------------------------------------------
void FDropDownListBox::init()
{
setAlwaysOnTop();
ignorePadding();
setShadow();
// initialize geometry values
setGeometry (FPoint{1, 1}, FSize{3, 3}, false);
setMinimumSize (FSize{3, 3});
hide();
list.setGeometry (FPoint{1, 1}, FSize{3, 3}, false);
}
//----------------------------------------------------------------------
void FDropDownListBox::draw()
{
// Fill the background
const auto& wc = getColorTheme();
setForegroundColor (wc->list_fg);
setBackgroundColor (wc->list_bg);
setColor();
if ( FTerm::isMonochron() )
setReverse(true);
clearArea();
drawShadow();
if ( FTerm::isMonochron() )
setReverse(false);
}
//----------------------------------------------------------------------
void FDropDownListBox::drawShadow()
{
const auto& wc = getColorTheme();
finalcut::drawShadow(this);
setColor (wc->shadow_fg, wc->shadow_bg);
print() << FPoint{int(getWidth()) + 1, 1} << UniChar::FullBlock; // █
}
//----------------------------------------------------------------------
bool FDropDownListBox::containsWidget (const FPoint& p)
{
// Check mouse click position for item, menu and all sub menus
FWidget* parent = getParentWidget();
if ( getTermGeometry().contains(p) )
return true;
else if ( parent && parent->isInstanceOf("FComboBox") )
return static_cast<FComboBox*>(parent)->getTermGeometry().contains(p);
else
return false;
}
//----------------------------------------------------------------------
// class FComboBox
//----------------------------------------------------------------------
// constructors and destructor
//----------------------------------------------------------------------
FComboBox::FComboBox (FWidget* parent)
: FWidget{parent}
{
init();
}
//----------------------------------------------------------------------
FComboBox::~FComboBox() noexcept = default; // destructor
// public methods of FComboBox
//----------------------------------------------------------------------
void FComboBox::setSize (const FSize& size, bool adjust)
{
FWidget::setSize (size, adjust);
FSize input_field_size{size};
input_field_size.scaleBy(-(1 + nf), 0);
input_field.setSize (input_field_size, adjust);
}
//----------------------------------------------------------------------
void FComboBox::setGeometry ( const FPoint& pos, const FSize& size
, bool adjust )
{
FWidget::setGeometry (pos, size, adjust);
FSize input_field_size{size};
input_field_size.scaleBy(-(1 + nf), 0);
input_field.setGeometry (FPoint{1, 1}, input_field_size, adjust);
}
//----------------------------------------------------------------------
bool FComboBox::setEnable (bool enable)
{
FWidget::setEnable(enable);
input_field.setEnable(enable);
return enable;
}
//----------------------------------------------------------------------
bool FComboBox::setFocus (bool enable)
{
FWidget::setFocus(enable);
input_field.setFocus(enable);
return enable;
}
//----------------------------------------------------------------------
bool FComboBox::setShadow (bool enable)
{
if ( enable
&& FTerm::getEncoding() != Encoding::VT100
&& FTerm::getEncoding() != Encoding::ASCII )
{
setFlags().shadow = true;
setShadowSize(FSize{1, 1});
}
else
{
setFlags().shadow = false;
setShadowSize(FSize{0, 0});
}
return getFlags().shadow;
}
//----------------------------------------------------------------------
bool FComboBox::setEditable (bool enable)
{
if ( is_editable == enable )
return is_editable;
if ( enable )
unsetVisibleCursor();
else
setVisibleCursor();
input_field.setReadOnly(! enable);
return (is_editable = enable);
}
//----------------------------------------------------------------------
void FComboBox::setCurrentItem (std::size_t index)
{
if ( index > getCount() )
index = getCount();
else if ( index < 1 )
index = 1;
if ( index == list_window.list.currentItem() )
return;
list_window.list.setCurrentItem(index);
input_field = list_window.list.getItem(index).getText();
input_field.redraw();
processChanged();
}
//----------------------------------------------------------------------
void FComboBox::setMaxVisibleItems (std::size_t items)
{
// Sets the maximum height of the combo box in elements
if ( items > getCount() )
max_items = getCount();
else
max_items = items;
}
//----------------------------------------------------------------------
void FComboBox::insert (const FListBoxItem& listItem)
{
list_window.list.insert(listItem);
if ( getCount() == 1 )
input_field = list_window.list.getItem(1).getText();
}
//----------------------------------------------------------------------
void FComboBox::remove (std::size_t item)
{
list_window.list.remove(item);
if ( ! list_window.isEmpty() )
{
const std::size_t index = list_window.list.currentItem();
input_field = list_window.list.getItem(index).getText();
input_field.redraw();
}
if ( list_window.isShown() )
{
// Adjusting the size of the drop-down list
hideDropDown();
showDropDown();
}
}
//----------------------------------------------------------------------
void FComboBox::clear()
{
if ( list_window.isShown() )
hideDropDown();
list_window.list.clear();
input_field.clear();
redraw();
}
//----------------------------------------------------------------------
void FComboBox::showDropDown()
{
if ( list_window.isEmpty() )
return;
static constexpr std::size_t border = 2; // Size of the top and bottom border
setOpenMenu(&list_window);
FPoint p{getTermPos()};
p.move(0 - int(nf), 1);
setClickedWidget(&list_window.list);
const std::size_t w = getWidth();
const std::size_t h = ( getCount() <= max_items ) ? getCount() : max_items;
list_window.setGeometry(p, FSize{w + std::size_t(nf), h + border});
list_window.show();
list_window.list.setFocus();
list_window.redraw();
}
//----------------------------------------------------------------------
void FComboBox::hideDropDown()
{
if ( list_window.isHidden() )
return;
list_window.hide();
input_field.setFocus();
input_field.redraw();
}
//----------------------------------------------------------------------
void FComboBox::onKeyPress (FKeyEvent* ev)
{
if ( ! isEnabled() )
return;
const auto key = ev->key();
if ( key == FKey::Tab )
{
focusNextChild();
}
else if ( key == FKey::Back_tab )
{
focusPrevChild();
}
else if ( key == FKey::Up )
{
onePosUp();
ev->accept();
}
else if ( key == FKey::Down )
{
onePosDown();
ev->accept();
}
else if ( key == FKey::Meta_up
|| key == FKey::Ctrl_up
|| key == FKey::Escape
|| key == FKey::Escape_mintty )
{
hideDropDown();
ev->accept();
}
else if ( key == FKey::F4
|| key == FKey::Meta_down
|| key == FKey::Ctrl_down )
{
showDropDown();
ev->accept();
}
}
//----------------------------------------------------------------------
void FComboBox::onMouseDown (FMouseEvent* ev)
{
if ( ev->getButton() != MouseButton::Left )
return;
setWidgetFocus(this);
const int mouse_x = ev->getX();
const int mouse_y = ev->getY();
if ( mouse_x >= int(getWidth()) - nf
&& mouse_x <= int(getWidth()) && mouse_y == 1 )
{
redraw();
if ( list_window.isHidden() )
showDropDown();
else
list_window.hide();
}
}
//----------------------------------------------------------------------
void FComboBox::onMouseMove (FMouseEvent* ev)
{
if ( ev->getButton() != MouseButton::Left )
return;
if ( isMouseOverListWindow(ev->getTermPos()) )
{
passEventToListWindow(*ev); // Event handover to window list
return;
}
}
//----------------------------------------------------------------------
void FComboBox::onWheel (FWheelEvent* ev)
{
if ( ev->getWheel() == MouseWheel::Up )
onePosUp();
else if ( ev->getWheel() == MouseWheel::Down )
onePosDown();
}
//----------------------------------------------------------------------
void FComboBox::onFocusOut (FFocusEvent*)
{
hideDropDown();
}
// private methods of FComboBox
//----------------------------------------------------------------------
bool FComboBox::isMouseOverListWindow (const FPoint& termpos)
{
if ( list_window.isShown() )
{
auto list_geometry = list_window.getTermGeometry();
if ( list_geometry.contains(termpos) )
return true;
}
return false;
}
//----------------------------------------------------------------------
void FComboBox::init()
{
setShadow();
const auto& parent_widget = getParentWidget();
FLabel* label = input_field.getLabelObject();
label->setParent(getParent());
label->setForegroundColor (parent_widget->getForegroundColor());
label->setBackgroundColor (parent_widget->getBackgroundColor());
input_field.setLabelAssociatedWidget(this);
input_field.unsetShadow();
adjustSize();
initCallbacks();
if ( FTerm::isNewFont() )
nf = 1;
}
//----------------------------------------------------------------------
void FComboBox::initCallbacks()
{
input_field.addCallback
(
"mouse-press",
this, &FComboBox::cb_inputFieldSwitch
);
input_field.addCallback
(
"mouse-move",
this, &FComboBox::cb_inputFieldHandOver
);
list_window.list.addCallback
(
"row-changed",
this, &FComboBox::cb_setInputField
);
for (const auto& signal : {"row-selected", "clicked"})
{
list_window.list.addCallback
(
signal,
this, &FComboBox::cb_closeComboBox
);
}
}
//----------------------------------------------------------------------
void FComboBox::draw()
{
const auto& wc = getColorTheme();
const FColorPair button_color = [this, &wc] ()
{
if ( list_window.isEmpty() )
return FColorPair { wc->scrollbar_button_inactive_fg
, wc->scrollbar_button_inactive_bg };
else
return FColorPair { wc->scrollbar_button_fg
, wc->scrollbar_button_bg };
}();
print() << FPoint{int(getWidth()) - nf, 1}
<< button_color;
if ( FTerm::isNewFont() )
print() << NF_button_arrow_down;
else
print() << UniChar::BlackDownPointingTriangle; // ▼
if ( getFlags().shadow )
drawShadow(this);
}
//----------------------------------------------------------------------
void FComboBox::onePosUp()
{
std::size_t index = list_window.list.currentItem();
if ( index > 1 )
index--;
else
return;
list_window.list.setCurrentItem(index);
input_field = list_window.list.getItem(index).getText();
input_field.redraw();
processChanged();
}
//----------------------------------------------------------------------
void FComboBox::onePosDown()
{
std::size_t index = list_window.list.currentItem();
if ( index < getCount() )
index++;
else
return;
list_window.list.setCurrentItem(index);
input_field = list_window.list.getItem(index).getText();
input_field.redraw();
processChanged();
}
//----------------------------------------------------------------------
void FComboBox::passEventToListWindow (const FMouseEvent& ev)
{
// Mouse event handover to list window
const auto& t = ev.getTermPos();
const auto& p = list_window.list.termToWidgetPos(t);
const auto b = ev.getButton();
const auto& _ev = \
std::make_shared<FMouseEvent>(Event::MouseMove, p, t, b);
setClickedWidget(&list_window.list);
list_window.list.setFocus();
list_window.list.onMouseMove(_ev.get());
}
//----------------------------------------------------------------------
void FComboBox::processClick() const
{
emitCallback("clicked");
}
//----------------------------------------------------------------------
void FComboBox::processChanged() const
{
emitCallback("row-changed");
}
//----------------------------------------------------------------------
void FComboBox::cb_setInputField()
{
auto& list = list_window.list;
const std::size_t index = list.currentItem();
input_field = list.getItem(index).getText();
input_field.redraw();
processChanged();
}
//----------------------------------------------------------------------
void FComboBox::cb_closeComboBox()
{
hideDropDown();
processClick();
}
//----------------------------------------------------------------------
void FComboBox::cb_inputFieldSwitch()
{
auto& mouse = FMouseControl::getInstance();
if ( ! mouse.isLeftButtonPressed() )
return;
if ( list_window.isShown() )
{
hideDropDown();
}
else if ( ! is_editable )
{
setWidgetFocus(this);
showDropDown();
}
}
//----------------------------------------------------------------------
void FComboBox::cb_inputFieldHandOver()
{
auto& mouse = FMouseControl::getInstance();
if ( list_window.isHidden() )
return;
const auto& t = mouse.getPos();
const auto& p = list_window.list.termToWidgetPos(t);
const auto b = ( mouse.isLeftButtonPressed() ) ? MouseButton::Left : MouseButton::None;
const auto& _ev = \
std::make_shared<FMouseEvent>(Event::MouseMove, p, t, b);
setClickedWidget(&list_window.list);
list_window.list.setFocus();
list_window.list.onMouseMove(_ev.get());
}
// non-member functions
//----------------------------------------------------------------------
void closeOpenComboBox()
{
// Close open comboboxes
auto openmenu = FWidget::getOpenMenu();
if ( ! openmenu )
return;
if ( openmenu->isInstanceOf("FDropDownListBox") )
{
auto drop_down = static_cast<FDropDownListBox*>(openmenu);
drop_down->hide();
}
}
//----------------------------------------------------------------------
bool closeComboBox ( FDropDownListBox* list_window
, const FPoint& mouse_position )
{
// Close the drop down list box
if ( ! list_window )
return false;
if ( list_window->containsWidget(mouse_position) )
return false;
list_window->hide();
return true;
}
} // namespace finalcut