2865 lines
72 KiB
C++
2865 lines
72 KiB
C++
/***********************************************************************
|
||
* flistview.cpp - Widget FListView and FListViewItem *
|
||
* *
|
||
* This file is part of the FINAL CUT widget toolkit *
|
||
* *
|
||
* Copyright 2017-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/>. *
|
||
***********************************************************************/
|
||
|
||
#if defined(__CYGWIN__)
|
||
#include <strings.h> // need for strcasecmp
|
||
#endif
|
||
|
||
#include <limits>
|
||
#include <memory>
|
||
#include <unordered_map>
|
||
#include <utility>
|
||
#include <vector>
|
||
|
||
#include "final/emptyfstring.h"
|
||
#include "final/fapplication.h"
|
||
#include "final/fcolorpair.h"
|
||
#include "final/fevent.h"
|
||
#include "final/flistview.h"
|
||
#include "final/fstatusbar.h"
|
||
#include "final/fstring.h"
|
||
#include "final/ftermbuffer.h"
|
||
#include "final/fwidgetcolors.h"
|
||
|
||
namespace finalcut
|
||
{
|
||
|
||
// Function prototypes
|
||
uInt64 firstNumberFromString (const FString&);
|
||
bool sortAscendingByName (const FObject*, const FObject*);
|
||
bool sortDescendingByName (const FObject*, const FObject*);
|
||
bool sortAscendingByNumber (const FObject*, const FObject*);
|
||
bool sortDescendingByNumber (const FObject*, const FObject*);
|
||
|
||
// non-member functions
|
||
//----------------------------------------------------------------------
|
||
uInt64 firstNumberFromString (const FString& str)
|
||
{
|
||
auto iter = str.begin();
|
||
|
||
while ( iter != str.end() )
|
||
{
|
||
if ( wchar_t(*iter) >= L'0' && wchar_t(*iter) <= L'9' )
|
||
{
|
||
if ( iter != str.begin() && wchar_t(*(iter - 1)) == L'-' )
|
||
--iter;
|
||
|
||
break;
|
||
}
|
||
|
||
++iter;
|
||
}
|
||
|
||
auto first_pos = iter;
|
||
|
||
if ( first_pos == str.end() )
|
||
return 0;
|
||
|
||
while ( iter != str.end() )
|
||
{
|
||
if ( wchar_t(*iter) < L'0' || wchar_t(*iter) > L'9' )
|
||
break;
|
||
|
||
++iter;
|
||
}
|
||
|
||
auto last_pos = iter;
|
||
|
||
if ( last_pos == str.end() )
|
||
return 0;
|
||
|
||
uInt64 number;
|
||
const auto pos = std::size_t(std::distance(str.begin(), first_pos)) + 1;
|
||
const auto length = std::size_t(std::distance(first_pos, last_pos));
|
||
const auto& num_str = str.mid(pos, length);
|
||
|
||
try
|
||
{
|
||
number = uInt64(num_str.toLong());
|
||
}
|
||
catch (const std::invalid_argument&)
|
||
{
|
||
return 0;
|
||
}
|
||
catch (const std::underflow_error&)
|
||
{
|
||
return std::numeric_limits<uInt64>::min();
|
||
}
|
||
catch (const std::overflow_error&)
|
||
{
|
||
return std::numeric_limits<uInt64>::max();
|
||
}
|
||
|
||
return number;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool sortAscendingByName (const FObject* lhs, const FObject* rhs)
|
||
{
|
||
const auto& l_item = static_cast<const FListViewItem*>(lhs);
|
||
const auto& r_item = static_cast<const FListViewItem*>(rhs);
|
||
const int column = l_item->getSortColumn();
|
||
const auto& l_string = l_item->getText(column);
|
||
const auto& r_string = r_item->getText(column);
|
||
|
||
// lhs < rhs
|
||
return strcasecmp(l_string.c_str(), r_string.c_str()) < 0;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool sortDescendingByName (const FObject* lhs, const FObject* rhs)
|
||
{
|
||
const auto& l_item = static_cast<const FListViewItem*>(lhs);
|
||
const auto& r_item = static_cast<const FListViewItem*>(rhs);
|
||
const int column = l_item->getSortColumn();
|
||
const auto& l_string = l_item->getText(column);
|
||
const auto& r_string = r_item->getText(column);
|
||
|
||
// lhs > rhs
|
||
return strcasecmp(l_string.c_str(), r_string.c_str()) > 0;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool sortAscendingByNumber (const FObject* lhs, const FObject* rhs)
|
||
{
|
||
const auto& l_item = static_cast<const FListViewItem*>(lhs);
|
||
const auto& r_item = static_cast<const FListViewItem*>(rhs);
|
||
const int column = l_item->getSortColumn();
|
||
const auto& l_number = firstNumberFromString(l_item->getText(column));
|
||
const auto& r_number = firstNumberFromString(r_item->getText(column));
|
||
|
||
// lhs < rhs
|
||
return l_number < r_number;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool sortDescendingByNumber (const FObject* lhs, const FObject* rhs)
|
||
{
|
||
const auto& l_item = static_cast<const FListViewItem*>(lhs);
|
||
const auto& r_item = static_cast<const FListViewItem*>(rhs);
|
||
const int column = l_item->getSortColumn();
|
||
const auto& l_number = firstNumberFromString(l_item->getText(column));
|
||
const auto& r_number = firstNumberFromString(r_item->getText(column));
|
||
|
||
// lhs > rhs
|
||
return l_number > r_number;
|
||
}
|
||
|
||
|
||
//----------------------------------------------------------------------
|
||
// class FListViewItem
|
||
//----------------------------------------------------------------------
|
||
|
||
// constructor and destructor
|
||
//----------------------------------------------------------------------
|
||
FListViewItem::FListViewItem (const FListViewItem& item)
|
||
: FObject{item.getParent()}
|
||
, column_list{item.column_list}
|
||
, data_pointer{item.data_pointer}
|
||
{
|
||
auto parent = getParent();
|
||
|
||
if ( ! parent )
|
||
return;
|
||
|
||
if ( parent->isInstanceOf("FListView") )
|
||
{
|
||
static_cast<FListView*>(parent)->insert (this);
|
||
}
|
||
else if ( parent->isInstanceOf("FListViewItem") )
|
||
{
|
||
static_cast<FListViewItem*>(parent)->insert (this);
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewItem::FListViewItem (iterator parent_iter)
|
||
: FObject{(*parent_iter)->getParent()}
|
||
{
|
||
insert (this, parent_iter);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewItem::~FListViewItem() // destructor
|
||
{
|
||
// Remove from parent itemlist
|
||
|
||
auto parent = getParent();
|
||
|
||
if ( ! parent )
|
||
return;
|
||
|
||
if ( parent->isInstanceOf("FListView") )
|
||
{
|
||
static_cast<FListView*>(parent)->remove (this);
|
||
}
|
||
else if ( parent->isInstanceOf("FListViewItem") )
|
||
{
|
||
static_cast<FListViewItem*>(parent)->remove (this);
|
||
}
|
||
}
|
||
|
||
|
||
// public methods of FListViewItem
|
||
//----------------------------------------------------------------------
|
||
int FListViewItem::getSortColumn() const
|
||
{
|
||
if ( ! *root )
|
||
return -1;
|
||
|
||
const auto& root_obj = static_cast<FListView*>(*root);
|
||
return root_obj->getSortColumn();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FString FListViewItem::getText (int column) const
|
||
{
|
||
if ( column < 1
|
||
|| column_list.empty()
|
||
|| column > int(column_list.size()) )
|
||
return fc::emptyFString::get();
|
||
|
||
// Convert column position to address offset (index)
|
||
const auto index = std::size_t(column - 1);
|
||
return column_list[index];
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
uInt FListViewItem::getDepth() const
|
||
{
|
||
const auto& parent = getParent();
|
||
|
||
if ( parent && parent->isInstanceOf("FListViewItem") )
|
||
{
|
||
const auto& parent_item = static_cast<FListViewItem*>(parent);
|
||
return parent_item->getDepth() + 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::setText (int column, const FString& text)
|
||
{
|
||
if ( column < 1
|
||
|| column_list.empty()
|
||
|| column > int(column_list.size()) )
|
||
return;
|
||
|
||
// Convert column position to address offset (index)
|
||
const auto index = std::size_t(column - 1);
|
||
auto parent = getParent();
|
||
|
||
if ( parent && parent->isInstanceOf("FListView") )
|
||
{
|
||
auto listview = static_cast<FListView*>(parent);
|
||
|
||
if ( ! listview->header[index].fixed_width )
|
||
{
|
||
const auto column_width = int(getColumnWidth(text));
|
||
|
||
if ( column_width > listview->header[index].width )
|
||
listview->header[index].width = column_width;
|
||
}
|
||
}
|
||
|
||
column_list[index] = text;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListViewItem::insert (FListViewItem* child)
|
||
{
|
||
// Add a FListViewItem as child element
|
||
if ( ! child )
|
||
return FListView::getNullIterator();
|
||
|
||
return appendItem(child);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListViewItem::insert ( FListViewItem* child
|
||
, iterator parent_iter ) const
|
||
{
|
||
if ( parent_iter == FListView::getNullIterator() )
|
||
return FListView::getNullIterator();
|
||
|
||
if ( *parent_iter )
|
||
{
|
||
if ( (*parent_iter)->isInstanceOf("FListView") )
|
||
{
|
||
// Add FListViewItem to a FListView parent
|
||
auto parent = static_cast<FListView*>(*parent_iter);
|
||
return parent->insert (child);
|
||
}
|
||
else if ( (*parent_iter)->isInstanceOf("FListViewItem") )
|
||
{
|
||
// Add FListViewItem to a FListViewItem parent
|
||
auto parent = static_cast<FListViewItem*>(*parent_iter);
|
||
return parent->insert (child);
|
||
}
|
||
}
|
||
|
||
return FListView::getNullIterator();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::remove (FListViewItem* item) const
|
||
{
|
||
if ( item == nullptr || item == *FListView::getNullIterator() )
|
||
return;
|
||
|
||
auto parent = item->getParent();
|
||
|
||
// Search for a FListView parent in my object tree
|
||
while ( parent && ! parent->isInstanceOf("FListView") )
|
||
{
|
||
parent = parent->getParent();
|
||
}
|
||
|
||
if ( parent == nullptr )
|
||
return;
|
||
|
||
if ( parent->isInstanceOf("FListView") )
|
||
{
|
||
auto listview = static_cast<FListView*>(parent);
|
||
listview->remove(item);
|
||
}
|
||
else
|
||
{
|
||
parent = item->getParent();
|
||
parent->delChild(item);
|
||
auto parent_item = static_cast<FListViewItem*>(parent);
|
||
parent_item->visible_lines--;
|
||
|
||
if ( ! parent_item->hasChildren() )
|
||
{
|
||
parent_item->expandable = false;
|
||
parent_item->is_expand = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::expand()
|
||
{
|
||
if ( isExpand() || ! hasChildren() )
|
||
return;
|
||
|
||
resetVisibleLineCounter();
|
||
is_expand = true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::collapse()
|
||
{
|
||
if ( ! isExpand() )
|
||
return;
|
||
|
||
resetVisibleLineCounter();
|
||
is_expand = false;
|
||
}
|
||
|
||
// private methods of FListView
|
||
//----------------------------------------------------------------------
|
||
template <typename Compare>
|
||
void FListViewItem::sort (Compare cmp)
|
||
{
|
||
if ( ! isExpandable() )
|
||
return;
|
||
|
||
// Sort the top level
|
||
FObject::FObjectList& children = getChildren();
|
||
|
||
if ( ! children.empty() )
|
||
children.sort(cmp);
|
||
|
||
// Sort the sublevels
|
||
for (auto&& item : children)
|
||
static_cast<FListViewItem*>(item)->sort(cmp);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListViewItem::appendItem (FListViewItem* child)
|
||
{
|
||
expandable = true;
|
||
resetVisibleLineCounter();
|
||
child->root = root;
|
||
addChild (child);
|
||
// Return iterator to child/last element
|
||
return --FObject::end();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::replaceControlCodes()
|
||
{
|
||
// Replace the control codes characters
|
||
auto iter = column_list.begin();
|
||
|
||
while ( iter != column_list.end() )
|
||
{
|
||
*iter = iter->replaceControlCodes();
|
||
++iter;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
std::size_t FListViewItem::getVisibleLines()
|
||
{
|
||
if ( visible_lines > 1 )
|
||
return visible_lines;
|
||
|
||
visible_lines = 1;
|
||
|
||
if ( ! isExpand() || ! hasChildren() )
|
||
return visible_lines;
|
||
|
||
auto iter = FObject::begin();
|
||
|
||
while ( iter != FObject::end() )
|
||
{
|
||
const auto& child = static_cast<FListViewItem*>(*iter);
|
||
visible_lines += child->getVisibleLines();
|
||
++iter;
|
||
}
|
||
|
||
return visible_lines;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::setCheckable (bool enable)
|
||
{
|
||
checkable = enable;
|
||
|
||
if ( *root )
|
||
{
|
||
auto root_obj = static_cast<FListView*>(*root);
|
||
|
||
if ( ! root_obj->hasCheckableItems() && isCheckable() )
|
||
root_obj->has_checkable_items = true;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewItem::resetVisibleLineCounter()
|
||
{
|
||
visible_lines = 0;
|
||
auto parent = getParent();
|
||
|
||
if ( parent && parent->isInstanceOf("FListViewItem") )
|
||
{
|
||
auto parent_item = static_cast<FListViewItem*>(parent);
|
||
return parent_item->resetVisibleLineCounter();
|
||
}
|
||
}
|
||
|
||
|
||
//----------------------------------------------------------------------
|
||
// class FListViewIterator
|
||
//----------------------------------------------------------------------
|
||
|
||
// constructor and destructor
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator::FListViewIterator (Iterator iter)
|
||
: node{iter}
|
||
{ }
|
||
|
||
// FListViewIterator operators
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator& FListViewIterator::operator ++ () // prefix
|
||
{
|
||
nextElement(node);
|
||
return *this;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator FListViewIterator::operator ++ (int) // postfix
|
||
{
|
||
FListViewIterator tmp = *this;
|
||
++(*this);
|
||
return tmp;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator& FListViewIterator::operator -- () // prefix
|
||
{
|
||
prevElement(node);
|
||
return *this;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator FListViewIterator::operator -- (int) // postfix
|
||
{
|
||
FListViewIterator tmp = *this;
|
||
--(*this);
|
||
return tmp;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator& FListViewIterator::operator += (int n)
|
||
{
|
||
for (int i = n; i > 0 ; i--)
|
||
nextElement(node);
|
||
|
||
return *this;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListViewIterator& FListViewIterator::operator -= (int n)
|
||
{
|
||
for (int i = n; i > 0 ; i--)
|
||
prevElement(node);
|
||
|
||
return *this;
|
||
}
|
||
|
||
// private methods of FListViewIterator
|
||
//----------------------------------------------------------------------
|
||
void FListViewIterator::nextElement (Iterator& iter)
|
||
{
|
||
const auto& item = static_cast<FListViewItem*>(*iter);
|
||
|
||
if ( item->isExpandable() && item->isExpand() )
|
||
{
|
||
iter_path.push(iter);
|
||
iter = item->begin();
|
||
position++;
|
||
}
|
||
else
|
||
{
|
||
position++;
|
||
bool forward{};
|
||
|
||
do
|
||
{
|
||
forward = false; // Reset forward
|
||
++iter;
|
||
|
||
if ( ! iter_path.empty() )
|
||
{
|
||
const auto& parent_iter = iter_path.top();
|
||
|
||
if ( iter == (*parent_iter)->end() )
|
||
{
|
||
iter = parent_iter;
|
||
iter_path.pop();
|
||
forward = true;
|
||
}
|
||
}
|
||
}
|
||
while ( forward );
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewIterator::prevElement (Iterator& iter)
|
||
{
|
||
auto start_iter = iter;
|
||
|
||
if ( ! iter_path.empty() )
|
||
{
|
||
const auto& parent_iter = iter_path.top();
|
||
|
||
if ( start_iter == (*parent_iter)->begin() ) // First sub-item
|
||
{
|
||
iter = parent_iter;
|
||
position--;
|
||
iter_path.pop();
|
||
return;
|
||
}
|
||
}
|
||
|
||
--iter;
|
||
auto item = static_cast<FListViewItem*>(*iter);
|
||
|
||
if ( iter == start_iter ) // No changes
|
||
return;
|
||
else
|
||
position--;
|
||
|
||
while ( item->isExpandable() && item->isExpand() )
|
||
{
|
||
iter_path.push(iter);
|
||
iter = item->end();
|
||
--iter;
|
||
item = static_cast<FListViewItem*>(*iter);
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListViewIterator::parentElement()
|
||
{
|
||
if ( iter_path.empty() )
|
||
return;
|
||
|
||
const auto& parent_iter = iter_path.top();
|
||
|
||
while ( node != parent_iter )
|
||
prevElement(node);
|
||
}
|
||
|
||
|
||
//----------------------------------------------------------------------
|
||
// class FListView
|
||
//----------------------------------------------------------------------
|
||
|
||
// constructor and destructor
|
||
//----------------------------------------------------------------------
|
||
FListView::FListView (FWidget* parent)
|
||
: FWidget{parent}
|
||
{
|
||
init();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FListView::~FListView() // destructor
|
||
{
|
||
delOwnTimers();
|
||
}
|
||
|
||
// public methods of FListView
|
||
//----------------------------------------------------------------------
|
||
std::size_t FListView::getCount() const
|
||
{
|
||
int n{0};
|
||
|
||
for (auto&& item : itemlist)
|
||
{
|
||
const auto& listitem = static_cast<FListViewItem*>(item);
|
||
n += listitem->getVisibleLines();
|
||
}
|
||
|
||
return std::size_t(n);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
Align FListView::getColumnAlignment (int column) const
|
||
{
|
||
// Get the alignment for a column
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
return Align::Left;
|
||
|
||
// Convert column position to address offset (index)
|
||
const auto index = std::size_t(column - 1);
|
||
return header[index].alignment;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FString FListView::getColumnText (int column) const
|
||
{
|
||
// Get the text of column
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
return fc::emptyFString::get();
|
||
|
||
// Convert column position to address offset (index)
|
||
const auto index = std::size_t(column - 1);
|
||
return header[index].name;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
SortType FListView::getColumnSortType (int column) const
|
||
{
|
||
SortType s_type;
|
||
const auto col = std::size_t(column);
|
||
|
||
try
|
||
{
|
||
s_type = sort_type.at(col);
|
||
}
|
||
catch (const std::out_of_range&)
|
||
{
|
||
s_type = SortType::Unknown;
|
||
}
|
||
|
||
return s_type;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setSize (const FSize& size, bool adjust)
|
||
{
|
||
FWidget::setSize (size, adjust);
|
||
changeOnResize();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setGeometry ( const FPoint& pos, const FSize& size
|
||
, bool adjust)
|
||
{
|
||
FWidget::setGeometry (pos, size, adjust);
|
||
changeOnResize();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setColumnAlignment (int column, Align align)
|
||
{
|
||
// Set the alignment for a column
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
return;
|
||
|
||
// Convert column position to address offset (index)
|
||
const auto index = std::size_t(column - 1);
|
||
header[index].alignment = align;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setColumnText (int column, const FString& label)
|
||
{
|
||
// Set the text for a column
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
return;
|
||
|
||
// Convert column position to address offset (index)
|
||
auto index = std::size_t(column - 1);
|
||
|
||
if ( ! header[index].fixed_width )
|
||
{
|
||
const auto column_width = int(getColumnWidth(label));
|
||
|
||
if ( column_width > header[index].width )
|
||
header[index].width = column_width;
|
||
}
|
||
|
||
header[index].name = label;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setColumnSortType (int column, SortType type)
|
||
{
|
||
// Sets the sort type by which the list is to be sorted
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
return;
|
||
|
||
const std::size_t size = std::size_t(column) + 1;
|
||
|
||
if ( sort_type.empty() || sort_type.size() < size )
|
||
sort_type.resize(size);
|
||
|
||
sort_type[uInt(column)] = type;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setColumnSort (int column, SortOrder order)
|
||
{
|
||
// Sets the column to sort by + the sorting order
|
||
|
||
if ( column < 1 || header.empty() || column > int(header.size()) )
|
||
column = -1;
|
||
|
||
sort_column = column;
|
||
sort_order = order;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
int FListView::addColumn (const FString& label, int width)
|
||
{
|
||
Header new_column{};
|
||
new_column.name = label;
|
||
new_column.width = width;
|
||
|
||
if ( new_column.width == USE_MAX_SIZE )
|
||
new_column.width = int(getColumnWidth(label));
|
||
else
|
||
new_column.fixed_width = true;
|
||
|
||
header.push_back (new_column);
|
||
return int(std::distance(header.begin(), header.end()));
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::hide()
|
||
{
|
||
FWidget::hide();
|
||
hideArea (getSize());
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListView::insert ( FListViewItem* item
|
||
, iterator parent_iter )
|
||
{
|
||
iterator item_iter;
|
||
|
||
if ( parent_iter == getNullIterator() )
|
||
return getNullIterator();
|
||
|
||
beforeInsertion(item); // preprocessing
|
||
|
||
if ( parent_iter == root )
|
||
{
|
||
item_iter = appendItem (item);
|
||
}
|
||
else if ( *parent_iter )
|
||
{
|
||
if ( (*parent_iter)->isInstanceOf("FListView") )
|
||
{
|
||
// Add FListViewItem to a FListView parent
|
||
auto parent = static_cast<FListView*>(*parent_iter);
|
||
item_iter = parent->appendItem (item);
|
||
}
|
||
else if ( (*parent_iter)->isInstanceOf("FListViewItem") )
|
||
{
|
||
// Add FListViewItem to a FListViewItem parent
|
||
auto parent = static_cast<FListViewItem*>(*parent_iter);
|
||
item_iter = parent->appendItem (item);
|
||
}
|
||
else
|
||
item_iter = getNullIterator();
|
||
}
|
||
else
|
||
item_iter = getNullIterator();
|
||
|
||
afterInsertion(); // post-processing
|
||
return item_iter;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::remove (FListViewItem* item)
|
||
{
|
||
if ( item == nullptr || itemlist.empty() )
|
||
return;
|
||
|
||
auto parent = item->getParent();
|
||
const auto& current_item = static_cast<FListViewItem*>(*current_iter);
|
||
const auto& first_item = itemlist.front();
|
||
auto end_iter = static_cast<FListViewIterator>(itemlist.end());
|
||
const auto& last_item = *(--end_iter);
|
||
const bool is_current_line( item == current_item );
|
||
const bool is_first_line( item == first_item );
|
||
const bool is_last_line( item == last_item );
|
||
|
||
if ( is_current_line )
|
||
{
|
||
if ( is_last_line || current_item == itemlist.back() )
|
||
stepBackward();
|
||
else
|
||
{
|
||
collapseSubtree();
|
||
stepForward();
|
||
}
|
||
}
|
||
|
||
if ( is_first_line )
|
||
++first_visible_line;
|
||
|
||
if ( parent )
|
||
{
|
||
if ( this == parent )
|
||
{
|
||
itemlist.remove(item);
|
||
delChild(item);
|
||
current_iter.getPosition()--;
|
||
}
|
||
else
|
||
{
|
||
parent->delChild(item);
|
||
auto parent_item = static_cast<FListViewItem*>(parent);
|
||
parent_item->visible_lines--;
|
||
current_iter.getPosition()--;
|
||
|
||
if ( ! parent_item->hasChildren() )
|
||
{
|
||
parent_item->expandable = false;
|
||
parent_item->is_expand = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
const std::size_t element_count = getCount();
|
||
recalculateVerticalBar (element_count);
|
||
|
||
if ( itemlist.empty() )
|
||
{
|
||
current_iter = getNullIterator();
|
||
first_visible_line = getNullIterator();
|
||
last_visible_line = getNullIterator();
|
||
clearList();
|
||
}
|
||
else
|
||
{
|
||
drawList();
|
||
drawBorder();
|
||
drawHeadlines();
|
||
drawScrollbars();
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::clear()
|
||
{
|
||
itemlist.clear();
|
||
current_iter = getNullIterator();
|
||
first_visible_line = getNullIterator();
|
||
last_visible_line = getNullIterator();
|
||
recalculateVerticalBar (0);
|
||
first_line_position_before = -1;
|
||
xoffset = 0;
|
||
vbar->setMinimum(0);
|
||
vbar->setValue(0);
|
||
vbar->hide();
|
||
clearList();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::sort()
|
||
{
|
||
// Sorts the list view according to the specified setting
|
||
|
||
if ( sort_column < 1 && sort_column > int(header.size()) )
|
||
return;
|
||
|
||
SortType column_sort_type = getColumnSortType(sort_column);
|
||
assert ( column_sort_type == SortType::Name
|
||
|| column_sort_type == SortType::Number
|
||
|| column_sort_type == SortType::UserDefined
|
||
|| column_sort_type == SortType::Unknown );
|
||
|
||
switch ( column_sort_type )
|
||
{
|
||
case SortType::Unknown:
|
||
case SortType::Name:
|
||
if ( sort_order == SortOrder::Ascending )
|
||
{
|
||
sort (sortAscendingByName);
|
||
}
|
||
else if ( sort_order == SortOrder::Descending )
|
||
{
|
||
sort (sortDescendingByName);
|
||
}
|
||
break;
|
||
|
||
case SortType::Number:
|
||
if ( sort_order == SortOrder::Ascending )
|
||
{
|
||
sort (sortAscendingByNumber);
|
||
}
|
||
else if ( sort_order == SortOrder::Descending )
|
||
{
|
||
sort (sortDescendingByNumber);
|
||
}
|
||
break;
|
||
|
||
case SortType::UserDefined:
|
||
if ( sort_order == SortOrder::Ascending && user_defined_ascending )
|
||
{
|
||
sort (user_defined_ascending);
|
||
}
|
||
else if ( sort_order == SortOrder::Descending && user_defined_descending )
|
||
{
|
||
sort (user_defined_descending);
|
||
}
|
||
break;
|
||
}
|
||
|
||
current_iter = itemlist.begin();
|
||
first_visible_line = itemlist.begin();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onKeyPress (FKeyEvent* ev)
|
||
{
|
||
const int position_before = current_iter.getPosition();
|
||
const int xoffset_before = xoffset;
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
clicked_expander_pos.setPoint(-1, -1);
|
||
processKeyAction(ev); // Process the keystrokes
|
||
|
||
if ( position_before != current_iter.getPosition() )
|
||
processChanged();
|
||
|
||
if ( ev->isAccepted() )
|
||
{
|
||
const bool draw_vbar( first_line_position_before
|
||
!= first_visible_line.getPosition() );
|
||
const bool draw_hbar(xoffset_before != xoffset);
|
||
updateDrawing (draw_vbar, draw_hbar);
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onMouseDown (FMouseEvent* ev)
|
||
{
|
||
if ( ev->getButton() != MouseButton::Left )
|
||
{
|
||
clicked_expander_pos.setPoint(-1, -1);
|
||
return;
|
||
}
|
||
|
||
if ( ! hasFocus() )
|
||
{
|
||
auto focused_widget = getFocusWidget();
|
||
setFocus();
|
||
|
||
if ( focused_widget )
|
||
focused_widget->redraw();
|
||
|
||
if ( getStatusBar() )
|
||
getStatusBar()->drawMessage();
|
||
}
|
||
|
||
const int mouse_x = ev->getX();
|
||
const int mouse_y = ev->getY();
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
|
||
if ( mouse_x > 1 && mouse_x < int(getWidth()) )
|
||
{
|
||
if ( mouse_y == 1 ) // Header
|
||
{
|
||
clicked_header_pos = ev->getPos();
|
||
}
|
||
else if ( mouse_y > 1 && mouse_y < int(getHeight()) ) // List
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
int indent = 0;
|
||
const int new_pos = first_visible_line.getPosition() + mouse_y - 2;
|
||
|
||
if ( new_pos < int(getCount()) )
|
||
setRelativePosition (mouse_y - 2);
|
||
|
||
const auto& item = getCurrentItem();
|
||
|
||
if ( tree_view )
|
||
{
|
||
indent = int(item->getDepth() << 1); // indent = 2 * depth
|
||
|
||
if ( item->isExpandable() && mouse_x - 2 == indent - xoffset )
|
||
clicked_expander_pos = ev->getPos();
|
||
}
|
||
|
||
if ( hasCheckableItems() )
|
||
{
|
||
if ( tree_view )
|
||
indent++; // Plus one space
|
||
|
||
if ( mouse_x >= 3 + indent - xoffset
|
||
&& mouse_x <= 5 + indent - xoffset
|
||
&& item->isCheckable() )
|
||
{
|
||
clicked_checkbox_item = item;
|
||
}
|
||
}
|
||
|
||
if ( isShown() )
|
||
drawList();
|
||
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( first_line_position_before != first_visible_line.getPosition() )
|
||
vbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onMouseUp (FMouseEvent* ev)
|
||
{
|
||
if ( drag_scroll != DragScrollMode::None )
|
||
stopDragScroll();
|
||
|
||
if ( ev->getButton() == MouseButton::Left )
|
||
{
|
||
const int mouse_x = ev->getX();
|
||
const int mouse_y = ev->getY();
|
||
|
||
if ( mouse_x > 1 && mouse_x < int(getWidth()) )
|
||
{
|
||
if ( mouse_y == 1 && clicked_header_pos == ev->getPos() ) // Header
|
||
{
|
||
mouseHeaderClicked();
|
||
}
|
||
else if ( mouse_y > 1 && mouse_y < int(getHeight()) ) // List
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
int indent{0};
|
||
const auto& item = getCurrentItem();
|
||
|
||
if ( tree_view )
|
||
{
|
||
indent = int(item->getDepth() << 1); // indent = 2 * depth
|
||
|
||
if ( item->isExpandable()
|
||
&& clicked_expander_pos == ev->getPos() )
|
||
{
|
||
if ( item->isExpand() )
|
||
item->collapse();
|
||
else
|
||
item->expand();
|
||
|
||
adjustScrollbars (getCount());
|
||
|
||
if ( isShown() )
|
||
draw();
|
||
}
|
||
}
|
||
|
||
if ( hasCheckableItems() )
|
||
{
|
||
if ( tree_view )
|
||
indent++; // Plus one space
|
||
|
||
if ( mouse_x >= 3 + indent - xoffset
|
||
&& mouse_x <= 5 + indent - xoffset
|
||
&& clicked_checkbox_item == item )
|
||
{
|
||
item->setChecked(! item->isChecked());
|
||
|
||
if ( isShown() )
|
||
draw();
|
||
}
|
||
}
|
||
|
||
processChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
clicked_expander_pos.setPoint(-1, -1);
|
||
clicked_header_pos.setPoint(-1, -1);
|
||
clicked_checkbox_item = nullptr;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onMouseMove (FMouseEvent* ev)
|
||
{
|
||
if ( ev->getButton() != MouseButton::Left )
|
||
{
|
||
clicked_expander_pos.setPoint(-1, -1);
|
||
return;
|
||
}
|
||
|
||
const int mouse_x = ev->getX();
|
||
const int mouse_y = ev->getY();
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
|
||
if ( mouse_x > 1 && mouse_x < int(getWidth())
|
||
&& mouse_y > 1 && mouse_y < int(getHeight()) )
|
||
{
|
||
const int new_pos = first_visible_line.getPosition() + mouse_y - 2;
|
||
|
||
if ( new_pos < int(getCount()) )
|
||
setRelativePosition (mouse_y - 2);
|
||
|
||
if ( isShown() )
|
||
drawList();
|
||
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( first_line_position_before != first_visible_line.getPosition() )
|
||
vbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
|
||
// auto-scrolling when dragging mouse outside the widget
|
||
if ( mouse_y < 2 )
|
||
dragUp (ev->getButton());
|
||
else if ( mouse_y >= int(getHeight()) )
|
||
dragDown (ev->getButton());
|
||
else
|
||
stopDragScroll();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onMouseDoubleClick (FMouseEvent* ev)
|
||
{
|
||
if ( ev->getButton() != MouseButton::Left )
|
||
return;
|
||
|
||
const int mouse_x = ev->getX();
|
||
const int mouse_y = ev->getY();
|
||
|
||
if ( mouse_x > 1 && mouse_x < int(getWidth())
|
||
&& mouse_y > 1 && mouse_y < int(getHeight()) )
|
||
{
|
||
if ( first_visible_line.getPosition() + mouse_y - 1 > int(getCount()) )
|
||
return;
|
||
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
auto item = getCurrentItem();
|
||
|
||
if ( tree_view && item->isExpandable() )
|
||
{
|
||
if ( item->isExpand() )
|
||
item->collapse();
|
||
else
|
||
item->expand();
|
||
|
||
adjustScrollbars (getCount()); // after expand or collapse
|
||
|
||
if ( isShown() )
|
||
draw();
|
||
}
|
||
|
||
processClick();
|
||
}
|
||
|
||
clicked_expander_pos.setPoint(-1, -1);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onTimer (FTimerEvent*)
|
||
{
|
||
const int position_before = current_iter.getPosition();
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
|
||
if ( ( drag_scroll == DragScrollMode::Upward
|
||
|| drag_scroll == DragScrollMode::SelectUpward )
|
||
&& ! dragScrollUp(position_before) )
|
||
{
|
||
return;
|
||
}
|
||
else if ( ( drag_scroll == DragScrollMode::Downward
|
||
|| drag_scroll == DragScrollMode::SelectDownward )
|
||
&& ! dragScrollDown(position_before) )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( isShown() )
|
||
drawList();
|
||
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( first_line_position_before != first_visible_line.getPosition() )
|
||
vbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onWheel (FWheelEvent* ev)
|
||
{
|
||
const int position_before = current_iter.getPosition();
|
||
static constexpr int wheel_distance = 4;
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
|
||
if ( drag_scroll != DragScrollMode::None )
|
||
stopDragScroll();
|
||
|
||
if ( ev->getWheel() == MouseWheel::Up )
|
||
wheelUp (wheel_distance);
|
||
else if ( ev->getWheel() == MouseWheel::Down )
|
||
wheelDown (wheel_distance);
|
||
|
||
if ( position_before != current_iter.getPosition() )
|
||
processChanged();
|
||
|
||
if ( isShown() )
|
||
drawList();
|
||
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( first_line_position_before != first_visible_line.getPosition() )
|
||
vbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onFocusIn (FFocusEvent*)
|
||
{
|
||
if ( getStatusBar() )
|
||
getStatusBar()->drawMessage();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::onFocusOut (FFocusEvent*)
|
||
{
|
||
if ( getStatusBar() )
|
||
{
|
||
getStatusBar()->clearMessage();
|
||
getStatusBar()->drawMessage();
|
||
}
|
||
|
||
delOwnTimers();
|
||
}
|
||
|
||
|
||
// protected methods of FListView
|
||
//----------------------------------------------------------------------
|
||
void FListView::adjustViewport (const int element_count)
|
||
{
|
||
const auto height = int(getClientHeight());
|
||
|
||
if ( height <= 0 || element_count == 0 )
|
||
return;
|
||
|
||
if ( element_count < height )
|
||
{
|
||
first_visible_line = itemlist.begin();
|
||
last_visible_line = first_visible_line;
|
||
last_visible_line += element_count - 1;
|
||
}
|
||
|
||
if ( first_visible_line.getPosition() > element_count - height )
|
||
{
|
||
const int difference = first_visible_line.getPosition()
|
||
- (element_count - height);
|
||
|
||
if ( first_visible_line.getPosition() >= difference )
|
||
{
|
||
first_visible_line -= difference;
|
||
last_visible_line -= difference;
|
||
}
|
||
}
|
||
|
||
const int after_last_visible_line = first_visible_line.getPosition()
|
||
+ height;
|
||
|
||
if ( last_visible_line.getPosition() >= after_last_visible_line )
|
||
{
|
||
last_visible_line = first_visible_line;
|
||
last_visible_line += height - 1;
|
||
}
|
||
|
||
if ( current_iter.getPosition() > last_visible_line.getPosition() )
|
||
current_iter = last_visible_line;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::adjustScrollbars (const std::size_t element_count) const
|
||
{
|
||
const std::size_t width = getClientWidth();
|
||
const std::size_t height = getClientHeight();
|
||
const int vmax = ( element_count > height )
|
||
? int(element_count - height)
|
||
: 0;
|
||
vbar->setMaximum (vmax);
|
||
vbar->setPageSize (int(element_count), int(height));
|
||
vbar->setX (int(getWidth()));
|
||
vbar->setHeight (height, false);
|
||
vbar->resize();
|
||
|
||
const int hmax = ( max_line_width > width )
|
||
? int(max_line_width - width)
|
||
: 0;
|
||
hbar->setMaximum (hmax);
|
||
hbar->setPageSize (int(max_line_width), int(width));
|
||
hbar->setY (int(getHeight()));
|
||
hbar->setWidth (width, false);
|
||
hbar->resize();
|
||
|
||
if ( isShown() )
|
||
{
|
||
if ( isHorizontallyScrollable() )
|
||
hbar->show();
|
||
else
|
||
hbar->hide();
|
||
|
||
if ( isVerticallyScrollable() )
|
||
vbar->show();
|
||
else
|
||
vbar->hide();
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::adjustSize()
|
||
{
|
||
FWidget::adjustSize();
|
||
const std::size_t element_count = getCount();
|
||
adjustViewport (int(element_count));
|
||
adjustScrollbars (element_count);
|
||
}
|
||
|
||
|
||
// private methods of FListView
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator& FListView::getNullIterator()
|
||
{
|
||
static iterator null_iter; // Saves the global null iterator
|
||
return null_iter;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setNullIterator (const iterator& null_iter)
|
||
{
|
||
getNullIterator() = null_iter;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::init()
|
||
{
|
||
initScrollbar (vbar, Orientation::Vertical, this, &FListView::cb_vbarChange);
|
||
initScrollbar (hbar, Orientation::Horizontal, this, &FListView::cb_hbarChange);
|
||
selflist.push_back(this);
|
||
root = selflist.begin();
|
||
getNullIterator() = selflist.end();
|
||
setGeometry (FPoint{1, 1}, FSize{5, 4}, false); // initialize geometry values
|
||
nf_offset = FTerm::isNewFont() ? 1 : 0;
|
||
setTopPadding(1);
|
||
setLeftPadding(1);
|
||
setBottomPadding(1);
|
||
setRightPadding(1);
|
||
mapKeyFunctions();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::mapKeyFunctions()
|
||
{
|
||
key_map[FKey::Return] = [this] { processClick(); };
|
||
key_map[FKey::Enter] = [this] { processClick(); };
|
||
key_map[FKey::Space] = [this] { toggleCheckbox(); };
|
||
key_map[FKey::Up] = [this] { stepBackward(); };
|
||
key_map[FKey::Down] = [this] { stepForward(); };
|
||
key_map[FKey::Left] = [this] { collapseAndScrollLeft(); };
|
||
key_map[FKey::Right] = [this] { expandAndScrollRight(); };
|
||
key_map[FKey::Page_up] = [this] { stepBackward(int(getClientHeight()) - 1); };
|
||
key_map[FKey::Page_down] = [this] { stepForward(int(getClientHeight()) - 1); };
|
||
key_map[FKey::Home] = [this] { firstPos(); };
|
||
key_map[FKey::End] = [this] { lastPos(); };
|
||
key_map_result[FKey('+')] = [this] { return expandSubtree(); };
|
||
key_map_result[FKey('-')] = [this] { return collapseSubtree(); };
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::processKeyAction (FKeyEvent* ev)
|
||
{
|
||
const auto idx = ev->key();
|
||
const auto& entry = key_map[idx];
|
||
|
||
if ( entry )
|
||
{
|
||
entry();
|
||
ev->accept();
|
||
}
|
||
else
|
||
{
|
||
const auto& entry_result = key_map_result[idx];
|
||
|
||
if ( entry_result )
|
||
{
|
||
if ( entry_result() )
|
||
ev->accept();
|
||
}
|
||
else
|
||
{
|
||
ev->ignore();
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
template <typename Compare>
|
||
void FListView::sort (Compare cmp)
|
||
{
|
||
// Sort the top level
|
||
itemlist.sort(cmp);
|
||
|
||
// Sort the sublevels
|
||
for (auto&& item : itemlist)
|
||
static_cast<FListViewItem*>(item)->sort(cmp);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
std::size_t FListView::getAlignOffset ( const Align align
|
||
, const std::size_t column_width
|
||
, const std::size_t width ) const
|
||
{
|
||
if ( align == Align::Center )
|
||
{
|
||
if ( column_width < width )
|
||
return (width - column_width) / 2;
|
||
}
|
||
else if ( align == Align::Right && column_width < width )
|
||
{
|
||
return width - column_width;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListView::getListEnd (const FListViewItem* item)
|
||
{
|
||
auto parent = item->getParent();
|
||
|
||
if ( ! parent )
|
||
return getNullIterator();
|
||
|
||
if ( this == parent )
|
||
return itemlist.end();
|
||
else if ( parent->isInstanceOf("FListViewItem") )
|
||
return static_cast<FListViewItem*>(parent)->end();
|
||
else
|
||
return getNullIterator();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::draw()
|
||
{
|
||
if ( current_iter.getPosition() < 1 )
|
||
current_iter = itemlist.begin();
|
||
|
||
useParentWidgetColor();
|
||
|
||
if ( FTerm::isMonochron() )
|
||
setReverse(true);
|
||
|
||
drawBorder();
|
||
|
||
if ( FTerm::isNewFont() && ! vbar->isShown() )
|
||
{
|
||
setColor();
|
||
|
||
for (auto y{2}; y < int(getHeight()); y++)
|
||
{
|
||
print() << FPoint{int(getWidth()) - 1, y}
|
||
<< ' '; // clear right side of the scrollbar
|
||
}
|
||
}
|
||
|
||
drawHeadlines();
|
||
|
||
if ( FTerm::isMonochron() )
|
||
setReverse(false);
|
||
|
||
drawScrollbars();
|
||
drawList();
|
||
|
||
if ( getFlags().focus && getStatusBar() )
|
||
{
|
||
const auto& msg = getStatusbarMessage();
|
||
const auto& curMsg = getStatusBar()->getMessage();
|
||
|
||
if ( curMsg != msg )
|
||
{
|
||
getStatusBar()->setMessage(msg);
|
||
getStatusBar()->drawMessage();
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawBorder()
|
||
{
|
||
const FRect box(FPoint{1, 1}, getSize());
|
||
finalcut::drawListBorder (this, box);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawScrollbars() const
|
||
{
|
||
if ( ! hbar->isShown() && isHorizontallyScrollable() )
|
||
hbar->show();
|
||
else
|
||
hbar->redraw();
|
||
|
||
if ( ! vbar->isShown() && isVerticallyScrollable() )
|
||
vbar->show();
|
||
else
|
||
vbar->redraw();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawHeadlines()
|
||
{
|
||
if ( header.empty()
|
||
|| getHeight() <= 2
|
||
|| getWidth() <= 4
|
||
|| max_line_width < 1 )
|
||
return;
|
||
|
||
HeaderItems::const_iterator iter = header.begin();
|
||
headerline.clear();
|
||
|
||
if ( hasCheckableItems() )
|
||
drawHeaderBorder(4); // Draw into FTermBuffer object
|
||
|
||
while ( iter != header.end() )
|
||
{
|
||
if ( ! iter->name.isEmpty() )
|
||
drawHeadlineLabel(iter); // Draw into FTermBuffer object
|
||
|
||
++iter;
|
||
}
|
||
|
||
// Print the FTermBuffer object
|
||
drawBufferedHeadline();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawList()
|
||
{
|
||
if ( itemlist.empty() || getHeight() <= 2 || getWidth() <= 4 )
|
||
return;
|
||
|
||
uInt y{0};
|
||
const uInt page_height = uInt(getHeight()) - 2;
|
||
const auto& itemlist_end = itemlist.end();
|
||
auto path_end = itemlist_end;
|
||
auto iter = first_visible_line;
|
||
|
||
while ( iter != path_end && iter != itemlist_end && y < page_height )
|
||
{
|
||
const bool is_current_line( iter == current_iter );
|
||
const auto& item = static_cast<FListViewItem*>(*iter);
|
||
const int tree_offset = tree_view ? int(item->getDepth() << 1) + 1 : 0;
|
||
const int checkbox_offset = item->isCheckable() ? 1 : 0;
|
||
path_end = getListEnd(item);
|
||
print() << FPoint{2, 2 + int(y)};
|
||
|
||
// Draw one FListViewItem
|
||
drawListLine (item, getFlags().focus, is_current_line);
|
||
|
||
if ( getFlags().focus && is_current_line )
|
||
{
|
||
int xpos = 3 + tree_offset + checkbox_offset - xoffset;
|
||
|
||
if ( xpos < 2 ) // Hide the cursor
|
||
xpos = -9999; // by moving it outside the visible area
|
||
|
||
setVisibleCursor (item->isCheckable());
|
||
setCursorPos ({xpos, 2 + int(y)}); // first character
|
||
}
|
||
|
||
last_visible_line = iter;
|
||
y++;
|
||
++iter;
|
||
}
|
||
|
||
// Reset color
|
||
setColor();
|
||
|
||
if ( FTerm::isMonochron() )
|
||
setReverse(true);
|
||
|
||
// Clean empty space after last element
|
||
while ( y < uInt(getClientHeight()) )
|
||
{
|
||
print() << FPoint{2, 2 + int(y)}
|
||
<< FString{std::size_t(getClientWidth()), ' '};
|
||
y++;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawListLine ( const FListViewItem* item
|
||
, bool is_focus
|
||
, bool is_current )
|
||
{
|
||
// Set line color and attributes
|
||
setLineAttributes (is_current, is_focus);
|
||
|
||
// Print the entry
|
||
const std::size_t indent = item->getDepth() << 1; // indent = 2 * depth
|
||
FString line{getLinePrefix (item, indent)};
|
||
|
||
// Print columns
|
||
if ( ! item->column_list.empty() )
|
||
{
|
||
for (std::size_t col{0}; col < item->column_list.size(); )
|
||
{
|
||
static constexpr std::size_t ellipsis_length = 2;
|
||
const auto& text = item->column_list[col];
|
||
auto width = std::size_t(header[col].width);
|
||
const std::size_t column_width = getColumnWidth(text);
|
||
// Increment the value of col for the column position
|
||
// and the next iteration
|
||
col++;
|
||
const Align align = getColumnAlignment(int(col));
|
||
const std::size_t align_offset = getAlignOffset (align, column_width, width);
|
||
|
||
if ( tree_view && col == 1 )
|
||
{
|
||
width -= (indent + 1);
|
||
|
||
if ( item->isCheckable() )
|
||
width -= checkbox_space;
|
||
}
|
||
|
||
// Insert alignment spaces
|
||
if ( align_offset > 0 )
|
||
line += FString{align_offset, L' '};
|
||
|
||
if ( align_offset + column_width <= width )
|
||
{
|
||
// Insert text and trailing space
|
||
static constexpr std::size_t leading_space = 1;
|
||
line += getColumnSubString (text, 1, width);
|
||
line += FString { leading_space + width
|
||
- align_offset - column_width, L' '};
|
||
}
|
||
else if ( align == Align::Right )
|
||
{
|
||
// Ellipse right align text
|
||
const std::size_t first = getColumnWidth(text) + 1 - width;
|
||
line += FString {L".."};
|
||
line += getColumnSubString (text, first, width - ellipsis_length);
|
||
line += L' ';
|
||
}
|
||
else
|
||
{
|
||
// Ellipse left align text and center text
|
||
line += getColumnSubString (text, 1, width - ellipsis_length);
|
||
line += FString {L".. "};
|
||
}
|
||
}
|
||
}
|
||
|
||
const std::size_t width = getWidth() - nf_offset - 2;
|
||
line = getColumnSubString ( line, std::size_t(xoffset) + 1, width );
|
||
const std::size_t len = line.getLength();
|
||
std::size_t char_width{0};
|
||
|
||
for (std::size_t i{0}; i < len; i++)
|
||
{
|
||
try
|
||
{
|
||
char_width += getColumnWidth(line[i]);
|
||
print() << line[i];
|
||
}
|
||
catch (const std::out_of_range&)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
for (std::size_t i = char_width; i < width; i++)
|
||
print (' ');
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::clearList()
|
||
{
|
||
// Clear list from terminal screen
|
||
|
||
const auto& wc = getColorTheme();
|
||
setColor (wc->list_fg, wc->list_bg);
|
||
const std::size_t size = getWidth() - 2;
|
||
drawBorder();
|
||
drawHeadlines();
|
||
|
||
if ( size == 0 )
|
||
return;
|
||
|
||
for (auto y{0}; y < int(getHeight()) - 2; y++)
|
||
{
|
||
print() << FPoint{2, 2 + y} << FString{size, L' '};
|
||
}
|
||
|
||
drawScrollbars();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::setLineAttributes ( bool is_current
|
||
, bool is_focus ) const
|
||
{
|
||
const auto& wc = getColorTheme();
|
||
setColor (wc->list_fg, wc->list_bg);
|
||
|
||
if ( is_current )
|
||
{
|
||
if ( is_focus && FTerm::getMaxColor() < 16 )
|
||
setBold();
|
||
|
||
if ( FTerm::isMonochron() )
|
||
unsetBold();
|
||
|
||
if ( is_focus )
|
||
{
|
||
setColor ( wc->current_element_focus_fg
|
||
, wc->current_element_focus_bg );
|
||
}
|
||
else
|
||
setColor ( wc->current_element_fg
|
||
, wc->current_element_bg );
|
||
|
||
if ( FTerm::isMonochron() )
|
||
setReverse(false);
|
||
}
|
||
else
|
||
{
|
||
if ( FTerm::isMonochron() )
|
||
setReverse(true);
|
||
else if ( is_focus && FTerm::getMaxColor() < 16 )
|
||
unsetBold();
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline FString FListView::getCheckBox (const FListViewItem* item) const
|
||
{
|
||
FString checkbox{""};
|
||
|
||
if ( FTerm::isNewFont() )
|
||
{
|
||
checkbox = ( item->isChecked() ) ? CHECKBOX_ON : CHECKBOX;
|
||
checkbox += L' ';
|
||
}
|
||
else
|
||
{
|
||
checkbox.setString("[ ] ");
|
||
|
||
if ( item->isChecked() )
|
||
{
|
||
try
|
||
{
|
||
checkbox[1] = wchar_t(UniChar::Times); // Times ×
|
||
}
|
||
catch (const std::out_of_range&)
|
||
{
|
||
return checkbox;
|
||
}
|
||
}
|
||
}
|
||
|
||
return checkbox;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline FString FListView::getLinePrefix ( const FListViewItem* item
|
||
, std::size_t indent ) const
|
||
{
|
||
FString line{""};
|
||
|
||
if ( tree_view )
|
||
{
|
||
if ( indent > 0 )
|
||
line = FString{indent, L' '};
|
||
|
||
if ( item->isExpandable() )
|
||
{
|
||
if ( item->isExpand() )
|
||
{
|
||
line += UniChar::BlackDownPointingTriangle; // ▼
|
||
line += L' ';
|
||
}
|
||
else
|
||
{
|
||
line += UniChar::BlackRightPointingPointer; // ►
|
||
line += L' ';
|
||
}
|
||
}
|
||
else
|
||
line += L" ";
|
||
}
|
||
else
|
||
line.setString(" ");
|
||
|
||
if ( item->isCheckable() )
|
||
line += getCheckBox(item);
|
||
|
||
return line;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::drawSortIndicator ( std::size_t& length
|
||
, std::size_t column_max )
|
||
{
|
||
if ( length >= column_max )
|
||
return;
|
||
|
||
setColor();
|
||
length++;
|
||
|
||
if ( sort_order == SortOrder::Ascending )
|
||
headerline << UniChar::BlackUpPointingTriangle; // ▲
|
||
else if ( sort_order == SortOrder::Descending )
|
||
headerline << UniChar::BlackDownPointingTriangle; // ▼
|
||
|
||
if ( length < column_max )
|
||
{
|
||
length++;
|
||
headerline << ' ';
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::drawHeaderBorder (std::size_t length)
|
||
{
|
||
setColor();
|
||
const FString line {length, UniChar::BoxDrawingsHorizontal};
|
||
headerline << line; // horizontal line
|
||
}
|
||
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawHeadlineLabel (const HeaderItems::const_iterator& iter)
|
||
{
|
||
// Print label text
|
||
static constexpr std::size_t leading_space = 1;
|
||
const auto& text = iter->name;
|
||
FString txt{" " + text};
|
||
const auto width = std::size_t(iter->width);
|
||
std::size_t column_width = getColumnWidth(txt);
|
||
const std::size_t column_max = leading_space + width;
|
||
const HeaderItems::const_iterator first = header.begin();
|
||
const int column = int(std::distance(first, iter)) + 1;
|
||
const bool has_sort_indicator( sort_column == column && ! hide_sort_indicator );
|
||
const auto& wc = getColorTheme();
|
||
|
||
if ( isEnabled() )
|
||
setColor (wc->label_emphasis_fg, wc->label_bg);
|
||
else
|
||
setColor (wc->label_inactive_fg, wc->label_inactive_bg);
|
||
|
||
if ( has_sort_indicator && column_width >= column_max - 1 && column_width > 1 )
|
||
{
|
||
column_width = column_max - 2;
|
||
txt = getColumnSubString (txt, 1, column_width);
|
||
}
|
||
|
||
if ( column_width <= column_max )
|
||
{
|
||
headerline << txt;
|
||
|
||
if ( column_width < column_max )
|
||
{
|
||
column_width++;
|
||
headerline << ' '; // trailing space
|
||
}
|
||
|
||
if ( has_sort_indicator )
|
||
drawSortIndicator (column_width, column_max );
|
||
|
||
if ( column_width < column_max )
|
||
drawHeaderBorder (column_max - column_width);
|
||
}
|
||
else
|
||
drawColumnEllipsis (iter, text); // Print ellipsis
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawBufferedHeadline()
|
||
{
|
||
// Print the FTermBuffer object
|
||
|
||
if ( headerline.isEmpty() )
|
||
return;
|
||
|
||
std::size_t column_offset{0};
|
||
std::size_t column_width{0};
|
||
std::size_t offset{0};
|
||
bool left_truncated_fullwidth{false};
|
||
bool right_truncated_fullwidth{false};
|
||
std::vector<FChar>::const_iterator first{};
|
||
std::vector<FChar>::const_iterator last{};
|
||
last = headerline.end();
|
||
|
||
// Search for the start position
|
||
for (auto&& tc : headerline)
|
||
{
|
||
if ( xoffset == 0 )
|
||
break;
|
||
|
||
column_offset += getColumnWidth(tc);
|
||
offset++;
|
||
|
||
if ( column_offset == std::size_t(xoffset) )
|
||
break;
|
||
|
||
if ( column_offset > std::size_t(xoffset) && column_offset >= 2 )
|
||
{
|
||
left_truncated_fullwidth = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
first = headerline.begin();
|
||
std::advance(first, offset);
|
||
|
||
// Search for the end position
|
||
if ( getColumnWidth(headerline) > getClientWidth() )
|
||
{
|
||
std::size_t character{0};
|
||
|
||
if ( left_truncated_fullwidth )
|
||
column_width++;
|
||
|
||
for (auto&& tc : FTermBuffer(first, last))
|
||
{
|
||
const uInt8 char_width = tc.attr.bit.char_width;
|
||
|
||
if ( column_width + char_width > getClientWidth() )
|
||
{
|
||
column_width++;
|
||
right_truncated_fullwidth = true;
|
||
break;
|
||
}
|
||
|
||
column_width += char_width;
|
||
character++;
|
||
|
||
if ( column_width == getClientWidth() )
|
||
break;
|
||
}
|
||
|
||
last = first;
|
||
std::advance(last, character);
|
||
}
|
||
else
|
||
column_width = getColumnWidth(headerline);
|
||
|
||
// Print the header line
|
||
print() << FPoint{2, 1};
|
||
|
||
if ( left_truncated_fullwidth )
|
||
print (UniChar::SingleLeftAngleQuotationMark); // ‹
|
||
|
||
print() << FTermBuffer(first, last);
|
||
|
||
if ( right_truncated_fullwidth )
|
||
print (UniChar::SingleRightAngleQuotationMark); // ›
|
||
|
||
while ( column_width < getClientWidth() )
|
||
{
|
||
setColor();
|
||
print(UniChar::BoxDrawingsHorizontal);
|
||
column_width++;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::drawColumnEllipsis ( const HeaderItems::const_iterator& iter
|
||
, const FString& text )
|
||
{
|
||
// Print label ellipsis
|
||
static constexpr int ellipsis_length = 2;
|
||
const int width = iter->width;
|
||
const auto& wc = getColorTheme();
|
||
|
||
headerline << ' '
|
||
<< getColumnSubString (text, 1, uInt(width - ellipsis_length))
|
||
<< FColorPair {wc->label_ellipsis_fg, wc->label_bg}
|
||
<< "..";
|
||
|
||
if ( iter == header.end() - 1 ) // Last element
|
||
headerline << ' ';
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::updateDrawing (bool draw_vbar, bool draw_hbar)
|
||
{
|
||
if ( isShown() )
|
||
draw();
|
||
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( draw_vbar )
|
||
vbar->drawBar();
|
||
|
||
hbar->setValue (xoffset);
|
||
|
||
if ( draw_hbar )
|
||
hbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
std::size_t FListView::determineLineWidth (FListViewItem* item)
|
||
{
|
||
static constexpr std::size_t padding_space = 1;
|
||
std::size_t line_width = padding_space; // leading space
|
||
std::size_t column_idx{0};
|
||
const auto entries = std::size_t(item->column_list.size());
|
||
|
||
if ( hasCheckableItems() )
|
||
line_width += checkbox_space;
|
||
|
||
for (auto&& header_item : header)
|
||
{
|
||
const auto width = std::size_t(header_item.width);
|
||
const bool fixed_width = header_item.fixed_width;
|
||
|
||
if ( ! fixed_width )
|
||
{
|
||
std::size_t len{0};
|
||
|
||
if ( column_idx < entries )
|
||
len = getColumnWidth(item->column_list[column_idx]);
|
||
|
||
if ( len > width )
|
||
header_item.width = int(len);
|
||
}
|
||
|
||
// width + trailing space
|
||
line_width += std::size_t(header_item.width) + padding_space;
|
||
|
||
column_idx++;
|
||
}
|
||
|
||
return line_width;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::beforeInsertion (FListViewItem* item)
|
||
{
|
||
std::size_t line_width = determineLineWidth (item);
|
||
recalculateHorizontalBar (line_width);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::afterInsertion()
|
||
{
|
||
if ( itemlist.size() == 1 )
|
||
{
|
||
// Select first item on insert
|
||
current_iter = itemlist.begin();
|
||
// The visible area of the list begins with the first element
|
||
first_visible_line = itemlist.begin();
|
||
}
|
||
|
||
// Sort list by a column (only if activated)
|
||
sort();
|
||
|
||
const std::size_t element_count = getCount();
|
||
recalculateVerticalBar (element_count);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::recalculateHorizontalBar (std::size_t len)
|
||
{
|
||
if ( len <= max_line_width )
|
||
return;
|
||
|
||
max_line_width = len;
|
||
|
||
if ( len >= getWidth() - nf_offset - 3 )
|
||
{
|
||
const int hmax = ( max_line_width > getWidth() - nf_offset - 4 )
|
||
? int(max_line_width - getWidth() + nf_offset + 4)
|
||
: 0;
|
||
hbar->setMaximum (hmax);
|
||
hbar->setPageSize (int(max_line_width), int(getWidth() - nf_offset) - 4);
|
||
hbar->calculateSliderValues();
|
||
|
||
if ( isShown() )
|
||
{
|
||
if ( isHorizontallyScrollable() )
|
||
hbar->show();
|
||
else
|
||
hbar->hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::recalculateVerticalBar (std::size_t element_count) const
|
||
{
|
||
const std::size_t height = getClientHeight();
|
||
const int vmax = ( element_count > height )
|
||
? int(element_count - height)
|
||
: 0;
|
||
vbar->setMaximum (vmax);
|
||
vbar->setPageSize (int(element_count), int(height));
|
||
vbar->calculateSliderValues();
|
||
|
||
if ( isShown() )
|
||
{
|
||
if ( isVerticallyScrollable() )
|
||
vbar->show();
|
||
else
|
||
vbar->hide();
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::mouseHeaderClicked()
|
||
{
|
||
int column{1};
|
||
const int checkbox_offset = ( hasCheckableItems() ) ? checkbox_space : 0;
|
||
const int header_pos = clicked_header_pos.getX() + xoffset;
|
||
int header_start = 2 + checkbox_offset;
|
||
|
||
for (auto&& item : header)
|
||
{
|
||
static constexpr int leading_space = 1;
|
||
const bool has_sort_indicator( column == sort_column );
|
||
auto click_width = int(getColumnWidth(item.name));
|
||
|
||
if ( has_sort_indicator )
|
||
click_width += 2;
|
||
|
||
if ( click_width > item.width )
|
||
click_width = item.width;
|
||
|
||
if ( header_pos > header_start
|
||
&& header_pos <= header_start + click_width )
|
||
{
|
||
if ( has_sort_indicator && sort_order == SortOrder::Ascending )
|
||
setColumnSort (column, SortOrder::Descending);
|
||
else
|
||
setColumnSort (column, SortOrder::Ascending);
|
||
|
||
sort();
|
||
|
||
if ( isShown() )
|
||
updateDrawing (true, false);
|
||
|
||
break;
|
||
}
|
||
|
||
header_start += leading_space + item.width;
|
||
column++;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::wheelUp (int pagesize)
|
||
{
|
||
if ( itemlist.empty() || current_iter.getPosition() == 0 )
|
||
return;
|
||
|
||
if ( first_visible_line.getPosition() >= pagesize )
|
||
{
|
||
current_iter -= pagesize;
|
||
first_visible_line -= pagesize;
|
||
last_visible_line -= pagesize;
|
||
}
|
||
else
|
||
{
|
||
// Save relative position from the first line
|
||
const int ry = current_iter.getPosition() - first_visible_line.getPosition();
|
||
// Save difference from top
|
||
const int difference = first_visible_line.getPosition();
|
||
first_visible_line -= difference;
|
||
last_visible_line -= difference;
|
||
setRelativePosition(ry);
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::wheelDown (int pagesize)
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
const auto element_count = int(getCount());
|
||
|
||
if ( current_iter.getPosition() + 1 == element_count )
|
||
return;
|
||
|
||
if ( last_visible_line.getPosition() < element_count - pagesize )
|
||
{
|
||
current_iter += pagesize;
|
||
first_visible_line += pagesize;
|
||
last_visible_line += pagesize;
|
||
}
|
||
else
|
||
{
|
||
// Save relative position from the first line
|
||
const int ry = current_iter.getPosition() - first_visible_line.getPosition();
|
||
// Save difference from bottom
|
||
const int differenz = element_count - last_visible_line.getPosition() - 1;
|
||
first_visible_line += differenz;
|
||
last_visible_line += differenz;
|
||
setRelativePosition(ry);
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool FListView::dragScrollUp (int position_before)
|
||
{
|
||
if ( position_before == 0 )
|
||
{
|
||
drag_scroll = DragScrollMode::None;
|
||
return false;
|
||
}
|
||
|
||
stepBackward(scroll_distance);
|
||
return true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
bool FListView::dragScrollDown (int position_before)
|
||
{
|
||
const auto element_count = int(getCount());
|
||
|
||
if ( position_before + 1 == element_count )
|
||
{
|
||
drag_scroll = DragScrollMode::None;
|
||
return false;
|
||
}
|
||
|
||
stepForward(scroll_distance);
|
||
return true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::dragUp (MouseButton mouse_button)
|
||
{
|
||
if ( drag_scroll != DragScrollMode::None
|
||
&& scroll_distance < int(getClientHeight()) )
|
||
scroll_distance++;
|
||
|
||
if ( ! scroll_timer && current_iter.getPosition() > 0 )
|
||
{
|
||
scroll_timer = true;
|
||
addTimer(scroll_repeat);
|
||
|
||
if ( mouse_button == MouseButton::Right )
|
||
drag_scroll = DragScrollMode::SelectUpward;
|
||
else
|
||
drag_scroll = DragScrollMode::Upward;
|
||
}
|
||
|
||
if ( current_iter.getPosition() == 0 )
|
||
{
|
||
delOwnTimers();
|
||
drag_scroll = DragScrollMode::None;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::dragDown (MouseButton mouse_button)
|
||
{
|
||
if ( drag_scroll != DragScrollMode::None
|
||
&& scroll_distance < int(getClientHeight()) )
|
||
scroll_distance++;
|
||
|
||
if ( ! scroll_timer && current_iter.getPosition() <= int(getCount()) )
|
||
{
|
||
scroll_timer = true;
|
||
addTimer(scroll_repeat);
|
||
|
||
if ( mouse_button == MouseButton::Right )
|
||
drag_scroll = DragScrollMode::SelectDownward;
|
||
else
|
||
drag_scroll = DragScrollMode::Downward;
|
||
}
|
||
|
||
if ( current_iter.getPosition() - 1 == int(getCount()) )
|
||
{
|
||
delOwnTimers();
|
||
drag_scroll = DragScrollMode::None;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::stopDragScroll()
|
||
{
|
||
delOwnTimers();
|
||
scroll_timer = false;
|
||
scroll_distance = 1;
|
||
drag_scroll = DragScrollMode::None;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
FObject::iterator FListView::appendItem (FListViewItem* item)
|
||
{
|
||
item->root = root;
|
||
addChild (item);
|
||
itemlist.push_back (item);
|
||
return --itemlist.end();
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::processClick() const
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
emitCallback("clicked");
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::processChanged() const
|
||
{
|
||
emitCallback("row-changed");
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::changeOnResize() const
|
||
{
|
||
if ( FTerm::isNewFont() )
|
||
{
|
||
vbar->setGeometry (FPoint{int(getWidth()), 2}, FSize{2, getHeight() - 2});
|
||
hbar->setGeometry (FPoint{1, int(getHeight())}, FSize{getWidth() - 2, 1});
|
||
}
|
||
else
|
||
{
|
||
vbar->setGeometry (FPoint{int(getWidth()), 2}, FSize{1, getHeight() - 2});
|
||
hbar->setGeometry (FPoint{2, int(getHeight())}, FSize{getWidth() - 2, 1});
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::toggleCheckbox()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
auto item = getCurrentItem();
|
||
|
||
if ( item->isCheckable() )
|
||
item->setChecked(! item->isChecked());
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::collapseAndScrollLeft()
|
||
{
|
||
const int position_before = current_iter.getPosition();
|
||
auto item = getCurrentItem();
|
||
|
||
if ( xoffset == 0 && item && ! itemlist.empty() )
|
||
{
|
||
if ( tree_view && item->isExpandable() && item->isExpand() )
|
||
{
|
||
// Collapse element
|
||
item->collapse();
|
||
adjustSize();
|
||
vbar->calculateSliderValues();
|
||
// Force vertical scrollbar redraw
|
||
first_line_position_before = -1;
|
||
}
|
||
else if ( item->hasParent() )
|
||
{
|
||
// Jump to parent element
|
||
const auto& parent = item->getParent();
|
||
|
||
if ( parent->isInstanceOf("FListViewItem") )
|
||
{
|
||
current_iter.parentElement();
|
||
|
||
if ( current_iter.getPosition() < first_line_position_before )
|
||
{
|
||
const int difference = position_before - current_iter.getPosition();
|
||
|
||
if ( first_visible_line.getPosition() - difference >= 0 )
|
||
{
|
||
first_visible_line -= difference;
|
||
last_visible_line -= difference;
|
||
}
|
||
else
|
||
{
|
||
const int d = first_visible_line.getPosition();
|
||
first_visible_line -= d;
|
||
last_visible_line -= d;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Scroll left
|
||
if ( xoffset > 0 )
|
||
xoffset--;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::expandAndScrollRight()
|
||
{
|
||
const int xoffset_end = int(max_line_width) - int(getClientWidth());
|
||
auto item = getCurrentItem();
|
||
|
||
if ( tree_view && ! itemlist.empty() && item
|
||
&& item->isExpandable() && ! item->isExpand() )
|
||
{
|
||
// Expand element
|
||
item->expand();
|
||
adjustScrollbars (getCount());
|
||
// Force vertical scrollbar redraw
|
||
first_line_position_before = -1;
|
||
}
|
||
else
|
||
{
|
||
// Scroll right
|
||
if ( xoffset < xoffset_end )
|
||
xoffset++;
|
||
|
||
if ( xoffset < 0 )
|
||
xoffset = 0;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::firstPos()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
current_iter -= current_iter.getPosition();
|
||
const int difference = first_visible_line.getPosition();
|
||
first_visible_line -= difference;
|
||
last_visible_line -= difference;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline void FListView::lastPos()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
const auto element_count = int(getCount());
|
||
current_iter += element_count - current_iter.getPosition() - 1;
|
||
const int difference = element_count - last_visible_line.getPosition() - 1;
|
||
first_visible_line += difference;
|
||
last_visible_line += difference;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline bool FListView::expandSubtree()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return false;
|
||
|
||
auto item = getCurrentItem();
|
||
|
||
if ( tree_view && item->isExpandable() && ! item->isExpand() )
|
||
{
|
||
item->expand();
|
||
adjustScrollbars (getCount());
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
inline bool FListView::collapseSubtree()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return false;
|
||
|
||
auto item = getCurrentItem();
|
||
|
||
if ( tree_view && item->isExpandable() && item->isExpand() )
|
||
{
|
||
item->collapse();
|
||
adjustScrollbars (getCount());
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::setRelativePosition (int ry)
|
||
{
|
||
current_iter = first_visible_line;
|
||
current_iter += ry;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::stepForward()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
if ( current_iter == last_visible_line )
|
||
{
|
||
++last_visible_line;
|
||
|
||
if ( last_visible_line == itemlist.end() )
|
||
--last_visible_line;
|
||
else
|
||
++first_visible_line;
|
||
}
|
||
|
||
++current_iter;
|
||
|
||
if ( current_iter == itemlist.end() )
|
||
--current_iter;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::stepBackward()
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
if ( current_iter == first_visible_line
|
||
&& current_iter != itemlist.begin() )
|
||
{
|
||
--first_visible_line;
|
||
--last_visible_line;
|
||
}
|
||
|
||
if ( current_iter != itemlist.begin() )
|
||
--current_iter;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::stepForward (int distance)
|
||
{
|
||
if ( itemlist.empty() )
|
||
return;
|
||
|
||
const auto element_count = int(getCount());
|
||
|
||
if ( current_iter.getPosition() + 1 == element_count )
|
||
return;
|
||
|
||
if ( current_iter.getPosition() + distance < element_count )
|
||
{
|
||
current_iter += distance;
|
||
}
|
||
else
|
||
{
|
||
current_iter += element_count - current_iter.getPosition() - 1;
|
||
}
|
||
|
||
if ( current_iter.getPosition() > last_visible_line.getPosition() )
|
||
{
|
||
if ( last_visible_line.getPosition() + distance < element_count )
|
||
{
|
||
first_visible_line += distance;
|
||
last_visible_line += distance;
|
||
}
|
||
else
|
||
{
|
||
const int differenz = element_count - last_visible_line.getPosition() - 1;
|
||
first_visible_line += differenz;
|
||
last_visible_line += differenz;
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::stepBackward (int distance)
|
||
{
|
||
if ( itemlist.empty() || current_iter.getPosition() == 0 )
|
||
return;
|
||
|
||
if ( current_iter.getPosition() - distance >= 0 )
|
||
{
|
||
current_iter -= distance;
|
||
}
|
||
else
|
||
{
|
||
current_iter -= current_iter.getPosition();
|
||
}
|
||
|
||
if ( current_iter.getPosition() < first_visible_line.getPosition() )
|
||
{
|
||
if ( first_visible_line.getPosition() - distance >= 0 )
|
||
{
|
||
first_visible_line -= distance;
|
||
last_visible_line -= distance;
|
||
}
|
||
else
|
||
{
|
||
const int difference = first_visible_line.getPosition();
|
||
first_visible_line -= difference;
|
||
last_visible_line -= difference;
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::scrollToX (int x)
|
||
{
|
||
if ( xoffset == x )
|
||
return;
|
||
|
||
xoffset = x;
|
||
const int xoffset_end = int(max_line_width) - int(getClientWidth());
|
||
|
||
if ( xoffset > xoffset_end )
|
||
xoffset = xoffset_end;
|
||
|
||
if ( xoffset < 0 )
|
||
xoffset = 0;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::scrollToY (int y)
|
||
{
|
||
const int pagesize = int(getClientHeight()) - 1;
|
||
const auto element_count = int(getCount());
|
||
|
||
if ( first_visible_line.getPosition() == y )
|
||
return;
|
||
|
||
// Save relative position from the top line
|
||
const int ry = current_iter.getPosition() - first_visible_line.getPosition();
|
||
|
||
if ( y + pagesize <= element_count )
|
||
{
|
||
first_visible_line = itemlist.begin();
|
||
first_visible_line += y;
|
||
setRelativePosition (ry);
|
||
last_visible_line = first_visible_line;
|
||
last_visible_line += pagesize;
|
||
}
|
||
else
|
||
{
|
||
const int differenz = element_count - last_visible_line.getPosition() - 1;
|
||
current_iter += differenz;
|
||
first_visible_line += differenz;
|
||
last_visible_line += differenz;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::scrollTo (int x, int y)
|
||
{
|
||
scrollToX(x);
|
||
scrollToY(y);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::scrollBy (int dx, int dy)
|
||
{
|
||
scrollToX(xoffset + dx);
|
||
|
||
if ( dy > 0 )
|
||
stepForward(dy);
|
||
|
||
if ( dy < 0 )
|
||
stepBackward(-dy);
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::cb_vbarChange (const FWidget*)
|
||
{
|
||
const FScrollbar::ScrollType scrollType = vbar->getScrollType();
|
||
static constexpr int wheel_distance = 4;
|
||
int distance{1};
|
||
first_line_position_before = first_visible_line.getPosition();
|
||
assert ( scrollType == FScrollbar::ScrollType::None
|
||
|| scrollType == FScrollbar::ScrollType::Jump
|
||
|| scrollType == FScrollbar::ScrollType::StepBackward
|
||
|| scrollType == FScrollbar::ScrollType::StepForward
|
||
|| scrollType == FScrollbar::ScrollType::PageBackward
|
||
|| scrollType == FScrollbar::ScrollType::PageForward
|
||
|| scrollType == FScrollbar::ScrollType::WheelUp
|
||
|| scrollType == FScrollbar::ScrollType::WheelDown );
|
||
|
||
switch ( scrollType )
|
||
{
|
||
case FScrollbar::ScrollType::None:
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::PageBackward:
|
||
distance = int(getClientHeight());
|
||
// fall through
|
||
case FScrollbar::ScrollType::StepBackward:
|
||
stepBackward(distance);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::PageForward:
|
||
distance = int(getClientHeight());
|
||
// fall through
|
||
case FScrollbar::ScrollType::StepForward:
|
||
stepForward(distance);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::Jump:
|
||
scrollToY (vbar->getValue());
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::WheelUp:
|
||
wheelUp (wheel_distance);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::WheelDown:
|
||
wheelDown (wheel_distance);
|
||
break;
|
||
}
|
||
|
||
if ( isShown() )
|
||
drawList();
|
||
|
||
if ( scrollType >= FScrollbar::ScrollType::StepBackward
|
||
&& scrollType <= FScrollbar::ScrollType::PageForward )
|
||
{
|
||
vbar->setValue (first_visible_line.getPosition());
|
||
|
||
if ( first_line_position_before != first_visible_line.getPosition() )
|
||
vbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
void FListView::cb_hbarChange (const FWidget*)
|
||
{
|
||
const FScrollbar::ScrollType scrollType = hbar->getScrollType();
|
||
static constexpr int wheel_distance = 4;
|
||
int distance{1};
|
||
const int xoffset_before = xoffset;
|
||
assert ( scrollType == FScrollbar::ScrollType::None
|
||
|| scrollType == FScrollbar::ScrollType::Jump
|
||
|| scrollType == FScrollbar::ScrollType::StepBackward
|
||
|| scrollType == FScrollbar::ScrollType::StepForward
|
||
|| scrollType == FScrollbar::ScrollType::PageBackward
|
||
|| scrollType == FScrollbar::ScrollType::PageForward
|
||
|| scrollType == FScrollbar::ScrollType::WheelUp
|
||
|| scrollType == FScrollbar::ScrollType::WheelDown );
|
||
|
||
switch ( scrollType )
|
||
{
|
||
case FScrollbar::ScrollType::None:
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::PageBackward:
|
||
distance = int(getClientWidth());
|
||
// fall through
|
||
case FScrollbar::ScrollType::StepBackward:
|
||
scrollBy (-distance, 0);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::PageForward:
|
||
distance = int(getClientWidth());
|
||
// fall through
|
||
case FScrollbar::ScrollType::StepForward:
|
||
scrollBy (distance, 0);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::Jump:
|
||
scrollToX (hbar->getValue());
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::WheelUp:
|
||
scrollBy (-wheel_distance, 0);
|
||
break;
|
||
|
||
case FScrollbar::ScrollType::WheelDown:
|
||
scrollBy (wheel_distance, 0);
|
||
break;
|
||
}
|
||
|
||
if ( isShown() )
|
||
{
|
||
drawHeadlines();
|
||
drawList();
|
||
}
|
||
|
||
if ( scrollType >= FScrollbar::ScrollType::StepBackward )
|
||
{
|
||
hbar->setValue (xoffset);
|
||
|
||
if ( xoffset_before != xoffset )
|
||
hbar->drawBar();
|
||
|
||
forceTerminalUpdate();
|
||
}
|
||
}
|
||
|
||
} // namespace finalcut
|