From ce68505a51bfa4b7f682361aa393ba926a99ebac Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 25 May 2016 23:31:32 -0400 Subject: working but incomplete version of new Pane replacement widget --- libs/gtkmm2ext/gtkmm2ext/pane.h | 100 +++++++++++ libs/gtkmm2ext/pane.cc | 361 ++++++++++++++++++++++++++++++++++++++++ libs/gtkmm2ext/wscript | 1 + 3 files changed, 462 insertions(+) create mode 100644 libs/gtkmm2ext/gtkmm2ext/pane.h create mode 100644 libs/gtkmm2ext/pane.cc (limited to 'libs') diff --git a/libs/gtkmm2ext/gtkmm2ext/pane.h b/libs/gtkmm2ext/gtkmm2ext/pane.h new file mode 100644 index 0000000000..ca3ce511a3 --- /dev/null +++ b/libs/gtkmm2ext/gtkmm2ext/pane.h @@ -0,0 +1,100 @@ +/* + Copyright (C) 2016 Paul Davis + + 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __libgtkmm2ext_pane_h__ +#define __libgtkmm2ext_pane_h__ + +#include +#include + +#include + +#include +#include + +//#include "gtkmm2ext/visibility.h" +#define LIBGTKMM2EXT_API + +namespace Gtk { + class Widget; +} + +namespace Gtkmm2ext { + +class LIBGTKMM2EXT_API Pane : public Gtk::Container +{ + private: + class Divider; + + public: + Pane (bool horizontal); + void set_divider (std::vector::size_type divider, float fract); + float get_divider (std::vector::size_type divider); + + GType child_type_vfunc() const; + + protected: + bool horizontal; + bool dragging; + + void on_add (Gtk::Widget*); + void on_remove (Gtk::Widget*); + void on_size_request (GtkRequisition*); + void on_size_allocate (Gtk::Allocation&); + bool on_expose_event (GdkEventExpose*); + + bool handle_press_event (GdkEventButton*, Divider*); + bool handle_release_event (GdkEventButton*, Divider*); + bool handle_motion_event (GdkEventMotion*, Divider*); + + void forall_vfunc (gboolean include_internals, GtkCallback callback, gpointer callback_data); + + private: + void reallocate (Gtk::Allocation const &); + + typedef std::list Children; + Children children; + + struct Divider : public Gtk::EventBox { + Divider (); + float fract; + bool on_expose_event (GdkEventExpose* ev); + }; + + typedef std::vector Dividers; + Dividers dividers; + int divider_width; + void add_divider (); +}; + +class LIBGTKMM2EXT_API HPane : public Pane +{ + public: + HPane () : Pane (true) {} +}; + +class LIBGTKMM2EXT_API VPane : public Pane +{ + public: + VPane () : Pane (false) {} +}; + +} /* namespace */ + +#endif /* __libgtkmm2ext_pane_h__ */ diff --git a/libs/gtkmm2ext/pane.cc b/libs/gtkmm2ext/pane.cc new file mode 100644 index 0000000000..2470116ea3 --- /dev/null +++ b/libs/gtkmm2ext/pane.cc @@ -0,0 +1,361 @@ +/* + Copyright (C) 2016 Paul Davis + + 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "gtkmm2ext/pane.h" + +#include "i18n.h" + +using namespace PBD; +using namespace Gtk; +using namespace Gtkmm2ext; +using namespace std; + +Pane::Pane (bool h) + : horizontal (h) + , dragging (false) + , divider_width (5) +{ + set_has_window (false); +} + +void +Pane::on_size_request (GtkRequisition* req) +{ + GtkRequisition largest; + + /* iterate over all children, get their size requests */ + + /* horizontal pane is as high as its tallest child, but has no width + * requirement. + * + * vertical pane is as wide as its widest child, but has no height + * requirement. + */ + + if (horizontal) { + largest.width = (children.size() - 1) * divider_width; + largest.height = 0; + } else { + largest.height = (children.size() - 1) * divider_width; + largest.width = 0; + } + + for (Children::iterator child = children.begin(); child != children.end(); ++child) { + GtkRequisition r; + + (*child)->size_request (r); + + if (horizontal) { + largest.height = max (largest.height, r.height); + largest.width += r.width; + } else { + largest.width = max (largest.width, r.width); + largest.height += r.height; + } + } + + *req = largest; +} + +GType +Pane::child_type_vfunc() const +{ + return Gtk::Widget::get_type(); +} + +void +Pane::add_divider () +{ + Divider* d = new Divider; + d->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_press_event), d), false); + d->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_release_event), d), false); + d->signal_motion_notify_event().connect (sigc::bind (sigc::mem_fun (*this, &Pane::handle_motion_event), d), false); + d->set_parent (*this); + d->show (); + dividers.push_back (d); +} + +void +Pane::on_add (Widget* w) +{ + children.push_back (w); + + w->set_parent (*this); + + while (dividers.size() < (children.size() - 1)) { + add_divider (); + } +} + +void +Pane::on_remove (Widget* w) +{ + w->unparent (); + children.remove (w); +} + +void +Pane::on_size_allocate (Gtk::Allocation& alloc) +{ + reallocate (alloc); + Container::on_size_allocate (alloc); +} + +void +Pane::reallocate (Gtk::Allocation const & alloc) +{ + Children::size_type n = 0; + int remaining; + int xpos = 0; + int ypos = 0; + float fract; + + if (children.empty()) { + return; + } + + if (children.size() == 1) { + children.front()->size_allocate (alloc); + return; + } + + if (horizontal) { + remaining = alloc.get_width (); + } else { + remaining = alloc.get_height (); + } + + Children::iterator child; + Children::iterator next; + Dividers::iterator div; + + for (child = children.begin(), div = dividers.begin(); child != children.end(); ++n) { + + Gtk::Allocation child_alloc; + next = child; + ++next; + + child_alloc.set_x (xpos); + child_alloc.set_y (ypos); + + if (n >= dividers.size()) { + /* the next child gets all the remaining space */ + fract = 1.0; + } else { + /* the next child gets the fraction of the remaining space given by the divider that follows it */ + fract = dividers[n]->fract; + } + + Gtk::Requisition cr; + (*child)->size_request (cr); + + if (horizontal) { + child_alloc.set_width ((gint) floor (remaining * fract)); + child_alloc.set_height (alloc.get_height()); + remaining = max (0, (remaining - child_alloc.get_width())); + xpos += child_alloc.get_width(); + } else { + child_alloc.set_width (alloc.get_width()); + child_alloc.set_height ((gint) floor (remaining * fract)); + remaining = max (0, (remaining - child_alloc.get_height())); + ypos += child_alloc.get_height (); + } + + (*child)->size_allocate (child_alloc); + ++child; + + if (child == children.end()) { + /* done, no more children, no need for a divider */ + break; + } + + /* add a divider between children */ + + Gtk::Allocation divider_allocation; + + divider_allocation.set_x (xpos); + divider_allocation.set_y (ypos); + + if (horizontal) { + divider_allocation.set_width (divider_width); + divider_allocation.set_height (alloc.get_height()); + remaining = max (0, remaining - divider_width); + xpos += divider_width; + } else { + divider_allocation.set_width (alloc.get_width()); + divider_allocation.set_height (divider_width); + remaining = max (0, remaining - divider_width); + ypos += divider_width; + } + + (*div)->size_allocate (divider_allocation); + ++div; + } +} + +bool +Pane::on_expose_event (GdkEventExpose* ev) +{ + Children::size_type n = 0; + + for (Children::iterator child = children.begin(); child != children.end(); ++child, ++n) { + + propagate_expose (**child, ev); + + if (n < dividers.size()) { + propagate_expose (*dividers[n], ev); + } + } + + return true; +} + +bool +Pane::handle_press_event (GdkEventButton* ev, Divider*) +{ + dragging = true; + return false; +} + +bool +Pane::handle_release_event (GdkEventButton* ev, Divider*) +{ + children.front()->queue_resize (); + dragging = false; + return false; +} + +bool +Pane::handle_motion_event (GdkEventMotion* ev, Divider* d) +{ + if (!dragging) { + return true; + } + + /* determine new position for handle */ + + float new_fract; + int px, py; + + d->translate_coordinates (*this, ev->x, ev->y, px, py); + + Dividers::iterator prev = dividers.end(); + + for (Dividers::iterator di = dividers.begin(); di != dividers.end(); ++di) { + if (*di == d) { + break; + } + prev = di; + } + + int space_remaining; + int prev_edge; + + if (horizontal) { + if (prev != dividers.end()) { + prev_edge = (*prev)->get_allocation().get_x() + (*prev)->get_allocation().get_width(); + } else { + prev_edge = 0; + } + space_remaining = get_allocation().get_width() - prev_edge; + new_fract = (float) (px - prev_edge) / space_remaining; + } else { + if (prev != dividers.end()) { + prev_edge = (*prev)->get_allocation().get_y() + (*prev)->get_allocation().get_height(); + } else { + prev_edge = 0; + } + space_remaining = get_allocation().get_height() - prev_edge; + new_fract = (float) (py - prev_edge) / space_remaining; + } + + new_fract = min (1.0f, max (0.0f, new_fract)); + + if (new_fract != d->fract) { + d->fract = new_fract; + reallocate (get_allocation ()); + queue_draw (); + } + + return true; +} + +void +Pane::set_divider (Dividers::size_type div, float fract) +{ + bool redraw = false; + + while (dividers.size() <= div) { + add_divider (); + } + + if (fract != dividers[div]->fract) { + dividers[div]->fract = fract; + redraw = true; + } + + if (redraw) { + /* our size hasn't changed, but our internal allocations have */ + reallocate (get_allocation()); + queue_draw (); + } +} + +float +Pane::get_divider (Dividers::size_type div) +{ + if (div >= dividers.size()) { + return -1; + } + + return dividers[div]->fract; +} + +void +Pane::forall_vfunc (gboolean include_internals, GtkCallback callback, gpointer callback_data) +{ + for (Children::iterator w = children.begin(); w != children.end(); ++w) { + callback ((*w)->gobj(), callback_data); + } + + if (include_internals) { + for (Dividers::iterator d = dividers.begin(); d != dividers.end(); ++d) { + callback (GTK_WIDGET((*d)->gobj()), callback_data); + } + } +} + +Pane::Divider::Divider () + : fract (0.0) +{ + set_events (Gdk::EventMask (Gdk::BUTTON_PRESS|Gdk::BUTTON_RELEASE|Gdk::MOTION_NOTIFY)); +} + +bool +Pane::Divider::on_expose_event (GdkEventExpose* ev) +{ + Widget::on_expose_event (ev); + + Cairo::RefPtr draw_context = get_window()->create_cairo_context (); + draw_context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height); + draw_context->clip_preserve (); + draw_context->set_source_rgba (1.0, 0.0, 0.0, 0.6); + draw_context->fill (); + return true; +} diff --git a/libs/gtkmm2ext/wscript b/libs/gtkmm2ext/wscript index b5676d1423..4e1a031906 100644 --- a/libs/gtkmm2ext/wscript +++ b/libs/gtkmm2ext/wscript @@ -50,6 +50,7 @@ gtkmm2ext_sources = [ 'gtkapplication.c', 'idle_adjustment.cc', 'keyboard.cc', + 'pane.cc', 'paths_dialog.cc', 'persistent_tooltip.cc', 'pixfader.cc', -- cgit v1.2.3