/***********************************************************************
* fscrollbar.cpp - Widget FScrollbar *
* *
* This file is part of the FINAL CUT widget toolkit *
* *
* Copyright 2012-2020 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/fevent.h"
#include "final/fscrollbar.h"
#include "final/fsize.h"
#include "final/fwidgetcolors.h"
namespace finalcut
{
//----------------------------------------------------------------------
// class FScrollbar
//----------------------------------------------------------------------
// constructors and destructor
//----------------------------------------------------------------------
FScrollbar::FScrollbar(FWidget* parent)
: FWidget{parent}
{
// The default scrollbar orientation is vertical
setGeometry(FPoint{1, 1}, FSize{1, length}, false);
init();
}
//----------------------------------------------------------------------
FScrollbar::FScrollbar(fc::orientation o, FWidget* parent)
: FWidget{parent}
{
setOrientation (o);
init();
}
//----------------------------------------------------------------------
FScrollbar::~FScrollbar() // destructor
{
delOwnTimers();
}
// public methods of FScrollbar
//----------------------------------------------------------------------
void FScrollbar::setMinimum (int minimum)
{
min = minimum;
calculateSliderValues();
}
//----------------------------------------------------------------------
void FScrollbar::setMaximum (int maximum)
{
max = maximum;
calculateSliderValues();
}
//----------------------------------------------------------------------
void FScrollbar::setRange (int minimum, int maximum)
{
min = minimum;
max = maximum;
calculateSliderValues();
}
//----------------------------------------------------------------------
void FScrollbar::setValue (int value)
{
if ( value < min )
val = min;
else if ( value > max )
val = max;
else
val = value;
calculateSliderValues();
}
//----------------------------------------------------------------------
void FScrollbar::setSteps (double st)
{
if ( st <= 0.0 )
steps = 1.0;
else
steps = st;
if ( pagesize == 0 )
pagesize = int(double(max)/steps);
}
//----------------------------------------------------------------------
void FScrollbar::setPageSize (int document_size, int page_size)
{
if ( page_size == 0 )
{
pagesize = document_size;
steps = 1.0;
}
else
{
pagesize = page_size;
if ( document_size <= 0 || page_size <= 0 )
steps = 1.0;
else
steps = double(double(document_size) / double(page_size));
}
}
//----------------------------------------------------------------------
void FScrollbar::setOrientation (fc::orientation o)
{
length = ( o == fc::vertical ) ? getHeight() : getWidth();
if ( o == fc::vertical && bar_orientation == fc::horizontal )
{
setWidth(1);
setHeight(length);
}
else if ( o == fc::horizontal && bar_orientation == fc::vertical )
{
setWidth(length);
setHeight(1);
}
calculateSliderValues();
bar_orientation = o;
}
//----------------------------------------------------------------------
void FScrollbar::setSize (const FSize& size, bool adjust)
{
// Set the scrollbar size
FWidget::setSize (size, adjust);
changeOnResize();
}
//----------------------------------------------------------------------
void FScrollbar::setGeometry ( const FPoint& pos, const FSize& size
, bool adjust )
{
// Set the scrollbar geometry
FWidget::setGeometry (pos, size, adjust);
changeOnResize();
}
//----------------------------------------------------------------------
void FScrollbar::resize()
{
FWidget::resize();
setOrientation (bar_orientation);
setValue (val);
calculateSliderValues();
}
//----------------------------------------------------------------------
void FScrollbar::redraw()
{
if ( isShown() )
draw();
}
//----------------------------------------------------------------------
void FScrollbar::calculateSliderValues()
{
if ( FTerm::isNewFont() && bar_orientation == fc::horizontal )
bar_length = ( length > 2 ) ? length - 4 : 1;
else
bar_length = ( length > 2 ) ? length - 2 : 1;
slider_length = std::size_t(double(bar_length) / steps);
if ( slider_length < 1 )
slider_length = 1;
else if ( slider_length > bar_length )
slider_length = bar_length;
if ( val == min )
{
slider_pos = 0;
return;
}
if ( val == max )
{
slider_pos = int(bar_length - slider_length);
return;
}
const std::size_t v = ( min < 0 ) ? std::size_t(val - min) : std::size_t(val);
if ( slider_length >= bar_length )
slider_pos = 0;
else
slider_pos = int( round ( double((bar_length - slider_length) * v)
/ double(max - min) ) );
if ( slider_pos > int(bar_length - slider_length) )
slider_pos = int(bar_length - slider_length);
}
//----------------------------------------------------------------------
void FScrollbar::drawBar()
{
if ( ! isShown() )
return;
if ( slider_pos == current_slider_pos || length < 3 )
return;
if ( bar_orientation == fc::vertical )
drawVerticalBar();
else // horizontal
drawHorizontalBar();
current_slider_pos = slider_pos;
}
//----------------------------------------------------------------------
void FScrollbar::onMouseDown (FMouseEvent* ev)
{
if ( ev->getButton() != fc::LeftButton
&& ev->getButton() != fc::MiddleButton )
return;
if ( min == max )
return;
const int mouse_x = ev->getX();
const int mouse_y = ev->getY();
if ( ev->getButton() == fc::MiddleButton )
{
jumpToClickPos (mouse_x, mouse_y);
return;
}
// Process left mouse button
scroll_type = getClickedScrollType(mouse_x, mouse_y);
if ( scroll_type == FScrollbar::noScroll )
{
slider_click_pos = getSliderClickPos (mouse_x, mouse_y);
if ( slider_click_pos > 0 )
scroll_type = FScrollbar::scrollJump;
}
if ( scroll_type == FScrollbar::scrollPageBackward
|| scroll_type == FScrollbar::scrollPageForward )
{
if ( bar_orientation == fc::vertical )
slider_click_stop_pos = mouse_y - 2;
else
{
if ( FTerm::isNewFont() )
slider_click_stop_pos = mouse_x - 3;
else
slider_click_stop_pos = mouse_x - 2;
}
}
else
slider_click_stop_pos = -1;
if ( scroll_type >= FScrollbar::scrollStepBackward
&& scroll_type <= FScrollbar::scrollPageForward )
{
processScroll();
threshold_reached = false;
addTimer(threshold_time);
}
}
//----------------------------------------------------------------------
void FScrollbar::onMouseUp (FMouseEvent* ev)
{
if ( ev->getButton() != fc::LeftButton
&& ev->getButton() != fc::MiddleButton )
return;
slider_click_pos = -1;
if ( scroll_type != FScrollbar::noScroll )
{
delOwnTimers();
scroll_type = FScrollbar::noScroll;
}
}
//----------------------------------------------------------------------
void FScrollbar::onMouseMove (FMouseEvent* ev)
{
if ( ev->getButton() != fc::LeftButton
&& ev->getButton() != fc::MiddleButton )
return;
const int mouse_x = ev->getX();
const int mouse_y = ev->getY();
if ( ev->getButton() == fc::MiddleButton )
{
jumpToClickPos (mouse_x, mouse_y);
return;
}
// Process left mouse button
const int new_scroll_type = getClickedScrollType(mouse_x, mouse_y);
if ( scroll_type == FScrollbar::scrollJump )
{
int new_val{};
if ( bar_orientation == fc::vertical )
{
const int dy = mouse_y - slider_click_pos;
slider_click_pos = mouse_y;
new_val = int( round ( double((max - min) * (slider_pos + dy))
/ double(bar_length - slider_length) ) );
}
else // horizontal
{
const int dx = mouse_x - slider_click_pos;
slider_click_pos = mouse_x;
new_val = int( round ( double((max - min) * (slider_pos + dx))
/ double(bar_length - slider_length) ) );
}
if ( new_val != val )
{
setValue(new_val);
drawBar();
updateTerminal();
processScroll();
}
}
if ( mouse_x < 1 || mouse_x > int(getWidth())
|| mouse_y < 1 || mouse_y > int(getHeight()) )
{
delOwnTimers();
}
else if ( scroll_type != FScrollbar::scrollJump )
{
addTimer(repeat_time);
}
if ( scroll_type != new_scroll_type )
{
delOwnTimers();
}
}
//----------------------------------------------------------------------
void FScrollbar::onWheel (FWheelEvent* ev)
{
const int wheel = ev->getWheel();
if ( scroll_type != FScrollbar::noScroll )
{
delOwnTimers();
scroll_type = FScrollbar::noScroll;
}
if ( wheel == fc::WheelUp )
scroll_type = FScrollbar::scrollWheelUp;
else if ( wheel == fc::WheelDown )
scroll_type = FScrollbar::scrollWheelDown;
processScroll();
}
//----------------------------------------------------------------------
void FScrollbar::onTimer (FTimerEvent*)
{
if ( scroll_type == FScrollbar::noScroll )
return;
if ( ! threshold_reached )
{
threshold_reached = true;
delOwnTimers();
addTimer(repeat_time);
}
// Timer stop condition
if ( ( scroll_type == FScrollbar::scrollPageBackward
&& slider_pos == slider_click_stop_pos )
|| ( scroll_type == FScrollbar::scrollPageForward
&& slider_pos == slider_click_stop_pos ) )
{
const int max_slider_pos = int(bar_length - slider_length);
if ( scroll_type == FScrollbar::scrollPageBackward
&& slider_pos == 0 )
{
jumpToClickPos(0); // Scroll to the start
processScroll();
}
else if ( scroll_type == FScrollbar::scrollPageForward
&& slider_pos == max_slider_pos )
{
jumpToClickPos (max_slider_pos); // Scroll to the end
processScroll();
}
delOwnTimers();
return;
}
processScroll();
}
// private methods of FScrollbar
//----------------------------------------------------------------------
void FScrollbar::init()
{
unsetFocusable();
ignorePadding();
setGeometry(FPoint{1, 1}, FSize{getWidth(), getHeight()});
}
//----------------------------------------------------------------------
void FScrollbar::draw()
{
if ( length < 2 )
return;
if ( isShown() )
drawButtons();
current_slider_pos = -1;
max_color = FTerm::getMaxColor();
drawBar();
}
//----------------------------------------------------------------------
void FScrollbar::drawVerticalBar()
{
const auto& wc = getColorTheme();
setColor (wc->scrollbar_fg, wc->scrollbar_bg);
for (int z{1}; z <= slider_pos; z++)
{
print() << FPoint{1, 1 + z};
drawVerticalBackgroundLine();
}
setColor (wc->scrollbar_bg, wc->scrollbar_fg);
if ( FTerm::isMonochron() )
setReverse(false);
for (int z{1}; z <= int(slider_length); z++) // Draw slider
{
print() << FPoint{1, 1 + slider_pos + z};
if ( FTerm::isNewFont() )
print (' ');
print (' ');
}
if ( FTerm::isMonochron() )
setReverse(true);
setColor (wc->scrollbar_fg, wc->scrollbar_bg);
for (int z = slider_pos + int(slider_length) + 1; z <= int(bar_length); z++)
{
print() << FPoint{1, 1 + z};
drawVerticalBackgroundLine();
}
if ( FTerm::isMonochron() )
setReverse(false);
}
//----------------------------------------------------------------------
inline void FScrollbar::drawVerticalBackgroundLine()
{
if ( FTerm::isNewFont() )
{
if ( FTerm::isMonochron() || max_color < 16 )
print (fc::MediumShade); // ▒
else
print (fc::NF_border_line_left); // ⎸
}
if ( FTerm::isMonochron() || max_color < 16 )
print (fc::MediumShade); // ▒
else if ( FTerm::isNewFont() )
print (fc::NF_rev_border_line_right); //⎹
else
print (' ');
}
//----------------------------------------------------------------------
void FScrollbar::drawHorizontalBar()
{
const auto& wc = getColorTheme();
setColor (wc->scrollbar_fg, wc->scrollbar_bg);
if ( FTerm::isNewFont() )
print() << FPoint{3, 1};
else
print() << FPoint{2, 1};
for (int z{0}; z < slider_pos; z++)
drawHorizontalBackgroundColumn();
setColor (wc->scrollbar_bg, wc->scrollbar_fg);
if ( FTerm::isMonochron() )
setReverse(false);
for (int z{0}; z < int(slider_length); z++) // Draw slider
print (' ');
if ( FTerm::isMonochron() )
setReverse(true);
setColor (wc->scrollbar_fg, wc->scrollbar_bg);
int z = slider_pos + int(slider_length) + 1;
for (; z <= int(bar_length); z++)
drawHorizontalBackgroundColumn();
if ( FTerm::isMonochron() )
setReverse(false);
}
//----------------------------------------------------------------------
inline void FScrollbar::drawHorizontalBackgroundColumn()
{
if ( FTerm::isNewFont() && max_color > 8 )
print (fc::NF_border_line_up_and_down); // ニ
else if ( FTerm::isMonochron() || max_color < 16 )
print (fc::MediumShade); // ▒
else
print (' ');
}
//----------------------------------------------------------------------
void FScrollbar::drawButtons()
{
const auto& wc = getColorTheme();
setColor (wc->scrollbar_button_fg, wc->scrollbar_button_bg);
if ( FTerm::isNewFont() )
{
print() << FPoint{1, 1};
if ( bar_orientation == fc::vertical )
{
print() << NF_button_arrow_up
<< FPoint{1, int(length)}
<< NF_button_arrow_down;
}
else // horizontal
{
print() << NF_button_arrow_left
<< FPoint{int(length) - 1, 1}
<< NF_button_arrow_right;
}
}
else
{
print() << FPoint{1, 1};
if ( FTerm::isMonochron() )
setReverse(true);
if ( bar_orientation == fc::vertical )
{
print() << fc::BlackUpPointingTriangle // ▲
<< FPoint{1, int(length)}
<< fc::BlackDownPointingTriangle; // ▼
}
else // horizontal
{
print() << fc::BlackLeftPointingPointer // ◄
<< FPoint{int(length), 1}
<< fc::BlackRightPointingPointer; // ►
}
if ( FTerm::isMonochron() )
setReverse(false);
}
}
//----------------------------------------------------------------------
FScrollbar::sType FScrollbar::getClickedScrollType (int x, int y) const
{
if ( bar_orientation == fc::vertical )
{
return getVerticalClickedScrollType(y);
}
else // horizontal
{
return getHorizontalClickedScrollType(x);
}
}
//----------------------------------------------------------------------
FScrollbar::sType FScrollbar::getVerticalClickedScrollType (int y) const
{
if ( y == 1 )
{
return FScrollbar::scrollStepBackward; // decrement button
}
else if ( y > 1 && y <= slider_pos + 1 )
{
return FScrollbar::scrollPageBackward; // before slider
}
else if ( y > slider_pos + int(slider_length) + 1 && y < int(getHeight()) )
{
return FScrollbar::scrollPageForward; // after slider
}
else if ( y == int(getHeight()) )
{
return FScrollbar::scrollStepForward; // increment button
}
return FScrollbar::noScroll;
}
//----------------------------------------------------------------------
FScrollbar::sType FScrollbar::getHorizontalClickedScrollType (int x) const
{
if ( FTerm::isNewFont() )
{
if ( x == 1 || x == 2 )
{
return FScrollbar::scrollStepBackward; // decrement button
}
else if ( x > 2 && x <= slider_pos + 2 )
{
return FScrollbar::scrollPageBackward; // before slider
}
else if ( x > slider_pos + int(slider_length) + 2 && x < int(getWidth()) - 1 )
{
return FScrollbar::scrollPageForward; // after slider
}
else if ( x == int(getWidth()) - 1 || x == int(getWidth()) )
{
return FScrollbar::scrollStepForward; // increment button
}
return FScrollbar::noScroll;
}
else
{
if ( x == 1 )
{
return FScrollbar::scrollStepBackward; // decrement button
}
else if ( x > 1 && x <= slider_pos + 1 )
{
return FScrollbar::scrollPageBackward; // before slider
}
else if ( x > slider_pos + int(slider_length) + 1 && x < int(getWidth()) )
{
return FScrollbar::scrollPageForward; // after slider
}
else if ( x == int(getWidth()) )
{
return FScrollbar::scrollStepForward; // increment button
}
return FScrollbar::noScroll;
}
}
//----------------------------------------------------------------------
int FScrollbar::getSliderClickPos (int mouse_x, int mouse_y) const
{
// Get the clicked position on the slider
if ( bar_orientation == fc::vertical )
{
if ( mouse_y > slider_pos + 1
&& mouse_y <= slider_pos + int(slider_length) + 1 )
return mouse_y; // on slider
}
else // horizontal bar orientation
{
if ( FTerm::isNewFont() )
{
if ( mouse_x > slider_pos + 2
&& mouse_x <= slider_pos + int(slider_length) + 2 )
return mouse_x; // on slider
}
else
{
if ( mouse_x > slider_pos + 1
&& mouse_x <= slider_pos + int(slider_length) + 1 )
return mouse_x; // on slider
}
}
return -1;
}
//----------------------------------------------------------------------
void FScrollbar::jumpToClickPos (int x, int y)
{
int new_val{};
if ( bar_orientation == fc::vertical )
{
if ( y > 1 && y < int(getHeight()) )
{
new_val = int( round ( double(max - min) * (y - 2.0 - double(slider_length/2))
/ double(bar_length - slider_length) ) );
}
else
return;
}
else // horizontal
{
const int nf = FTerm::isNewFont() ? 1 : 0;
if ( x > 1 + nf && x < int(getWidth()) - nf )
{
new_val = int( round ( double(max - min) * (x - 2.0 - nf - double(slider_length/2))
/ double(bar_length - slider_length) ) );
}
else
return;
}
if ( new_val != val )
{
setValue(new_val);
drawBar();
updateTerminal();
scroll_type = FScrollbar::scrollJump;
processScroll();
}
}
//----------------------------------------------------------------------
void FScrollbar::jumpToClickPos (int pos)
{
if ( bar_orientation == fc::vertical )
jumpToClickPos (0, pos + 2);
else
{
if ( FTerm::isNewFont() )
jumpToClickPos (pos + 3, 0);
else
jumpToClickPos (pos + 2, 0);
}
}
//----------------------------------------------------------------------
void FScrollbar::avoidScrollOvershoot()
{
// Avoid overshoot
if ( ( scroll_type == FScrollbar::scrollPageBackward
&& slider_pos < slider_click_stop_pos )
|| ( scroll_type == FScrollbar::scrollPageForward
&& slider_pos > slider_click_stop_pos ) )
{
jumpToClickPos (slider_click_stop_pos);
delOwnTimers();
}
}
//----------------------------------------------------------------------
void FScrollbar::processScroll()
{
emitCallback("change-value");
avoidScrollOvershoot();
}
//----------------------------------------------------------------------
void FScrollbar::changeOnResize()
{
const FSize& size = getSize();
const std::size_t w = size.getWidth();
const std::size_t h = size.getHeight();
length = ( bar_orientation == fc::vertical ) ? h : w;
if ( bar_orientation == fc::vertical )
{
setWidth(FTerm::isNewFont() ? 2 : 1);
setHeight(length);
}
else // horizontal
{
setWidth(length);
setHeight(1);
}
calculateSliderValues();
}
} // namespace finalcut