You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
12 KiB
344 lines
12 KiB
#include "XournalWidget.h" |
|
|
|
#include <cmath> |
|
|
|
#include <config-debug.h> |
|
#include <gdk/gdk.h> |
|
#include <gdk/gdkkeysyms.h> |
|
|
|
#include "control/Control.h" |
|
#include "control/settings/Settings.h" |
|
#include "control/tools/EditSelection.h" |
|
#include "gui/Layout.h" |
|
#include "gui/Shadow.h" |
|
#include "gui/XournalView.h" |
|
#include "gui/inputdevices/InputContext.h" |
|
#include "gui/inputdevices/old/NewGtkInputDevice.h" |
|
#include "gui/scroll/ScrollHandling.h" |
|
|
|
#include "Rectangle.h" |
|
#include "Util.h" |
|
|
|
static void gtk_xournal_class_init(GtkXournalClass* klass); |
|
static void gtk_xournal_init(GtkXournal* xournal); |
|
static void gtk_xournal_get_preferred_width(GtkWidget* widget, gint* minimal_width, gint* natural_width); |
|
static void gtk_xournal_get_preferred_height(GtkWidget* widget, gint* minimal_height, gint* natural_height); |
|
static void gtk_xournal_size_allocate(GtkWidget* widget, GtkAllocation* allocation); |
|
static void gtk_xournal_realize(GtkWidget* widget); |
|
static auto gtk_xournal_draw(GtkWidget* widget, cairo_t* cr) -> gboolean; |
|
static void gtk_xournal_destroy(GtkWidget* object); |
|
|
|
auto gtk_xournal_get_type(void) -> GType { |
|
static GType gtk_xournal_type = 0; |
|
|
|
if (!gtk_xournal_type) { |
|
static const GTypeInfo gtk_xournal_info = {sizeof(GtkXournalClass), |
|
// base initialize |
|
nullptr, |
|
// base finalize |
|
nullptr, |
|
// class initialize |
|
reinterpret_cast<GClassInitFunc>(gtk_xournal_class_init), |
|
// class finalize |
|
nullptr, |
|
// class data, |
|
nullptr, |
|
// instance size |
|
sizeof(GtkXournal), |
|
// n_preallocs |
|
0, |
|
// instance init |
|
reinterpret_cast<GInstanceInitFunc>(gtk_xournal_init), |
|
// value table |
|
nullptr}; |
|
|
|
gtk_xournal_type = |
|
g_type_register_static(GTK_TYPE_WIDGET, "GtkXournal", >k_xournal_info, static_cast<GTypeFlags>(0)); |
|
} |
|
|
|
return gtk_xournal_type; |
|
} |
|
|
|
auto gtk_xournal_new(XournalView* view, InputContext* inputContext) -> GtkWidget* { |
|
GtkXournal* xoj = GTK_XOURNAL(g_object_new(gtk_xournal_get_type(), nullptr)); |
|
xoj->view = view; |
|
xoj->scrollHandling = inputContext->getScrollHandling(); |
|
xoj->x = 0; |
|
xoj->y = 0; |
|
xoj->layout = new Layout(view, inputContext->getScrollHandling()); |
|
xoj->selection = nullptr; |
|
|
|
xoj->input = inputContext; |
|
|
|
xoj->input->connect(GTK_WIDGET(xoj)); |
|
|
|
return GTK_WIDGET(xoj); |
|
} |
|
|
|
auto gtk_xournal_new_deprecated(XournalView* view, ScrollHandling* scrollHandling) -> GtkWidget* { |
|
GtkXournal* xoj = GTK_XOURNAL(g_object_new(gtk_xournal_get_type(), nullptr)); |
|
xoj->view = view; |
|
xoj->scrollHandling = scrollHandling; |
|
xoj->x = 0; |
|
xoj->y = 0; |
|
xoj->layout = new Layout(view, scrollHandling); |
|
xoj->selection = nullptr; |
|
|
|
xoj->depInput = new NewGtkInputDevice(GTK_WIDGET(xoj), view, scrollHandling); |
|
|
|
xoj->depInput->initWidget(); |
|
|
|
return GTK_WIDGET(xoj); |
|
} |
|
|
|
static void gtk_xournal_class_init(GtkXournalClass* klass) { |
|
GtkWidgetClass* widget_class = nullptr; |
|
|
|
widget_class = reinterpret_cast<GtkWidgetClass*>(klass); |
|
|
|
widget_class->realize = gtk_xournal_realize; |
|
widget_class->get_preferred_width = gtk_xournal_get_preferred_width; |
|
widget_class->get_preferred_height = gtk_xournal_get_preferred_height; |
|
widget_class->size_allocate = gtk_xournal_size_allocate; |
|
|
|
widget_class->draw = gtk_xournal_draw; |
|
|
|
widget_class->destroy = gtk_xournal_destroy; |
|
} |
|
|
|
auto gtk_xournal_get_visible_area(GtkWidget* widget, XojPageView* p) -> Rectangle<double>* { |
|
g_return_val_if_fail(widget != nullptr, nullptr); |
|
g_return_val_if_fail(GTK_IS_XOURNAL(widget), nullptr); |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
|
|
GtkAdjustment* vadj = xournal->scrollHandling->getVertical(); |
|
GtkAdjustment* hadj = xournal->scrollHandling->getHorizontal(); |
|
|
|
GdkRectangle r2; |
|
r2.x = static_cast<int>(gtk_adjustment_get_value(hadj)); |
|
r2.y = static_cast<int>(gtk_adjustment_get_value(vadj)); |
|
r2.width = static_cast<int>(gtk_adjustment_get_page_size(hadj)); |
|
r2.height = static_cast<int>(gtk_adjustment_get_page_size(vadj)); |
|
|
|
GdkRectangle r1; |
|
r1.x = p->getX(); |
|
r1.y = p->getY(); |
|
r1.width = p->getDisplayWidth(); |
|
r1.height = p->getDisplayHeight(); |
|
|
|
GdkRectangle r3 = {0, 0, 0, 0}; |
|
gdk_rectangle_intersect(&r1, &r2, &r3); |
|
|
|
if (r3.width == 0 && r3.height == 0) { |
|
return nullptr; |
|
} |
|
|
|
r3.x -= r1.x; |
|
r3.y -= r1.y; |
|
|
|
double zoom = xournal->view->getZoom(); |
|
|
|
if (r3.x < 0 || r3.y < 0) { |
|
g_warning("XournalWidget:gtk_xournal_get_visible_area: intersection rectangle coordinates are negative which " |
|
"should never happen"); |
|
} |
|
|
|
return new Rectangle<double>(std::max(r3.x, 0) / zoom, std::max(r3.y, 0) / zoom, r3.width / zoom, r3.height / zoom); |
|
} |
|
|
|
auto gtk_xournal_get_layout(GtkWidget* widget) -> Layout* { |
|
g_return_val_if_fail(widget != nullptr, nullptr); |
|
g_return_val_if_fail(GTK_IS_XOURNAL(widget), nullptr); |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
return xournal->layout; |
|
} |
|
|
|
static void gtk_xournal_init(GtkXournal* xournal) { |
|
GtkWidget* widget = GTK_WIDGET(xournal); |
|
|
|
gtk_widget_set_can_focus(widget, true); |
|
} |
|
|
|
static void gtk_xournal_get_preferred_width(GtkWidget* widget, gint* minimal_width, gint* natural_width) { |
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
*minimal_width = *natural_width = xournal->scrollHandling->getPreferredWidth(); |
|
} |
|
|
|
static void gtk_xournal_get_preferred_height(GtkWidget* widget, gint* minimal_height, gint* natural_height) { |
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
*minimal_height = *natural_height = xournal->scrollHandling->getPreferredHeight(); |
|
} |
|
|
|
/** |
|
* This method is called while scrolling or after the XournalWidget size has changed |
|
*/ |
|
static void gtk_xournal_size_allocate(GtkWidget* widget, GtkAllocation* allocation) { |
|
g_return_if_fail(widget != nullptr); |
|
g_return_if_fail(GTK_IS_XOURNAL(widget)); |
|
g_return_if_fail(allocation != nullptr); |
|
|
|
gtk_widget_set_allocation(widget, allocation); |
|
|
|
if (gtk_widget_get_realized(widget)) { |
|
gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, |
|
allocation->height); |
|
} |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
|
|
// layout the pages in the XournalWidget |
|
xournal->layout->layoutPages(allocation->width, allocation->height); |
|
} |
|
|
|
static void gtk_xournal_realize(GtkWidget* widget) { |
|
GdkWindowAttr attributes; |
|
guint attributes_mask = 0; |
|
|
|
g_return_if_fail(widget != nullptr); |
|
g_return_if_fail(GTK_IS_XOURNAL(widget)); |
|
|
|
gtk_widget_set_realized(widget, true); |
|
|
|
gtk_widget_set_hexpand(widget, true); |
|
gtk_widget_set_vexpand(widget, true); |
|
|
|
GtkAllocation allocation; |
|
gtk_widget_get_allocation(widget, &allocation); |
|
attributes.window_type = GDK_WINDOW_CHILD; |
|
attributes.x = allocation.x; |
|
attributes.y = allocation.y; |
|
attributes.width = allocation.width; |
|
attributes.height = allocation.height; |
|
|
|
attributes.wclass = GDK_INPUT_OUTPUT; |
|
attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK; |
|
|
|
attributes_mask = GDK_WA_X | GDK_WA_Y; |
|
|
|
gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask)); |
|
gdk_window_set_user_data(gtk_widget_get_window(widget), widget); |
|
} |
|
|
|
static void gtk_xournal_draw_shadow(GtkXournal* xournal, cairo_t* cr, int left, int top, int width, int height, |
|
bool selected) { |
|
if (selected) { |
|
Shadow::drawShadow(cr, left - 2, top - 2, width + 4, height + 4); |
|
|
|
Settings* settings = xournal->view->getControl()->getSettings(); |
|
|
|
// Draw border |
|
Util::cairo_set_source_rgbi(cr, settings->getBorderColor()); |
|
cairo_set_line_width(cr, 4.0); |
|
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); |
|
cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); |
|
|
|
cairo_move_to(cr, left, top); |
|
cairo_line_to(cr, left, top + height); |
|
cairo_line_to(cr, left + width, top + height); |
|
cairo_line_to(cr, left + width, top); |
|
cairo_close_path(cr); |
|
|
|
cairo_stroke(cr); |
|
} else { |
|
Shadow::drawShadow(cr, left, top, width, height); |
|
} |
|
} |
|
|
|
void gtk_xournal_repaint_area(GtkWidget* widget, int x1, int y1, int x2, int y2) { |
|
g_return_if_fail(widget != nullptr); |
|
g_return_if_fail(GTK_IS_XOURNAL(widget)); |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
|
|
x1 -= xournal->x; |
|
x2 -= xournal->x; |
|
y1 -= xournal->y; |
|
y2 -= xournal->y; |
|
|
|
if (x2 < 0 || y2 < 0) { |
|
return; // outside visible area |
|
} |
|
|
|
GtkAllocation alloc = {0}; |
|
gtk_widget_get_allocation(widget, &alloc); |
|
|
|
if (x1 > alloc.width || y1 > alloc.height) { |
|
return; // outside visible area |
|
} |
|
|
|
gtk_widget_queue_draw_area(widget, x1, y1, x2 - x1, y2 - y1); |
|
} |
|
|
|
static auto gtk_xournal_draw(GtkWidget* widget, cairo_t* cr) -> gboolean { |
|
g_return_val_if_fail(widget != nullptr, false); |
|
g_return_val_if_fail(GTK_IS_XOURNAL(widget), false); |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(widget); |
|
|
|
double x1 = NAN, x2 = NAN, y1 = NAN, y2 = NAN; |
|
|
|
cairo_clip_extents(cr, &x1, &y1, &x2, &y2); |
|
|
|
// Draw background |
|
Settings* settings = xournal->view->getControl()->getSettings(); |
|
Util::cairo_set_source_rgbi(cr, settings->getBackgroundColor()); |
|
cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); |
|
cairo_fill(cr); |
|
|
|
xournal->scrollHandling->translate(cr, x1, x2, y1, y2); |
|
|
|
Rectangle clippingRect(x1 - 10, y1 - 10, x2 - x1 + 20, y2 - y1 + 20); |
|
|
|
for (auto&& pv: xournal->view->getViewPages()) { |
|
int px = pv->getX(); |
|
int py = pv->getY(); |
|
int pw = pv->getDisplayWidth(); |
|
int ph = pv->getDisplayHeight(); |
|
|
|
if (!clippingRect.intersects(pv->getRect())) { |
|
continue; |
|
} |
|
|
|
if (!xournal->view->getControl()->getZoomControl()->isZoomPresentationMode()) { |
|
gtk_xournal_draw_shadow(xournal, cr, px, py, pw, ph, pv->isSelected()); |
|
} |
|
|
|
cairo_save(cr); |
|
cairo_translate(cr, px, py); |
|
|
|
pv->paintPage(cr, nullptr); |
|
cairo_restore(cr); |
|
} |
|
|
|
if (xournal->selection) { |
|
double zoom = xournal->view->getZoom(); |
|
|
|
Redrawable* red = xournal->selection->getView(); |
|
cairo_translate(cr, red->getX(), red->getY()); |
|
|
|
xournal->selection->paint(cr, zoom); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static void gtk_xournal_destroy(GtkWidget* object) { |
|
g_return_if_fail(object != nullptr); |
|
g_return_if_fail(GTK_IS_XOURNAL(object)); |
|
|
|
GtkXournal* xournal = GTK_XOURNAL(object); |
|
|
|
delete xournal->selection; |
|
xournal->selection = nullptr; |
|
|
|
delete xournal->layout; |
|
xournal->layout = nullptr; |
|
|
|
delete xournal->input; |
|
xournal->input = nullptr; |
|
|
|
delete xournal->depInput; |
|
xournal->depInput = nullptr; |
|
}
|
|
|