/*********************************************************************** * 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 * * . * ***********************************************************************/ #include #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(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(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(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(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