From 851e3db49c14691da2eb5867a96ad20656df5a06 Mon Sep 17 00:00:00 2001 From: Markus Gans Date: Fri, 28 Sep 2018 06:45:02 +0200 Subject: [PATCH] FListView now has the ability to sort by columns --- ChangeLog | 3 + examples/listview.cpp | 12 ++ src/ffiledialog.cpp | 2 +- src/flistview.cpp | 280 ++++++++++++++++++++++++++++++++++ src/include/final/fc.h | 15 ++ src/include/final/flistview.h | 42 +++++ src/include/final/fobject.h | 5 + test/fobject-test.cpp | 2 +- 8 files changed, 359 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6021715f..36f4a1e8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2018-09-28 Markus Gans + * FListView now has the ability to sort by columns + 2018-09-27 Markus Gans * Move time event processing from FApplication to FObject diff --git a/examples/listview.cpp b/examples/listview.cpp index b93f0eac..c75bf625 100644 --- a/examples/listview.cpp +++ b/examples/listview.cpp @@ -80,6 +80,18 @@ Listview::Listview (finalcut::FWidget* parent) listView->setColumnAlignment (4, finalcut::fc::alignRight); listView->setColumnAlignment (5, finalcut::fc::alignRight); + // Set the type of sorting + listView->setColumnSortType (1, finalcut::fc::by_name); + listView->setColumnSortType (2, finalcut::fc::by_name); + listView->setColumnSortType (3, finalcut::fc::by_number); + listView->setColumnSortType (4, finalcut::fc::by_number); + listView->setColumnSortType (5, finalcut::fc::by_number); + + // Sort in ascending order by the 1st column + listView->setColumnSort (1, finalcut::fc::ascending); + // The sorting occurs later automatically at insert(). + // Otherwise you could start the sorting directly with sort() + // Populate FListView with a list of items populate (listView); diff --git a/src/ffiledialog.cpp b/src/ffiledialog.cpp index 3e196224..e2423822 100644 --- a/src/ffiledialog.cpp +++ b/src/ffiledialog.cpp @@ -33,7 +33,7 @@ bool sortByName ( const FFileDialog::dir_entry& lhs , const FFileDialog::dir_entry& rhs ) { // lhs < rhs - return bool(strcasecmp(lhs.name, rhs.name) < 0); + return bool( strcasecmp(lhs.name, rhs.name) < 0 ); } //---------------------------------------------------------------------- diff --git a/src/flistview.cpp b/src/flistview.cpp index d32eb156..6a88b953 100644 --- a/src/flistview.cpp +++ b/src/flistview.cpp @@ -34,6 +34,118 @@ namespace finalcut // Static class attribute FObject::FObjectIterator FListView::null_iter; +// Function prototypes +long 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 +//---------------------------------------------------------------------- +long firstNumberFromString (const FString& str) +{ + const FString::iterator last = str.end(); + FString::iterator iter = str.begin(); + FString::iterator first_pos; + FString::iterator last_pos; + long number; + + while ( iter != last ) + { + if ( wchar_t(*iter) >= L'0' && wchar_t(*iter) <= L'9' ) + break; + + ++iter; + } + + first_pos = iter; + + if ( first_pos == last ) + return 0; + + while ( iter != last ) + { + if ( wchar_t(*iter) < L'0' || wchar_t(*iter) > L'9' ) + break; + + ++iter; + } + + last_pos = iter; + + if ( last_pos == last ) + return 0; + + uInt pos = uInt(std::distance(str.begin(), first_pos)) + 1; + uInt length = uInt(std::distance(first_pos, last_pos)); + const FString num_str = str.mid(pos, length); + + try + { + number = num_str.toLong(); + } + catch (const std::exception&) + { + return 0; + } + + return number; +} + +//---------------------------------------------------------------------- +bool sortAscendingByName (const FObject* lhs, const FObject* rhs) +{ + const FListViewItem* l_item = static_cast(lhs); + const FListViewItem* r_item = static_cast(rhs); + const int column = l_item->getSortColumn(); + const FString l_string = l_item->getText(column); + const FString r_string = r_item->getText(column); + + // lhs < rhs + return bool( strcasecmp(l_string.c_str(), r_string.c_str()) < 0 ); +} + +//---------------------------------------------------------------------- +bool sortDescendingByName (const FObject* lhs, const FObject* rhs) +{ + const FListViewItem* l_item = static_cast(lhs); + const FListViewItem* r_item = static_cast(rhs); + const int column = l_item->getSortColumn(); + const FString l_string = l_item->getText(column); + const FString r_string = r_item->getText(column); + + // lhs > rhs + return bool( strcasecmp(l_string.c_str(), r_string.c_str()) > 0 ); +} + +//---------------------------------------------------------------------- +bool sortAscendingByNumber (const FObject* lhs, const FObject* rhs) +{ + const FListViewItem* l_item = static_cast(lhs); + const FListViewItem* r_item = static_cast(rhs); + const int column = l_item->getSortColumn(); + const long l_number = firstNumberFromString(l_item->getText(column)); + const long r_number = firstNumberFromString(r_item->getText(column)); + + // lhs < rhs + return bool( l_number < r_number ); +} + +//---------------------------------------------------------------------- +bool sortDescendingByNumber (const FObject* lhs, const FObject* rhs) +{ + const FListViewItem* l_item = static_cast(lhs); + const FListViewItem* r_item = static_cast(rhs); + const int column = l_item->getSortColumn(); + const long l_number = firstNumberFromString(l_item->getText(column)); + const long r_number = firstNumberFromString(r_item->getText(column)); + + // lhs > rhs + return bool( l_number > r_number ); +} + + //---------------------------------------------------------------------- // class FListViewItem //---------------------------------------------------------------------- @@ -44,6 +156,7 @@ FListViewItem::FListViewItem (const FListViewItem& item) : FObject(item.getParent()) , column_list(item.column_list) , data_pointer(item.data_pointer) + , root() , visible_lines(1) , expandable(false) , is_expand(false) @@ -68,6 +181,7 @@ FListViewItem::FListViewItem (FObjectIterator parent_iter) : FObject((*parent_iter)->getParent()) , column_list() , data_pointer(0) + , root() , visible_lines(1) , expandable(false) , is_expand(false) @@ -82,6 +196,7 @@ FListViewItem::FListViewItem ( const FStringList& cols : FObject(0) , column_list(cols) , data_pointer(data) + , root() , visible_lines(1) , expandable(false) , is_expand(false) @@ -99,6 +214,16 @@ FListViewItem::~FListViewItem() // destructor // public methods of FListViewItem +//---------------------------------------------------------------------- +int FListViewItem::getSortColumn() const +{ + if ( ! *root ) + return -1; + + FListView* root_obj = static_cast(*root); + return root_obj->getSortColumn(); +} + //---------------------------------------------------------------------- FString FListViewItem::getText (int column) const { @@ -208,11 +333,40 @@ void FListViewItem::collapse() } // private methods of FListView +//---------------------------------------------------------------------- +template +void FListViewItem::sort (Compare cmp) +{ + if ( ! expandable ) + return; + + // Sort the top level + FObject::FObjectList& children_list = getChildren(); + + if ( ! children_list.empty() ) + children_list.sort(cmp); + + // Sort the sublevels + FListViewIterator iter = children_list.begin(); + + while ( iter != children_list.end() ) + { + if ( *iter ) + { + FListViewItem* item = static_cast(*iter); + item->sort(cmp); + } + + ++iter; + } +} + //---------------------------------------------------------------------- FObject::FObjectIterator FListViewItem::appendItem (FListViewItem* child) { expandable = true; resetVisibleLineCounter(); + child->root = root; addChild (child); // Return iterator to child/last element return --FObject::end(); @@ -451,6 +605,11 @@ FListView::FListView (FWidget* parent) , xoffset(0) , nf_offset(0) , max_line_width(1) + , sort_column(-1) + , sort_type() + , sort_order(fc::unsorted) + , user_defined_ascending(0) + , user_defined_descending(0) { init(); } @@ -504,6 +663,24 @@ FString FListView::getColumnText (int column) const return header[uInt(column)].name; } +//---------------------------------------------------------------------- +fc::sorting_type FListView::getColumnSortType (int column) const +{ + fc::sorting_type type; + std::size_t size = uInt(column); + + try + { + type = sort_type.at(size); + } + catch (const std::out_of_range&) + { + type = fc::unknown; + } + + return type; +} + //---------------------------------------------------------------------- void FListView::setGeometry (int x, int y, int w, int h, bool adjust) { @@ -556,6 +733,34 @@ void FListView::setColumnText (int column, const FString& label) header[uInt(column)].name = label; } +//---------------------------------------------------------------------- +void FListView::setColumnSortType (int column, fc::sorting_type type) +{ + // Sets the sort type by which the list is to be sorted + + if ( column < 1 || header.empty() || column > int(header.size()) ) + return; + + std::size_t size = uInt(column + 1); + + if ( sort_type.empty() || sort_type.size() < size ) + sort_type.resize(size); + + sort_type[uInt(column)] = type; +} + +//---------------------------------------------------------------------- +void FListView::setColumnSort (int column, fc::sorting_order 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) { @@ -649,6 +854,9 @@ FObject::FObjectIterator FListView::insert ( FListViewItem* item first_visible_line = itemlist.begin(); } + // Sort list by a column (only if activated) + sort(); + int element_count = int(getCount()); recalculateVerticalBar (element_count); return item_iter; @@ -700,6 +908,55 @@ FObject::FObjectIterator FListView::insert ( const std::vector& cols return item_iter; } +//---------------------------------------------------------------------- +void FListView::sort() +{ + // Sorts the list view according to the specified setting + + if ( sort_column < 1 && sort_column > int(header.size()) ) + return; + + switch ( getColumnSortType(sort_column) ) + { + case fc::unknown: + case fc::by_name: + if ( sort_order == fc::ascending ) + { + sort (sortAscendingByName); + } + else if ( sort_order == fc::descending ) + { + sort (sortDescendingByName); + } + break; + + case fc::by_number: + if ( sort_order == fc::ascending ) + { + sort (sortAscendingByNumber); + } + else if ( sort_order == fc::descending ) + { + sort (sortDescendingByNumber); + } + break; + + case fc::user_defined: + if ( sort_order == fc::ascending && user_defined_ascending ) + { + sort (user_defined_ascending); + } + else if ( sort_order == fc::descending && user_defined_descending ) + { + sort (user_defined_descending); + } + break; + } + + current_iter = itemlist.begin(); + first_visible_line = itemlist.begin(); +} + //---------------------------------------------------------------------- void FListView::onKeyPress (FKeyEvent* ev) { @@ -1186,6 +1443,28 @@ void FListView::init() setRightPadding(1 + nf_offset); } +//---------------------------------------------------------------------- +template +void FListView::sort (Compare cmp) +{ + // Sort the top level + itemlist.sort(cmp); + + // Sort the sublevels + FListViewIterator iter = itemlist.begin(); + + while ( iter != itemlist.end() ) + { + if ( *iter ) + { + FListViewItem* item = static_cast(*iter); + item->sort(cmp); + } + + ++iter; + } +} + //---------------------------------------------------------------------- uInt FListView::getAlignOffset ( fc::text_alignment align , uInt txt_length @@ -1748,6 +2027,7 @@ void FListView::stopDragScroll() //---------------------------------------------------------------------- FObject::FObjectIterator FListView::appendItem (FListViewItem* item) { + item->root = root; addChild (item); itemlist.push_back (item); return --itemlist.end(); diff --git a/src/include/final/fc.h b/src/include/final/fc.h index b3b5ef18..626ed874 100644 --- a/src/include/final/fc.h +++ b/src/include/final/fc.h @@ -1039,6 +1039,21 @@ enum sides left = 3 }; +enum sorting_type +{ + by_name, + by_number, + user_defined, + unknown +}; + +enum sorting_order +{ + ascending, + descending, + unsorted +}; + enum brackets_type { NoBrackets = 0, diff --git a/src/include/final/flistview.h b/src/include/final/flistview.h index d4700f1e..5fdf3c40 100644 --- a/src/include/final/flistview.h +++ b/src/include/final/flistview.h @@ -92,6 +92,7 @@ class FListViewItem : public FObject // Accessors const char* getClassName() const; uInt getColumnCount() const; + int getSortColumn() const; FString getText (int) const; FWidget::data_ptr getData() const; uInt getDepth() const; @@ -114,6 +115,8 @@ class FListViewItem : public FObject bool isExpandable() const; // Methods + template + void sort (Compare); FObjectIterator appendItem (FListViewItem*); void replaceControlCodes(); int getVisibleLines(); @@ -122,6 +125,7 @@ class FListViewItem : public FObject // Data Members FStringList column_list; FWidget::data_ptr data_pointer; + FObjectIterator root; int visible_lines; bool expandable; bool is_expand; @@ -259,12 +263,23 @@ class FListView : public FWidget uInt getCount(); fc::text_alignment getColumnAlignment (int) const; FString getColumnText (int) const; + fc::sorting_type getColumnSortType (int) const; + fc::sorting_order getSortOrder() const; + int getSortColumn() const; FListViewItem* getCurrentItem(); // Mutators virtual void setGeometry (int, int, int, int, bool = true); void setColumnAlignment (int, fc::text_alignment); void setColumnText (int, const FString&); + void setColumnSortType (int,fc::sorting_type \ + = fc::by_name); + void setColumnSort (int, fc::sorting_order \ + = fc::ascending); + template + void setUserAscendingCompare (Compare); + template + void setUserDescendingCompare (Compare); bool setTreeView (bool); bool setTreeView(); bool unsetTreeView(); @@ -289,6 +304,7 @@ class FListView : public FWidget , FObjectIterator ); FObjectIterator beginOfList(); FObjectIterator endOfList(); + virtual void sort(); // Event handlers virtual void onKeyPress (FKeyEvent*); @@ -313,6 +329,7 @@ class FListView : public FWidget // Typedef struct Header; // forward declaration typedef std::vector
headerItems; + typedef std::vector sortTypes; // Constants static const int USE_MAX_SIZE = -1; @@ -325,6 +342,8 @@ class FListView : public FWidget // Methods void init(); + template + void sort (Compare); uInt getAlignOffset (fc::text_alignment, uInt, uInt); virtual void draw(); void drawColumnLabels(); @@ -389,6 +408,11 @@ class FListView : public FWidget int xoffset; int nf_offset; int max_line_width; + int sort_column; + sortTypes sort_type; + fc::sorting_order sort_order; + bool (*user_defined_ascending) (const FObject*, const FObject*); + bool (*user_defined_descending) (const FObject*, const FObject*); // Friend class friend class FListViewItem; @@ -425,10 +449,28 @@ struct FListView::Header inline const char* FListView::getClassName() const { return "FListView"; } +//---------------------------------------------------------------------- +inline fc::sorting_order FListView::getSortOrder() const +{ return sort_order; } + +//---------------------------------------------------------------------- +inline int FListView::getSortColumn() const +{ return sort_column; } + //---------------------------------------------------------------------- inline FListViewItem* FListView::getCurrentItem() { return static_cast(*current_iter); } +//---------------------------------------------------------------------- +template +inline void FListView::setUserAscendingCompare (Compare cmp) +{ user_defined_ascending = cmp; } + +//---------------------------------------------------------------------- +template +inline void FListView::setUserDescendingCompare (Compare cmp) +{ user_defined_descending = cmp; } + //---------------------------------------------------------------------- inline bool FListView::setTreeView (bool on) { return tree_view = ( on ) ? true : false; } diff --git a/src/include/final/fobject.h b/src/include/final/fobject.h index 64793e3c..3ebb31af 100644 --- a/src/include/final/fobject.h +++ b/src/include/final/fobject.h @@ -74,6 +74,7 @@ class FObject virtual const char* getClassName() const; FObject* getParent() const; FObject* getChild (int) const; + FObjectList& getChildren(); const FObjectList& getChildren() const; int numOfChildren() const; FObjectIterator begin(); @@ -156,6 +157,10 @@ inline const char* FObject::getClassName() const inline FObject* FObject::getParent() const { return parent_obj; } +//---------------------------------------------------------------------- +inline FObject::FObjectList& FObject::getChildren() +{ return children_list; } + //---------------------------------------------------------------------- inline const FObject::FObjectList& FObject::getChildren() const { return children_list; } diff --git a/test/fobject-test.cpp b/test/fobject-test.cpp index 009e4e6f..9ce2a57e 100644 --- a/test/fobject-test.cpp +++ b/test/fobject-test.cpp @@ -137,7 +137,7 @@ void FObjectTest::noArgumentTest() CPPUNIT_ASSERT ( o1.getChild(0) == 0 ); CPPUNIT_ASSERT ( o1.getChild(1) == 0 ); CPPUNIT_ASSERT ( o1.numOfChildren() == 0 ); - const finalcut::FObject::FObjectList& children_list = o1.getChildren(); + finalcut::FObject::FObjectList& children_list = o1.getChildren(); CPPUNIT_ASSERT ( children_list.begin() == o1.begin() ); CPPUNIT_ASSERT ( children_list.begin() == o1.end() ); CPPUNIT_ASSERT ( children_list.end() == o1.begin() );