[gtkmm] Add Gtk::BitsetConstIter and tests/bitset_iterator



commit 2edb9e9b95a47180ea69fdf4fcfc756187ed6d86
Author: Kjell Ahlstedt <kjellahlstedt gmail com>
Date:   Sun Jul 5 14:48:46 2020 +0200

    Add Gtk::BitsetConstIter and tests/bitset_iterator

 gtk/gtkmm/bitsetconstiter.cc  | 201 ++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkmm/bitsetconstiter.h   |  92 +++++++++++++++++++
 gtk/gtkmm/filelist.am         |   2 +
 gtk/gtkmm/meson.build         |   1 +
 gtk/src/bitset.ccg            |  15 ++++
 gtk/src/bitset.hg             |  13 ++-
 tests/Makefile.am             |   5 +-
 tests/bitset_iterator/main.cc | 105 ++++++++++++++++++++++
 tests/meson.build             |   1 +
 9 files changed, 433 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkmm/bitsetconstiter.cc b/gtk/gtkmm/bitsetconstiter.cc
new file mode 100644
index 00000000..50a997e3
--- /dev/null
+++ b/gtk/gtkmm/bitsetconstiter.cc
@@ -0,0 +1,201 @@
+/* Copyright (C) 2020 The gtkmm Development Team
+ *
+ * This library 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtkmm/bitsetconstiter.h>
+
+namespace Gtk
+{
+
+BitsetConstIter::BitsetConstIter(const GtkBitset* bitset, bool is_end)
+: gobject_(std::make_unique<GtkBitsetIter>()),
+  bitset_(bitset),
+  is_end_(is_end)
+{
+  if (is_end_)
+  {
+    // Initialize gobject_.get() to an invalid iterator.
+    if (gtk_bitset_iter_init_last(gobject_.get(), bitset_, nullptr))
+      gtk_bitset_iter_next(gobject_.get(), nullptr);
+  }
+  else
+    // Point to the first element in the bitset.
+    if (!gtk_bitset_iter_init_first(gobject_.get(), bitset_, nullptr))
+      // The bitset is empty.
+      is_end_ = true;
+}
+
+BitsetConstIter::BitsetConstIter(const BitsetConstIter& other)
+: gobject_(other.gobject_ ? std::make_unique<GtkBitsetIter>() : nullptr),
+  bitset_(other.bitset_),
+  is_end_(other.is_end_)
+{
+  if (other.gobject_)
+  {
+    if (gtk_bitset_iter_is_valid(other.gobject_.get()))
+      gtk_bitset_iter_init_at(gobject_.get(), bitset_,
+        gtk_bitset_iter_get_value(other.gobject_.get()), nullptr);
+    else
+      // other.gobject_.get() exists, but is invalid.
+      // Initialize gobject_.get() to an invalid iterator.
+      if (gtk_bitset_iter_init_last(gobject_.get(), bitset_, nullptr))
+        gtk_bitset_iter_next(gobject_.get(), nullptr);
+  }
+}
+
+BitsetConstIter& BitsetConstIter::operator=(const BitsetConstIter& other)
+{
+  if (&other == this)
+    return *this;
+
+  gobject_ = other.gobject_ ? std::make_unique<GtkBitsetIter>() : nullptr;
+  bitset_ = other.bitset_;
+  is_end_ = other.is_end_;
+
+  if (other.gobject_)
+  {
+    if (gtk_bitset_iter_is_valid(other.gobject_.get()))
+      gtk_bitset_iter_init_at(gobject_.get(), bitset_,
+        gtk_bitset_iter_get_value(other.gobject_.get()), nullptr);
+    else
+      // other.gobject_.get() exists, but is invalid.
+      // Initialize gobject_.get() to an invalid iterator.
+      if (gtk_bitset_iter_init_last(gobject_.get(), bitset_, nullptr))
+        gtk_bitset_iter_next(gobject_.get(), nullptr);
+  }
+  return *this;
+}
+
+BitsetConstIter::BitsetConstIter(BitsetConstIter&& other) noexcept
+: gobject_(std::move(other.gobject_)),
+  bitset_(other.bitset_),
+  is_end_(other.is_end_)
+{
+  other.bitset_ = nullptr;
+  other.is_end_ = true;
+}
+
+BitsetConstIter& BitsetConstIter::operator=(BitsetConstIter&& other) noexcept
+{
+  gobject_ = std::move(other.gobject_);
+  bitset_ = other.bitset_;
+  is_end_ = other.is_end_;
+  other.bitset_ = nullptr;
+  other.is_end_ = true;
+  return *this;
+}
+
+BitsetConstIter& BitsetConstIter::operator++()
+{
+  if (!(gobject_ && bitset_ && !is_end_))
+    return *this;
+
+  if (!gtk_bitset_iter_next(gobject_.get(), nullptr))
+    is_end_ = true;
+
+  return *this;
+}
+
+BitsetConstIter BitsetConstIter::operator++(int)
+{
+  if (!(gobject_ && bitset_ && !is_end_))
+    return *this;
+
+  BitsetConstIter orig(*this);
+
+  if (!gtk_bitset_iter_next(gobject_.get(), nullptr))
+    is_end_ = true;
+
+  return orig;
+}
+
+BitsetConstIter& BitsetConstIter::operator--()
+{
+  if (!(gobject_ && bitset_))
+    return *this;
+
+  if (is_end_)
+  {
+    if (gtk_bitset_iter_init_last(gobject_.get(), bitset_, nullptr))
+      is_end_ = false;
+  }
+  else
+    gtk_bitset_iter_previous(gobject_.get(), nullptr);
+
+  return *this;
+}
+
+BitsetConstIter BitsetConstIter::operator--(int)
+{
+  if (!(gobject_ && bitset_))
+    return *this;
+
+  BitsetConstIter orig(*this);
+
+  if (is_end_)
+  {
+    if (gtk_bitset_iter_init_last(gobject_.get(), bitset_, nullptr))
+      is_end_ = false;
+  }
+  else
+    gtk_bitset_iter_previous(gobject_.get(), nullptr);
+
+  return orig;
+}
+
+BitsetConstIter::reference BitsetConstIter::operator*() const
+{
+  if (!(gobject_ && bitset_ && !is_end_))
+    return 0;
+
+  return gtk_bitset_iter_get_value(gobject_.get());
+}
+
+BitsetConstIter::operator bool() const noexcept
+{
+  if (!(gobject_ && bitset_ && !is_end_))
+    return false;
+
+  return gtk_bitset_iter_is_valid(gobject_.get());
+}
+
+bool BitsetConstIter::equal(const BitsetConstIter& other) const noexcept
+{
+  // By far the most common test for equality or unequality between
+  // iterators is a comparison where one iterator is an end iterator.
+  // First test if the iterators are unequal because one is an end iterator
+  // and the other is not.
+  if (is_end_ != other.is_end_ || bitset_ != other.bitset_)
+    return false;
+
+  if (!gobject_ && !other.gobject_)
+    return true;
+
+  if (!gobject_ || !other.gobject_)
+    return false;
+
+  if (gtk_bitset_iter_is_valid(gobject_.get()) !=
+      gtk_bitset_iter_is_valid(other.gobject_.get()))
+    return false;
+
+  // There is no function that checks the equality of two GtkBitsetIters.
+  // The iterators iterate over a set where no two elements have the
+  // same value. If two iterators point to elements with the same value,
+  // the iterators are equal.
+  return gtk_bitset_iter_get_value(gobject_.get()) ==
+    gtk_bitset_iter_get_value(other.gobject_.get());
+}
+
+} // namespace Gtk
diff --git a/gtk/gtkmm/bitsetconstiter.h b/gtk/gtkmm/bitsetconstiter.h
new file mode 100644
index 00000000..e15e49b0
--- /dev/null
+++ b/gtk/gtkmm/bitsetconstiter.h
@@ -0,0 +1,92 @@
+#ifndef _GTKMM_BITSETCONSTITER_H
+#define _GTKMM_BITSETCONSTITER_H
+
+/* Copyright (C) 2020 The gtkmm Development Team
+ *
+ * This library 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtkmmconfig.h>
+#include <iterator>
+#include <memory>
+#include <gtk/gtk.h>
+
+namespace Gtk
+{
+
+/** Alias: Gtk::Bitset::const_iterator.
+ *
+ * A %BitsetConstIter is a reference to a specific element in a specific bitset.
+ *
+ * This is a const_iterator. There is no way to modify the bitset via an iterator.
+ * For modifying the bitset, use methods in Gtk::Bitset.
+ *
+ * @see Bitset
+ *
+ * @newin{3,98}
+ */
+class GTKMM_API BitsetConstIter
+{
+public:
+  using iterator_category = std::bidirectional_iterator_tag;
+  using value_type        = guint;
+  using difference_type   = int;
+  using reference         = value_type;
+  using pointer           = void;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  BitsetConstIter() {}
+  BitsetConstIter(const GtkBitset* bitset, bool is_end);
+#endif // DOXYGEN_SHOULD_SKIP_THIS
+
+  BitsetConstIter(const BitsetConstIter& other);
+  BitsetConstIter& operator=(const BitsetConstIter& other);
+  BitsetConstIter(BitsetConstIter&& other) noexcept;
+  BitsetConstIter& operator=(BitsetConstIter&& other) noexcept;
+
+  BitsetConstIter& operator++();
+  BitsetConstIter  operator++(int);
+  BitsetConstIter& operator--();
+  BitsetConstIter  operator--(int);
+  reference operator*() const;
+
+  /** Discovers whether the iterator is valid, and not equal to end().
+   * For instance,
+   * @code
+   * if (bitset_iter)
+   *   do_something();
+   * @endcode
+   */
+  explicit operator bool() const noexcept;
+
+  bool equal(const BitsetConstIter& other) const noexcept;
+
+private:
+  std::unique_ptr<GtkBitsetIter> gobject_;
+  // BitsetConstIter does not own a reference to the GtkBitset.
+  const GtkBitset* bitset_ = nullptr;
+  bool is_end_ = true;
+};
+
+/** @relates Gtk::BitsetConstIter */
+inline bool operator==(const BitsetConstIter& lhs, const BitsetConstIter& rhs)
+{ return lhs.equal(rhs); }
+
+/** @relates Gtk::BitsetConstIter */
+inline bool operator!=(const BitsetConstIter& lhs, const BitsetConstIter& rhs)
+{ return !lhs.equal(rhs); }
+
+} // namespace Gtk
+
+#endif /* _GTKMM_BITSETCONSTITER_H */
diff --git a/gtk/gtkmm/filelist.am b/gtk/gtkmm/filelist.am
index bf171ba8..45f27b59 100644
--- a/gtk/gtkmm/filelist.am
+++ b/gtk/gtkmm/filelist.am
@@ -6,6 +6,7 @@ gtkmm_files_built_h  = $(gtkmm_files_used_hg:.hg=.h)
 gtkmm_files_extra_any_cc =             \
        accelerator.cc \
        accelkey.cc                     \
