From 5407e8346b241d7fb7868f0fbe70fd37b1d08108 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 30 Jul 2018 10:24:18 +0200 Subject: [PATCH] modern-gtk2: Introduce OptionComboBox class (!17) Gtk::OptionMenu is a combobox type widget that is constructed from a Gtk::Menu rather than a Gtk::TreeModel. However Gtk::OptionMenu was deprecated in gtkmm 2.4.1. In GParted the Gtk::OptionMenu widget is used for: - partition alignment combobox - partition type combobox - file system combobox While they consist only of text we cannot use Gtk::ComboBoxText because it doesn't expose functionality in its interface to make items inactive. Create OptionComboBox helper class that builds a combobox consisting of only text items, much like Gtk::ComboBoxText, but has the added functionality to set items as inactive. References: https://developer.gnome.org/gtkmm/2.24/classGtk_1_1OptionMenu.html#details https://gitlab.gnome.org/GNOME/gtkmm/blob/GTKMM_2_10_1/ChangeLog#L3515 https://gitlab.gnome.org/GNOME/gtkmm/commit/bba503b0473413e474d9b6d297226479d29fd47f https://developer.gnome.org/gtkmm/2.24/classGtk_1_1ComboBoxText.html Closes !17 - Gtk2 modernisation --- include/Makefile.am | 1 + include/OptionComboBox.h | 195 ++++++++++++++++++++ po/POTFILES.in | 1 + src/Makefile.am | 1 + src/OptionComboBox.cc | 377 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 575 insertions(+) create mode 100644 include/OptionComboBox.h create mode 100644 src/OptionComboBox.cc diff --git a/include/Makefile.am b/include/Makefile.am index 18290bba..1601a5bf 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -39,6 +39,7 @@ EXTRA_DIST = \ OperationLabelFileSystem.h \ OperationNamePartition.h \ OperationResizeMove.h \ + OptionComboBox.h \ Partition.h \ PartitionLUKS.h \ PartitionVector.h \ diff --git a/include/OptionComboBox.h b/include/OptionComboBox.h new file mode 100644 index 00000000..c7b6d031 --- /dev/null +++ b/include/OptionComboBox.h @@ -0,0 +1,195 @@ +/* Copyright (C) 2018 Luca Bacci + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef GPARTED_OPTIONCOMBOBOX_H +#define GPARTED_OPTIONCOMBOBOX_H + + +#include +#include +#include +#include + + +namespace GParted +{ + +class OptionStore_Item; +class OptionStore_Item_Collection; +typedef const OptionStore_Item OptionStore_Item_Const; +typedef const OptionStore_Item_Collection OptionStore_Item_Collection_Const; +class OptionStore; + + +// OptionStore_Item is the class that represents a single item (row). +// It lets you manipulate the data of a single item with a convenient +// high level interface. +class OptionStore_Item +{ +public: + explicit OptionStore_Item(const Glib::RefPtr& ref_model, + const Gtk::TreeModel::iterator& iter); + explicit OptionStore_Item(const Glib::RefPtr& ref_model, + const Gtk::TreeModel::Path& path); + explicit OptionStore_Item(const Gtk::TreeModel::RowReference& rowreference); + + void set(const Glib::ustring& text, bool sensitive = true); + void set_text(const Glib::ustring& text); + void set_sensitive(bool sensitive = true); + + Glib::ustring text() const; + bool sensitive() const; + + operator Gtk::TreeModel::iterator() + { + return to_iterator_(); + } + operator Gtk::TreeModel::const_iterator() const + { + return to_iterator_(); + } + +private: + Gtk::TreeModel::iterator to_iterator_() const; + friend class OptionStore_Item_Collection; + +private: + mutable Gtk::TreeModel::RowReference m_rowreference; +}; + + +// OptionStore_Item_Collection lets you operate on OptionStore model +// with an STL-like container interface. You can act on it like a +// sequence of items. +// Usually you get an OptionStore_Item_Collection by calling the items() +// method on a OptionStore model. You can also call the items() method +// on an OptionComboBox; it simply redirects the call to the associated +// OptionStore model. +class OptionStore_Item_Collection +{ +public: + explicit OptionStore_Item_Collection(const Glib::RefPtr& ref_model); + + void push_front(const Glib::ustring& text, bool sensitive = true); + void push_back(const Glib::ustring& text, bool sensitive = true); + + void insert(const OptionStore_Item& item, + const Glib::ustring& text, + bool sensitive = true); + void insert(unsigned position, + const Glib::ustring& text, + bool sensitive = true); + + void pop_front(); + void pop_back(); + + void erase(const OptionStore_Item& item); + void erase(unsigned position); + void clear(); + + OptionStore_Item front(); + OptionStore_Item back(); + + OptionStore_Item at(unsigned position); + OptionStore_Item operator[](unsigned position); + + unsigned size() const; + + OptionStore_Item_Const front() const; + OptionStore_Item_Const back() const; + + OptionStore_Item_Const at(unsigned position) const; + OptionStore_Item_Const operator[] (unsigned position) const; + +private: + Glib::RefPtr m_ref_model; +}; + + +// OptionStore is the model that backs the data for OptionComboBox. +// It's a specialized Gtk::ListStore with a string slot for row text +// and a boolean slot for row sensitivity (if it's false the row is +// "grayed out"). +// Although you can call any Gtk::ListStore method (from which +// OptionStore inherits) it is usually convenient to call the items() +// method to get a OptionStore_Item_Collection object that provides +// a nice, high level interface. +class OptionStore + : public Gtk::ListStore +{ +public: + typedef Gtk::ListStore Base; + + typedef OptionStore_Item Item; + typedef OptionStore_Item_Const Item_Const; + typedef OptionStore_Item_Collection Item_Collection; + typedef OptionStore_Item_Collection_Const Item_Collection_Const; + + static + Glib::RefPtr create(); + + Item_Collection items(); + Item_Collection_Const items() const; + + struct Slots + { + static Gtk::TreeModelColumn text; + static Gtk::TreeModelColumn sensitive; + + private: + static Gtk::TreeModel::ColumnRecord record_; + friend class OptionStore; + }; + +protected: + explicit OptionStore(); +}; + + +// OptionComboBox is a specialized ComboBox that shows a list of rows, +// some of which may be selectively grayed out. It is commonly used to +// display a list of options where not all of the options can be +// selected in all cases. +class OptionComboBox + : public Gtk::ComboBox +{ +public: + typedef Gtk::ComboBox Base; + + explicit OptionComboBox(); + explicit OptionComboBox(const Glib::RefPtr& ref_model); + + void set_model(const Glib::RefPtr& ref_model); + + Glib::RefPtr get_model(); + OptionStore_Item_Collection items(); + OptionStore_Item get_active(); + + Glib::RefPtr get_model() const; + OptionStore_Item_Collection_Const items() const; + OptionStore_Item_Const get_active() const; + +protected: + void pack_cell_renderers(); + +protected: + Glib::RefPtr m_ref_model; +}; + + +}//GParted + +#endif /* GPARTED_OPTIONCOMBOBOX_H */ diff --git a/po/POTFILES.in b/po/POTFILES.in index f7ede23b..9d08b7db 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -36,6 +36,7 @@ src/OperationFormat.cc src/OperationLabelFileSystem.cc src/OperationNamePartition.cc src/OperationResizeMove.cc +src/OptionComboBox.cc src/Partition.cc src/PartitionLUKS.cc src/PartitionVector.cc diff --git a/src/Makefile.am b/src/Makefile.am index 099adbd4..33f8c5bd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,7 @@ gpartedbin_SOURCES = \ OperationLabelFileSystem.cc \ OperationNamePartition.cc \ OperationResizeMove.cc \ + OptionComboBox.cc \ Partition.cc \ PartitionLUKS.cc \ PartitionVector.cc \ diff --git a/src/OptionComboBox.cc b/src/OptionComboBox.cc new file mode 100644 index 00000000..8e8f70ed --- /dev/null +++ b/src/OptionComboBox.cc @@ -0,0 +1,377 @@ +/* Copyright (C) 2018 Luca Bacci + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "OptionComboBox.h" + +#include +#include +#include + + +namespace GParted +{ + +// NOTE: As stated in Gtkmm3 documentation, slots can be shared for all model instances. +// See: Class Reference for Gtk::TreeModelColumn and Gtk::TreeModelColumnRecord. +// https://developer.gnome.org/gtkmm/3.22/classGtk_1_1TreeModelColumnRecord.html#details +Gtk::TreeModelColumn OptionStore::Slots::text; +Gtk::TreeModelColumn OptionStore::Slots::sensitive; + +Gtk::TreeModel::ColumnRecord OptionStore::Slots::record_; + + +OptionStore_Item::OptionStore_Item(const Glib::RefPtr& ref_model, + const Gtk::TreeModel::iterator& iter) + : m_rowreference(ref_model, ref_model->get_path(iter)) +{} + + +OptionStore_Item::OptionStore_Item(const Glib::RefPtr& ref_model, + const Gtk::TreeModel::Path& path) + : m_rowreference(ref_model, path) +{} + + +OptionStore_Item::OptionStore_Item(const Gtk::TreeModel::RowReference& rowreference) + : m_rowreference(rowreference) +{} + + +void OptionStore_Item::set(const Glib::ustring& text, + bool sensitive) +{ + Gtk::TreeModel::iterator iter = *this; + (*iter)[OptionStore::Slots::text] = text; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +void OptionStore_Item::set_text(const Glib::ustring& text) +{ + Gtk::TreeModel::iterator iter = *this; + (*iter)[OptionStore::Slots::text] = text; +} + + +void OptionStore_Item::set_sensitive(bool sensitive) +{ + Gtk::TreeModel::iterator iter = *this; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +Glib::ustring OptionStore_Item::text() const +{ + Gtk::TreeModel::const_iterator iter = *this; + return (*iter)[OptionStore::Slots::text]; +} + + +bool OptionStore_Item::sensitive() const +{ + Gtk::TreeModel::const_iterator iter = *this; + return (*iter)[OptionStore::Slots::sensitive]; +} + + +Gtk::TreeModel::iterator OptionStore_Item::to_iterator_() const +{ + Gtk::TreeModel::Path path = m_rowreference.get_path(); + + Glib::RefPtr ref_model = + Glib::RefPtr::cast_dynamic(m_rowreference.get_model()); + + if (!ref_model) + throw std::runtime_error ("incompatible Gtk::TreeModel type."); + + return ref_model->get_iter(path); +} + + +OptionStore_Item_Collection::OptionStore_Item_Collection(const Glib::RefPtr& ref_model) + : m_ref_model(ref_model) +{} + + +void OptionStore_Item_Collection::push_front(const Glib::ustring& text, + bool sensitive) +{ + Gtk::TreeModel::iterator iter = m_ref_model->prepend(); + (*iter)[OptionStore::Slots::text] = text; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +void OptionStore_Item_Collection::push_back(const Glib::ustring& text, + bool sensitive) +{ + Gtk::TreeModel::iterator iter = m_ref_model->append(); + (*iter)[OptionStore::Slots::text] = text; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +void OptionStore_Item_Collection::insert(const OptionStore_Item& item, + const Glib::ustring& text, + bool sensitive) +{ + Gtk::TreeModel::iterator previous_iter = item.to_iterator_(); + Gtk::TreeModel::iterator iter = m_ref_model->insert(previous_iter); + (*iter)[OptionStore::Slots::text] = text; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +void OptionStore_Item_Collection::insert(unsigned position, + const Glib::ustring& text, + bool sensitive ) +{ + Gtk::TreeModel::iterator previous_iter = m_ref_model->children()[position]; + Gtk::TreeModel::iterator iter = m_ref_model->insert(previous_iter); + (*iter)[OptionStore::Slots::text] = text; + (*iter)[OptionStore::Slots::sensitive] = sensitive; +} + + +void OptionStore_Item_Collection::pop_front() +{ + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + m_ref_model->erase(iter); +} + + +void OptionStore_Item_Collection::pop_back() +{ + // NOTE: To get an iterator to the last item we do not use Gtk::TreeNodeChildren::rbegin() + // because Gtk::TreeModel::reverse_iterator is broken and has been deprecated in Gtkmm3. + // https://bugzilla.gnome.org/show_bug.cgi?id=554889 + + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + + for (unsigned i = 1; i < m_ref_model->children().size(); ++i) + ++iter; + + m_ref_model->erase(iter); +} + + +void OptionStore_Item_Collection::erase(const OptionStore_Item& item) +{ + Gtk::TreeModel::iterator iter = item.to_iterator_(); + m_ref_model->erase(iter); +} + + +void OptionStore_Item_Collection::erase(unsigned position) +{ + Gtk::TreeModel::iterator iter = m_ref_model->children()[position]; + m_ref_model->erase(iter); +} + + +void OptionStore_Item_Collection::clear() +{ + m_ref_model->clear(); +} + + +OptionStore_Item OptionStore_Item_Collection::front() +{ + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item OptionStore_Item_Collection::back() +{ + // NOTE: To get an iterator to the last item we do not use Gtk::TreeNodeChildren::rbegin() + // because Gtk::TreeModel::reverse_iterator is broken and has been deprecated in Gtkmm3. + // https://bugzilla.gnome.org/show_bug.cgi?id=554889 + + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + + for (unsigned i = 1; i < m_ref_model->children().size(); ++i) + ++iter; + + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item OptionStore_Item_Collection::at(unsigned position) +{ + Gtk::TreeModel::iterator iter = m_ref_model->children()[position]; + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item OptionStore_Item_Collection::operator[](unsigned position) +{ + Gtk::TreeModel::iterator iter = m_ref_model->children()[position]; + return OptionStore_Item(m_ref_model, iter); +} + + +unsigned OptionStore_Item_Collection::size() const +{ + return m_ref_model->children().size(); +} + + +OptionStore_Item_Const OptionStore_Item_Collection::front() const +{ + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item_Const OptionStore_Item_Collection::back() const +{ + // NOTE: To get an iterator to the last item we do not use Gtk::TreeNodeChildren::rbegin() + // because Gtk::TreeModel::reverse_iterator is broken and has been deprecated in Gtkmm3. + // https://bugzilla.gnome.org/show_bug.cgi?id=554889 + + Gtk::TreeModel::iterator iter = m_ref_model->children().begin(); + + for (unsigned i = 1; i < m_ref_model->children().size(); ++i) + ++iter; + + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item_Const OptionStore_Item_Collection::at(unsigned position) const +{ + Gtk::TreeModel::iterator iter = m_ref_model->children()[position]; + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore_Item_Const OptionStore_Item_Collection::operator[](unsigned position) const +{ + Gtk::TreeModel::iterator iter = m_ref_model->children()[position]; + return OptionStore_Item(m_ref_model, iter); +} + + +OptionStore::OptionStore() + : Glib::ObjectBase("GParted_OptionStore") +{ + if (!Slots::record_.size()) + { + Slots::record_.add(Slots::text); + Slots::record_.add(Slots::sensitive); + } + + set_column_types(Slots::record_); +} + + +Glib::RefPtr OptionStore::create() +{ + return Glib::RefPtr(new OptionStore()); +} + + +OptionStore_Item_Collection OptionStore::items() +{ + return OptionStore_Item_Collection(Glib::RefPtr(this)); +} + + +OptionStore_Item_Collection_Const OptionStore::items() const +{ + OptionStore *this_ = const_cast(this); + + return OptionStore_Item_Collection(Glib::RefPtr(this_)); +} + + +OptionComboBox::OptionComboBox() + : Glib::ObjectBase("GParted_OptionComboBox") +{ + OptionComboBox::set_model(OptionStore::create()); + + pack_cell_renderers(); +} + + +OptionComboBox::OptionComboBox(const Glib::RefPtr& ref_model) + : Glib::ObjectBase("GParted_OptionComboBox") +{ + OptionComboBox::set_model(ref_model); + + pack_cell_renderers(); +} + + +void OptionComboBox::pack_cell_renderers() +{ + Gtk::CellLayout::clear(); + + Gtk::CellRendererText *cell = manage(new Gtk::CellRendererText()); + pack_start(*cell); + add_attribute(*cell, "text", OptionStore::Slots::text); + add_attribute(*cell, "sensitive", OptionStore::Slots::sensitive); +} + + +OptionStore_Item_Collection OptionComboBox::items() +{ + return OptionStore_Item_Collection(m_ref_model); +} + + +OptionStore_Item_Collection_Const OptionComboBox::items() const +{ + return OptionStore_Item_Collection(m_ref_model); +} + + +void OptionComboBox::set_model(const Glib::RefPtr& ref_model) +{ + Base::set_model(ref_model); + m_ref_model = ref_model; +} + + +Glib::RefPtr OptionComboBox::get_model() +{ + return m_ref_model; +} + + +Glib::RefPtr OptionComboBox::get_model() const +{ + return m_ref_model; +} + + +OptionStore_Item OptionComboBox::get_active() +{ + return OptionStore_Item(m_ref_model, Base::get_active()); +} + + +OptionStore_Item_Const OptionComboBox::get_active() const +{ + // NOTE: (for Gtkmm4) having a const_iterator, you can get an iterator with + // iter = m_ref_model->get_iter(m_ref_model->get_path(const_iter)); + + return OptionStore_Item(m_ref_model, Base::get_active()); +} + + +}//GParted