lumiera_/src/gui/widget/timeline/timeline-header-container.cpp
Ichthyostega d964e98601 style-adjustment: GUI namespaces
it is a widely accepted rule to shape names with the usage site in mind.
Especially this means, that we use the singular form for all kinds
of collections and assortments.

Thus, the namespace should be called "widget" not "widgets",
because at usage site this becomes gui::widget::TimelineWidget

Likewise for "dialogs" and "pannels"
2015-05-28 18:47:25 +02:00

519 lines
14 KiB
C++

/*
timeline-header-container.cpp - Implementation of the timeline
header container widget
Copyright (C) Lumiera.org
2008, Joel Holdsworth <joel@airwebreathe.org.uk>
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 "gui/widgets/timeline/timeline-header-container.hpp"
#include "gui/widgets/timeline/timeline-track.hpp"
#include "gui/widgets/timeline-widget.hpp"
#include "gui/util/rectangle.hpp"
#include <boost/foreach.hpp>
#include <memory>
using std::pair;
using std::shared_ptr;
using namespace Gtk;
using namespace util;
namespace gui {
namespace widgets {
namespace timeline {
// ===== Constants ===== //
const int TimelineHeaderContainer::ScrollSlideRateDivisor = 4;
const int TimelineHeaderContainer::ScrollSlideEventInterval = 40;
// ===== Implementation ===== //
TimelineHeaderContainer::TimelineHeaderContainer(
gui::widgets::TimelineWidget &timeline_widget) :
Glib::ObjectBase("TimelineHeaderContainer"),
timelineWidget(timeline_widget)
{
// This widget will not have a window at first
set_has_window (false);
set_redraw_on_allocate(false);
// Connect to the timeline widget's vertical scroll event,
// so that we get notified when the view shifts
timelineWidget.verticalAdjustment->signal_value_changed().connect(
sigc::mem_fun(this, &TimelineHeaderContainer::on_scroll) );
// Connect to the timeline widget's hover event,
// so that we get notified when tracks are hovered on
timelineWidget.hovering_track_changed_signal().connect(
sigc::mem_fun(this,
&TimelineHeaderContainer::on_hovering_track_changed) );
#if 0
// Create the context menu
Menu::MenuList& menu_list = contextMenu.items();
menu_list.push_back( Menu_Helpers::MenuElem(_("_Add Track"),
sigc::mem_fun(timelineWidget,
&TimelineWidget::on_add_track_command) ) );
#endif
}
void
TimelineHeaderContainer::update_headers()
{
// Ensure headers are parented correctly
pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> > pair;
BOOST_FOREACH( pair, timelineWidget.trackMap )
{
REQUIRE(pair.second);
Widget &widget = pair.second->get_header_widget();
if(widget.get_parent() == NULL) // Is the header unparented?
widget.set_parent(*this);
ENSURE(widget.get_parent() == this);
}
}
void
TimelineHeaderContainer::clear_headers()
{
// Unparent all the headers
pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> > pair;
BOOST_FOREACH( pair, timelineWidget.trackMap )
{
REQUIRE(pair.second);
Widget &widget = pair.second->get_header_widget();
if(widget.get_parent() != NULL) // Is the header unparented?
widget.unparent();
}
}
void
TimelineHeaderContainer::on_realize()
{
set_has_window (false);
// Call base class:
Gtk::Container::on_realize();
// Create the GdkWindow:
GdkWindowAttr attributes;
memset(&attributes, 0, sizeof (attributes));
Allocation allocation = get_allocation();
// Set initial position and size of the Gdk::Window:
attributes.x = allocation.get_x();
attributes.y = allocation.get_y();
attributes.width = allocation.get_width();
attributes.height = allocation.get_height();
attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
gdkWindow = Gdk::Window::create(get_window(), &attributes,
GDK_WA_X | GDK_WA_Y);
//unset_flags(Gtk::NO_WINDOW);
set_window(gdkWindow);
// Unset the background so as to make the colour match the parent
// window
//unset_bg(STATE_NORMAL);
// Make the widget receive expose events
gdkWindow->set_user_data(gobj());
// Make the widget sensitive to mouse events
add_events(
Gdk::POINTER_MOTION_MASK |
Gdk::BUTTON_PRESS_MASK |
Gdk::BUTTON_RELEASE_MASK);
}
void
TimelineHeaderContainer::on_unrealize()
{
// Unreference any window we may have created
gdkWindow.clear();
// Call base class:
Container::on_unrealize();
}
bool TimelineHeaderContainer::on_button_press_event (
GdkEventButton* event)
{
REQUIRE(event != NULL);
switch(event->button)
{
case 1: // Left Click
break;
case 3: // Right Click
{
// Popup the context menu
shared_ptr<Track> header(
timelineWidget.layoutHelper.header_from_point(
Gdk::Point(event->x, event->y)));
// Are we hovering on a header?
if(header)
{
// Yes - show the header's context menu
header->show_header_context_menu(
event->button, event->time);
}
else
{
// No - show the default context menu
contextMenu.popup(event->button, event->time);
}
break;
}
}
return true;
}
bool TimelineHeaderContainer::on_button_release_event (
GdkEventButton* event)
{
end_drag();
return Container::on_button_release_event(event);
}
bool TimelineHeaderContainer::on_motion_notify_event (
GdkEventMotion* event)
{
REQUIRE(event != NULL);
REQUIRE(gdkWindow);
const bool result = Container::on_motion_notify_event(event);
TimelineLayoutHelper &layout = timelineWidget.layoutHelper;
// Get the mouse point
int window_x = 0, window_y = 0;
gdkWindow->get_origin(window_x, window_y);
mousePoint = Gdk::Point(event->x_root - window_x,
event->y_root - window_y);
// Are we beginning to drag a header?
if((event->state & GDK_BUTTON1_MASK) && hoveringTrack &&
!layout.is_dragging_track())
{
begin_drag();
return result;
}
// Are we currently dragging?
if(layout.is_dragging_track())
{
// Forward the message to the layout manager
layout.drag_to_point(mousePoint);
// Is the mouse out of bounds? if so we must begin scrolling
const int height = get_allocation().get_height();
const int y = mousePoint.get_y();
if(y < 0)
begin_scroll_slide(y / ScrollSlideRateDivisor);
else if(y > height)
begin_scroll_slide((y - height) / ScrollSlideRateDivisor);
else end_scroll_slide();
return result;
}
// Hit test the rectangle
hoveringTrack = layout.header_from_point(mousePoint);
return result;
}
void
TimelineHeaderContainer::on_size_request (Requisition* requisition)
{
// We don't care about the size of all the child widgets, but if we
// don't send the size request down the tree, some widgets fail to
// calculate their text layout correctly.
const TimelineLayoutHelper::TrackTree &layout_tree =
timelineWidget.layoutHelper.get_layout_tree();
// Send a size request to all the children
if(!layout_tree.empty())
{
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
Widget &widget =
lookup_timeline_track(*iterator)->get_header_widget();
if(widget.get_visible())
{ /* widget.size_request(); */ }
}
}
// Initialize the output parameter:
*requisition = Gtk::Requisition();
requisition->width = TimelineWidget::HeaderWidth;
requisition->height = 0;
}
void
TimelineHeaderContainer::on_size_allocate (Allocation& allocation)
{
// Use the offered allocation for this container:
set_allocation(allocation);
// Resize the widget's window
if(gdkWindow)
{
gdkWindow->move_resize(allocation.get_x(), allocation.get_y(),
allocation.get_width(), allocation.get_height());
}
// Relayout the child widgets of the headers
layout_headers();
}
void
TimelineHeaderContainer::forall_vfunc(gboolean /* include_internals */,
GtkCallback callback, gpointer callback_data)
{
REQUIRE(callback != NULL);
pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> > pair;
BOOST_FOREACH( pair, timelineWidget.trackMap )
{
REQUIRE(pair.second);
GtkWidget *widget = pair.second->get_header_widget().gobj();
REQUIRE(widget);
callback(widget, callback_data);
}
}
void
TimelineHeaderContainer::on_remove(Widget* widget)
{
REQUIRE(widget);
// Ensure headers are parented correctly
pair<shared_ptr<model::Track>, shared_ptr<timeline::Track> > pair;
BOOST_FOREACH( pair, timelineWidget.trackMap )
{
REQUIRE(pair.second);
Widget &this_widget = pair.second->get_header_widget();
if(&this_widget == widget)
this_widget.unparent();
}
}
void
TimelineHeaderContainer::on_layout_changed()
{
layout_headers();
}
void
TimelineHeaderContainer::on_scroll()
{
// If the scroll has changed, we will have to shift all the
// header widgets
layout_headers();
}
void
TimelineHeaderContainer::on_hovering_track_changed( shared_ptr<timeline::Track>)
{
/* do nothing */
}
bool
TimelineHeaderContainer::on_scroll_slide_timer()
{
// Shift the view
const int view_height = get_allocation().get_height();
timelineWidget.set_y_scroll_offset(
timelineWidget.get_y_scroll_offset() +
scrollSlideRate * view_height / 256);
// Keep the layout manager updated
timelineWidget.layoutHelper.drag_to_point(mousePoint);
// Return true to keep the timer going
return true;
}
void
TimelineHeaderContainer::layout_headers()
{
// We can't layout before the widget has been set up
if(!gdkWindow)
return;
bool headers_shown = false;
TimelineLayoutHelper &layout_helper =
timelineWidget.layoutHelper;
const TimelineLayoutHelper::TrackTree &layout_tree =
layout_helper.get_layout_tree();
if(!layout_tree.empty())
{
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*iterator);
Widget &widget = timeline_track->get_header_widget();
boost::optional<Gdk::Rectangle> header_rect =
layout_helper.get_track_header_rect(timeline_track);
if(header_rect)
{
REQUIRE(header_rect->get_width() >= 0);
REQUIRE(header_rect->get_height() >= 0);
// Apply the allocation to the header
widget.size_allocate (*header_rect);
if(!widget.get_visible())
{
widget.show();
headers_shown = true;
}
}
else // No header rect, so the track must be hidden
if(widget.get_visible())
widget.hide();
}
// If headers have been shown while we're dragging, the dragging
// branch headers have to be brought back to the top again
if(headers_shown && layout_helper.is_dragging_track())
raise_recursive(layout_helper.get_dragging_track_iter());
// Repaint the background of our parenting
queue_draw();
}
}
shared_ptr<timeline::Track>
TimelineHeaderContainer::lookup_timeline_track(
shared_ptr<model::Track> modelTrack)
{
REQUIRE(modelTrack != NULL);
shared_ptr<timeline::Track> timeline_track =
timelineWidget.lookup_timeline_track(modelTrack);
ENSURE(timeline_track);
return timeline_track;
}
void
TimelineHeaderContainer::begin_drag()
{
TimelineLayoutHelper &layout = timelineWidget.layoutHelper;
layout.begin_dragging_track(mousePoint);
// Raise all the header widgets so they float above the widgets not
// being dragged
raise_recursive(layout.get_dragging_track_iter());
// Set the cursor to a hand
REQUIRE(gdkWindow);
//gdkWindow->set_cursor(Gdk::Cursor(Gdk::FLEUR));
}
void
TimelineHeaderContainer::end_drag(bool apply)
{
TimelineLayoutHelper &layout = timelineWidget.layoutHelper;
// Has the user been dragging?
if(layout.is_dragging_track())
layout.end_dragging_track(apply);
// End the scroll slide
end_scroll_slide();
// Reset the arrow as a cursor
REQUIRE(gdkWindow);
//gdkWindow->set_cursor(Gdk::Cursor(Gdk::LEFT_PTR));
}
void
TimelineHeaderContainer::raise_recursive(
TimelineLayoutHelper::TrackTree::iterator_base node)
{
TimelineLayoutHelper::TrackTree::pre_order_iterator iter;
const TimelineLayoutHelper::TrackTree &layout_tree =
timelineWidget.layoutHelper.get_layout_tree();
shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*node);
REQUIRE(timeline_track);
Glib::RefPtr<Gdk::Window> window =
timeline_track->get_header_widget().get_window();
ENSURE(window); // Something strange has happened if there was no
// window
window->raise();
for(iter = layout_tree.begin(node);
iter != layout_tree.end(node);
iter++)
{
raise_recursive(iter);
}
}
void
TimelineHeaderContainer::begin_scroll_slide(int scroll_slide_rate)
{
scrollSlideRate = scroll_slide_rate;
if(!scrollSlideEvent.connected())
scrollSlideEvent = Glib::signal_timeout().connect(sigc::mem_fun(
this, &TimelineHeaderContainer::on_scroll_slide_timer),
ScrollSlideEventInterval);
}
void
TimelineHeaderContainer::end_scroll_slide()
{
scrollSlideRate = 0;
if(scrollSlideEvent.connected())
scrollSlideEvent.disconnect();
}
} // namespace timeline
} // namespace widgets
} // namespace gui