+       bitsetconstiter.cc \
        cellrenderer_generation.cc      \
        listviewtext.cc                 \
        object.cc \
@@ -18,6 +19,7 @@ gtkmm_files_extra_deprecated_cc =
 gtkmm_files_extra_any_h =                      \
        accelerator.h \
        accelkey.h                      \
+       bitsetconstiter.h \
        cellrenderer_generation.h       \
        listviewtext.h                  \
        object.h \
diff --git a/gtk/gtkmm/meson.build b/gtk/gtkmm/meson.build
index f5291dbb..887ccb3f 100644
--- a/gtk/gtkmm/meson.build
+++ b/gtk/gtkmm/meson.build
@@ -255,6 +255,7 @@ gtkmm_deprecated_hg_ccg_basenames = []
 gtkmm_extra_any_h_cc_basenames = [
   'accelerator',
   'accelkey',
+  'bitsetconstiter',
   'cellrenderer_generation',
   'listviewtext',
   'object',
diff --git a/gtk/src/bitset.ccg b/gtk/src/bitset.ccg
index 57b87fdc..729a8e71 100644
--- a/gtk/src/bitset.ccg
+++ b/gtk/src/bitset.ccg
@@ -15,3 +15,18 @@
  */
 
 #include <gtk/gtk.h>
+
+namespace Gtk
+{
+
+Bitset::const_iterator Bitset::begin() const
+{
+  return BitsetConstIter(gobj(), false);
+}
+
+Bitset::const_iterator Bitset::end() const
+{
+  return BitsetConstIter(gobj(), true);
+}
+
+} // namespace Gtk
diff --git a/gtk/src/bitset.hg b/gtk/src/bitset.hg
index 8c6e7d44..cdabc887 100644
--- a/gtk/src/bitset.hg
+++ b/gtk/src/bitset.hg
@@ -19,6 +19,7 @@ _CONFIGINCLUDE(gtkmmconfig.h)
 _DEFS(gtkmm,gtk)
 
 #include <glibmm/refptr.h>
+#include <gtkmm/bitsetconstiter.h>
 
 extern "C" typedef struct _GtkBitset GtkBitset;
 
@@ -38,10 +39,13 @@ namespace Gtk
  * in the set. %Gtk::Bitset also contains various functions to query metadata about
  * the bitset, such as the minimum or maximum values or its size.
  *
+ * The fastest way to iterate values in a bitset is Gtk::BitsetConstIter which
+ * allows quick iteration of all the values in a bitset.
+ *
  * The main use case for %Gtk::Bitset is implementing complex selections for
  * Gtk::SelectionModel.
  *
- * @see SelectionModel
+ * @see SelectionModel, BitsetConstIter
  *
  * @newin{3,98}
  */
@@ -53,6 +57,13 @@ class GTKMM_API Bitset final
   _IGNORE(gtk_bitset_ref, gtk_bitset_unref)
 
 public:
+  using const_iterator = BitsetConstIter;
+
+  const_iterator begin() const;
+  const_iterator end() const;
+  const_iterator cbegin() const { return begin(); }
+  const_iterator cend() const { return end(); }
+
   _WRAP_METHOD(static Glib::RefPtr<Bitset> create(), gtk_bitset_new_empty)
 
   _WRAP_METHOD(bool contains(guint value) const, gtk_bitset_contains)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e15d3111..effb39ef 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -18,6 +18,7 @@
 AUTOMAKE_OPTIONS = subdir-objects
 
 check_PROGRAMS =                       \
+       bitset_iterator/test \
        builder/test            \
        child_widget/test               \
        child_widget2/test              \
@@ -33,10 +34,12 @@ check_PROGRAMS =                    \
        tree_model_iterator/test \
        wrap_existing/test
 
-TESTS =        object_move/test \
+TESTS = bitset_iterator/test \
+       object_move/test \
   gdk_rgba/test \
   test_validate_docs_xhtml.sh
 
+bitset_iterator_test_SOURCES       = bitset_iterator/main.cc
 builder_test_SOURCES               = builder/main.cc
 child_widget_test_SOURCES          = child_widget/main.cc      \
                                      child_widget/testwindow.cc        \
diff --git a/tests/bitset_iterator/main.cc b/tests/bitset_iterator/main.cc
new file mode 100644
index 00000000..12149980
--- /dev/null
+++ b/tests/bitset_iterator/main.cc
@@ -0,0 +1,105 @@
+#include <gtkmm.h>
+#include <iostream>
+#include <set>
+
+namespace
+{
+
+bool success = true;
+
+void test_traversal1()
+{
+  auto bitset = Gtk::Bitset::create();
+  bitset->add_range(200, 5); // Add 200, 201, 202, 203, 204
+  bitset->add(42);
+
+  // A std::set with the same elements as bitset.
+  std::set<guint> stdset{ 42, 200, 201, 202, 203, 204 };
+
+  for (auto i : *bitset)
+  {
+    if (!stdset.erase(i))
+    {
+      success = false;
+      std::cerr << "test_traversal1(): Element " << i << " not in stdset\n";
+    }
+  }
+  if (!stdset.empty())
+  {
+    success = false;
+    std::cerr << "test_traversal1(): stdset.size() = " << stdset.size() << " (not empty)\n";
+  }
+}
+
+void test_traversal2()
+{
+  auto bitset = Gtk::Bitset::create();
+  bitset->add_range(200, 5); // Add 200, 201, 202, 203, 204
+  bitset->add(42);
+
+  // A std::set with the same elements as bitset.
+  std::set<guint> stdset{ 42, 200, 201, 202, 203, 204 };
+
+  const auto end = bitset->end();
+  for (auto it = bitset->begin(); it != end; ++it)
+  {
+    if (!stdset.erase(*it))
+    {
+      success = false;
+      std::cerr << "test_traversal2(): Element " << *it << " not in stdset\n";
+    }
+  }
+  if (!stdset.empty())
+  {
+    success = false;
+    std::cerr << "test_traversal2(): stdset.size() = " << stdset.size() << " (not empty)\n";
+  }
+}
+
+void test_copy()
+{
+  auto bitset = Gtk::Bitset::create();
+  bitset->add_range(200, 5); // Add 200, 201, 202, 203, 204
+  bitset->add(42);
+
+  auto iter1  = bitset->end();
+  if (iter1)
+  {
+    success = false;
+    std::cerr << "test_copy(): iter1 is valid\n";
+  }
+  --iter1; // Shall point to the last element.
+  if (!iter1)
+  {
+    success = false;
+    std::cerr << "test_copy(): iter1 is not valid\n";
+  }
+  auto iter2 = iter1;
+  if (!iter1 || !iter2)
+  {
+    success = false;
+    std::cerr << "test_copy(): (bool)iter1 = " << static_cast<bool>(iter1)
+              << " (bool)iter2 = " << static_cast<bool>(iter2) << "\n";
+  }
+  auto iter3 = std::move(iter1);
+  if (iter1 || !iter3)
+  {
+    success = false;
+    std::cerr << "test_copy(): (bool)iter1 = " << static_cast<bool>(iter1)
+              << " (bool)iter3 = " << static_cast<bool>(iter3) << "\n";
+  }
+}
+
+} // anonymous namespace
+
+int main(int /* argc */, char** /* argv */)
+{
+  gtk_init();
+  Gtk::Main::init_gtkmm_internals();
+
+  test_traversal1();
+  test_traversal2();
+  test_copy();
+
+  return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 23d44cf5..5bb08722 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -5,6 +5,7 @@
 
 test_programs = [
 # [[dir-name], exe-name, [sources], GUI, execute]
+  [['bitset_iterator'], 'test', ['main.cc'], false, true],
   [['builder'], 'test', ['main.cc'], true, false],
   [['child_widget'], 'test', ['main.cc', 'testwindow.cc'], true, false],
   [['child_widget2'], 'test', ['main.cc'], true, false],


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]