mirror of
https://gitcode.com/shufeng2012/ohos-xournalpp.git
synced 2026-11-06 14:11:11 +08:00
🔥 删除gui相关
This commit is contained in:
1 parent
88ccf893d9
commit
ace2ea9a2c
341 files changed
-34818
No files matched your search
@@ -1,10 +0,0 @@
|
||||
#include "Builder.h"
|
||||
|
||||
#include "util/StringUtils.h"
|
||||
|
||||
#include "GladeSearchpath.h" // for GladeSearchpath
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
Builder::Builder(GladeSearchpath* gladeSearchPath, const std::string& uiFile):
|
||||
builder(gtk_builder_new_from_file(char_cast(gladeSearchPath->findFile("", uiFile).u8string().c_str())),
|
||||
xoj::util::adopt) {}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* gtk_builder wrapper to parse ui files
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "util/Assert.h"
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
class Builder final {
|
||||
public:
|
||||
Builder(GladeSearchpath* gladeSearchPath, const std::string& uiFile);
|
||||
|
||||
template <class GObjectType>
|
||||
GObjectType* get(const std::string& name) {
|
||||
GObjectType* res = static_cast<GObjectType*>(gtk_builder_get_object(builder.get(), name.c_str()));
|
||||
xoj_assert_message(res, "ERROR: Builder::get: Could not find UI object: " + name + "\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
inline GtkWidget* get(const std::string& name) { return GTK_WIDGET(get<GObject>(name)); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* The Glade resources
|
||||
*/
|
||||
xoj::util::GObjectSPtr<GtkBuilder> builder;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "CreatePreviewImage.h"
|
||||
|
||||
#include "model/PageType.h" // for PageType
|
||||
#include "util/Color.h" // for Color
|
||||
#include "util/raii/CairoWrappers.h"
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
#include "view/background/BackgroundView.h" // for BackgroundView
|
||||
|
||||
namespace xoj::helper {
|
||||
auto createPreviewImage(const PageType& pt) -> GtkWidget* {
|
||||
const double zoom = 0.5;
|
||||
|
||||
xoj::util::CairoSurfaceSPtr surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PREVIEW_WIDTH, PREVIEW_HEIGHT),
|
||||
xoj::util::adopt);
|
||||
xoj::util::CairoSPtr crSPtr(cairo_create(surface.get()), xoj::util::adopt);
|
||||
auto cr = crSPtr.get();
|
||||
|
||||
cairo_scale(cr, zoom, zoom);
|
||||
|
||||
auto bgView = xoj::view::BackgroundView::createRuled(PREVIEW_WIDTH / zoom, PREVIEW_HEIGHT / zoom, Colors::white, pt,
|
||||
1. / zoom);
|
||||
bgView->draw(cr);
|
||||
|
||||
cairo_identity_matrix(cr);
|
||||
|
||||
cairo_set_line_width(cr, 2);
|
||||
cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
|
||||
cairo_move_to(cr, 0, 0);
|
||||
cairo_line_to(cr, PREVIEW_WIDTH, 0);
|
||||
cairo_line_to(cr, PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||
cairo_line_to(cr, 0, PREVIEW_HEIGHT);
|
||||
cairo_line_to(cr, 0, 0);
|
||||
cairo_stroke(cr);
|
||||
|
||||
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
|
||||
gdk_pixbuf_get_from_surface(surface.get(), 0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT), xoj::util::adopt);
|
||||
return gtk_image_new_from_pixbuf(pixbuf.get());
|
||||
}
|
||||
}; // namespace xoj::helper
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Helper function
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
class PageType;
|
||||
|
||||
namespace xoj::helper {
|
||||
constexpr auto PREVIEW_WIDTH = 100;
|
||||
constexpr auto PREVIEW_HEIGHT = 141;
|
||||
/**
|
||||
* @brief Create a GtkImage containing a miniature of the given (standard) page type
|
||||
* The returned widget is a floating ref.
|
||||
*/
|
||||
auto createPreviewImage(const PageType& pt) -> GtkWidget*;
|
||||
}; // namespace xoj::helper
|
||||
@@ -1,217 +0,0 @@
|
||||
#include "FloatingToolbox.h"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <memory> // for allocator
|
||||
|
||||
#include <gdk/gdk.h> // for GdkRectangle, GDK_LEAVE_...
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_con...
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/ToolEnums.h" // for TOOL_FLOATING_TOOLBOX
|
||||
#include "control/settings/ButtonConfig.h" // for ButtonConfig
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "control/settings/SettingsEnums.h" // for BUTTON_COUNT
|
||||
#include "util/glib_casts.h"
|
||||
|
||||
#include "MainWindow.h" // for MainWindow
|
||||
#include "ToolbarDefinitions.h" // for ToolbarEntryDefintion
|
||||
|
||||
|
||||
FloatingToolbox::FloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay) {
|
||||
this->mainWindow = theMainWindow;
|
||||
this->floatingToolbox = theMainWindow->get("floatingToolbox");
|
||||
this->floatingToolboxX = 200;
|
||||
this->floatingToolboxY = 200;
|
||||
this->floatingToolboxState = recalcSize;
|
||||
|
||||
gtk_overlay_add_overlay(overlay, this->floatingToolbox);
|
||||
gtk_overlay_set_overlay_pass_through(overlay, this->floatingToolbox, true);
|
||||
gtk_widget_add_events(this->floatingToolbox, GDK_LEAVE_NOTIFY_MASK);
|
||||
g_signal_connect(this->floatingToolbox, "leave-notify-event",
|
||||
xoj::util::wrap_for_g_callback_v<handleLeaveFloatingToolbox>, this);
|
||||
// position overlay widgets
|
||||
g_signal_connect(overlay, "get-child-position", xoj::util::wrap_for_g_callback_v<getOverlayPosition>, this);
|
||||
}
|
||||
|
||||
|
||||
FloatingToolbox::~FloatingToolbox() = default;
|
||||
|
||||
|
||||
void FloatingToolbox::show(int x, int y) {
|
||||
this->floatingToolboxX = x;
|
||||
this->floatingToolboxY = y;
|
||||
this->show();
|
||||
}
|
||||
|
||||
|
||||
/****
|
||||
* floatingToolboxActivated
|
||||
* True if the user has:
|
||||
* assigned a mouse or stylus button to bring up the floatingToolbox;
|
||||
* or enabled tapAction and Show FloatingToolbox( prefs->DrawingArea->ActionOnToolTap );
|
||||
* or put tools in the FloatingToolbox.
|
||||
*
|
||||
*/
|
||||
auto FloatingToolbox::floatingToolboxActivated() -> bool {
|
||||
Settings* settings = this->mainWindow->getControl()->getSettings();
|
||||
ButtonConfig* cfg = nullptr;
|
||||
|
||||
// check if any buttons assigned to bring up toolbox
|
||||
for (unsigned int id = 0; id < BUTTON_COUNT; id++) {
|
||||
cfg = settings->getButtonConfig(id);
|
||||
|
||||
if (cfg->getAction() == TOOL_FLOATING_TOOLBOX) {
|
||||
return true; // return true
|
||||
}
|
||||
}
|
||||
|
||||
// check if user can show Floating Menu with tap.
|
||||
if (settings->getDoActionOnStrokeFiltered() && settings->getStrokeFilterEnabled()) {
|
||||
return true; // return true
|
||||
}
|
||||
|
||||
return this->hasWidgets();
|
||||
}
|
||||
|
||||
|
||||
auto FloatingToolbox::hasWidgets() -> bool {
|
||||
for (int index = TBFloatFirst; index <= TBFloatLast; index++) {
|
||||
GtkToolbar* toolbar1 = GTK_TOOLBAR(this->mainWindow->get(TOOLBAR_DEFINITIONS[index].guiName));
|
||||
if (gtk_toolbar_get_n_items(toolbar1) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void FloatingToolbox::showForConfiguration() {
|
||||
if (this->floatingToolboxActivated()) // Do not show if not being used - at least while experimental.
|
||||
{
|
||||
GtkWidget* boxContents = this->mainWindow->get("boxContents");
|
||||
gint wx = 0, wy = 0;
|
||||
gtk_widget_translate_coordinates(boxContents, gtk_widget_get_toplevel(boxContents), 0, 0, &wx, &wy);
|
||||
this->floatingToolboxX = wx + 40; // when configuration state these are
|
||||
this->floatingToolboxY = wy + 40; // topleft coordinates( otherwise center).
|
||||
this->floatingToolboxState = configuration;
|
||||
this->show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FloatingToolbox::show() {
|
||||
gtk_widget_show_all(this->floatingToolbox);
|
||||
gtk_widget_set_visible(this->mainWindow->get("labelFloatingToolbox"), this->floatingToolboxState == configuration);
|
||||
gtk_widget_set_visible(this->mainWindow->get("showIfEmpty"),
|
||||
this->floatingToolboxState != configuration && !hasWidgets());
|
||||
}
|
||||
|
||||
|
||||
void FloatingToolbox::hide() {
|
||||
if (this->floatingToolboxState == configuration) {
|
||||
this->floatingToolboxState = recalcSize;
|
||||
}
|
||||
|
||||
gtk_widget_hide(this->floatingToolbox);
|
||||
}
|
||||
|
||||
|
||||
void FloatingToolbox::flagRecalculateSizeRequired() { this->floatingToolboxState = recalcSize; }
|
||||
|
||||
|
||||
/**
|
||||
* getOverlayPosition - this is how we position the widget in the overlay under the mouse
|
||||
*
|
||||
* The requested location is communicated via the FloatingToolbox member variables:
|
||||
* ->floatingToolbox, so we can operate on the right widget
|
||||
* ->floatingToolboxState, are we configuring, resizing or just moving
|
||||
* ->floatingToolboxX, where to display
|
||||
* ->floatingToolboxY.
|
||||
*
|
||||
*/
|
||||
auto FloatingToolbox::getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
|
||||
FloatingToolbox* self) -> bool {
|
||||
if (widget != self->floatingToolbox) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gtk_widget_get_allocation(widget, allocation);
|
||||
|
||||
if (self->floatingToolboxState != noChange || allocation->height < 2) {
|
||||
GtkRequisition natural;
|
||||
gtk_widget_get_preferred_size(widget, nullptr, &natural);
|
||||
allocation->width = natural.width;
|
||||
allocation->height = natural.height;
|
||||
}
|
||||
|
||||
// Get scrolled window for boundary clamping
|
||||
GtkWidget* mainBox = self->mainWindow->get("mainBox");
|
||||
GtkWidget* boxContents = self->mainWindow->get("boxContents");
|
||||
|
||||
GtkWidget* scrolledWindow = nullptr;
|
||||
GList* children = gtk_container_get_children(GTK_CONTAINER(boxContents));
|
||||
if (children != nullptr) {
|
||||
scrolledWindow = GTK_WIDGET(children->data);
|
||||
g_list_free(children);
|
||||
}
|
||||
|
||||
if (scrolledWindow == nullptr) {
|
||||
// Fallback: no clamping if scrolled window not found
|
||||
allocation->x = self->floatingToolboxX - allocation->width / 2;
|
||||
allocation->y = self->floatingToolboxY - allocation->height / 2;
|
||||
self->floatingToolboxState = noChange;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get scrolled window position relative to mainBox
|
||||
gint scrollX, scrollY;
|
||||
gtk_widget_translate_coordinates(scrolledWindow, mainBox, 0, 0, &scrollX, &scrollY);
|
||||
|
||||
GtkAllocation scrollAllocation;
|
||||
gtk_widget_get_allocation(scrolledWindow, &scrollAllocation);
|
||||
|
||||
switch (self->floatingToolboxState) {
|
||||
case recalcSize:
|
||||
[[fallthrough]];
|
||||
case noChange: {
|
||||
int centerX = self->floatingToolboxX - allocation->width / 2;
|
||||
int centerY = self->floatingToolboxY - allocation->height / 2;
|
||||
|
||||
// Clamp to scrolled window bounds with margin
|
||||
constexpr int margin = 10;
|
||||
int minX = scrollX + margin;
|
||||
int maxX = scrollX + scrollAllocation.width - allocation->width - margin;
|
||||
int minY = scrollY + margin;
|
||||
int maxY = scrollY + scrollAllocation.height - allocation->height - margin;
|
||||
|
||||
// Ensure valid clamp bounds when toolbox is larger than viewport
|
||||
maxX = std::max(maxX, minX);
|
||||
maxY = std::max(maxY, minY);
|
||||
|
||||
allocation->x = std::clamp(centerX, minX, maxX);
|
||||
allocation->y = std::clamp(centerY, minY, maxY);
|
||||
self->floatingToolboxState = noChange;
|
||||
break;
|
||||
}
|
||||
|
||||
case configuration:
|
||||
allocation->x = self->floatingToolboxX;
|
||||
allocation->y = self->floatingToolboxY;
|
||||
allocation->width = std::max(allocation->width + 32, 50);
|
||||
allocation->height = std::max(allocation->height, 50);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool FloatingToolbox::handleLeaveFloatingToolbox(GtkWidget* floatingToolbox, GdkEvent* event, FloatingToolbox* self) {
|
||||
if (floatingToolbox == self->floatingToolbox) {
|
||||
if (self->floatingToolboxState != configuration) {
|
||||
self->hide();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The Floating Toolbox
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <gdk/gdk.h> // for GdkEvent, GdkRectangle
|
||||
#include <glib.h> // for gboolean
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkOverlay
|
||||
|
||||
class MainWindow;
|
||||
|
||||
enum FloatingToolBoxState { recalcSize = 0, configuration, noChange };
|
||||
|
||||
/************************
|
||||
* FloatingToolbox:
|
||||
* Uses a GtkOverlay to show a widget containing toolbars.
|
||||
* The getOverlayPosition callback is used to position the toolbox widget relative to the users input device.
|
||||
* The ui/main.glade file defines the floatingToolbox as a GtkRevealer widget - attempts to use another widget
|
||||
* had issues with leave-notify-event.
|
||||
*/
|
||||
|
||||
class FloatingToolbox {
|
||||
public:
|
||||
FloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay);
|
||||
virtual ~FloatingToolbox();
|
||||
|
||||
public:
|
||||
/**
|
||||
* show(x,y): Show Toolbox at centered at x,y relative to main window.
|
||||
*/
|
||||
void show(int x, int y);
|
||||
|
||||
/**
|
||||
* showForConfiguration
|
||||
* i.e. appear in fixed position in top left, extra space and do not hide automatically.
|
||||
*/
|
||||
void showForConfiguration();
|
||||
|
||||
void hide();
|
||||
|
||||
/**
|
||||
* flagRecalculateSizeRequired(): trigger recalc size on next getOverlayPosition. Used when new toolbars loaded.
|
||||
*/
|
||||
void flagRecalculateSizeRequired();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Callback for positioning overlayed floating menu
|
||||
*/
|
||||
static bool getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* alloc, FloatingToolbox* self);
|
||||
|
||||
/**
|
||||
* Callback to hide floating Toolbar when mouse leaves it
|
||||
*/
|
||||
static bool handleLeaveFloatingToolbox(GtkWidget* floatingToolbox, GdkEvent* event, FloatingToolbox* self);
|
||||
|
||||
/**
|
||||
* Show the Floating Toolbox
|
||||
* ... but hide some labels depending on conditions.
|
||||
*/
|
||||
void show();
|
||||
|
||||
/**
|
||||
* check if user has assigned a button to activate, or has put tools in the FloatingToolbox.
|
||||
*/
|
||||
bool floatingToolboxActivated();
|
||||
|
||||
/**
|
||||
* Whether there are items in the toolbox.
|
||||
* Note this includes non-tools such as spacers and separators.
|
||||
*/
|
||||
bool hasWidgets();
|
||||
|
||||
|
||||
private:
|
||||
MainWindow* mainWindow;
|
||||
GtkWidget* floatingToolbox;
|
||||
|
||||
/**
|
||||
* Communicating with getOverlayPosition callback
|
||||
* */
|
||||
int floatingToolboxX = 0;
|
||||
int floatingToolboxY = 0;
|
||||
FloatingToolBoxState floatingToolboxState = recalcSize;
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "GladeGui.h"
|
||||
|
||||
#include <cstdlib> // for exit
|
||||
|
||||
#include <glib-object.h> // for g_object_unref
|
||||
#include <glib.h> // for g_error_free, GError, g_warning
|
||||
|
||||
#include "util/PlaceholderString.h" // for PlaceholderString
|
||||
#include "util/StringUtils.h" // for char_cast
|
||||
#include "util/XojMsgBox.h" // for XojMsgBox
|
||||
#include "util/i18n.h" // for FS, _F
|
||||
|
||||
#include "GladeSearchpath.h" // for GladeSearchpath
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
GladeGui::GladeGui(GladeSearchpath* gladeSearchPath, const std::string& glade, const std::string& mainWnd) {
|
||||
this->gladeSearchPath = gladeSearchPath;
|
||||
|
||||
auto filepath = this->gladeSearchPath->findFile("", glade);
|
||||
|
||||
GError* error = nullptr;
|
||||
builder.reset(gtk_builder_new(), xoj::util::adopt);
|
||||
|
||||
if (!gtk_builder_add_from_file(builder.get(), char_cast(filepath.u8string().c_str()), &error)) {
|
||||
std::string msg = FS(_F("Error loading glade file \"{1}\" (try to load \"{2}\")") % glade %
|
||||
char_cast(filepath.u8string()));
|
||||
|
||||
if (error != nullptr) {
|
||||
msg += "\n";
|
||||
msg += error->message;
|
||||
g_error_free(error);
|
||||
}
|
||||
XojMsgBox::showErrorAndQuit(msg, -1);
|
||||
}
|
||||
|
||||
this->window = get(mainWnd);
|
||||
}
|
||||
|
||||
GladeGui::~GladeGui() {
|
||||
if (!gtk_widget_get_parent(window)) {
|
||||
gtk_widget_destroy(window);
|
||||
}
|
||||
}
|
||||
|
||||
auto GladeGui::get(const std::string& name) -> GtkWidget* {
|
||||
GtkWidget* w = GTK_WIDGET(gtk_builder_get_object(builder.get(), name.c_str()));
|
||||
if (w == nullptr) {
|
||||
g_warning("GladeGui::get: Could not find glade Widget: \"%s\"", name.c_str());
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
auto GladeGui::getWindow() const -> GtkWidget* { return this->window; }
|
||||
|
||||
auto GladeGui::getGladeSearchPath() const -> GladeSearchpath* { return this->gladeSearchPath; }
|
||||
|
||||
auto GladeGui::getBuilder() const -> GtkBuilder* { return this->builder.get(); }
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Abstract GUI class, which loads the glade objects
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string> // for string
|
||||
|
||||
#include <gdk/gdk.h> // for GdkWindow
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkWindow, GtkBuilder
|
||||
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
class GladeGui {
|
||||
public:
|
||||
GladeGui(GladeSearchpath* gladeSearchPath, const std::string& glade, const std::string& mainWnd);
|
||||
virtual ~GladeGui();
|
||||
|
||||
virtual void show(GtkWindow* parent) = 0;
|
||||
|
||||
GtkWidget* get(const std::string& name);
|
||||
|
||||
void setThemePath(std::string themePath);
|
||||
|
||||
GtkWidget* getWindow() const;
|
||||
GladeSearchpath* getGladeSearchPath() const;
|
||||
|
||||
GtkBuilder* getBuilder() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The Glade resources
|
||||
*/
|
||||
xoj::util::GObjectSPtr<GtkBuilder> builder;
|
||||
|
||||
/**
|
||||
* Our search paths
|
||||
*/
|
||||
GladeSearchpath* gladeSearchPath;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* This window
|
||||
*/
|
||||
GtkWidget* window = nullptr;
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Search directory for icons and Glade files
|
||||
*
|
||||
* @author andreas
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#include "GladeSearchpath.h"
|
||||
|
||||
GladeSearchpath::GladeSearchpath() = default;
|
||||
|
||||
GladeSearchpath::~GladeSearchpath() { directories.clear(); }
|
||||
|
||||
auto GladeSearchpath::findFile(fs::path const& subdir, fs::path const& file) const -> fs::path {
|
||||
fs::path filepath;
|
||||
if (subdir.empty()) {
|
||||
filepath = file;
|
||||
} else {
|
||||
filepath = subdir / file;
|
||||
}
|
||||
|
||||
// We step through each directory to find it.
|
||||
for (const auto& dir: directories) {
|
||||
auto pathname = dir / filepath;
|
||||
|
||||
if (fs::exists(pathname)) {
|
||||
return pathname;
|
||||
}
|
||||
}
|
||||
|
||||
return fs::path{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The first search path
|
||||
*/
|
||||
auto GladeSearchpath::getFirstSearchPath() const -> fs::path {
|
||||
if (this->directories.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this->directories[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to set the directory containing installed pixmaps and Glade XML files.
|
||||
*/
|
||||
void GladeSearchpath::addSearchDirectory(fs::path const& directory) { this->directories.push_back(directory); }
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Search directory for icons and Glade files
|
||||
*
|
||||
* @author andreas
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class GladeSearchpath {
|
||||
public:
|
||||
GladeSearchpath();
|
||||
virtual ~GladeSearchpath();
|
||||
|
||||
public:
|
||||
void addSearchDirectory(fs::path const& directory);
|
||||
|
||||
/**
|
||||
* Searches for a path, return the path, an empty string if not found
|
||||
*/
|
||||
fs::path findFile(fs::path const& subdir, fs::path const& file) const;
|
||||
|
||||
/**
|
||||
* @return The first search path
|
||||
*/
|
||||
fs::path getFirstSearchPath() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Search directory for icons and Glade files
|
||||
*/
|
||||
std::vector<fs::path> directories;
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "IconNameHelper.h"
|
||||
|
||||
#include <gtk/gtk.h> // for gtk_icon_theme_get_default
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
|
||||
IconNameHelper::IconNameHelper(Settings* settings): settings(settings) {}
|
||||
|
||||
auto IconNameHelper::iconName(const char* icon) const -> std::string {
|
||||
std::string xoppName = std::string("xopp-") + icon;
|
||||
auto iconName = this->settings->areStockIconsUsed() && gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), icon) ?
|
||||
std::string(icon) :
|
||||
xoppName;
|
||||
return iconName;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Helper which allows to switch between Xournal++ icons and stock icons
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class Settings;
|
||||
|
||||
class IconNameHelper final {
|
||||
|
||||
public:
|
||||
IconNameHelper(Settings* settings);
|
||||
|
||||
protected:
|
||||
const Settings* settings;
|
||||
|
||||
public:
|
||||
std::string iconName(const char* icon) const;
|
||||
};
|
||||
@@ -1,432 +0,0 @@
|
||||
#include "Layout.h"
|
||||
|
||||
#include <algorithm> // for max, lower_bound, transform
|
||||
#include <cmath> // for abs
|
||||
#include <iterator> // for begin, end, distance
|
||||
#include <numeric> // for accumulate
|
||||
#include <optional> // for optional
|
||||
#include <type_traits> // for make_signed_t, remove_referen...
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/LayoutMapper.h" // for LayoutMapper, GridPosition
|
||||
#include "gui/PageView.h" // for XojPageView
|
||||
#include "gui/scroll/ScrollHandling.h" // for ScrollHandling
|
||||
#include "model/Document.h" // for Document
|
||||
#include "util/Rectangle.h" // for Rectangle
|
||||
#include "util/safe_casts.h" // for strict_cast, as_signed, as_si...
|
||||
|
||||
#include "XournalView.h" // for XournalView
|
||||
|
||||
using xoj::util::Rectangle;
|
||||
|
||||
/**
|
||||
* Padding outside the pages, including shadow
|
||||
*/
|
||||
constexpr auto const XOURNAL_PADDING = 10;
|
||||
|
||||
/**
|
||||
* Allowance for shadow between page pairs in paired page mode
|
||||
*/
|
||||
constexpr auto const XOURNAL_ROOM_FOR_SHADOW = 3;
|
||||
|
||||
/**
|
||||
* Padding between the pages
|
||||
*/
|
||||
constexpr auto const XOURNAL_PADDING_BETWEEN = 15;
|
||||
|
||||
|
||||
Layout::Layout(XournalView* view, ScrollHandling* scrollHandling): view(view), scrollHandling(scrollHandling) {
|
||||
g_signal_connect(scrollHandling->getHorizontal(), "value-changed", G_CALLBACK(horizontalScrollChanged), this);
|
||||
g_signal_connect(scrollHandling->getVertical(), "value-changed", G_CALLBACK(verticalScrollChanged), this);
|
||||
|
||||
|
||||
lastScrollHorizontal = gtk_adjustment_get_value(scrollHandling->getHorizontal());
|
||||
lastScrollVertical = gtk_adjustment_get_value(scrollHandling->getVertical());
|
||||
}
|
||||
|
||||
void Layout::horizontalScrollChanged(GtkAdjustment* adjustment, Layout* layout) {
|
||||
Layout::checkScroll(adjustment, layout->lastScrollHorizontal);
|
||||
layout->updateVisibility();
|
||||
}
|
||||
|
||||
void Layout::verticalScrollChanged(GtkAdjustment* adjustment, Layout* layout) {
|
||||
Layout::checkScroll(adjustment, layout->lastScrollVertical);
|
||||
layout->updateVisibility();
|
||||
|
||||
layout->maybeAddLastPage(layout);
|
||||
}
|
||||
|
||||
void Layout::maybeAddLastPage(Layout* layout) {
|
||||
auto* control = this->view->getControl();
|
||||
auto* settings = control->getSettings();
|
||||
if (settings->getEmptyLastPageAppend() == EmptyLastPageAppendType::OnScrollToEndOfLastPage) {
|
||||
// If the layout is 5px away from the end of the last page
|
||||
if (std::abs((layout->getMinimalHeight() - layout->getVisibleRect().y) - layout->getVisibleRect().height) < 5) {
|
||||
auto* doc = control->getDocument();
|
||||
doc->lock_shared();
|
||||
auto pdfPageCount = doc->getPdfPageCount();
|
||||
auto lastPage = doc->getPageCount() - 1;
|
||||
doc->unlock_shared();
|
||||
if (pdfPageCount == 0) {
|
||||
auto currentPage = control->getCurrentPageNo();
|
||||
if (currentPage == lastPage) {
|
||||
control->insertNewPage(currentPage + 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Layout::checkScroll(GtkAdjustment* adjustment, double& lastScroll) {
|
||||
lastScroll = gtk_adjustment_get_value(adjustment);
|
||||
}
|
||||
|
||||
void Layout::updateVisibility() {
|
||||
Rectangle visRect = getVisibleRect();
|
||||
|
||||
// step through every possible page position and update using p->setIsVisible()
|
||||
// Using initial grid aprox speeds things up by a factor of 5. See previous git check-in for specifics.
|
||||
int x1 = 0;
|
||||
int y1 = 0;
|
||||
|
||||
// Data to select page based on visibility
|
||||
std::optional<size_t> mostPageNr;
|
||||
double mostPagePercent = 0;
|
||||
|
||||
for (size_t row = 0; row < this->rowYStart.size(); ++row) {
|
||||
auto y2 = as_signed_strict(this->rowYStart[row]);
|
||||
for (size_t col = 0; col < this->colXStart.size(); ++col) {
|
||||
auto x2 = as_signed_strict(this->colXStart[col]);
|
||||
auto optionalPage = this->mapper.at({col, row});
|
||||
if (optionalPage) // a page exists at this grid location
|
||||
{
|
||||
auto& pageView = this->view->viewPages[*optionalPage];
|
||||
|
||||
// check if grid location is visible as an aprox for page visiblity:
|
||||
if (!(visRect.x > x2 || visRect.x + visRect.width < x1) // visrect not outside current row/col
|
||||
&& !(visRect.y > y2 || visRect.y + visRect.height < y1)) {
|
||||
// now use exact check of page itself:
|
||||
// visrect not outside current page dimensions:
|
||||
auto const& pageRect = pageView->getRect();
|
||||
if (auto intersection = pageRect.intersects(visRect); intersection) {
|
||||
pageView->setIsVisible(true);
|
||||
// Set the selected page
|
||||
double percent = intersection->area() / pageRect.area();
|
||||
|
||||
if (percent > mostPagePercent) {
|
||||
mostPageNr = *optionalPage;
|
||||
mostPagePercent = percent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pageView->setIsVisible(false);
|
||||
}
|
||||
}
|
||||
x1 = x2;
|
||||
}
|
||||
y1 = y2;
|
||||
x1 = 0;
|
||||
}
|
||||
|
||||
if (mostPageNr) {
|
||||
this->view->getControl()->firePageSelected(*mostPageNr);
|
||||
}
|
||||
}
|
||||
|
||||
auto Layout::getVisibleRect() -> Rectangle<double> {
|
||||
return Rectangle(gtk_adjustment_get_value(scrollHandling->getHorizontal()),
|
||||
gtk_adjustment_get_value(scrollHandling->getVertical()),
|
||||
gtk_adjustment_get_page_size(scrollHandling->getHorizontal()),
|
||||
gtk_adjustment_get_page_size(scrollHandling->getVertical()));
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the addend to base if the predicate is true
|
||||
*/
|
||||
|
||||
[[maybe_unused]] constexpr auto sumIf = [](auto base, auto addend, bool predicate) {
|
||||
if constexpr (std::is_signed_v<decltype(base)> || std::is_signed_v<decltype(addend)>) {
|
||||
using RT = std::make_signed_t<decltype(base + addend)>;
|
||||
if (predicate) {
|
||||
return RT(base) + RT(addend);
|
||||
}
|
||||
return RT(base);
|
||||
} else if constexpr (!(std::is_signed_v<decltype(base)> || std::is_signed_v<decltype(addend)>)) {
|
||||
using RT = decltype(base + addend);
|
||||
if (predicate) {
|
||||
return RT(base) + RT(addend);
|
||||
}
|
||||
return RT(base);
|
||||
}
|
||||
};
|
||||
void Layout::recalculate_int() const {
|
||||
auto* settings = view->getControl()->getSettings();
|
||||
auto len = view->viewPages.size();
|
||||
mapper.configureFromSettings(len, settings);
|
||||
auto colCount = mapper.getColumns();
|
||||
auto rowCount = mapper.getRows();
|
||||
|
||||
pc.widthCols.assign(colCount, 0);
|
||||
pc.heightRows.assign(rowCount, 0);
|
||||
|
||||
for (size_t pageIdx{}; pageIdx < len; ++pageIdx) {
|
||||
auto const& raster_p = mapper.at(pageIdx); // auto [c, r] raster = mapper.at();
|
||||
auto const& c = raster_p.col;
|
||||
auto const& r = raster_p.row;
|
||||
auto& v = view->viewPages[pageIdx];
|
||||
pc.widthCols[c] = std::max(pc.widthCols[c], v->getDisplayWidthDouble());
|
||||
pc.heightRows[r] = std::max(pc.heightRows[r], v->getDisplayHeightDouble());
|
||||
}
|
||||
|
||||
// add space around the entire page area to accommodate older Wacom tablets with limited sense area.
|
||||
auto vPadding = 2 * XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
vPadding += 2 * static_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
|
||||
} else if (settings->getAddVerticalSpace()) {
|
||||
vPadding += settings->getAddVerticalSpaceAmountAbove();
|
||||
vPadding += settings->getAddVerticalSpaceAmountBelow();
|
||||
}
|
||||
|
||||
auto hPadding = 2 * XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
hPadding += 2 * static_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
|
||||
} else if (settings->getAddHorizontalSpace()) {
|
||||
hPadding += settings->getAddHorizontalSpaceAmountLeft();
|
||||
hPadding += settings->getAddHorizontalSpaceAmountRight();
|
||||
}
|
||||
|
||||
pc.minWidth = as_unsigned(hPadding + as_signed_strict((pc.widthCols.size() - 1) * XOURNAL_PADDING_BETWEEN));
|
||||
pc.minHeight = as_unsigned(vPadding + as_signed_strict((pc.heightRows.size() - 1) * XOURNAL_PADDING_BETWEEN));
|
||||
|
||||
pc.minWidth = floor_cast<size_t>(std::accumulate(begin(pc.widthCols), end(pc.widthCols), double(pc.minWidth)));
|
||||
pc.minHeight = floor_cast<size_t>(std::accumulate(begin(pc.heightRows), end(pc.heightRows), double(pc.minHeight)));
|
||||
pc.valid = true;
|
||||
}
|
||||
|
||||
void Layout::recalculate() {
|
||||
pc.valid = false;
|
||||
gtk_widget_queue_resize(view->getWidget());
|
||||
}
|
||||
|
||||
void Layout::layoutPages(int width, int height) {
|
||||
std::lock_guard g{pc.m};
|
||||
if (!pc.valid) {
|
||||
recalculate_int();
|
||||
}
|
||||
// Todo: remove, just a hack-hotfix
|
||||
scrollHandling->setLayoutSize(std::max(width, strict_cast<int>(this->pc.minWidth)),
|
||||
std::max(height, strict_cast<int>(this->pc.minHeight)));
|
||||
|
||||
size_t const len = this->view->viewPages.size();
|
||||
Settings* settings = this->view->getControl()->getSettings();
|
||||
|
||||
// get from mapper (some may have changed to accommodate paired setting etc.)
|
||||
bool const isPairedPages = this->mapper.isPairedPages();
|
||||
|
||||
auto const rows = this->pc.heightRows.size();
|
||||
auto const columns = this->pc.widthCols.size();
|
||||
|
||||
|
||||
// add space around the entire page area to accommodate older Wacom tablets with limited sense area.
|
||||
auto v_padding = XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
v_padding += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
|
||||
} else if (settings->getAddVerticalSpace()) {
|
||||
v_padding += settings->getAddVerticalSpaceAmountAbove();
|
||||
}
|
||||
|
||||
auto h_padding = XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
h_padding += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
|
||||
} else if (settings->getAddHorizontalSpace()) {
|
||||
h_padding += settings->getAddHorizontalSpaceAmountLeft();
|
||||
}
|
||||
|
||||
auto const centeringXBorder = (width - as_signed(pc.minWidth)) / 2;
|
||||
auto const centeringYBorder = (height - as_signed(pc.minHeight)) / 2;
|
||||
|
||||
using SBig = decltype(as_signed(h_padding * centeringXBorder));
|
||||
auto const borderX = static_cast<double>(std::max<SBig>(h_padding, centeringXBorder));
|
||||
auto const borderY = static_cast<double>(std::max<SBig>(v_padding, centeringYBorder));
|
||||
|
||||
// initialize here and x again in loop below.
|
||||
auto x = borderX;
|
||||
auto y = borderY;
|
||||
|
||||
|
||||
// Iterate over ALL possible rows and columns.
|
||||
// We don't know which page, if any, is to be displayed in each row, column - ask the mapper object!
|
||||
// Then assign that page coordinates with center, left or right justify within row,column grid cell as required.
|
||||
for (size_t r = 0; r < rows; r++) {
|
||||
for (size_t c = 0; c < columns; c++) {
|
||||
auto optionalPage = this->mapper.at({c, r});
|
||||
|
||||
if (optionalPage) {
|
||||
|
||||
auto& v = this->view->viewPages[*optionalPage];
|
||||
v->setMappedRowCol(strict_cast<int>(r),
|
||||
strict_cast<int>(c)); // store row and column for e.g. proper arrow key navigation
|
||||
auto vDisplayWidth = v->getDisplayWidthDouble();
|
||||
{
|
||||
auto paddingLeft = 0.0;
|
||||
auto paddingRight = 0.0;
|
||||
auto columnPadding = this->pc.widthCols[c] - vDisplayWidth;
|
||||
|
||||
if (isPairedPages && len > 1) {
|
||||
// pair pages mode
|
||||
if (c % 2 == 0) {
|
||||
// align right
|
||||
paddingLeft = XOURNAL_PADDING_BETWEEN - XOURNAL_ROOM_FOR_SHADOW + columnPadding;
|
||||
paddingRight = XOURNAL_ROOM_FOR_SHADOW;
|
||||
} else { // align left
|
||||
paddingLeft = XOURNAL_ROOM_FOR_SHADOW;
|
||||
paddingRight = XOURNAL_PADDING_BETWEEN - XOURNAL_ROOM_FOR_SHADOW + columnPadding;
|
||||
}
|
||||
} else { // not paired page mode - center
|
||||
paddingLeft = XOURNAL_PADDING_BETWEEN / 2.0 + columnPadding / 2.0; // center justify
|
||||
paddingRight = XOURNAL_PADDING_BETWEEN - paddingLeft + columnPadding;
|
||||
}
|
||||
|
||||
x += paddingLeft;
|
||||
|
||||
v->setX(floor_cast<int>(x)); // set the page position
|
||||
v->setY(floor_cast<int>(y));
|
||||
|
||||
x += vDisplayWidth + paddingRight;
|
||||
}
|
||||
} else {
|
||||
x += this->pc.widthCols[c] + XOURNAL_PADDING_BETWEEN;
|
||||
}
|
||||
}
|
||||
x = borderX;
|
||||
y += this->pc.heightRows[r] + XOURNAL_PADDING_BETWEEN;
|
||||
}
|
||||
|
||||
this->colXStart.resize(this->pc.widthCols.size());
|
||||
this->rowYStart.resize(this->pc.heightRows.size());
|
||||
|
||||
|
||||
// accumulated - absolute pixel location for use by getViewAt() and updateVisibility()
|
||||
auto totalWidth = borderX;
|
||||
std::transform(
|
||||
begin(this->pc.widthCols), end(this->pc.widthCols), begin(this->colXStart), [&totalWidth](auto&& widthCol) {
|
||||
return strict_cast<std::remove_reference_t<decltype(widthCol)>>(totalWidth +=
|
||||
widthCol + XOURNAL_PADDING_BETWEEN);
|
||||
});
|
||||
auto totalHeight = borderY;
|
||||
std::transform(begin(this->pc.heightRows), end(this->pc.heightRows), begin(this->rowYStart),
|
||||
[&totalHeight](auto&& heightRow) {
|
||||
return strict_cast<std::remove_reference_t<decltype(heightRow)>>(
|
||||
(totalHeight += heightRow + XOURNAL_PADDING_BETWEEN));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
auto Layout::getPaddingAbovePage(size_t pageIndex) const -> int {
|
||||
const Settings* settings = this->view->getControl()->getSettings();
|
||||
|
||||
// User-configured padding above all pages.
|
||||
auto paddingAbove = XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
paddingAbove += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getVertical()));
|
||||
} else if (settings->getAddVerticalSpace()) {
|
||||
paddingAbove += settings->getAddVerticalSpaceAmountAbove();
|
||||
}
|
||||
|
||||
// (x, y) coordinate pair gives grid indices. This handles paired pages and different page layouts for us.
|
||||
auto pageYLocation = strict_cast<int>(this->mapper.at(pageIndex).row);
|
||||
return pageYLocation * XOURNAL_PADDING_BETWEEN + paddingAbove;
|
||||
}
|
||||
|
||||
|
||||
auto Layout::getPaddingLeftOfPage(size_t pageIndex) const -> int {
|
||||
bool isPairedPages = this->mapper.isPairedPages();
|
||||
const Settings* settings = this->view->getControl()->getSettings();
|
||||
|
||||
auto paddingBefore = XOURNAL_PADDING;
|
||||
if (settings->getUnlimitedScrolling()) {
|
||||
paddingBefore += ceil_cast<int>(gtk_adjustment_get_page_size(scrollHandling->getHorizontal()));
|
||||
} else if (settings->getAddHorizontalSpace()) {
|
||||
paddingBefore += settings->getAddHorizontalSpaceAmountLeft();
|
||||
}
|
||||
|
||||
auto const pageXLocation = strict_cast<int>(this->mapper.at(pageIndex).col);
|
||||
|
||||
// No page pairing or we haven't rendered enough pages in the row for page pairing to have an effect,
|
||||
if (!isPairedPages) {
|
||||
return pageXLocation * XOURNAL_PADDING_BETWEEN + XOURNAL_PADDING_BETWEEN / 2 + paddingBefore;
|
||||
} else {
|
||||
auto columnPadding = XOURNAL_PADDING_BETWEEN + pageXLocation * XOURNAL_PADDING_BETWEEN;
|
||||
if (pageXLocation % 2 == 0) {
|
||||
return columnPadding - XOURNAL_ROOM_FOR_SHADOW + paddingBefore;
|
||||
} else {
|
||||
return columnPadding + XOURNAL_ROOM_FOR_SHADOW + paddingBefore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Layout::scrollRelative(double x, double y) {
|
||||
if (this->view->getControl()->getSettings()->isPresentationMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_adjustment_set_value(scrollHandling->getHorizontal(),
|
||||
gtk_adjustment_get_value(scrollHandling->getHorizontal()) + x);
|
||||
gtk_adjustment_set_value(scrollHandling->getVertical(),
|
||||
gtk_adjustment_get_value(scrollHandling->getVertical()) + y);
|
||||
}
|
||||
|
||||
void Layout::scrollAbs(double x, double y) {
|
||||
if (this->view->getControl()->getSettings()->isPresentationMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_adjustment_set_value(scrollHandling->getHorizontal(), x);
|
||||
gtk_adjustment_set_value(scrollHandling->getVertical(), y);
|
||||
}
|
||||
|
||||
|
||||
void Layout::ensureRectIsVisible(int x, int y, int width, int height) {
|
||||
gtk_adjustment_clamp_page(scrollHandling->getHorizontal(), x - 5, x + width + 10);
|
||||
gtk_adjustment_clamp_page(scrollHandling->getVertical(), y - 5, y + height + 10);
|
||||
}
|
||||
|
||||
|
||||
auto Layout::getPageViewAt(int x, int y) -> XojPageView* {
|
||||
// Binary Search:
|
||||
auto rit = std::lower_bound(this->rowYStart.begin(), this->rowYStart.end(), y);
|
||||
auto const foundRow = size_t(std::distance(this->rowYStart.begin(), rit));
|
||||
auto cit = std::lower_bound(this->colXStart.begin(), this->colXStart.end(), x);
|
||||
auto const foundCol = size_t(std::distance(this->colXStart.begin(), cit));
|
||||
|
||||
auto optionalPage = this->mapper.at({foundCol, foundRow});
|
||||
|
||||
if (optionalPage && this->view->viewPages[*optionalPage]->containsPoint(x, y, false)) {
|
||||
return this->view->viewPages[*optionalPage].get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto Layout::getPageIndexAtGridMap(size_t row, size_t col) -> std::optional<size_t> {
|
||||
return this->mapper.at({col, row}); // watch out.. x,y --> c,r
|
||||
}
|
||||
|
||||
auto Layout::getMinimalHeight() const -> int {
|
||||
std::lock_guard g{pc.m};
|
||||
if (!pc.valid) {
|
||||
recalculate_int();
|
||||
}
|
||||
return strict_cast<int>(this->pc.minHeight);
|
||||
}
|
||||
|
||||
auto Layout::getMinimalWidth() const -> int {
|
||||
std::lock_guard g{pc.m};
|
||||
if (!pc.valid) {
|
||||
recalculate_int();
|
||||
}
|
||||
return strict_cast<int>(this->pc.minWidth);
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Handles the layout of the pages within a Xournal document
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <mutex> // for mutex
|
||||
#include <optional> // for optional
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for GtkAdjustment
|
||||
|
||||
#include "util/Rectangle.h" // for Rectangle
|
||||
|
||||
#include "LayoutMapper.h" // for LayoutMapper
|
||||
|
||||
class XojPageView;
|
||||
class XournalView;
|
||||
class ScrollHandling;
|
||||
|
||||
|
||||
/**
|
||||
* @brief The Layout manager for the XournalWidget
|
||||
*
|
||||
* This class manages the layout of the XojPageView's contained
|
||||
* in the XournalWidget
|
||||
*/
|
||||
class Layout final {
|
||||
public:
|
||||
Layout(XournalView* view, ScrollHandling* scrollHandling);
|
||||
struct PreCalculated {
|
||||
std::mutex m;
|
||||
|
||||
// The width and height of all our pages
|
||||
size_t minWidth = 0;
|
||||
size_t minHeight = 0;
|
||||
std::vector<double> widthCols;
|
||||
std::vector<double> heightRows;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
public:
|
||||
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
|
||||
/**
|
||||
* Increases the adjustments by the given amounts
|
||||
*/
|
||||
void scrollRelative(double x, double y);
|
||||
|
||||
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
|
||||
/**
|
||||
* Changes the adjustments by absolute amounts (for pinch-to-zoom)
|
||||
*/
|
||||
void scrollAbs(double x, double y);
|
||||
|
||||
// Todo(Fabian): move to XournalView
|
||||
/**
|
||||
* Changes the adjustments in such a way as to make sure that
|
||||
* the given Rectangle is visible
|
||||
*
|
||||
* @remark If the given Rectangle won't fit into the scrolled window
|
||||
* then only its top left corner will be visible
|
||||
*/
|
||||
void ensureRectIsVisible(int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* Returns the height of the entire Layout
|
||||
*/
|
||||
int getMinimalHeight() const;
|
||||
|
||||
/**
|
||||
* Returns the width of the entire Layout
|
||||
*/
|
||||
int getMinimalWidth() const;
|
||||
|
||||
// Todo(Fabian): move to XournalView this must not depend on Layout directly
|
||||
/**
|
||||
* Returns the Rectangle which is currently visible
|
||||
*/
|
||||
xoj::util::Rectangle<double> getVisibleRect();
|
||||
|
||||
|
||||
/**
|
||||
* recalculate and resize Layout
|
||||
*/
|
||||
void recalculate();
|
||||
|
||||
/**
|
||||
* Performs a layout of the XojPageView's managed in this Layout
|
||||
* Sets out pages in a grid.
|
||||
* Document pages are assigned to grid positions by the mapper object and may be ordered in a myriad of ways.
|
||||
* ONLY call this on size allocation
|
||||
*/
|
||||
void layoutPages(int width, int height);
|
||||
|
||||
// Todo(Fabian): move to View:
|
||||
/**
|
||||
* Updates the current XojPageView. The XojPageView is selected based on
|
||||
* the percentage of the visible area of the XojPageView relative
|
||||
* to its total area.
|
||||
*/
|
||||
void updateVisibility();
|
||||
|
||||
/**
|
||||
* Return the pageview containing co-ordinates.
|
||||
*/
|
||||
XojPageView* getPageViewAt(int x, int y);
|
||||
|
||||
/**
|
||||
* Return the page index found ( or std::nullopt if not found) at layout grid row,col
|
||||
*
|
||||
*/
|
||||
std::optional<size_t> getPageIndexAtGridMap(size_t row, size_t col);
|
||||
|
||||
/**
|
||||
* @brief Get the total padding above the page at the given index.
|
||||
*
|
||||
* The size of this padding does not scale with pages as the user zooms in and out.
|
||||
* As such, it is often necessary to get _just_ this padding.
|
||||
*
|
||||
* @param pageIndex is the index of the XojPageView as returned by [getIndexAtGridMap]
|
||||
* @return the sum of the padding between pages above the page with the given index
|
||||
* and any padding the user added vertically above all pages (i.e. in settings).
|
||||
*/
|
||||
int getPaddingAbovePage(size_t pageIndex) const;
|
||||
|
||||
/**
|
||||
* @brief Get the static padding added to the left of the current page.
|
||||
*
|
||||
* This does not include padding added to center the page on the screen.
|
||||
*
|
||||
* @param pageIndex is the index of the XojPageView, as given by [getIndexAtGridMap]
|
||||
* @return the sum of the padding between pages left of the page at [pageIndex] and
|
||||
* any horizontal padding the user decided to add (from settings)
|
||||
*/
|
||||
int getPaddingLeftOfPage(size_t pageIndex) const;
|
||||
|
||||
protected:
|
||||
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
|
||||
static void horizontalScrollChanged(GtkAdjustment* adjustment, Layout* layout);
|
||||
static void verticalScrollChanged(GtkAdjustment* adjustment, Layout* layout);
|
||||
|
||||
private:
|
||||
void recalculate_int() const;
|
||||
|
||||
void maybeAddLastPage(Layout* layout);
|
||||
|
||||
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
|
||||
static void checkScroll(GtkAdjustment* adjustment, double& lastScroll);
|
||||
|
||||
private:
|
||||
XournalView* view = nullptr;
|
||||
ScrollHandling* scrollHandling = nullptr;
|
||||
|
||||
// Todo(Fabian): move to ScrollHandling also it must not depend on Layout
|
||||
double lastScrollHorizontal = -1;
|
||||
double lastScrollVertical = -1;
|
||||
|
||||
/**
|
||||
* layoutPages invalidates the precalculation of recalculate
|
||||
* this bool prevents that layotPages can be called without a previously call to recalculate
|
||||
* Todo: we may want to remove the additional calculation in layoutPages, since we stored those values in
|
||||
*/
|
||||
mutable LayoutMapper mapper;
|
||||
mutable PreCalculated pc{};
|
||||
mutable std::vector<unsigned> colXStart;
|
||||
mutable std::vector<unsigned> rowYStart;
|
||||
};
|
||||
@@ -1,177 +0,0 @@
|
||||
#include "LayoutMapper.h"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <map> // for __alloc_traits<>::value_type
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
|
||||
constexpr auto calc_default_grid_pos = [](size_t page_nr, GridPosition grid_size,
|
||||
LayoutSettings::Orientation orientation) {
|
||||
return orientation == LayoutSettings::Orientation::Vertical ?
|
||||
GridPosition{page_nr / grid_size.row, page_nr % grid_size.row} :
|
||||
GridPosition{page_nr % grid_size.col, page_nr / grid_size.col};
|
||||
};
|
||||
|
||||
constexpr auto map1 = [](LayoutSettings const& data, size_t page_nr) {
|
||||
GridPosition grid_pos{};
|
||||
auto new_nr = page_nr + static_cast<size_t>(data.offset);
|
||||
if (data.showPairedPages) {
|
||||
auto grid_size = GridPosition{data.cols / 2, data.rows};
|
||||
grid_pos = calc_default_grid_pos(new_nr / 2, grid_size, data.orientation);
|
||||
(grid_pos.col *= 2) += (new_nr % 2);
|
||||
} else {
|
||||
grid_pos = calc_default_grid_pos(new_nr, {data.cols, data.rows}, data.orientation);
|
||||
}
|
||||
decltype(grid_pos) grid_pos2 = {
|
||||
(data.horizontalDir == LayoutSettings::HorizontalDirection::RightToLeft ? data.cols - 1 - grid_pos.col :
|
||||
grid_pos.col),
|
||||
(data.verticalDir == LayoutSettings::VerticalDirection::BottomToTop ? data.rows - 1 - grid_pos.row :
|
||||
grid_pos.row)};
|
||||
return grid_pos2;
|
||||
};
|
||||
|
||||
constexpr auto map2 = [](LayoutSettings const& data, GridPosition pos) -> std::optional<size_t> {
|
||||
auto isRightToLeft = [&]() { return data.horizontalDir == LayoutSettings::RightToLeft; };
|
||||
auto isBottomToTop = [&]() { return data.verticalDir == LayoutSettings::BottomToTop; };
|
||||
auto isVertical = [&]() { return data.orientation == LayoutSettings::Vertical; };
|
||||
|
||||
if (isRightToLeft()) { // reverse x
|
||||
pos.col = data.cols - 1 - pos.col;
|
||||
}
|
||||
if (isBottomToTop()) { // reverse y
|
||||
pos.row = data.rows - 1 - pos.row;
|
||||
}
|
||||
size_t res{};
|
||||
if (isVertical()) {
|
||||
if (data.showPairedPages) {
|
||||
res = ((pos.row + data.rows * (pos.col / 2)) * 2) + pos.col % 2;
|
||||
} else {
|
||||
res = pos.row + data.rows * pos.col;
|
||||
}
|
||||
} else { // Horizontal
|
||||
res = pos.col + data.cols * pos.row;
|
||||
}
|
||||
res -= data.offset;
|
||||
if (res >= data.actualPages) {
|
||||
return {};
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
// Testers
|
||||
|
||||
constexpr LayoutSettings my_data0{1, 91, 92, 1};
|
||||
constexpr LayoutSettings my_data1{91, 1, 90, 1};
|
||||
constexpr LayoutSettings my_data2{
|
||||
6, 6, 35, 1, true, LayoutSettings::Vertical, LayoutSettings::RightToLeft, LayoutSettings::BottomToTop};
|
||||
constexpr GridPosition test_pos0{0, 3};
|
||||
constexpr GridPosition test_pos1{3, 0};
|
||||
constexpr GridPosition test_pos2{3, 3};
|
||||
|
||||
static_assert(map1(my_data0, *map2(my_data0, test_pos0)) == test_pos0);
|
||||
static_assert(map1(my_data1, *map2(my_data1, test_pos1)) == test_pos1);
|
||||
static_assert(map1(my_data2, *map2(my_data2, test_pos2)) == test_pos2);
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hash(T&& data_, GridPosition const& pos) {
|
||||
return pos.col + pos.row * data_.cols;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calculate
|
||||
*
|
||||
* Set mapper to LayoutType with number of pages and of fixed rows or columns
|
||||
* @param data the data to be configured
|
||||
* @param numRows Number of rows ( used if useRows )
|
||||
* @param numCols Number of columns ( used if !useRows )
|
||||
* @param useRows use pages/rows to recalculate cols else recalculate rows
|
||||
* @param firstPageOffset Pages to offset - usually one or zero in order to pair up properly
|
||||
*/
|
||||
|
||||
void calculate(LayoutSettings& data, size_t numRows, size_t numCols, bool useRows, uint32_t firstPageOffset) {
|
||||
if (useRows) {
|
||||
data.rows = std::max<size_t>(1UL, numRows);
|
||||
|
||||
// using + ( rows-1) to round up (int)pages/rows
|
||||
data.cols = std::max<size_t>(1UL, (data.actualPages + firstPageOffset + (data.rows - 1)) / data.rows);
|
||||
if (data.showPairedPages) {
|
||||
data.cols += data.cols % 2; // make even
|
||||
}
|
||||
} else {
|
||||
data.cols = std::max<size_t>(1UL, numCols);
|
||||
if (data.showPairedPages) {
|
||||
data.cols += data.cols % 2; // make even
|
||||
}
|
||||
data.rows = std::max<size_t>(1UL, (data.actualPages + firstPageOffset + (data.cols - 1)) / data.cols);
|
||||
}
|
||||
|
||||
|
||||
if (data.orientation == LayoutSettings::Vertical) {
|
||||
// Vertical Layout
|
||||
if (data.showPairedPages) {
|
||||
data.offset = firstPageOffset % static_cast<uint32_t>(2 * data.rows);
|
||||
} else {
|
||||
data.offset = firstPageOffset % static_cast<uint32_t>(data.rows);
|
||||
}
|
||||
} else {
|
||||
// Horizontal Layout
|
||||
data.offset = firstPageOffset % static_cast<uint32_t>(data.cols);
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutMapper::configureFromSettings(size_t numPages, Settings* settings) {
|
||||
LayoutSettings data;
|
||||
// get from user settings:
|
||||
data.actualPages = numPages;
|
||||
data.showPairedPages = settings->isShowPairedPages();
|
||||
auto const& pairsOffset = data.showPairedPages ? static_cast<uint32_t>(settings->getPairsOffset()) : 0U;
|
||||
|
||||
auto const& isPresentationMode = settings->isPresentationMode();
|
||||
auto const& fixRows = !isPresentationMode && settings->isViewFixedRows();
|
||||
auto const& numCols = size_t(isPresentationMode ? 1 : settings->getViewColumns());
|
||||
auto const& numRows = size_t(isPresentationMode ? 1 : settings->getViewRows());
|
||||
|
||||
// assemble bitflags for LayoutType
|
||||
data.orientation = (isPresentationMode || settings->getViewLayoutVert()) ? LayoutSettings::Vertical :
|
||||
LayoutSettings::Horizontal;
|
||||
data.horizontalDir = settings->getViewLayoutR2L() ? LayoutSettings::RightToLeft : LayoutSettings::LeftToRight;
|
||||
data.verticalDir = settings->getViewLayoutB2T() ? LayoutSettings::BottomToTop : LayoutSettings::TopToBottom;
|
||||
|
||||
calculate(data, numRows, numCols, fixRows, pairsOffset);
|
||||
if (data == data_) {
|
||||
return;
|
||||
}
|
||||
data_ = data;
|
||||
precalculateMappers();
|
||||
}
|
||||
|
||||
void LayoutMapper::precalculateMappers() {
|
||||
this->pageToRaster.clear();
|
||||
this->rasterToPage.clear();
|
||||
this->pageToRaster.resize(data_.actualPages);
|
||||
this->rasterToPage.resize(data_.rows * data_.cols, size_t(-1));
|
||||
|
||||
for (size_t i = 0; i != data_.actualPages; ++i) {
|
||||
auto&& val = map1(data_, i);
|
||||
this->pageToRaster[i] = val;
|
||||
this->rasterToPage[hash(data_, val)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
auto LayoutMapper::getColumns() const -> size_t { return data_.cols; }
|
||||
|
||||
auto LayoutMapper::getRows() const -> size_t { return data_.rows; }
|
||||
|
||||
auto LayoutMapper::isPairedPages() const -> bool { return data_.showPairedPages; }
|
||||
|
||||
auto LayoutMapper::at(size_t page) const -> GridPosition { return pageToRaster.at(page); }
|
||||
|
||||
auto LayoutMapper::at(GridPosition const& rasterXY) const -> std::optional<size_t> {
|
||||
auto index = hash(data_, rasterXY);
|
||||
if (index >= rasterToPage.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto val = rasterToPage[index];
|
||||
return val >= data_.actualPages ? std::nullopt : std::optional<size_t>(val); // Maybe optimize this
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* A layout manager - map where( row,column) to which page( document index)
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <optional> // for optional
|
||||
#include <tuple> // for operator==, tie, tuple
|
||||
#include <vector> // for vector
|
||||
|
||||
class Settings;
|
||||
|
||||
struct GridPosition {
|
||||
size_t col{};
|
||||
size_t row{};
|
||||
|
||||
constexpr friend bool operator==(GridPosition const& lhs, GridPosition const& rhs) noexcept {
|
||||
return lhs.col == rhs.col && lhs.row == rhs.row;
|
||||
}
|
||||
};
|
||||
|
||||
struct LayoutSettings {
|
||||
/// @brief The Layout of the pages
|
||||
enum Orientation : bool {
|
||||
Horizontal = false,
|
||||
Vertical = true,
|
||||
};
|
||||
|
||||
/// Horizontal read direction
|
||||
enum HorizontalDirection : bool {
|
||||
LeftToRight = false,
|
||||
RightToLeft = true,
|
||||
};
|
||||
|
||||
/// Vertical read direction
|
||||
enum VerticalDirection : bool {
|
||||
TopToBottom = false,
|
||||
BottomToTop = true,
|
||||
};
|
||||
|
||||
size_t cols = 0;
|
||||
size_t rows = 0;
|
||||
size_t actualPages = 0;
|
||||
uint32_t offset = 0;
|
||||
|
||||
bool showPairedPages = false;
|
||||
Orientation orientation = Vertical;
|
||||
HorizontalDirection horizontalDir = LeftToRight;
|
||||
VerticalDirection verticalDir = TopToBottom;
|
||||
|
||||
constexpr friend auto operator==(LayoutSettings const& lhs, LayoutSettings const& rhs) {
|
||||
auto tier = [](auto&& other) {
|
||||
return std::tie(other.cols, other.rows, other.actualPages, other.offset, other.showPairedPages,
|
||||
other.orientation, other.horizontalDir, other.verticalDir);
|
||||
};
|
||||
return tier(lhs) == tier(rhs);
|
||||
}
|
||||
};
|
||||
auto layoutSettings(Settings const*) -> LayoutSettings;
|
||||
|
||||
/**
|
||||
* @brief Layout asks this mapper what page ( if any ) should be at a given column,row.
|
||||
*/
|
||||
class LayoutMapper {
|
||||
public:
|
||||
LayoutMapper() = default;
|
||||
|
||||
/**
|
||||
* configureFromSettings
|
||||
* Obtain user settings to determine arguments to configure().
|
||||
*
|
||||
* @param pages The number of pages in the document
|
||||
* @param settings The Settings from which users settings are obtained
|
||||
*/
|
||||
|
||||
void configureFromSettings(size_t numPages, Settings* settings);
|
||||
|
||||
auto at(size_t) const -> GridPosition;
|
||||
auto at(GridPosition const&) const -> std::optional<size_t>;
|
||||
|
||||
size_t getColumns() const;
|
||||
size_t getRows() const;
|
||||
|
||||
bool isPairedPages() const;
|
||||
|
||||
private:
|
||||
void precalculateMappers();
|
||||
|
||||
/**
|
||||
* Map page location to document index
|
||||
*
|
||||
* @param x Row we are interested in
|
||||
* @param y Column we are interested in
|
||||
*
|
||||
* @return Page index to put at coordinates
|
||||
*/
|
||||
|
||||
public:
|
||||
LayoutSettings data_;
|
||||
|
||||
friend void calculate(LayoutSettings& data, size_t numRows, size_t numCols, bool useRows, uint32_t firstPageOffset);
|
||||
|
||||
std::vector<GridPosition> pageToRaster;
|
||||
std::vector<size_t> rasterToPage; // access with x*y_max+y+
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "LegacyRedrawable.h"
|
||||
|
||||
#include "model/Element.h" // for Element
|
||||
#include "util/Range.h" // for Range
|
||||
|
||||
void LegacyRedrawable::repaintElement(Element* e) const {
|
||||
repaintArea(e->getX(), e->getY(), e->getElementWidth() + e->getX(), e->getElementHeight() + e->getY());
|
||||
}
|
||||
|
||||
void LegacyRedrawable::repaintRect(double x, double y, double width, double height) const {
|
||||
repaintArea(x, y, x + width, y + height);
|
||||
}
|
||||
|
||||
void LegacyRedrawable::rerenderRange(const Range& r) { rerenderRect(r.getX(), r.getY(), r.getWidth(), r.getHeight()); }
|
||||
|
||||
void LegacyRedrawable::rerenderElement(const Element* e) {
|
||||
rerenderRect(e->getX(), e->getY(), e->getElementWidth(), e->getElementHeight());
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Interface for GUI handling
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gdk/gdk.h> // for GdkRGBA
|
||||
|
||||
class Element;
|
||||
class Range;
|
||||
|
||||
/*
|
||||
* The (base) class Redrawable (now LegacyRedrawable) is deprecated. Use xoj::view::Repaintable instead.
|
||||
*
|
||||
* Reasons for the deprecation:
|
||||
*
|
||||
* * It is basically used synonymously to XojPageView.
|
||||
* * More importantly, the coexistence of Redrawable::repaint... and Redrawable::rerender... is bothersome.
|
||||
* it is not clear (without diving in the code) what the difference should be.
|
||||
* (repaint = ask gtk to call us back to blitt the buffer, rerender = change the buffer's content)
|
||||
* The rerender... methods should not exist. The derived class should be told what happened (something changed),
|
||||
* and decide by itself if the buffer should be updated or not (if there is a buffer to begin with).
|
||||
*/
|
||||
class [[deprecated]] LegacyRedrawable {
|
||||
protected:
|
||||
LegacyRedrawable() = default;
|
||||
|
||||
public:
|
||||
LegacyRedrawable(LegacyRedrawable&&) = default;
|
||||
LegacyRedrawable(LegacyRedrawable const&) = default;
|
||||
virtual ~LegacyRedrawable() = default;
|
||||
LegacyRedrawable& operator=(LegacyRedrawable&&) = default;
|
||||
LegacyRedrawable& operator=(LegacyRedrawable const&) = default;
|
||||
|
||||
/**
|
||||
* Call this if you only need to repaint the view, this means the buffer will be painted again,
|
||||
* and all selections, text editors etc. are drawn again, but the view buffer is not refreshed.
|
||||
*
|
||||
* for refreshing the view buffer (if you have changed the document) call rerender.
|
||||
*/
|
||||
virtual void repaintArea(double x1, double y1, double x2, double y2) const = 0;
|
||||
[[maybe_unused]] void repaintRect(double x, double y, double width, double height) const;
|
||||
[[maybe_unused]] void repaintElement(Element* e) const;
|
||||
|
||||
/**
|
||||
* Call this if you only need to redraw the view, this means the buffer will be painted again,
|
||||
* and all selections, text editors etc. are drawn again, but the view buffer is not refreshed.
|
||||
*
|
||||
* for refreshing the view buffer (if you have changed the document) call rerender.
|
||||
*/
|
||||
virtual void repaintPage() const = 0;
|
||||
|
||||
/**
|
||||
* Rerender our buffer, then redraw the widget
|
||||
* @param sizeChanged whether or not this rerendering is caused by the size of the page changing
|
||||
*/
|
||||
virtual void rerenderPage(bool sizeChanged = false) = 0;
|
||||
|
||||
/**
|
||||
* Call this if you add an element, remove an element etc.
|
||||
*/
|
||||
void rerenderElement(const Element* e);
|
||||
void rerenderRange(const Range& r);
|
||||
|
||||
/**
|
||||
* This updated the view buffer and then rerender the the region, call this if you changed the document
|
||||
*/
|
||||
virtual void rerenderRect(double x, double y, double width, double height) = 0;
|
||||
|
||||
/**
|
||||
* Return the GTK selection color
|
||||
*/
|
||||
virtual GdkRGBA getSelectionColor() = 0;
|
||||
|
||||
|
||||
virtual void deleteViewBuffer() = 0;
|
||||
|
||||
virtual int getX() const = 0;
|
||||
virtual int getY() const = 0;
|
||||
};
|
||||
@@ -1,742 +0,0 @@
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h> // for gdk_pixbuf_new_fr...
|
||||
#include <gdk/gdk.h> // for gdk_screen_get_de...
|
||||
#include <gio/gio.h> // for g_cancellable_is_...
|
||||
#include <gtk/gtkcssprovider.h> // for gtk_css_provider_...
|
||||
|
||||
#include "control/AudioController.h" // for AudioController
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/DeviceListHelper.h" // for getSourceMapping
|
||||
#include "control/ScrollHandler.h" // for ScrollHandler
|
||||
#include "control/actions/ActionDatabase.h" // for ActionDatabase
|
||||
#include "control/jobs/XournalScheduler.h" // for XournalScheduler
|
||||
#include "control/layer/LayerController.h" // for LayerController
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "control/settings/SettingsEnums.h" // for SCROLLBAR_HIDE_HO...
|
||||
#include "control/zoom/ZoomControl.h" // for ZoomControl
|
||||
#include "gui/FloatingToolbox.h" // for FloatingToolbox
|
||||
#include "gui/GladeGui.h" // for GladeGui
|
||||
#include "gui/PdfFloatingToolbox.h" // for PdfFloatingToolbox
|
||||
#include "gui/SearchBar.h" // for SearchBar
|
||||
#include "gui/inputdevices/InputEvents.h" // for INPUT_DEVICE_TOUC...
|
||||
#include "gui/menus/menubar/Menubar.h" // for Menubar
|
||||
#include "gui/menus/menubar/ToolbarSelectionSubmenu.h" // for ToolbarSelectionSubmenu
|
||||
#include "gui/scroll/ScrollHandling.h" // for ScrollHandling
|
||||
#include "gui/sidebar/Sidebar.h" // for Sidebar
|
||||
#include "gui/toolbarMenubar/ToolMenuHandler.h" // for ToolMenuHandler
|
||||
#include "gui/toolbarMenubar/model/ToolbarData.h" // for ToolbarData
|
||||
#include "gui/toolbarMenubar/model/ToolbarModel.h" // for ToolbarModel
|
||||
#include "gui/widgets/SpinPageAdapter.h" // for SpinPageAdapter
|
||||
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_get_l...
|
||||
#include "util/GListView.h" // for GListView, GListV...
|
||||
#include "util/GtkUtil.h" // for getWidgetDPI
|
||||
#include "util/PathUtil.h" // for getConfigFile
|
||||
#include "util/StringUtils.h" // for char_cast
|
||||
#include "util/Util.h" // for execInUiThread, npos
|
||||
#include "util/XojMsgBox.h" // for XojMsgBox
|
||||
#include "util/glib_casts.h" // for wrap_for_once_v
|
||||
#include "util/gtk4_helper.h" // for gtk_widget_get_width
|
||||
#include "util/i18n.h" // for FS, _F
|
||||
#include "util/raii/CStringWrapper.h" // for OwnedCString
|
||||
|
||||
#include "GladeSearchpath.h" // for GladeSearchpath
|
||||
#include "ToolbarDefinitions.h" // for TOOLBAR_DEFINITIO...
|
||||
#include "XournalView.h" // for XournalView
|
||||
#include "config-dev.h" // for TOOLBAR_CONFIG
|
||||
#include "filesystem.h" // for path, exists
|
||||
|
||||
#ifdef __APPLE__
|
||||
// the following header file contains a definition of struct Point that conflicts with model/Point.h
|
||||
#define Point Point_CF
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#undef Point
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
|
||||
static void themeCallback(GObject*, GParamSpec*, gpointer data) { static_cast<MainWindow*>(data)->updateColorscheme(); }
|
||||
|
||||
MainWindow::MainWindow(GladeSearchpath* gladeSearchPath, Control* control, GtkApplication* parent):
|
||||
GladeGui(gladeSearchPath, "main.glade", "mainWindow"),
|
||||
control(control),
|
||||
toolbar(std::make_unique<ToolMenuHandler>(control, this)),
|
||||
menubar(std::make_unique<Menubar>()) {
|
||||
gtk_window_set_application(GTK_WINDOW(getWindow()), parent);
|
||||
|
||||
panedContainerWidget.reset(get("panelMainContents"), xoj::util::ref);
|
||||
boxContainerWidget.reset(get("mainContentContainer"), xoj::util::ref);
|
||||
mainContentWidget.reset(get("boxContents"), xoj::util::ref);
|
||||
sidebarWidget.reset(get("sidebar"), xoj::util::ref);
|
||||
|
||||
loadMainCSS(gladeSearchPath, "xournalpp.css");
|
||||
|
||||
GtkOverlay* overlay = GTK_OVERLAY(get("mainOverlay"));
|
||||
this->pdfFloatingToolBox = std::make_unique<PdfFloatingToolbox>(this, overlay);
|
||||
this->floatingToolbox = std::make_unique<FloatingToolbox>(this, overlay);
|
||||
|
||||
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
|
||||
this->toolbarWidgets[i].reset(get(TOOLBAR_DEFINITIONS[i].guiName), xoj::util::ref);
|
||||
}
|
||||
|
||||
initXournalWidget();
|
||||
|
||||
setSidebarVisible(control->getSettings()->isSidebarVisible());
|
||||
|
||||
// Window handler
|
||||
g_signal_connect(this->window, "delete-event", xoj::util::wrap_for_g_callback_v<deleteEventCallback>,
|
||||
this->control);
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
g_signal_connect(this->window, "notify::is-maximized", xoj::util::wrap_for_g_callback_v<windowMaximizedCallback>,
|
||||
this);
|
||||
#else
|
||||
g_signal_connect(this->window, "notify::maximized", xoj::util::wrap_for_g_callback_v<windowMaximizedCallback>,
|
||||
this);
|
||||
#endif
|
||||
|
||||
// "watch over" all key events
|
||||
auto keyPropagate = +[](GtkWidget* w, GdkEvent* e, gpointer) {
|
||||
return gtk_window_propagate_key_event(GTK_WINDOW(w), (GdkEventKey*)(e));
|
||||
};
|
||||
g_signal_connect(this->window, "key-press-event", G_CALLBACK(keyPropagate), nullptr);
|
||||
g_signal_connect(this->window, "key-release-event", G_CALLBACK(keyPropagate), nullptr);
|
||||
|
||||
updateScrollbarSidebarPosition();
|
||||
|
||||
gtk_window_set_default_size(GTK_WINDOW(this->window), control->getSettings()->getMainWndWidth(),
|
||||
control->getSettings()->getMainWndHeight());
|
||||
|
||||
if (control->getSettings()->isMainWndMaximized()) {
|
||||
gtk_window_maximize(GTK_WINDOW(this->window));
|
||||
} else {
|
||||
gtk_window_unmaximize(GTK_WINDOW(this->window));
|
||||
}
|
||||
|
||||
Util::execInUiThread([=]() {
|
||||
// Execute after the window is visible, else the check won't work
|
||||
control->setShowMenubar(control->getSettings()->isMenubarVisible());
|
||||
});
|
||||
|
||||
// Drag and Drop
|
||||
g_signal_connect(this->window, "drag-data-received", G_CALLBACK(dragDataRecived), this);
|
||||
|
||||
gtk_drag_dest_set(this->window, GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY);
|
||||
gtk_drag_dest_add_uri_targets(this->window);
|
||||
gtk_drag_dest_add_image_targets(this->window);
|
||||
gtk_drag_dest_add_text_targets(this->window);
|
||||
|
||||
g_signal_connect(gtk_widget_get_settings(this->window), "notify::gtk-theme-name", G_CALLBACK(themeCallback), this);
|
||||
g_signal_connect(gtk_widget_get_settings(this->window), "notify::gtk-application-prefer-dark-theme",
|
||||
G_CALLBACK(themeCallback), this);
|
||||
|
||||
updateColorscheme();
|
||||
}
|
||||
|
||||
void MainWindow::populate(GladeSearchpath* gladeSearchPath) {
|
||||
|
||||
toolbar->populate(gladeSearchPath);
|
||||
menubar->populate(gladeSearchPath, this);
|
||||
|
||||
// need to create tool buttons registered in plugins, so they can be added to toolbars
|
||||
control->registerPluginToolButtons(this->toolbar.get());
|
||||
|
||||
createToolbar();
|
||||
|
||||
setToolbarVisible(control->getSettings()->isToolbarVisible());
|
||||
}
|
||||
|
||||
GMenuModel* MainWindow::getMenuModel() const { return menubar->getModel(); }
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
struct ThemeProperties {
|
||||
bool dark;
|
||||
bool darkSuffix;
|
||||
std::string rootname; ///< Name without any putative -dark suffix
|
||||
};
|
||||
static ThemeProperties getThemeProperties(GtkWidget* w) {
|
||||
xoj::util::OwnedCString name;
|
||||
[[maybe_unused]] bool useEnv = false;
|
||||
// Gtk prioritizes GTK_THEME over GtkSettings content
|
||||
// cf https://gitlab.gnome.org/GNOME/gtk/blob/90d84a2af8b367bd5a5312b3fa3b67563462c0ef/gtk/gtksettings.c#L1567-L1622
|
||||
if (auto* p = g_getenv("GTK_THEME")) {
|
||||
*(name.contentReplacer()) = g_strdup(p);
|
||||
useEnv = true;
|
||||
} else {
|
||||
g_object_get(gtk_widget_get_settings(w), "gtk-theme-name", name.contentReplacer(), nullptr);
|
||||
}
|
||||
|
||||
// Try to figure out if the theme is dark or light
|
||||
// Some themes handle their dark variant via "gtk-application-prefer-dark-theme" while other just append "-dark"
|
||||
const std::regex nameparser("([a-zA-Z-]+?)([:-][dD]ark)?");
|
||||
std::cmatch sm;
|
||||
std::regex_match(name.get(), sm, nameparser);
|
||||
|
||||
ThemeProperties props;
|
||||
if (sm.size() < 3) {
|
||||
g_warning("Fails to extract theme root name from: \"%s\"", name.get());
|
||||
props.rootname = name.get();
|
||||
props.darkSuffix = false;
|
||||
} else {
|
||||
props.rootname = sm[1];
|
||||
props.darkSuffix = sm[2].length() > 0;
|
||||
}
|
||||
gboolean dark = false;
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (!useEnv) {
|
||||
CFStringRef interfaceStyle =
|
||||
(CFStringRef)CFPreferencesCopyAppValue(CFSTR("AppleInterfaceStyle"), kCFPreferencesCurrentApplication);
|
||||
if (interfaceStyle) {
|
||||
char buffer[128];
|
||||
if (CFStringGetCString(interfaceStyle, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
|
||||
std::string style = buffer;
|
||||
if (auto pos = style.find("Dark"); pos != std::string::npos) {
|
||||
dark = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
g_object_get(gtk_widget_get_settings(w), "gtk-application-prefer-dark-theme", &dark, nullptr);
|
||||
#endif
|
||||
|
||||
g_debug("Extracted theme info: Name = %s, rootname = %s, dark = %s", name.get(), props.rootname.c_str(),
|
||||
dark ? "true" : "false");
|
||||
|
||||
props.dark = props.darkSuffix || dark; // Some themes handle their dark variant via this setting
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
void MainWindow::updateColorscheme() {
|
||||
g_signal_handlers_block_by_func(gtk_widget_get_settings(this->window),
|
||||
reinterpret_cast<gpointer>(G_CALLBACK(themeCallback)), this);
|
||||
auto variant = control->getSettings()->getThemeVariant();
|
||||
if (variant == THEME_VARIANT_USE_SYSTEM) {
|
||||
gtk_settings_reset_property(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme");
|
||||
if (modifiedGtkSettingsTheme) {
|
||||
// Some bug in Gtk makes an infinite loop despite us blocking the signals
|
||||
gtk_settings_reset_property(gtk_widget_get_settings(this->window), "gtk-theme-name");
|
||||
modifiedGtkSettingsTheme = false;
|
||||
}
|
||||
}
|
||||
auto props = getThemeProperties(this->window);
|
||||
|
||||
this->darkMode = (props.dark && variant != THEME_VARIANT_FORCE_LIGHT) || variant == THEME_VARIANT_FORCE_DARK;
|
||||
|
||||
// Set up icons
|
||||
{
|
||||
const auto uiPath = this->getGladeSearchPath()->getFirstSearchPath();
|
||||
const auto lightColorIcons = (uiPath / "iconsColor-light");
|
||||
const auto darkColorIcons = (uiPath / "iconsColor-dark");
|
||||
const auto lightLucideIcons = (uiPath / "iconsLucide-light");
|
||||
const auto darkLucideIcons = (uiPath / "iconsLucide-dark");
|
||||
|
||||
// icon load order from lowest priority to highest priority
|
||||
std::vector<fs::path> iconLoadOrder = {};
|
||||
const auto chosenTheme = control->getSettings()->getIconTheme();
|
||||
switch (chosenTheme) {
|
||||
case ICON_THEME_COLOR:
|
||||
iconLoadOrder = {darkLucideIcons, lightLucideIcons, darkColorIcons, lightColorIcons};
|
||||
break;
|
||||
case ICON_THEME_LUCIDE:
|
||||
iconLoadOrder = {darkColorIcons, lightColorIcons, darkLucideIcons, lightLucideIcons};
|
||||
break;
|
||||
default:
|
||||
g_message("Unknown icon theme!");
|
||||
}
|
||||
|
||||
if (this->darkMode) {
|
||||
for (size_t i = 0; 2 * i + 1 < iconLoadOrder.size(); ++i) {
|
||||
std::swap(iconLoadOrder[2 * i], iconLoadOrder[2 * i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& p: iconLoadOrder) {
|
||||
gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), char_cast(p.u8string().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
GtkStyleContext* context = gtk_widget_get_style_context(GTK_WIDGET(this->window));
|
||||
|
||||
if (this->darkMode) {
|
||||
gtk_style_context_add_class(context, "darkMode");
|
||||
g_object_set(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", true, nullptr);
|
||||
} else {
|
||||
gtk_style_context_remove_class(context, "darkMode");
|
||||
g_object_set(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", false, nullptr);
|
||||
if (props.darkSuffix) { // The active theme is all dark. Remove the trailing "-dark"
|
||||
g_object_set(gtk_widget_get_settings(this->window), "gtk-theme-name", props.rootname.c_str(), nullptr);
|
||||
modifiedGtkSettingsTheme = true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
gchar* name = nullptr;
|
||||
g_object_get(gtk_widget_get_settings(this->window), "gtk-theme-name", &name, nullptr);
|
||||
g_debug("Theme name: %s", name);
|
||||
g_debug("Modified in GtkSettings: %s", modifiedGtkSettingsTheme ? "true" : "false");
|
||||
g_free(name);
|
||||
gboolean gtkdark = true;
|
||||
g_object_get(gtk_widget_get_settings(this->window), "gtk-application-prefer-dark-theme", >kdark, nullptr);
|
||||
g_debug("Theme variant: %s", gtkdark ? "dark" : "light");
|
||||
g_debug("Icon theme: %s", iconThemeToString(control->getSettings()->getIconTheme()));
|
||||
}
|
||||
g_signal_handlers_unblock_by_func(gtk_widget_get_settings(this->window), reinterpret_cast<gpointer>(themeCallback),
|
||||
this);
|
||||
}
|
||||
|
||||
void MainWindow::initXournalWidget() {
|
||||
winXournal = gtk_scrolled_window_new();
|
||||
|
||||
setGtkTouchscreenScrollingForDeviceMapping();
|
||||
|
||||
gtk_box_append(GTK_BOX(get("boxContents")), winXournal);
|
||||
|
||||
GtkWidget* vpXournal = gtk_viewport_new(nullptr, nullptr);
|
||||
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(winXournal), vpXournal);
|
||||
|
||||
scrollHandling = std::make_unique<ScrollHandling>(GTK_SCROLLED_WINDOW(winXournal));
|
||||
|
||||
this->xournal = std::make_unique<XournalView>(vpXournal, control, scrollHandling.get());
|
||||
|
||||
control->getZoomControl()->initZoomHandler(this->window, winXournal, xournal.get(), control);
|
||||
gtk_widget_show_all(winXournal);
|
||||
|
||||
scrollHandling->init(this->xournal->getWidget(), this->xournal->getLayout());
|
||||
}
|
||||
|
||||
void MainWindow::setGtkTouchscreenScrollingForDeviceMapping() {
|
||||
InputDeviceClass touchscreenClass =
|
||||
DeviceListHelper::getSourceMapping(GDK_SOURCE_TOUCHSCREEN, this->getControl()->getSettings());
|
||||
|
||||
setGtkTouchscreenScrollingEnabled(touchscreenClass == INPUT_DEVICE_TOUCHSCREEN &&
|
||||
!control->getSettings()->getTouchDrawingEnabled());
|
||||
}
|
||||
|
||||
void MainWindow::setGtkTouchscreenScrollingEnabled(bool enabled) {
|
||||
if (!control->getSettings()->getGtkTouchInertialScrollingEnabled()) {
|
||||
enabled = false;
|
||||
}
|
||||
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(winXournal), enabled);
|
||||
}
|
||||
|
||||
auto MainWindow::getLayout() const -> Layout* { return this->xournal->getLayout(); }
|
||||
|
||||
auto MainWindow::getNegativeXournalWidgetPos() const -> xoj::util::Point<double> {
|
||||
return Util::toWidgetCoords(this->winXournal, xoj::util::Point{0.0, 0.0});
|
||||
}
|
||||
|
||||
auto cancellable_cancel(GCancellable* cancel) -> bool {
|
||||
g_cancellable_cancel(cancel);
|
||||
|
||||
g_warning("Timeout... Cancel loading URL");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MainWindow::dragDataRecived(GtkWidget* widget, GdkDragContext* dragContext, gint x, gint y, GtkSelectionData* data,
|
||||
guint info, guint time, MainWindow* win) {
|
||||
GtkWidget* source = gtk_drag_get_source_widget(dragContext);
|
||||
if (source && widget == gtk_widget_get_toplevel(source)) {
|
||||
gtk_drag_finish(dragContext, false, false, time);
|
||||
return;
|
||||
}
|
||||
|
||||
guchar* text = gtk_selection_data_get_text(data);
|
||||
if (text) {
|
||||
win->control->clipboardPasteText(reinterpret_cast<const char*>(text));
|
||||
|
||||
g_free(text);
|
||||
gtk_drag_finish(dragContext, true, false, time);
|
||||
return;
|
||||
}
|
||||
|
||||
xoj::util::GObjectSPtr<GdkPixbuf> image(gtk_selection_data_get_pixbuf(data), xoj::util::adopt);
|
||||
if (image) {
|
||||
win->control->clipboardPasteImage(image.get());
|
||||
|
||||
gtk_drag_finish(dragContext, true, false, time);
|
||||
return;
|
||||
}
|
||||
|
||||
gchar** uris = gtk_selection_data_get_uris(data);
|
||||
if (uris) {
|
||||
for (int i = 0; uris[i] != nullptr && i < 3; i++) {
|
||||
const char* uri = uris[i];
|
||||
|
||||
GCancellable* cancel = g_cancellable_new();
|
||||
auto cancelTimeout = g_timeout_add(3000, xoj::util::wrap_for_once_v<cancellable_cancel>, cancel);
|
||||
|
||||
xoj::util::GObjectSPtr<GFile> file(g_file_new_for_uri(uri), xoj::util::adopt);
|
||||
GError* err = nullptr;
|
||||
GFileInputStream* in = g_file_read(file.get(), cancel, &err);
|
||||
if (g_cancellable_is_cancelled(cancel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == nullptr) {
|
||||
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
|
||||
gdk_pixbuf_new_from_stream(G_INPUT_STREAM(in), cancel, nullptr), xoj::util::adopt);
|
||||
if (g_cancellable_is_cancelled(cancel)) {
|
||||
continue;
|
||||
}
|
||||
g_input_stream_close(G_INPUT_STREAM(in), cancel, nullptr);
|
||||
if (g_cancellable_is_cancelled(cancel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
win->control->clipboardPasteImage(pixbuf.get());
|
||||
}
|
||||
} else {
|
||||
g_error_free(err);
|
||||
}
|
||||
|
||||
if (!g_cancellable_is_cancelled(cancel)) {
|
||||
g_source_remove(cancelTimeout);
|
||||
}
|
||||
g_object_unref(cancel);
|
||||
}
|
||||
|
||||
gtk_drag_finish(dragContext, true, false, time);
|
||||
|
||||
g_strfreev(uris);
|
||||
}
|
||||
|
||||
gtk_drag_finish(dragContext, false, false, time);
|
||||
}
|
||||
|
||||
auto MainWindow::getControl() const -> Control* { return control; }
|
||||
|
||||
void MainWindow::updateScrollbarSidebarPosition() {
|
||||
// Part 1: update scrollbar position
|
||||
if (winXournal != nullptr) {
|
||||
GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(winXournal);
|
||||
|
||||
ScrollbarHideType type = this->getControl()->getSettings()->getScrollbarHideType();
|
||||
|
||||
bool scrollbarOnLeft = control->getSettings()->isScrollbarOnLeft();
|
||||
if (scrollbarOnLeft) {
|
||||
gtk_scrolled_window_set_placement(scrolledWindow, GTK_CORNER_TOP_RIGHT);
|
||||
} else {
|
||||
gtk_scrolled_window_set_placement(scrolledWindow, GTK_CORNER_TOP_LEFT);
|
||||
}
|
||||
|
||||
gtk_widget_set_visible(gtk_scrolled_window_get_hscrollbar(scrolledWindow), !(type & SCROLLBAR_HIDE_HORIZONTAL));
|
||||
gtk_widget_set_visible(gtk_scrolled_window_get_vscrollbar(scrolledWindow), !(type & SCROLLBAR_HIDE_VERTICAL));
|
||||
|
||||
gtk_scrolled_window_set_overlay_scrolling(scrolledWindow,
|
||||
!control->getSettings()->isScrollbarFadeoutDisabled());
|
||||
}
|
||||
|
||||
// Part 2: update sidebar position
|
||||
GtkPaned* paned = GTK_PANED(this->panedContainerWidget.get());
|
||||
|
||||
// Allocation is reset when we switch up the contained elements. Fetch the
|
||||
// width here in case we need it afterwards.
|
||||
int contentWidth = gtk_widget_get_width(this->boxContainerWidget.get());
|
||||
|
||||
bool sidebarRight = control->getSettings()->isSidebarOnRight();
|
||||
if (sidebarRight != (gtk_paned_get_child2(paned) == this->sidebarWidget.get())) {
|
||||
// switch sidebar and main content
|
||||
GtkWidget* sidebar = this->sidebarWidget.get();
|
||||
GtkWidget* mainContent = this->sidebarVisible ? this->mainContentWidget.get() : nullptr;
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
if (this->sidebarVisible) {
|
||||
gtk_container_remove(GTK_CONTAINER(paned), sidebar);
|
||||
gtk_container_remove(GTK_CONTAINER(paned), mainContent);
|
||||
|
||||
if (sidebarRight) {
|
||||
gtk_paned_pack1(paned, mainContent, true, false);
|
||||
gtk_paned_pack2(paned, sidebar, false, false);
|
||||
} else {
|
||||
gtk_paned_pack1(paned, sidebar, false, false);
|
||||
gtk_paned_pack2(paned, mainContent, true, false);
|
||||
}
|
||||
} else {
|
||||
// The sidebar is hidden. That means the paned widget only contains the
|
||||
// sidebar while the main contents are shown alone in the box container.
|
||||
gtk_container_remove(GTK_CONTAINER(paned), sidebar);
|
||||
|
||||
if (sidebarRight) {
|
||||
gtk_paned_pack2(paned, sidebar, false, false);
|
||||
} else {
|
||||
gtk_paned_pack1(paned, sidebar, false, false);
|
||||
}
|
||||
}
|
||||
#else
|
||||
gtk_paned_set_start_child(paned, nullptr);
|
||||
gtk_paned_set_end_child(paned, nullptr);
|
||||
if (sidebarRight) {
|
||||
gtk_paned_set_start_child(paned, mainContent);
|
||||
gtk_paned_set_resize_start_child(paned, true);
|
||||
gtk_paned_set_shrink_start_child(paned, false);
|
||||
gtk_paned_set_end_child(paned, sidebar);
|
||||
gtk_paned_set_resize_end_child(paned, false);
|
||||
gtk_paned_set_shrink_end_child(paned, false);
|
||||
} else {
|
||||
gtk_paned_set_end_child(paned, mainContent);
|
||||
gtk_paned_set_resize_end_child(paned, true);
|
||||
gtk_paned_set_shrink_end_child(paned, false);
|
||||
gtk_paned_set_start_child(paned, sidebar);
|
||||
gtk_paned_set_resize_start_child(paned, false);
|
||||
gtk_paned_set_shrink_start_child(paned, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this->sidebarVisible) {
|
||||
updatePanedPosition(contentWidth);
|
||||
}
|
||||
}
|
||||
|
||||
auto MainWindow::deleteEventCallback(GtkWidget* widget, GdkEvent* event, Control* control) -> bool {
|
||||
control->quit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::setSidebarVisible(bool visible) {
|
||||
if (!visible && (this->control->getSidebar() != nullptr)) {
|
||||
this->control->getSidebar()->saveSize();
|
||||
}
|
||||
|
||||
if (visible != this->sidebarVisible) {
|
||||
// Due to a GTK bug, we can't just hide the sidebar widget in the GtkPaned.
|
||||
// If we do this, we create a dead region where the pane separator was previously.
|
||||
// In this region, we can't use the touchscreen to start horizontal strokes.
|
||||
// As such:
|
||||
if (!visible) {
|
||||
// hide sidebar
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
gtk_container_remove(GTK_CONTAINER(panedContainerWidget.get()), mainContentWidget.get());
|
||||
#else
|
||||
if (control->getSettings()->isSidebarOnRight()) {
|
||||
gtk_paned_set_start_child(GTK_PANED(panedContainerWidget.get()), nullptr);
|
||||
} else {
|
||||
gtk_paned_set_end_child(GTK_PANED(panedContainerWidget.get()), nullptr);
|
||||
}
|
||||
#endif
|
||||
gtk_box_remove(GTK_BOX(boxContainerWidget.get()), panedContainerWidget.get());
|
||||
gtk_box_append(GTK_BOX(boxContainerWidget.get()), mainContentWidget.get());
|
||||
this->sidebarVisible = false;
|
||||
} else {
|
||||
// show sidebar
|
||||
|
||||
// Allocation is reset when we switch up the contained elements. Fetch the
|
||||
// width here in case we need it afterwards.
|
||||
int contentWidth = gtk_widget_get_width(boxContainerWidget.get());
|
||||
|
||||
gtk_box_remove(GTK_BOX(boxContainerWidget.get()), mainContentWidget.get());
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
if (control->getSettings()->isSidebarOnRight()) {
|
||||
gtk_paned_pack1(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get(), true, false);
|
||||
} else {
|
||||
gtk_paned_pack2(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get(), true, false);
|
||||
}
|
||||
#else
|
||||
if (control->getSettings()->isSidebarOnRight()) {
|
||||
gtk_paned_set_start_child(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get());
|
||||
} else {
|
||||
gtk_paned_set_end_child(GTK_PANED(panedContainerWidget.get()), mainContentWidget.get());
|
||||
}
|
||||
#endif
|
||||
|
||||
gtk_box_append(GTK_BOX(boxContainerWidget.get()), panedContainerWidget.get());
|
||||
this->sidebarVisible = true;
|
||||
|
||||
updatePanedPosition(contentWidth);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_set_visible(sidebarWidget.get(), visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert the position of the paned widget and disconnect from the signal.
|
||||
* @param handlerId should be the ID of the signal handler that should be disconnected.
|
||||
*/
|
||||
static void invertPanedPosition(GtkWidget* widget, GtkAllocation* allocation, gulong* handlerId) {
|
||||
int newDividerPos = allocation->width - gtk_paned_get_position(GTK_PANED(widget));
|
||||
gtk_paned_set_position(GTK_PANED(widget), newDividerPos);
|
||||
|
||||
// We only need to switch the position once, so disconnect the signal right away.
|
||||
g_signal_handler_disconnect(widget, *handlerId);
|
||||
}
|
||||
|
||||
void MainWindow::updatePanedPosition(int contentWidth) {
|
||||
if (!this->control->getSettings()->isSidebarOnRight()) {
|
||||
// Sidebar is on the left side.
|
||||
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()),
|
||||
this->control->getSettings()->getSidebarWidth());
|
||||
} else {
|
||||
// Sidebar is on the right side.
|
||||
if (contentWidth > 0) {
|
||||
int dividerPos = contentWidth - this->control->getSettings()->getSidebarWidth();
|
||||
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()), dividerPos);
|
||||
} else {
|
||||
// Allocation is unkown (window hasn't been shown yet). We have to wait for the signal.
|
||||
// Set position as if the sidebar was on the left side, and let the signal handler
|
||||
// simply invert the position when the allocation is known.
|
||||
gtk_paned_set_position(GTK_PANED(this->panedContainerWidget.get()),
|
||||
this->control->getSettings()->getSidebarWidth());
|
||||
gulong* signal_id = new gulong{};
|
||||
*signal_id = g_signal_connect_data(
|
||||
this->panedContainerWidget.get(), "size-allocate",
|
||||
xoj::util::wrap_for_g_callback_v<invertPanedPosition>, signal_id,
|
||||
[](gpointer d, GClosure*) { delete reinterpret_cast<gulong*>(d); }, GConnectFlags(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setToolbarVisible(bool visible) {
|
||||
Settings* settings = control->getSettings();
|
||||
|
||||
settings->setToolbarVisible(visible);
|
||||
for (auto& w: this->toolbarWidgets) {
|
||||
if (!visible || (gtk_toolbar_get_n_items(GTK_TOOLBAR(w.get())) != 0)) {
|
||||
gtk_widget_set_visible(w.get(), visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setMenubarVisible(bool visible) {
|
||||
gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(this->getWindow()), visible);
|
||||
}
|
||||
|
||||
void MainWindow::setMaximized(bool maximized) { this->maximized = maximized; }
|
||||
|
||||
auto MainWindow::isMaximized() const -> bool { return this->maximized; }
|
||||
|
||||
auto MainWindow::setFullscreen(bool enabled) const -> void {
|
||||
if (enabled) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(this->getWindow()));
|
||||
} else {
|
||||
gtk_window_unfullscreen(GTK_WINDOW(this->getWindow()));
|
||||
}
|
||||
}
|
||||
|
||||
auto MainWindow::isDarkTheme() const -> bool { return this->darkMode; }
|
||||
|
||||
auto MainWindow::getXournal() const -> XournalView* { return xournal.get(); }
|
||||
|
||||
auto MainWindow::windowMaximizedCallback(GObject* window, GParamSpec*, MainWindow* win) -> void {
|
||||
win->setMaximized(gtk_window_is_maximized(GTK_WINDOW(window)));
|
||||
}
|
||||
|
||||
void MainWindow::toolbarSelected(const std::string& id) {
|
||||
const auto& toolbars = toolbar->getModel()->getToolbars();
|
||||
auto it = std::find_if(toolbars.begin(), toolbars.end(), [&](const auto& d) { return d->getId() == id; });
|
||||
toolbarSelected(it == toolbars.end() ? nullptr : it->get());
|
||||
}
|
||||
|
||||
void MainWindow::toolbarSelected(ToolbarData* d) {
|
||||
if (!d || this->selectedToolbar == d) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings* settings = control->getSettings();
|
||||
settings->setSelectedToolbar(d->getId());
|
||||
|
||||
this->clearToolbar();
|
||||
this->loadToolbar(d);
|
||||
}
|
||||
|
||||
auto MainWindow::clearToolbar() -> const ToolbarData* {
|
||||
if (this->selectedToolbar != nullptr) {
|
||||
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
|
||||
ToolMenuHandler::unloadToolbar(this->toolbarWidgets[i].get());
|
||||
}
|
||||
|
||||
this->toolbar->freeDynamicToolbarItems();
|
||||
}
|
||||
return std::exchange(this->selectedToolbar, nullptr);
|
||||
}
|
||||
|
||||
void MainWindow::loadToolbar(ToolbarData* d) {
|
||||
this->selectedToolbar = d;
|
||||
|
||||
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
|
||||
this->toolbar->load(d, this->toolbarWidgets[i].get(), TOOLBAR_DEFINITIONS[i].propName,
|
||||
TOOLBAR_DEFINITIONS[i].horizontal);
|
||||
}
|
||||
|
||||
this->floatingToolbox->flagRecalculateSizeRequired();
|
||||
}
|
||||
|
||||
void MainWindow::reloadToolbars() {
|
||||
ToolbarData* d = getSelectedToolbar();
|
||||
this->clearToolbar();
|
||||
this->toolbarSelected(d);
|
||||
}
|
||||
|
||||
auto MainWindow::getSelectedToolbar() const -> ToolbarData* { return this->selectedToolbar; }
|
||||
|
||||
auto MainWindow::getToolbarWidgets() const -> const ToolbarWidgetArray& { return toolbarWidgets; }
|
||||
|
||||
auto MainWindow::getToolbarName(GtkToolbar* toolbar) const -> const char* {
|
||||
for (size_t i = 0; i < TOOLBAR_DEFINITIONS_LEN; i++) {
|
||||
if (static_cast<void*>(this->toolbarWidgets[i].get()) == static_cast<void*>(toolbar)) {
|
||||
return TOOLBAR_DEFINITIONS[i].propName;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void MainWindow::setDynamicallyGeneratedSubmenuDisabled(bool disabled) { menubar->setDisabled(disabled); }
|
||||
|
||||
void MainWindow::updateToolbarMenu() {
|
||||
menubar->getToolbarSelectionSubmenu().update(toolbar.get(), this->selectedToolbar);
|
||||
}
|
||||
|
||||
void MainWindow::createToolbar() {
|
||||
toolbarSelected(control->getSettings()->getSelectedToolbar());
|
||||
|
||||
this->control->getScheduler()->unblockRerenderZoom();
|
||||
}
|
||||
|
||||
void MainWindow::updatePageNumbers(size_t page, size_t pagecount, size_t pdfpage) {
|
||||
toolbar->setPageInfo(page, pagecount, pdfpage);
|
||||
}
|
||||
|
||||
auto MainWindow::getMenubar() const -> Menubar* { return menubar.get(); }
|
||||
|
||||
void MainWindow::show(GtkWindow* parent) { gtk_widget_show(this->window); }
|
||||
|
||||
void MainWindow::setUndoDescription(const string& description) { menubar->setUndoDescription(description); }
|
||||
|
||||
void MainWindow::setRedoDescription(const string& description) { menubar->setRedoDescription(description); }
|
||||
|
||||
auto MainWindow::getToolbarModel() const -> ToolbarModel* { return this->toolbar->getModel(); }
|
||||
|
||||
auto MainWindow::getToolMenuHandler() const -> ToolMenuHandler* { return this->toolbar.get(); }
|
||||
|
||||
void MainWindow::loadMainCSS(GladeSearchpath* gladeSearchPath, const gchar* cssFilename) {
|
||||
auto filepath = gladeSearchPath->findFile("", cssFilename);
|
||||
xoj::util::GObjectSPtr<GtkCssProvider> provider(gtk_css_provider_new(), xoj::util::adopt);
|
||||
gtk_css_provider_load_from_path(provider.get(), char_cast(filepath.u8string().c_str()), nullptr);
|
||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider.get()),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
PdfFloatingToolbox* MainWindow::getPdfToolbox() const { return this->pdfFloatingToolBox.get(); }
|
||||
|
||||
FloatingToolbox* MainWindow::getFloatingToolbox() const { return this->floatingToolbox.get(); }
|
||||
|
||||
void MainWindow::setDPI() const {
|
||||
if (auto dpi = this->getControl()->getSettings()->getDisplayDpi(); dpi == -1) {
|
||||
auto res = xoj::util::gtk::getWidgetDPI(this->window);
|
||||
this->getControl()->getZoomControl()->setZoom100Value(res.value_or(Util::DPI_NORMALIZATION_FACTOR) /
|
||||
Util::DPI_NORMALIZATION_FACTOR);
|
||||
} else {
|
||||
this->getControl()->getZoomControl()->setZoom100Value(dpi / Util::DPI_NORMALIZATION_FACTOR);
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The Main window
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // for array
|
||||
#include <atomic> // for atomic_bool
|
||||
#include <cstddef> // for size_t
|
||||
#include <memory> // for unique_ptr
|
||||
#include <string> // for string
|
||||
|
||||
#include <gdk/gdk.h> // for GdkDragContext, GdkEvent
|
||||
#include <glib-object.h> // for GClosure
|
||||
#include <glib.h> // for gpointer, gboolean, gint
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkCheckMenu...
|
||||
|
||||
#include "util/Point.h"
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
#include "GladeGui.h" // for GladeGui
|
||||
#include "ToolbarDefinitions.h" // for TOOLBAR_DEFINITIONS_LEN
|
||||
|
||||
class Control;
|
||||
class Layout;
|
||||
class SpinPageAdapter;
|
||||
class ScrollHandling;
|
||||
class ToolMenuHandler;
|
||||
class ToolbarData;
|
||||
class ToolbarModel;
|
||||
class XournalView;
|
||||
class PdfFloatingToolbox;
|
||||
class FloatingToolbox;
|
||||
class GladeSearchpath;
|
||||
|
||||
class Menubar;
|
||||
|
||||
typedef std::array<xoj::util::WidgetSPtr, TOOLBAR_DEFINITIONS_LEN> ToolbarWidgetArray;
|
||||
|
||||
class MainWindow: public GladeGui {
|
||||
public:
|
||||
MainWindow(GladeSearchpath* gladeSearchPath, Control* control, GtkApplication* parent);
|
||||
~MainWindow() override;
|
||||
|
||||
void populate(GladeSearchpath* gladeSearchPath);
|
||||
|
||||
public:
|
||||
GMenuModel* getMenuModel() const;
|
||||
|
||||
void show(GtkWindow* parent) override;
|
||||
|
||||
void toolbarSelected(const std::string& id);
|
||||
void toolbarSelected(ToolbarData* d);
|
||||
ToolbarData* getSelectedToolbar() const;
|
||||
|
||||
private:
|
||||
const ToolbarData* clearToolbar();
|
||||
void loadToolbar(ToolbarData* d);
|
||||
|
||||
public:
|
||||
/**
|
||||
* reloadToolbars reloads the currently selected toolbar
|
||||
*
|
||||
* This is especially useful when a change in the SettingsDialog should be reflected right away
|
||||
*/
|
||||
void reloadToolbars();
|
||||
|
||||
void updatePageNumbers(size_t page, size_t pagecount, size_t pdfpage);
|
||||
|
||||
void setMaximized(bool maximized);
|
||||
bool isMaximized() const;
|
||||
|
||||
void setFullscreen(bool enabled) const;
|
||||
|
||||
bool isDarkTheme() const;
|
||||
|
||||
XournalView* getXournal() const;
|
||||
|
||||
void setMenubarVisible(bool visible);
|
||||
void setSidebarVisible(bool visible);
|
||||
void setToolbarVisible(bool visible);
|
||||
|
||||
Control* getControl() const;
|
||||
|
||||
PdfFloatingToolbox* getPdfToolbox() const;
|
||||
FloatingToolbox* getFloatingToolbox() const;
|
||||
|
||||
void updateScrollbarSidebarPosition();
|
||||
|
||||
void setUndoDescription(const std::string& description);
|
||||
void setRedoDescription(const std::string& description);
|
||||
|
||||
ToolbarModel* getToolbarModel() const;
|
||||
ToolMenuHandler* getToolMenuHandler() const;
|
||||
|
||||
void setDynamicallyGeneratedSubmenuDisabled(bool disabled);
|
||||
|
||||
void updateToolbarMenu();
|
||||
void updateColorscheme();
|
||||
|
||||
const ToolbarWidgetArray& getToolbarWidgets() const;
|
||||
const char* getToolbarName(GtkToolbar* toolbar) const;
|
||||
|
||||
Layout* getLayout() const;
|
||||
|
||||
[[maybe_unused]] Menubar* getMenubar() const;
|
||||
|
||||
/**
|
||||
* Get the position of the top left corner of screen (X11) or the window (Wayland)
|
||||
* relative to the Xournal Widget top left corner
|
||||
*
|
||||
* @see Util::toWidgetCoords()
|
||||
*/
|
||||
xoj::util::Point<double> getNegativeXournalWidgetPos() const;
|
||||
|
||||
/**
|
||||
* Disable kinetic scrolling if there is a touchscreen device that was manually mapped to another enabled input
|
||||
* device class. This is required so the GtkScrolledWindow does not swallow all the events.
|
||||
*/
|
||||
void setGtkTouchscreenScrollingForDeviceMapping();
|
||||
void setGtkTouchscreenScrollingEnabled(bool enabled);
|
||||
|
||||
/// Infer the window's DPI from available monitor info and use it to set the default zoom value.
|
||||
void setDPI() const;
|
||||
|
||||
private:
|
||||
void initXournalWidget();
|
||||
|
||||
void createToolbar();
|
||||
|
||||
/**
|
||||
* Update the position of the separator in the paned container, adjusting it to the saved sidebar width.
|
||||
* @param contentWidth should be the width of the paned container. The caller should retrieve the width
|
||||
* of the container before any modifications to it, as that will reset its allocation.
|
||||
*/
|
||||
void updatePanedPosition(int contentWidth);
|
||||
|
||||
/**
|
||||
* Window close Button is pressed
|
||||
*/
|
||||
static bool deleteEventCallback(GtkWidget* widget, GdkEvent* event, Control* control);
|
||||
|
||||
/**
|
||||
* Window is maximized/minimized
|
||||
*/
|
||||
static void windowMaximizedCallback(GObject* window, GParamSpec*, MainWindow* win);
|
||||
|
||||
/**
|
||||
* Callback for drag & drop files
|
||||
*/
|
||||
static void dragDataRecived(GtkWidget* widget, GdkDragContext* dragContext, gint x, gint y, GtkSelectionData* data,
|
||||
guint info, guint time, MainWindow* win);
|
||||
|
||||
/**
|
||||
* Load Overall CSS file with custom icons, other styling and potentially, user changes
|
||||
*/
|
||||
static void loadMainCSS(GladeSearchpath* gladeSearchPath, const gchar* cssFilename);
|
||||
|
||||
private:
|
||||
Control* control;
|
||||
|
||||
std::unique_ptr<XournalView> xournal;
|
||||
GtkWidget* winXournal = nullptr;
|
||||
std::unique_ptr<ScrollHandling> scrollHandling;
|
||||
|
||||
std::atomic_bool gtkTouchscreenScrollingEnabled{true};
|
||||
|
||||
std::unique_ptr<PdfFloatingToolbox> pdfFloatingToolBox;
|
||||
std::unique_ptr<FloatingToolbox> floatingToolbox;
|
||||
|
||||
// Toolbars
|
||||
std::unique_ptr<ToolMenuHandler> toolbar;
|
||||
ToolbarData* selectedToolbar = nullptr;
|
||||
|
||||
std::unique_ptr<Menubar> menubar;
|
||||
|
||||
bool maximized = false;
|
||||
bool darkMode = false;
|
||||
bool modifiedGtkSettingsTheme = false;
|
||||
|
||||
ToolbarWidgetArray toolbarWidgets;
|
||||
|
||||
bool sidebarVisible = true;
|
||||
|
||||
xoj::util::WidgetSPtr boxContainerWidget;
|
||||
xoj::util::WidgetSPtr panedContainerWidget;
|
||||
xoj::util::WidgetSPtr mainContentWidget;
|
||||
xoj::util::WidgetSPtr sidebarWidget;
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
#include "PagePreviewDecoration.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "control/Control.h"
|
||||
#include "control/settings/Settings.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "util/raii/CairoWrappers.h"
|
||||
|
||||
void PagePreviewDecoration::drawDecoration(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control) {
|
||||
switch (control->getSettings()->getSidebarNumberingStyle()) {
|
||||
case SidebarNumberingStyle::NUMBER_BELOW_PREVIEW:
|
||||
PagePreviewDecoration::drawPageNumberBelowPreview(cr, pageEntry, control);
|
||||
break;
|
||||
case SidebarNumberingStyle::NUMBER_WITH_CIRCULAR_BACKGROUND:
|
||||
PagePreviewDecoration::drawPageNumberWithCircleBackground(cr, pageEntry, control);
|
||||
break;
|
||||
case SidebarNumberingStyle::NUMBER_WITH_SQUARE_BACKGROUND:
|
||||
PagePreviewDecoration::drawPageNumberWithSquareBackground(cr, pageEntry, control);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PagePreviewDecoration::drawPageNumberBelowPreview(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
|
||||
Control* control) {
|
||||
xoj::util::CairoSaveGuard saveGuard(cr);
|
||||
cairo_text_extents_t extents;
|
||||
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
|
||||
Color color = control->getWindow()->isDarkTheme() ? Colors::white : Colors::xopp_darkslategray;
|
||||
if (pageEntry->isSelected()) {
|
||||
color = control->getSettings()->getBorderColor();
|
||||
}
|
||||
Util::cairo_set_source_rgbi(cr, color);
|
||||
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, FONT_SIZE);
|
||||
cairo_text_extents(cr, pageNumber.c_str(), &extents);
|
||||
double x = pageEntry->getWidth() / 2 - (extents.width / 2 + extents.x_bearing);
|
||||
double y = pageEntry->getHeight() - MARGIN_BOTTOM - (extents.height / 2 + extents.y_bearing);
|
||||
cairo_move_to(cr, x, y);
|
||||
cairo_show_text(cr, pageNumber.c_str());
|
||||
}
|
||||
|
||||
void PagePreviewDecoration::drawPageNumberWithCircleBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
|
||||
Control* control) {
|
||||
xoj::util::CairoSaveGuard saveGuard(cr);
|
||||
cairo_text_extents_t extents;
|
||||
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
|
||||
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, FONT_SIZE);
|
||||
cairo_text_extents(cr, pageNumber.c_str(), &extents);
|
||||
|
||||
// Set the color for drawing the circle. This circle is required to provide good contrast for the page number.
|
||||
if (pageEntry->isSelected()) {
|
||||
Color color = control->getSettings()->getBorderColor();
|
||||
Util::cairo_set_source_rgbi(cr, color, CIRCLE_LABEL_OPACITY);
|
||||
} else {
|
||||
Util::cairo_set_source_rgbi(cr, Colors::xopp_darkslategray, CIRCLE_LABEL_OPACITY);
|
||||
}
|
||||
|
||||
// How many pixels to indent the circle center from the widget border
|
||||
double indentRight = pageEntry->getWidth() * CIRCLE_INDENT_RIGHT;
|
||||
double indentBottom = pageEntry->getHeight() * CIRCLE_INDENT_BOTTOM;
|
||||
|
||||
// Pixel position of circle center
|
||||
double x = pageEntry->getWidth() - indentRight;
|
||||
double y = pageEntry->getHeight() - indentBottom;
|
||||
|
||||
// Inner circle width depends on the actual text width
|
||||
double addititionalWidth = std::max((extents.width - 2 * CIRCLE_RADIUS) + CIRCLE_TEXT_PADDING, 0.0);
|
||||
|
||||
// Draw an slightly transparent circle in the lower right corner of the preview
|
||||
cairo_set_line_width(cr, LINE_WIDTH);
|
||||
cairo_arc(cr, x - addititionalWidth, y, CIRCLE_RADIUS, M_PI * 0.5, M_PI * 1.5);
|
||||
cairo_arc(cr, x, y, CIRCLE_RADIUS, M_PI * 1.5, M_PI * 0.5);
|
||||
cairo_close_path(cr);
|
||||
cairo_stroke_preserve(cr);
|
||||
cairo_fill(cr);
|
||||
|
||||
// Draw the page number in the center of the previously drawn circle
|
||||
cairo_move_to(cr, x - addititionalWidth / 2 - (extents.width / 2 + extents.x_bearing), y + CIRCLE_TEXT_Y);
|
||||
Util::cairo_set_source_argb(cr, Colors::white);
|
||||
cairo_show_text(cr, pageNumber.c_str());
|
||||
}
|
||||
|
||||
void PagePreviewDecoration::drawPageNumberWithSquareBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry,
|
||||
Control* control) {
|
||||
xoj::util::CairoSaveGuard saveGuard(cr);
|
||||
cairo_text_extents_t extents;
|
||||
std::string pageNumber = std::to_string(pageEntry->getIndex() + 1);
|
||||
cairo_select_font_face(cr, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, FONT_SIZE);
|
||||
cairo_text_extents(cr, pageNumber.c_str(), &extents);
|
||||
|
||||
// Select the color for painting the square background
|
||||
if (pageEntry->isSelected()) {
|
||||
Util::cairo_set_source_rgbi(cr, control->getSettings()->getBorderColor());
|
||||
} else {
|
||||
Util::cairo_set_source_rgbi(cr, Colors::xopp_darkslategray);
|
||||
|
||||
// In case the page is not selected draw a border around the preview to match the selected
|
||||
// See discussion: <https://github.com/xournalpp/xournalpp/issues/4624#issue-1557719574>
|
||||
cairo_set_line_width(cr, PREVIEW_BORDER_LINE_WIDTH);
|
||||
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
|
||||
cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
|
||||
cairo_rectangle(cr, PREVIEW_UPPER_LEFT, PREVIEW_UPPER_LEFT, pageEntry->getWidth() - PREVIEW_WIDTH_INDENT,
|
||||
pageEntry->getHeight() - PREVIEW_HEIGHT_INDENT);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
// Calculate the rectangle width depending on the page index number of digits
|
||||
double recWidth = std::max(extents.width + 8, SQUARE_SIZE);
|
||||
|
||||
// Calculate the position of the upper left corner of the square
|
||||
double x = pageEntry->getWidth() - SQUARE_INDENT_RIGHT - recWidth;
|
||||
double y = pageEntry->getHeight() - SQUARE_INDENT_BOTTOM - SQUARE_SIZE;
|
||||
|
||||
// Draw an square in the lower right corner
|
||||
cairo_set_line_width(cr, LINE_WIDTH);
|
||||
cairo_rectangle(cr, x, y, recWidth, SQUARE_SIZE);
|
||||
cairo_stroke_preserve(cr);
|
||||
cairo_fill(cr);
|
||||
|
||||
// Draw the page number in the center of the square
|
||||
cairo_move_to(cr, x + recWidth / 2 - (extents.width / 2 + extents.x_bearing), y + SQUARE_TEXT_Y);
|
||||
Util::cairo_set_source_argb(cr, Colors::white);
|
||||
cairo_show_text(cr, pageNumber.c_str());
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h> // for cairo_surface_t, cairo_t
|
||||
|
||||
#include "sidebar/previews/page/SidebarPreviewPageEntry.h"
|
||||
|
||||
class Control;
|
||||
|
||||
class PagePreviewDecoration {
|
||||
public:
|
||||
static void drawDecoration(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
|
||||
|
||||
private:
|
||||
static void drawPageNumberBelowPreview(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
|
||||
static void drawPageNumberWithCircleBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
|
||||
static void drawPageNumberWithSquareBackground(cairo_t* cr, SidebarPreviewPageEntry* pageEntry, Control* control);
|
||||
|
||||
public:
|
||||
/* For the style with the page numbers below the preview the widget height has to be extended by this amount */
|
||||
static constexpr int MARGIN_BOTTOM = 20;
|
||||
|
||||
private:
|
||||
/* The font sized used for drawing the page number*/
|
||||
static constexpr double FONT_SIZE = 16.0;
|
||||
|
||||
/* Line width for drawing the circle and the square background*/
|
||||
static constexpr double LINE_WIDTH = 1.0;
|
||||
|
||||
/* Indention of the circle from the right border as percentage to the total widget width */
|
||||
static constexpr double CIRCLE_INDENT_RIGHT = 0.28;
|
||||
/* Indention of the circle from the bottom border as percentage to the total widget height */
|
||||
static constexpr double CIRCLE_INDENT_BOTTOM = 0.2;
|
||||
/* Transparency value for the circle background */
|
||||
static constexpr double CIRCLE_LABEL_OPACITY = 0.6;
|
||||
/* Circle radius in pixels */
|
||||
static constexpr double CIRCLE_RADIUS = 11;
|
||||
/* Vertical alignment of page number to circle center */
|
||||
static constexpr double CIRCLE_TEXT_Y = 6;
|
||||
/* Padding for the text in the circle */
|
||||
static constexpr double CIRCLE_TEXT_PADDING = 8;
|
||||
|
||||
/* Height and minimum width of background square in pixels */
|
||||
static constexpr double SQUARE_SIZE = 20;
|
||||
/* Pixel-Distance form widgets right border to previews right border */
|
||||
static constexpr double SQUARE_INDENT_RIGHT = 12;
|
||||
/* Pixel-Distance from widgets bottom border to previews bottom border */
|
||||
static constexpr double SQUARE_INDENT_BOTTOM = 12;
|
||||
/* Vertical alignment of the page number in the square */
|
||||
static constexpr double SQUARE_TEXT_Y = 16;
|
||||
/* Padding for the text in the circle, to be applied on both sides*/
|
||||
static constexpr double SQUARE_TEXT_PADDING = 4;
|
||||
|
||||
static constexpr double PREVIEW_BORDER_LINE_WIDTH = 2.0;
|
||||
static constexpr double PREVIEW_UPPER_LEFT = 8.5;
|
||||
static constexpr double PREVIEW_WIDTH_INDENT = 21;
|
||||
static constexpr double PREVIEW_HEIGHT_INDENT = 21;
|
||||
|
||||
static constexpr char FONT_NAME[] = "Sans";
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,323 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Displays a single page
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <memory> // for unique_ptr, shared_ptr
|
||||
#include <mutex> // for mutex
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <cairo.h> // for cairo_t
|
||||
#include <gdk/gdk.h> // for GdkEventKey, GdkRGBA, GdkRectangle
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
#include "gui/inputdevices/DeviceId.h"
|
||||
#include "gui/inputdevices/InputEvents.h"
|
||||
#include "model/PageListener.h" // for PageListener
|
||||
#include "model/PageRef.h" // for PageRef
|
||||
#include "util/Rectangle.h" // for Rectangle
|
||||
#include "util/raii/CairoWrappers.h" // for CairoSurfaceSPtr
|
||||
#include "view/Mask.h" // for Mask
|
||||
#include "view/Repaintable.h" // for Repaintable
|
||||
|
||||
#include "Layout.h" // for Layout
|
||||
#include "LegacyRedrawable.h" // for LegacyRedrawable
|
||||
|
||||
class EraseHandler;
|
||||
class ImageSizeSelection;
|
||||
class InputHandler;
|
||||
class LaserPointerHandler;
|
||||
class SearchControl;
|
||||
class Selector;
|
||||
class Settings;
|
||||
class Text;
|
||||
class TextEditor;
|
||||
class VerticalToolHandler;
|
||||
class XournalView;
|
||||
class Element;
|
||||
class PositionInputData;
|
||||
class Range;
|
||||
class TexImage;
|
||||
class XojPdfRectangle;
|
||||
class XojPdfPage;
|
||||
|
||||
namespace xoj::view {
|
||||
class OverlayView;
|
||||
class ToolView;
|
||||
} // namespace xoj::view
|
||||
|
||||
class XojPageView: public LegacyRedrawable, public PageListener, public xoj::view::Repaintable {
|
||||
public:
|
||||
XojPageView(XournalView* xournal, const PageRef& page);
|
||||
~XojPageView() override;
|
||||
|
||||
public:
|
||||
void addOverlayView(std::unique_ptr<xoj::view::OverlayView>);
|
||||
void rerenderPage(bool sizeChanged = false) override;
|
||||
void rerenderRect(double x, double y, double width, double height) override;
|
||||
|
||||
void repaintPage() const override;
|
||||
void repaintArea(double x1, double y1, double x2, double y2) const override;
|
||||
|
||||
// Repaintable interface
|
||||
void flagDirtyRegion(const Range& rg) const override;
|
||||
/**
|
||||
* @brief This draws the ToolView directly onto the buffer, deletes the view and repaints the given range
|
||||
* Used to avoid blinking when a tool finished editing an element
|
||||
*/
|
||||
void drawAndDeleteToolView(xoj::view::ToolView* v, const Range& rg) override;
|
||||
/**
|
||||
* @brief Simply deletes an overlay and any trace of it on the display (provided the overlay is contained in the
|
||||
* given range)
|
||||
*/
|
||||
void deleteOverlayView(xoj::view::OverlayView* v, const Range& rg) override;
|
||||
|
||||
double getZoom() const override;
|
||||
ZoomControl* getZoomControl() const override;
|
||||
Range getVisiblePart() const override;
|
||||
|
||||
double getWidth() const override;
|
||||
double getHeight() const override;
|
||||
// End of Repaintable interface
|
||||
|
||||
xoj::util::Rectangle<double> toWindowCoordinates(const xoj::util::Rectangle<double>& r) const override;
|
||||
|
||||
|
||||
void setSelected(bool selected);
|
||||
|
||||
void setIsVisible(bool visible);
|
||||
|
||||
bool isSelected() const;
|
||||
inline bool isVisible() const { return visible; }
|
||||
|
||||
void endText();
|
||||
|
||||
void endSpline();
|
||||
|
||||
bool searchTextOnPage(const std::string& text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect);
|
||||
|
||||
bool onKeyPressEvent(const KeyEvent& event);
|
||||
bool onKeyReleaseEvent(const KeyEvent& event);
|
||||
|
||||
bool cut();
|
||||
bool copy();
|
||||
bool paste();
|
||||
|
||||
bool actionDelete();
|
||||
|
||||
void deleteViewBuffer() override;
|
||||
|
||||
/**
|
||||
* Returns whether this PageView contains the
|
||||
* given point on the display
|
||||
*/
|
||||
bool containsPoint(int x, int y, bool local = false) const;
|
||||
|
||||
/**
|
||||
* Returns Row assigned in current layout
|
||||
*/
|
||||
int getMappedRow() const;
|
||||
|
||||
/**
|
||||
* Returns Column assigned in current layout
|
||||
*/
|
||||
int getMappedCol() const;
|
||||
|
||||
GdkRGBA getSelectionColor() override;
|
||||
bool hasBuffer() const;
|
||||
|
||||
TextEditor* getTextEditor();
|
||||
|
||||
/**
|
||||
* Returns a reference to the XojPage belonging to
|
||||
* this PageView
|
||||
*/
|
||||
const PageRef getPage() const;
|
||||
|
||||
XournalView* getXournal() const;
|
||||
|
||||
/**
|
||||
* Returns the width of this XojPageView as displayed
|
||||
* on the display taking into account the current zoom
|
||||
*/
|
||||
int getDisplayWidth() const;
|
||||
double getDisplayWidthDouble() const;
|
||||
/**
|
||||
* Returns the height of this XojPageView as displayed
|
||||
* on the display taking into account the current zoom
|
||||
*/
|
||||
int getDisplayHeight() const;
|
||||
double getDisplayHeightDouble() const;
|
||||
|
||||
/**
|
||||
* Returns the x coordinate of this XojPageView with
|
||||
* respect to the display
|
||||
*/
|
||||
int getX() const override;
|
||||
|
||||
/**
|
||||
* Returns the y coordinate of this XojPageView with
|
||||
* respect to the display
|
||||
*/
|
||||
int getY() const override;
|
||||
|
||||
const TexImage* getSelectedTex() const;
|
||||
const Text* getSelectedText() const;
|
||||
|
||||
xoj::util::Rectangle<double> getRect() const;
|
||||
|
||||
public: // event handler
|
||||
bool onButtonPressEvent(const PositionInputData& pos);
|
||||
bool onButtonReleaseEvent(const PositionInputData& pos);
|
||||
bool onButtonDoublePressEvent(const PositionInputData& pos);
|
||||
bool onButtonTriplePressEvent(const PositionInputData& pos);
|
||||
bool onMotionNotifyEvent(const PositionInputData& pos);
|
||||
void onSequenceCancelEvent(DeviceId id);
|
||||
void onTapEvent(const PositionInputData& pos);
|
||||
|
||||
/**
|
||||
* This event fires after onButtonPressEvent and also
|
||||
* if no input sequence is actively running and a stylus button was pressed
|
||||
*/
|
||||
bool onButtonClickEvent(const PositionInputData& pos);
|
||||
|
||||
/**
|
||||
* This method actually repaints the XojPageView, triggering
|
||||
* a rerender call if necessary
|
||||
*/
|
||||
bool paintPage(cairo_t* cr, GdkRectangle* rect);
|
||||
|
||||
void deleteLaserPointerHandler();
|
||||
|
||||
public: // listener
|
||||
void rectChanged(xoj::util::Rectangle<double>& rect) override;
|
||||
void rangeChanged(Range& range) override;
|
||||
void pageChanged() override;
|
||||
void elementChanged(const Element* elem) override;
|
||||
void elementsChanged(const std::vector<const Element*>& elements, const Range& range) override;
|
||||
|
||||
private:
|
||||
void startText(double x, double y);
|
||||
|
||||
void drawLoadingPage(cairo_t* cr);
|
||||
|
||||
/**
|
||||
* @brief Make and display a popover dialog near the given location.
|
||||
*
|
||||
* @param rect specifies the location of the dialog.
|
||||
* @param child is added to the dialog before displaying.
|
||||
* @returns a pointer to the popover dialog.
|
||||
*/
|
||||
GtkWidget* makePopover(const XojPdfRectangle& rect, GtkWidget* child);
|
||||
|
||||
/**
|
||||
* @brief Display a popover with link-related actions, if one
|
||||
* is at a given location on the page.
|
||||
*
|
||||
* @param page is the current page
|
||||
* @param targetX
|
||||
* @param targetY the link must contain (targetX, targetY)
|
||||
* @returns true iff a URI link exists near/at (targetX, targetY) => a popover was shown
|
||||
*/
|
||||
bool displayLinkPopover(std::shared_ptr<XojPdfPage> page, double targetX, double targetY);
|
||||
|
||||
void setX(int x);
|
||||
void setY(int y);
|
||||
|
||||
void setMappedRowCol(int row, int col); // row, column assigned by mapper during layout.
|
||||
|
||||
/**
|
||||
* Shows the floating toolbox at the location of an input event
|
||||
*/
|
||||
void showFloatingToolbox(const PositionInputData& pos);
|
||||
|
||||
/**
|
||||
* Shows the PDF toolbox at the location of an input event
|
||||
*/
|
||||
void showPdfToolbox(const PositionInputData& pos);
|
||||
|
||||
void deleteView(xoj::view::OverlayView* v);
|
||||
|
||||
private:
|
||||
PageRef page;
|
||||
XournalView* xournal = nullptr;
|
||||
Settings* settings = nullptr;
|
||||
std::unique_ptr<EraseHandler> eraser;
|
||||
std::unique_ptr<InputHandler> inputHandler;
|
||||
|
||||
std::vector<std::unique_ptr<xoj::view::OverlayView>> overlayViews;
|
||||
|
||||
/**
|
||||
* The selected (while selection)
|
||||
*/
|
||||
std::unique_ptr<Selector> selector;
|
||||
|
||||
/**
|
||||
* The text editor
|
||||
*/
|
||||
std::unique_ptr<TextEditor> textEditor;
|
||||
|
||||
/**
|
||||
* For image insertion with size (selects the size)
|
||||
*/
|
||||
std::unique_ptr<ImageSizeSelection> imageSizeSelection;
|
||||
|
||||
std::unique_ptr<LaserPointerHandler> laserPointer;
|
||||
|
||||
/**
|
||||
* For keeping old text changes to undo!
|
||||
*/
|
||||
Text* oldtext;
|
||||
|
||||
bool visible = true;
|
||||
bool selected = false;
|
||||
|
||||
xoj::view::Mask buffer;
|
||||
std::mutex drawingMutex;
|
||||
|
||||
bool inEraser = false;
|
||||
|
||||
/**
|
||||
* Vertical Space
|
||||
*/
|
||||
std::unique_ptr<VerticalToolHandler> verticalSpace;
|
||||
|
||||
/**
|
||||
* Search handling
|
||||
*/
|
||||
std::unique_ptr<SearchControl> search;
|
||||
|
||||
std::mutex repaintRectMutex;
|
||||
std::vector<xoj::util::Rectangle<double>> rerenderRects;
|
||||
bool rerenderComplete = false;
|
||||
bool sizeChanged = false;
|
||||
|
||||
int dispX{}; // position on display - set in Layout::layoutPages
|
||||
int dispY{};
|
||||
|
||||
|
||||
int mappedRow{};
|
||||
int mappedCol{};
|
||||
|
||||
DeviceId currentSequenceDeviceId;
|
||||
|
||||
|
||||
friend class RenderJob;
|
||||
friend class InputHandler;
|
||||
friend class BaseSelectObject;
|
||||
friend class SelectObject;
|
||||
friend class PlayObject;
|
||||
friend class PdfFloatingToolbox;
|
||||
// only function allowed to setX(), setY(), setMappedRowCol():
|
||||
friend void Layout::layoutPages(int width, int height);
|
||||
};
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Helper classes to find / select objects
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// No include needed, this is included after PageView.h
|
||||
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "control/AudioController.h"
|
||||
#include "control/Control.h"
|
||||
#include "control/layer/LayerController.h"
|
||||
#include "control/settings/Settings.h"
|
||||
#include "control/tools/EditSelection.h"
|
||||
#include "gui/PageView.h"
|
||||
#include "model/Layer.h"
|
||||
#include "model/XojPage.h"
|
||||
#include "util/safe_casts.h"
|
||||
|
||||
#include "XournalView.h"
|
||||
|
||||
class BaseSelectObject {
|
||||
public:
|
||||
BaseSelectObject(XojPageView* view): view(view) {}
|
||||
|
||||
virtual ~BaseSelectObject() = default;
|
||||
|
||||
public:
|
||||
bool findAt(double x, double y, bool multiLayer = false, bool clearSelection = true) {
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
|
||||
Control* ctrl = this->view->getXournal()->getControl();
|
||||
|
||||
if (clearSelection) {
|
||||
ctrl->clearSelection();
|
||||
}
|
||||
|
||||
std::shared_lock lock(*ctrl->getDocument());
|
||||
if (multiLayer) {
|
||||
const auto& layers = this->view->getPage()->getLayers();
|
||||
size_t layerNo = layers.size();
|
||||
for (auto l = layers.rbegin(); l != layers.rend(); l++, layerNo--) {
|
||||
if (checkLayer(*l)) {
|
||||
ctrl->getLayerController()->switchToLay(as_unsigned(std::distance(l, layers.rend())));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
Layer* layer = this->view->getPage()->getSelectedLayer();
|
||||
return checkLayer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool checkLayer(const Layer* l) = 0;
|
||||
|
||||
protected:
|
||||
XojPageView* view{};
|
||||
double x{0};
|
||||
double y{0};
|
||||
};
|
||||
|
||||
class SelectObject: public BaseSelectObject {
|
||||
public:
|
||||
SelectObject(XojPageView* view): BaseSelectObject(view), match(nullptr) {}
|
||||
|
||||
~SelectObject() override = default;
|
||||
|
||||
bool at(double x, double y, bool multiLayer = false) {
|
||||
findAt(x, y, multiLayer);
|
||||
|
||||
if (match) {
|
||||
Control* ctrl = view->getXournal()->getControl();
|
||||
auto sel = SelectionFactory::createFromElementOnActiveLayer(ctrl, view->getPage(), view, match, matchIndex);
|
||||
view->xournal->setSelection(sel.release());
|
||||
view->repaintPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool atAggregate(double x, double y) {
|
||||
EditSelection* previousSelection = view->getXournal()->getSelection();
|
||||
xoj_assert(previousSelection);
|
||||
|
||||
findAt(x, y, false, false);
|
||||
if (match) {
|
||||
auto sel = SelectionFactory::addElementFromActiveLayer(view->getXournal()->getControl(), previousSelection,
|
||||
match, matchIndex);
|
||||
view->getXournal()->setSelection(sel.release());
|
||||
view->repaintPage();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr double ACTION_RADIUS = 5.;
|
||||
|
||||
bool checkLayer(const Layer* l) override {
|
||||
double minDistance = ACTION_RADIUS;
|
||||
// Iterate starting from the front-most element
|
||||
Element::Index pos = as_signed(l->getElementsView().size());
|
||||
for (auto it = l->getElementsView().rbegin(); it < l->getElementsView().rend(); ++it) {
|
||||
pos--;
|
||||
// First perform a rough check to avoid expensive calls to Stroke::distanceTo()
|
||||
if ((*it)->intersectsArea(x - minDistance, y - minDistance, 2. * minDistance, 2. * minDistance)) {
|
||||
double d = (*it)->distanceTo(x, y);
|
||||
if (d == 0.0) {
|
||||
this->match = *it;
|
||||
this->matchIndex = pos;
|
||||
return true;
|
||||
}
|
||||
if (d < minDistance) {
|
||||
this->match = *it;
|
||||
this->matchIndex = pos;
|
||||
minDistance = d;
|
||||
// Keep going, we may find something closer
|
||||
}
|
||||
}
|
||||
}
|
||||
return minDistance != ACTION_RADIUS;
|
||||
}
|
||||
|
||||
private:
|
||||
const Element* match;
|
||||
Element::Index matchIndex;
|
||||
};
|
||||
|
||||
class PlayObject: public BaseSelectObject {
|
||||
public:
|
||||
PlayObject(XojPageView* view): BaseSelectObject(view), playbackStatus() {}
|
||||
|
||||
~PlayObject() override = default;
|
||||
|
||||
struct Status {
|
||||
bool success;
|
||||
fs::path filename;
|
||||
};
|
||||
|
||||
std::optional<Status> playbackStatus;
|
||||
|
||||
public:
|
||||
bool at(double x, double y, bool multiLayer = false) { return findAt(x, y, multiLayer); }
|
||||
|
||||
protected:
|
||||
static constexpr double ACTION_RADIUS = 15.;
|
||||
|
||||
/// Plays every element of the layer that are closer than ACTION_RADIUS
|
||||
bool checkLayer(const Layer* l) override {
|
||||
bool found = false;
|
||||
for (auto&& e: l->getElementsView()) {
|
||||
if (auto* audio = dynamic_cast<const AudioElement*>(e); audio) {
|
||||
// First perform a rough check to avoid expensive calls to Stroke::distanceTo()
|
||||
if (audio->intersectsArea(x - ACTION_RADIUS, y - ACTION_RADIUS, 2. * ACTION_RADIUS,
|
||||
2. * ACTION_RADIUS)) {
|
||||
double d = audio->distanceTo(x, y);
|
||||
if (d < ACTION_RADIUS) {
|
||||
found = playElement(audio) || found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool playElement(const AudioElement* s) {
|
||||
#ifdef ENABLE_AUDIO
|
||||
size_t ts = s->getTimestamp();
|
||||
|
||||
if (auto fn = s->getAudioFilename(); !fn.empty()) {
|
||||
if (!fn.has_parent_path() || fs::weakly_canonical(fn.parent_path()) == "/") {
|
||||
auto const& path = view->settings->getAudioFolder();
|
||||
// Assume path exists
|
||||
fn = path / fn;
|
||||
}
|
||||
auto* audioController = view->getXournal()->getControl()->getAudioController();
|
||||
if (!audioController) {
|
||||
g_warning("Audio has been disabled");
|
||||
return false;
|
||||
}
|
||||
bool success = audioController->startPlayback(fn, (unsigned int)ts);
|
||||
playbackStatus = {success, std::move(fn)};
|
||||
return success;
|
||||
}
|
||||
#else
|
||||
g_warning("Audio support was disabled at compile time");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "PaperFormatUtils.h"
|
||||
|
||||
#include "util/GListView.h"
|
||||
#include "util/StringUtils.h"
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
using xoj::util::GtkPaperSizeUPtr;
|
||||
|
||||
void PaperFormatUtils::loadDefaultPaperSizes(PaperFormatMenuOptionVector& paperSizes) {
|
||||
GList* default_sizes = gtk_paper_size_get_paper_sizes(false);
|
||||
for (auto& s: GListView<GtkPaperSize>(default_sizes)) {
|
||||
std::string name = gtk_paper_size_get_name(&s);
|
||||
if (name == GTK_PAPER_NAME_A3 || name == GTK_PAPER_NAME_A4 || name == GTK_PAPER_NAME_A5 ||
|
||||
name == GTK_PAPER_NAME_LETTER || name == GTK_PAPER_NAME_LEGAL) {
|
||||
paperSizes.emplace_back(GtkPaperSizeUPtr(&s));
|
||||
} else {
|
||||
gtk_paper_size_free(&s);
|
||||
}
|
||||
}
|
||||
g_list_free(default_sizes);
|
||||
|
||||
// Name format: ftp://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf
|
||||
paperSizes.emplace_back(GtkPaperSizeUPtr(gtk_paper_size_new("custom_16x9_320x180mm")));
|
||||
paperSizes.emplace_back(GtkPaperSizeUPtr(gtk_paper_size_new("custom_4x3_320x240mm")));
|
||||
}
|
||||
void PaperFormatUtils::fillPaperFormatDropDown(const PaperFormatMenuOptionVector& menuOptions,
|
||||
GtkComboBox* paperFormatComboBox) {
|
||||
// Todo (gtk4) Use GtkDropDown instead and use another model accordingly
|
||||
GtkCellRenderer* cell = gtk_cell_renderer_text_new();
|
||||
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(paperFormatComboBox), cell, true);
|
||||
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(paperFormatComboBox), cell, "text", 0, nullptr);
|
||||
|
||||
GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
|
||||
gtk_combo_box_set_model(GTK_COMBO_BOX(paperFormatComboBox), GTK_TREE_MODEL(store));
|
||||
g_object_unref(store); // store is now owned by paperFormatComboBox
|
||||
|
||||
for (auto& menuOption: menuOptions) {
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(store, &iter);
|
||||
|
||||
if (menuOption.index()) {
|
||||
auto& size = std::get<GtkPaperSizeUPtr>(menuOption);
|
||||
|
||||
std::string displayName = gtk_paper_size_get_display_name(size.get());
|
||||
if (StringUtils::startsWith(displayName, "custom_")) {
|
||||
displayName = displayName.substr(7);
|
||||
}
|
||||
gtk_list_store_set(store, &iter, 0, displayName.c_str(), -1);
|
||||
gtk_list_store_set(store, &iter, 1, size.get(), -1);
|
||||
} else {
|
||||
// When special non-GtkPaperSize option
|
||||
const std::string_view displayName = std::get<std::string>(menuOption);
|
||||
gtk_list_store_set(store, &iter, 0, displayName.data(), -1);
|
||||
gtk_list_store_set(store, &iter, 1, nullptr, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Paper format utilities
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "util/raii/GtkPaperSizeUPtr.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace PaperFormatUtils {
|
||||
/**
|
||||
* @brief A vector of menu options, either being a GtkPaperSize or the displayed name of the option.
|
||||
* @details The variant facilitates adding options which can't be expressed as a GtkPaperSize (e.g. a custom option
|
||||
* or a copy current page format option)
|
||||
*/
|
||||
using PaperFormatMenuOptionVector = std::vector<std::variant<std::string, xoj::util::GtkPaperSizeUPtr>>;
|
||||
|
||||
/**
|
||||
* @brief Loads the default PaperSizes into the passed vector
|
||||
* @param menuOptions The vector containg the menu options
|
||||
*/
|
||||
void loadDefaultPaperSizes(PaperFormatMenuOptionVector& menuOptions);
|
||||
|
||||
/**
|
||||
* @brief Adds the entries passed as vector of GtkPaperSize entries and/or special entries to the given combo box
|
||||
* @param menuOptions The vector of menu options
|
||||
* @param paperFormatComboBox A pointer to the combo box which should be configured as a paper format dropdown
|
||||
*/
|
||||
void fillPaperFormatDropDown(const PaperFormatMenuOptionVector& menuOptions, GtkComboBox* paperFormatComboBox);
|
||||
} // namespace PaperFormatUtils
|
||||
@@ -1,239 +0,0 @@
|
||||
#include "PdfFloatingToolbox.h"
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <cmath> // for abs
|
||||
#include <cstddef> // for size_t
|
||||
#include <memory>
|
||||
#include <string> // for string
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/ToolEnums.h" // for ToolType, TOOL_SELECT_PDF_TEXT_LI...
|
||||
#include "control/ToolHandler.h" // for ToolHandler
|
||||
#include "control/tools/PdfElemSelection.h"
|
||||
#include "gui/PageView.h" // for XojPageView
|
||||
#include "gui/XournalView.h" // for XournalView
|
||||
#include "model/Document.h" // for Document
|
||||
#include "model/Layer.h" // for Layer
|
||||
#include "model/PageRef.h" // for PageRef
|
||||
#include "model/Point.h" // for Point
|
||||
#include "model/Stroke.h" // for Stroke, BUTT, StrokeTool::HIGHLIG...
|
||||
#include "model/XojPage.h" // for XojPage
|
||||
#include "undo/GroupUndoAction.h" // for GroupUndoAction
|
||||
#include "undo/InsertUndoAction.h" // for InsertUndoAction
|
||||
#include "undo/UndoAction.h" // for UndoAction
|
||||
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
|
||||
#include "util/Assert.h" // for xoj_assert
|
||||
#include "util/gtk4_helper.h" // for gtk_widget_get_clipboard
|
||||
|
||||
#include "MainWindow.h" // for MainWindow
|
||||
|
||||
PdfFloatingToolbox::PdfFloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay):
|
||||
theMainWindow(theMainWindow), overlay(overlay, xoj::util::ref), position({0, 0}) {
|
||||
this->floatingToolbox = theMainWindow->get("pdfFloatingToolbox");
|
||||
|
||||
gtk_overlay_add_overlay(overlay, this->floatingToolbox);
|
||||
gtk_overlay_set_overlay_pass_through(overlay, this->floatingToolbox, true);
|
||||
|
||||
g_signal_connect(overlay, "get-child-position", G_CALLBACK(this->getOverlayPosition), this);
|
||||
|
||||
g_signal_connect(theMainWindow->get("pdfTbHighlight"), "clicked", G_CALLBACK(this->highlightCb), this);
|
||||
g_signal_connect(theMainWindow->get("pdfTbCopyText"), "clicked", G_CALLBACK(this->copyTextCb), this);
|
||||
g_signal_connect(theMainWindow->get("pdfTbUnderline"), "clicked", G_CALLBACK(this->underlineCb), this);
|
||||
g_signal_connect(theMainWindow->get("pdfTbStrikethrough"), "clicked", G_CALLBACK(this->strikethroughCb), this);
|
||||
g_signal_connect(theMainWindow->get("pdfTbChangeType"), "clicked", G_CALLBACK(this->switchSelectTypeCb), this);
|
||||
|
||||
this->clearSelection();
|
||||
this->hide();
|
||||
}
|
||||
|
||||
PdfFloatingToolbox::~PdfFloatingToolbox() = default;
|
||||
|
||||
PdfElemSelection* PdfFloatingToolbox::getSelection() const { return this->pdfElemSelection.get(); }
|
||||
bool PdfFloatingToolbox::hasSelection() const { return this->getSelection() != nullptr; }
|
||||
|
||||
void PdfFloatingToolbox::clearSelection() { this->pdfElemSelection.reset(); }
|
||||
|
||||
auto PdfFloatingToolbox::newSelection(double x, double y) -> const PdfElemSelection* {
|
||||
this->pdfElemSelection = std::make_unique<PdfElemSelection>(x, y, this->theMainWindow->getControl());
|
||||
return this->pdfElemSelection.get();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::show(int x, int y) {
|
||||
xoj_assert(this->getSelection());
|
||||
|
||||
// (x, y) are in the gtk window's coordinates.
|
||||
// However, we actually show the toolbox in the overlay's coordinate system.
|
||||
gtk_widget_translate_coordinates(gtk_widget_get_toplevel(this->floatingToolbox), GTK_WIDGET(overlay.get()), x, y,
|
||||
&this->position.x, &this->position.y);
|
||||
this->show();
|
||||
|
||||
// Record the color now: the active tool may change while the toolbox is up (e.g. if the tool is linked to a button)
|
||||
this->color = theMainWindow->getXournal()->getControl()->getToolHandler()->getColor();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::hide() {
|
||||
if (isHidden())
|
||||
return;
|
||||
|
||||
gtk_widget_hide(this->floatingToolbox);
|
||||
}
|
||||
|
||||
auto PdfFloatingToolbox::getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
|
||||
PdfFloatingToolbox* self) -> gboolean {
|
||||
if (widget == self->floatingToolbox) {
|
||||
// Get existing width and height
|
||||
GtkRequisition natural;
|
||||
gtk_widget_get_preferred_size(widget, nullptr, &natural);
|
||||
allocation->width = natural.width;
|
||||
allocation->height = natural.height;
|
||||
|
||||
// Make sure the "pdfFloatingToolbox" is fully displayed.
|
||||
const int gap = 5;
|
||||
|
||||
// By default, we show the toolbox below and to the right of the selected text.
|
||||
// If the toolbox will go out of the window, then we'll flip the corresponding directions.
|
||||
|
||||
GtkAllocation windowAlloc{};
|
||||
gtk_widget_get_allocation(GTK_WIDGET(overlay), &windowAlloc);
|
||||
|
||||
bool rightOK = self->position.x + allocation->width + gap <= windowAlloc.width;
|
||||
bool bottomOK = self->position.y + allocation->height + gap <= windowAlloc.height;
|
||||
|
||||
allocation->x = rightOK ? self->position.x + gap : self->position.x - allocation->width - gap;
|
||||
allocation->y = bottomOK ? self->position.y + gap : self->position.y - allocation->height - gap;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::userCancelSelection() {
|
||||
this->pdfElemSelection.reset();
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::highlightCb(GtkButton* button, PdfFloatingToolbox* pft) {
|
||||
int markerOpacity = pft->theMainWindow->getControl()->getToolHandler()->getSelectPDFTextMarkerOpacity();
|
||||
pft->createStrokes(PdfMarkerStyle::POS_TEXT_MIDDLE, PdfMarkerStyle::WIDTH_TEXT_HEIGHT, markerOpacity);
|
||||
pft->userCancelSelection();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::copyTextCb(GtkButton* button, PdfFloatingToolbox* pft) {
|
||||
pft->copyTextToClipboard();
|
||||
pft->userCancelSelection();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::underlineCb(GtkButton* button, PdfFloatingToolbox* pft) {
|
||||
pft->createStrokes(PdfMarkerStyle::POS_TEXT_BOTTOM, PdfMarkerStyle::WIDTH_TEXT_LINE, 230);
|
||||
pft->userCancelSelection();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::strikethroughCb(GtkButton* button, PdfFloatingToolbox* pft) {
|
||||
pft->createStrokes(PdfMarkerStyle::POS_TEXT_MIDDLE, PdfMarkerStyle::WIDTH_TEXT_LINE, 230);
|
||||
pft->userCancelSelection();
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::show() {
|
||||
gtk_widget_hide(this->floatingToolbox); // force showing in new position
|
||||
gtk_widget_show_all(this->floatingToolbox);
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::copyTextToClipboard() {
|
||||
GtkClipboard* clipboard = gtk_widget_get_clipboard(this->theMainWindow->getWindow());
|
||||
if (const std::string& text = this->pdfElemSelection->getSelectedText(); !text.empty()) {
|
||||
gtk_clipboard_set_text(clipboard, text.c_str(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::createStrokes(PdfMarkerStyle position, PdfMarkerStyle width, int markerOpacity) {
|
||||
const size_t pdfPageNo = this->pdfElemSelection->getSelectionPageNr();
|
||||
const size_t currentPage = theMainWindow->getXournal()->getCurrentPage();
|
||||
|
||||
// Get the PDF page that the current page corresponds to.
|
||||
// It should be the same as the PDF page of the selection.
|
||||
auto doc = this->theMainWindow->getControl()->getDocument();
|
||||
doc->lock_shared();
|
||||
const size_t pdfPageOfCurrentPage = doc->getPage(currentPage)->getPdfPageNr();
|
||||
doc->unlock_shared();
|
||||
|
||||
if (pdfPageOfCurrentPage != pdfPageNo) {
|
||||
// There's probably a bug that violates our assumptions, so no-op.
|
||||
g_warning("The current page's PDF page is not the same as the PDF page of the selection!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto textRects = this->pdfElemSelection->getSelectedTextRects();
|
||||
if (textRects.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* control = this->theMainWindow->getControl();
|
||||
PageRef page = control->getCurrentPage();
|
||||
Layer* layer = page->getSelectedLayer();
|
||||
|
||||
Range dirtyRange;
|
||||
std::vector<ElementPtr> strokes;
|
||||
for (XojPdfRectangle rect: textRects) {
|
||||
const double topOfLine = std::min(rect.y1, rect.y2);
|
||||
const double middleOfLine = (rect.y1 + rect.y2) / 2;
|
||||
const double bottomOfLine = std::max(rect.y1, rect.y2);
|
||||
const double rectWidth = std::abs(rect.y2 - rect.y1);
|
||||
|
||||
// the center line position of stroke
|
||||
const double h = position == PdfMarkerStyle::POS_TEXT_BOTTOM ? bottomOfLine :
|
||||
position == PdfMarkerStyle::POS_TEXT_MIDDLE ? middleOfLine :
|
||||
topOfLine;
|
||||
// the width of stroke
|
||||
const double w = width == PdfMarkerStyle::WIDTH_TEXT_LINE ? 1 : rectWidth;
|
||||
|
||||
auto stroke = std::make_unique<Stroke>();
|
||||
stroke->setColor(this->color);
|
||||
stroke->setFill(markerOpacity);
|
||||
stroke->setToolType(StrokeTool::HIGHLIGHTER);
|
||||
stroke->setWidth(w);
|
||||
stroke->addPoint(Point(rect.x1, h, -1));
|
||||
stroke->addPoint(Point(rect.x2, h, -1));
|
||||
stroke->setStrokeCapStyle(StrokeCapStyle::BUTT);
|
||||
|
||||
dirtyRange.addPoint(rect.x1, h - 0.5 * w);
|
||||
dirtyRange.addPoint(rect.x2, h + 0.5 * w);
|
||||
|
||||
strokes.push_back(std::move(stroke));
|
||||
}
|
||||
|
||||
std::vector<const Element*> strokePtrs(strokes.size());
|
||||
std::transform(strokes.begin(), strokes.end(), strokePtrs.begin(), [](auto& e) { return e.get(); });
|
||||
|
||||
doc->lock();
|
||||
for (auto&& s: strokes) {
|
||||
layer->addElement(std::move(s));
|
||||
}
|
||||
doc->unlock();
|
||||
page->fireElementsChanged(strokePtrs, dirtyRange);
|
||||
|
||||
auto undoAct = std::make_unique<GroupUndoAction>();
|
||||
for (auto* stroke: strokePtrs) {
|
||||
undoAct->addAction(std::make_unique<InsertUndoAction>(page, layer, stroke));
|
||||
}
|
||||
control->getUndoRedoHandler()->addUndoAction(std::move(undoAct));
|
||||
}
|
||||
|
||||
void PdfFloatingToolbox::switchSelectTypeCb(GtkButton* button, PdfFloatingToolbox* pft) {
|
||||
ToolType type = pft->theMainWindow->getControl()->getToolHandler()->getToolType();
|
||||
|
||||
type = type == ToolType::TOOL_SELECT_PDF_TEXT_LINEAR ? ToolType::TOOL_SELECT_PDF_TEXT_RECT :
|
||||
ToolType::TOOL_SELECT_PDF_TEXT_LINEAR;
|
||||
|
||||
pft->theMainWindow->getControl()->selectTool(type);
|
||||
|
||||
pft->pdfElemSelection->setToolType(type);
|
||||
pft->pdfElemSelection->finalizeSelection(PdfElemSelection::selectionStyleForToolType(type));
|
||||
}
|
||||
|
||||
bool PdfFloatingToolbox::isHidden() const { return !gtk_widget_is_visible(this->floatingToolbox); }
|
||||
@@ -1,97 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // for uint8_t
|
||||
#include <memory> // for unique_ptr
|
||||
|
||||
#include <gdk/gdk.h> // for GdkRectangle
|
||||
#include <glib.h> // for gboolean
|
||||
#include <gtk/gtk.h> // for GtkButton, GtkOverlay
|
||||
|
||||
#include "pdf/base/XojPdfPage.h" // for XojPdfPageSelectionStyle
|
||||
#include "util/Color.h" // for Color
|
||||
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
|
||||
|
||||
class MainWindow;
|
||||
class PdfElemSelection;
|
||||
|
||||
enum class PdfMarkerStyle : uint8_t {
|
||||
POS_TEXT_BOTTOM = 0,
|
||||
POS_TEXT_MIDDLE,
|
||||
POS_TEXT_TOP,
|
||||
|
||||
WIDTH_TEXT_LINE,
|
||||
WIDTH_TEXT_HEIGHT
|
||||
};
|
||||
|
||||
|
||||
class PdfFloatingToolbox {
|
||||
public:
|
||||
PdfFloatingToolbox(MainWindow* theMainWindow, GtkOverlay* overlay);
|
||||
PdfFloatingToolbox& operator=(const PdfFloatingToolbox&) = delete;
|
||||
PdfFloatingToolbox(const PdfFloatingToolbox&) = delete;
|
||||
PdfFloatingToolbox& operator=(PdfFloatingToolbox&&) = delete;
|
||||
PdfFloatingToolbox(PdfFloatingToolbox&&) = delete;
|
||||
~PdfFloatingToolbox();
|
||||
|
||||
public:
|
||||
/// Show the toolbox at the provided coordinates (relative to the application GTK window).
|
||||
///
|
||||
/// Must have an active selection.
|
||||
void show(int x, int y);
|
||||
|
||||
/// Hide the floating toolbox widget (keeping the current selection).
|
||||
void hide();
|
||||
|
||||
/// Returns true if the toolbox is currently hidden.
|
||||
bool isHidden() const;
|
||||
|
||||
/// Returns the selection, or nullptr if no PDF element is selected.
|
||||
PdfElemSelection* getSelection() const;
|
||||
|
||||
/// Clears the current selection (without any visual effects).
|
||||
void clearSelection();
|
||||
|
||||
/// Create a new selection (without any visual effects)
|
||||
const PdfElemSelection* newSelection(double x, double y);
|
||||
|
||||
/// Returns true iff a PDF element is selected;
|
||||
bool hasSelection() const;
|
||||
|
||||
/// Cancel the selection, rerender the page, and hide the toolbox.
|
||||
void userCancelSelection();
|
||||
|
||||
/// Track selection style used for unfinalized selections
|
||||
XojPdfPageSelectionStyle selectionStyle = XojPdfPageSelectionStyle::Linear;
|
||||
|
||||
/// Copy Selection to the Clipboard.
|
||||
void copyTextToClipboard();
|
||||
|
||||
private:
|
||||
void show();
|
||||
|
||||
static gboolean getOverlayPosition(GtkOverlay* overlay, GtkWidget* widget, GdkRectangle* allocation,
|
||||
PdfFloatingToolbox* self);
|
||||
|
||||
static void switchSelectTypeCb(GtkButton* button, PdfFloatingToolbox* pft);
|
||||
static void strikethroughCb(GtkButton* button, PdfFloatingToolbox* pft);
|
||||
static void underlineCb(GtkButton* button, PdfFloatingToolbox* pft);
|
||||
static void copyTextCb(GtkButton* button, PdfFloatingToolbox* pft);
|
||||
static void highlightCb(GtkButton* button, PdfFloatingToolbox* pft);
|
||||
|
||||
void createStrokes(PdfMarkerStyle position, PdfMarkerStyle width, int markerOpacity);
|
||||
|
||||
private:
|
||||
GtkWidget* floatingToolbox;
|
||||
MainWindow* theMainWindow;
|
||||
Color color; ///< Used for strokes/highlighting
|
||||
|
||||
/// The overlay that the toolbox should be displayed in.
|
||||
xoj::util::GObjectSPtr<GtkOverlay> overlay;
|
||||
|
||||
std::unique_ptr<PdfElemSelection> pdfElemSelection;
|
||||
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
} position;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Abstract class for creating popovers
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
class PopoverFactory {
|
||||
public:
|
||||
PopoverFactory() = default;
|
||||
virtual ~PopoverFactory() = default;
|
||||
|
||||
/// Creates a GtkPopover. The returned ref is floating
|
||||
virtual GtkWidget* createPopover() const = 0;
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "RepaintHandler.h"
|
||||
|
||||
#include <gtk/gtk.h> // for gtk_widget_queue_draw
|
||||
|
||||
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_repaint_area
|
||||
|
||||
#include "PageView.h" // for XojPageView
|
||||
#include "XournalView.h" // for XournalView
|
||||
|
||||
RepaintHandler::RepaintHandler(XournalView* xournal): xournal(xournal) {}
|
||||
|
||||
RepaintHandler::~RepaintHandler() { this->xournal = nullptr; }
|
||||
|
||||
void RepaintHandler::repaintPage(const XojPageView* view) {
|
||||
int x1 = view->getX();
|
||||
int y1 = view->getY();
|
||||
int x2 = x1 + view->getDisplayWidth();
|
||||
int y2 = y1 + view->getDisplayHeight();
|
||||
|
||||
gtk_xournal_repaint_area(this->xournal->getWidget(), x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void RepaintHandler::repaintPageArea(const XojPageView* view, int x1, int y1, int x2, int y2) {
|
||||
int x = view->getX();
|
||||
int y = view->getY();
|
||||
gtk_xournal_repaint_area(this->xournal->getWidget(), x + x1, y + y1, x + x2, y + y2);
|
||||
}
|
||||
|
||||
void RepaintHandler::repaintPageBorder(const XojPageView* view) { gtk_widget_queue_draw(this->xournal->getWidget()); }
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Class for render and repaint pages
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class XojPageView;
|
||||
class XournalView;
|
||||
|
||||
class RepaintHandler {
|
||||
public:
|
||||
RepaintHandler(XournalView* xournal);
|
||||
virtual ~RepaintHandler();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Repaint a page
|
||||
*/
|
||||
void repaintPage(const XojPageView* view);
|
||||
|
||||
/**
|
||||
* Repaint a page area, coordinates are in view coordinates
|
||||
*/
|
||||
void repaintPageArea(const XojPageView* view, int x1, int y1, int x2, int y2);
|
||||
|
||||
/**
|
||||
* Repaints the page border (at least)
|
||||
*/
|
||||
void repaintPageBorder(const XojPageView* view);
|
||||
|
||||
private:
|
||||
XournalView* xournal;
|
||||
};
|
||||
@@ -1,198 +0,0 @@
|
||||
#include "SearchBar.h"
|
||||
|
||||
#include <string> // for allocator, string
|
||||
|
||||
#include <gdk/gdk.h> // for GdkEventKey, GDK_SHIFT_MASK
|
||||
#include <gdk/gdkkeysyms.h> // for GDK_KEY_Return
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
#include <glib.h> // for g_free, g_strdup_printf
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/ScrollHandler.h" // for ScrollHandler
|
||||
#include "control/zoom/ZoomControl.h" // for ZoomControl
|
||||
#include "gui/MainWindow.h" // for MainWindow
|
||||
#include "model/Document.h" // for Document
|
||||
#include "util/PlaceholderString.h" // for PlaceholderString
|
||||
#include "util/i18n.h" // for _, FC, _F
|
||||
|
||||
SearchBar::SearchBar(Control* control): control(control) {
|
||||
MainWindow* win = control->getWindow();
|
||||
|
||||
GtkWidget* close = win->get("buttonCloseSearch");
|
||||
g_signal_connect(close, "clicked", G_CALLBACK(buttonCloseSearchClicked), this);
|
||||
|
||||
GtkWidget* next = win->get("btSearchForward");
|
||||
GtkWidget* previous = win->get("btSearchBack");
|
||||
g_signal_connect(next, "clicked",
|
||||
G_CALLBACK(+[](GtkButton*, gpointer d) { static_cast<SearchBar*>(d)->searchNext(); }), this);
|
||||
g_signal_connect(previous, "clicked",
|
||||
G_CALLBACK(+[](GtkButton*, gpointer d) { static_cast<SearchBar*>(d)->searchPrevious(); }), this);
|
||||
|
||||
GtkWidget* searchTextField = win->get("searchTextField");
|
||||
g_signal_connect(searchTextField, "search-changed", G_CALLBACK(searchTextChangedCallback), this);
|
||||
g_signal_connect(searchTextField, "stop-search",
|
||||
G_CALLBACK(+[](GtkSearchEntry*, gpointer d) { static_cast<SearchBar*>(d)->showSearchBar(false); }),
|
||||
this);
|
||||
g_signal_connect(searchTextField, "next-match", G_CALLBACK(+[](GtkSearchEntry* e, gpointer d) {
|
||||
static_cast<SearchBar*>(d)->searchNext();
|
||||
// Grab focus again since searching will take away focus
|
||||
gtk_widget_grab_focus(GTK_WIDGET(e));
|
||||
}),
|
||||
this);
|
||||
g_signal_connect(searchTextField, "previous-match", G_CALLBACK(+[](GtkSearchEntry* e, gpointer d) {
|
||||
static_cast<SearchBar*>(d)->searchPrevious();
|
||||
// Grab focus again since searching will take away focus
|
||||
gtk_widget_grab_focus(GTK_WIDGET(e));
|
||||
}),
|
||||
this);
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
GtkBindingSet* bindingSet = gtk_binding_set_by_class(GTK_SEARCH_ENTRY_GET_CLASS(searchTextField));
|
||||
gtk_binding_entry_add_signal(bindingSet, GDK_KEY_Return, GdkModifierType(0), "next-match", 0);
|
||||
gtk_binding_entry_add_signal(bindingSet, GDK_KEY_Return, GDK_SHIFT_MASK, "previous-match", 0);
|
||||
#else
|
||||
GtkEventController* ctrl = gtk_shortcut_controller_new();
|
||||
gtk_shortcut_controller_add_shortcut(GTK_SHORTCUT_CONTROLLER(ctrl),
|
||||
gtk_shortcut_new(gtk_keyval_trigger_new(GDK_KEY_Return, GdkModifierType(0)),
|
||||
gtk_signal_action_new("next-match")));
|
||||
gtk_shortcut_controller_add_shortcut(GTK_SHORTCUT_CONTROLLER(ctrl),
|
||||
gtk_shortcut_new(gtk_keyval_trigger_new(GDK_KEY_Return, GDK_SHIFT_MASK),
|
||||
gtk_signal_action_new("previous-match")));
|
||||
gtk_widget_add_controller(searchTextField, ctrl);
|
||||
#endif
|
||||
|
||||
cssTextFild = gtk_css_provider_new();
|
||||
gtk_style_context_add_provider(gtk_widget_get_style_context(win->get("searchTextField")),
|
||||
GTK_STYLE_PROVIDER(cssTextFild), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
SearchBar::~SearchBar() { this->control = nullptr; }
|
||||
|
||||
auto SearchBar::searchTextonCurrentPage(const char* text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect)
|
||||
-> bool {
|
||||
size_t p = control->getCurrentPageNo();
|
||||
this->page = p;
|
||||
|
||||
return control->searchTextOnPage(text, p, index, occurrences, matchRect);
|
||||
}
|
||||
|
||||
void SearchBar::search(const char* text) {
|
||||
MainWindow* win = control->getWindow();
|
||||
GtkWidget* lbSearchState = win->get("lbSearchState");
|
||||
|
||||
bool found = true;
|
||||
this->indexInPage = 0;
|
||||
|
||||
if (*text != 0) {
|
||||
found = searchTextonCurrentPage(text, 1, &this->occurrences, nullptr);
|
||||
if (found) {
|
||||
if (occurrences == 1) {
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text found once on this page"));
|
||||
} else {
|
||||
char* msg = g_strdup_printf(_("Text found %zu times on this page"), occurrences);
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState), msg);
|
||||
g_free(msg);
|
||||
}
|
||||
} else {
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text not found"));
|
||||
}
|
||||
} else {
|
||||
searchTextonCurrentPage("", 1, nullptr, nullptr);
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState), "");
|
||||
}
|
||||
|
||||
if (found) {
|
||||
gtk_css_provider_load_from_data(cssTextFild, "GtkSearchEntry {}", -1, nullptr);
|
||||
} else {
|
||||
gtk_css_provider_load_from_data(cssTextFild, "GtkSearchEntry { color: #ff0000; }", -1, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchBar::searchTextChangedCallback(GtkSearchEntry* entry, SearchBar* searchBar) {
|
||||
const char* text = gtk_entry_get_text(GTK_ENTRY(entry));
|
||||
searchBar->search(text);
|
||||
}
|
||||
|
||||
void SearchBar::buttonCloseSearchClicked(GtkButton* button, SearchBar* searchBar) { searchBar->showSearchBar(false); }
|
||||
|
||||
template <class Fun>
|
||||
void SearchBar::search(Fun next) {
|
||||
|
||||
MainWindow* win = control->getWindow();
|
||||
GtkWidget* searchTextField = win->get("searchTextField");
|
||||
const char* text = gtk_entry_get_text(GTK_ENTRY(searchTextField));
|
||||
GtkWidget* lbSearchState = win->get("lbSearchState");
|
||||
if (*text == 0) {
|
||||
return;
|
||||
}
|
||||
const size_t originalPage = page;
|
||||
|
||||
XojPdfRectangle matchRect = XojPdfRectangle();
|
||||
// Search backwards through the pages, wrapping around if needed.
|
||||
for (;;) {
|
||||
next(text);
|
||||
const bool found = control->searchTextOnPage(text, page, indexInPage, &occurrences, &matchRect);
|
||||
|
||||
if (found) {
|
||||
control->getScrollHandler()->scrollToPage(page, matchRect);
|
||||
control->getScrollHandler();
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState),
|
||||
(occurrences == 1 ? FC(_F("Text found once on page {1}") % (page + 1)) :
|
||||
FC(_F("Text found {1} times on page {2} ({3} of {1})") %
|
||||
occurrences % (page + 1) % indexInPage)));
|
||||
return;
|
||||
}
|
||||
if (page == originalPage) {
|
||||
gtk_label_set_text(GTK_LABEL(lbSearchState), _("Text not found, searched on all pages"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SearchBar::searchNext() {
|
||||
size_t pageCount = control->getDocument()->getPageCount();
|
||||
search([&](const char* text) {
|
||||
indexInPage++;
|
||||
if (indexInPage > occurrences) {
|
||||
control->searchTextOnPage(text, page, 1, &occurrences, nullptr); // clear the active marker
|
||||
page++;
|
||||
if (page >= pageCount) {
|
||||
page = 0;
|
||||
}
|
||||
indexInPage = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SearchBar::searchPrevious() {
|
||||
size_t pageCount = control->getDocument()->getPageCount();
|
||||
search([&](const char* text) {
|
||||
indexInPage--;
|
||||
if (indexInPage == 0 || indexInPage >= occurrences) {
|
||||
control->searchTextOnPage(text, page, 1, &occurrences, nullptr); // clear the active marker
|
||||
page--;
|
||||
if (page > pageCount) {
|
||||
page = pageCount - 1;
|
||||
}
|
||||
control->searchTextOnPage(text, page, 1, &occurrences, nullptr);
|
||||
indexInPage = occurrences;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SearchBar::showSearchBar(bool show) {
|
||||
MainWindow* win = control->getWindow();
|
||||
GtkWidget* searchBar = win->get("searchBar");
|
||||
|
||||
if (show) {
|
||||
GtkWidget* searchTextField = win->get("searchTextField");
|
||||
gtk_widget_show_all(searchBar);
|
||||
gtk_widget_grab_focus(searchTextField);
|
||||
this->indexInPage = 0;
|
||||
} else {
|
||||
gtk_widget_hide(searchBar);
|
||||
const size_t pageCount = control->getDocument()->getPageCount();
|
||||
for (size_t i = pageCount - 1; i < pageCount; i--) {
|
||||
control->searchTextOnPage("", i, 0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Handles the searchbar and search events
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h> // for GtkButton, GtkEntry
|
||||
#include <gtk/gtkcssprovider.h> // for GtkCssProvider
|
||||
|
||||
class Control;
|
||||
class XojPdfRectangle;
|
||||
|
||||
class SearchBar {
|
||||
public:
|
||||
SearchBar(Control* control);
|
||||
virtual ~SearchBar();
|
||||
|
||||
void showSearchBar(bool show);
|
||||
|
||||
private:
|
||||
static void buttonCloseSearchClicked(GtkButton* button, SearchBar* searchBar);
|
||||
static void searchTextChangedCallback(GtkSearchEntry* entry, SearchBar* searchBar);
|
||||
|
||||
static void buttonNextSearchClicked(GtkButton* button, SearchBar* searchBar);
|
||||
static void buttonPreviousSearchClicked(GtkButton* button, SearchBar* searchBar);
|
||||
|
||||
/**
|
||||
* @brief Searches the entire document until a match is found, starting from `page = next(currentPage)` and
|
||||
* iterating through the pages via page = next(page). The search stops after the first page with at least one match.
|
||||
* All the match on that page are stored in XojPageView::search of the corresponding page. The current page is not
|
||||
* search!
|
||||
* @param next The parameter `next` must be convertible to size_t(size_t) and satisfy the following assertions
|
||||
* * Iterating from page = next(currentPage) by page = next(page) must reach page == currentPage at
|
||||
* some point.
|
||||
* * If page is a valid page number, then so is next(page).
|
||||
*/
|
||||
template <class Fun>
|
||||
void search(Fun next);
|
||||
|
||||
/**
|
||||
* @brief Named specialization of search(), where next(page) = (page + 1) % pageCount
|
||||
*/
|
||||
void searchNext();
|
||||
/**
|
||||
* @brief Named specialization of search(), where next(page) = (page + pageCount - 1) % pageCount
|
||||
*/
|
||||
void searchPrevious();
|
||||
|
||||
void search(const char* text);
|
||||
bool searchTextonCurrentPage(const char* text, size_t index, size_t* occurrences, XojPdfRectangle* matchRect);
|
||||
|
||||
private:
|
||||
Control* control;
|
||||
GtkCssProvider* cssTextFild;
|
||||
size_t page = 0;
|
||||
size_t indexInPage = 0;
|
||||
size_t occurrences = 0;
|
||||
};
|
||||
@@ -1,217 +0,0 @@
|
||||
#include "Shadow.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "ShadowCode.c"
|
||||
|
||||
Shadow* Shadow::instance = new Shadow();
|
||||
|
||||
using u8ptr = const unsigned char*;
|
||||
|
||||
Shadow::Shadow() {
|
||||
const int sBrSize = shadowBottomRightSize;
|
||||
const int sTlSize = shadowTopLeftSize;
|
||||
|
||||
this->edgeTopLeft = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sTlSize * 2, sTlSize * 2);
|
||||
cairo_t* cr = cairo_create(this->edgeTopLeft);
|
||||
drawShadowEdge(cr, 0, 0, sTlSize, sTlSize, reinterpret_cast<u8ptr>(shadowEdge1), 0, 0, 0);
|
||||
cairo_destroy(cr);
|
||||
|
||||
this->edgeTopRight = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sBrSize * 2, sTlSize * 2);
|
||||
cr = cairo_create(this->edgeTopRight);
|
||||
drawShadowEdge(cr, 0, 0, sBrSize, sTlSize, reinterpret_cast<u8ptr>(shadowEdge2), 0, 0, 0);
|
||||
cairo_destroy(cr);
|
||||
|
||||
this->edgeBottomRight = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sBrSize * 2, sBrSize * 2);
|
||||
cr = cairo_create(this->edgeBottomRight);
|
||||
drawShadowEdge(cr, 0, 0, sBrSize, sBrSize, reinterpret_cast<u8ptr>(shadowEdge3), 0, 0, 0);
|
||||
cairo_destroy(cr);
|
||||
|
||||
this->edgeBottomLeft = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, sTlSize * 2, sBrSize * 2);
|
||||
cr = cairo_create(this->edgeBottomLeft);
|
||||
drawShadowEdge(cr, 0, 0, sTlSize, sBrSize, reinterpret_cast<u8ptr>(shadowEdge4), 0, 0, 0);
|
||||
cairo_destroy(cr);
|
||||
}
|
||||
|
||||
Shadow::~Shadow() {
|
||||
cairo_surface_destroy(this->edgeBottomLeft);
|
||||
cairo_surface_destroy(this->edgeBottomRight);
|
||||
cairo_surface_destroy(this->edgeTopLeft);
|
||||
cairo_surface_destroy(this->edgeTopRight);
|
||||
this->edgeBottomLeft = nullptr;
|
||||
this->edgeBottomRight = nullptr;
|
||||
this->edgeTopLeft = nullptr;
|
||||
this->edgeTopRight = nullptr;
|
||||
|
||||
if (this->left) {
|
||||
cairo_surface_destroy(this->left);
|
||||
}
|
||||
if (this->top) {
|
||||
cairo_surface_destroy(this->top);
|
||||
}
|
||||
if (this->right) {
|
||||
cairo_surface_destroy(this->right);
|
||||
}
|
||||
if (this->bottom) {
|
||||
cairo_surface_destroy(this->bottom);
|
||||
}
|
||||
|
||||
this->left = nullptr;
|
||||
this->top = nullptr;
|
||||
this->right = nullptr;
|
||||
this->bottom = nullptr;
|
||||
}
|
||||
|
||||
auto Shadow::getShadowBottomRightSize() -> int { return shadowBottomRightSize; }
|
||||
|
||||
auto Shadow::getShadowTopLeftSize() -> int { return shadowTopLeftSize; }
|
||||
|
||||
void Shadow::drawShadowEdge(cairo_t* cr, int x, int y, int width, int height, const unsigned char* edge, double r,
|
||||
double g, double b) {
|
||||
int w = width * 2;
|
||||
int h = height * 2;
|
||||
|
||||
for (int u = 0; u < h; u++) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
double a = edge[u * w + i] / 255.0;
|
||||
cairo_set_source_rgba(cr, r, g, b, a);
|
||||
|
||||
cairo_rectangle(cr, x + i, y + u, 1, 1);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Shadow::paintEdge(cairo_t* cr, cairo_surface_t* image, int x, int y, int width, int height) {
|
||||
cairo_set_source_surface(cr, image, x - width, y - height);
|
||||
cairo_rectangle(cr, x - width, y - height, width * 2, height * 2);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
static constexpr int PATTERN_LENGTH = 200; ///< Why not?
|
||||
|
||||
static void setPattern(cairo_t* cr, cairo_surface_t* patt, double offsetX, double offsetY) {
|
||||
cairo_pattern_t* p = cairo_pattern_create_for_surface(patt);
|
||||
cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
|
||||
cairo_matrix_t m;
|
||||
cairo_matrix_init_translate(&m, -offsetX, -offsetY);
|
||||
cairo_pattern_set_matrix(p, &m);
|
||||
cairo_set_source(cr, p);
|
||||
cairo_pattern_destroy(p);
|
||||
}
|
||||
|
||||
void Shadow::drawShadowTop(cairo_t* cr, int x, int y, int width, double r, double g, double b) {
|
||||
|
||||
if (!this->top) {
|
||||
this->top = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PATTERN_LENGTH, shadowTopLeftSize);
|
||||
cairo_t* cr2 = cairo_create(this->top);
|
||||
|
||||
double a = NAN;
|
||||
|
||||
// draw shadow on top and left
|
||||
for (int i = 0; i < shadowTopLeftSize; i++) {
|
||||
a = shadowTopLeft[i] / 255.0;
|
||||
cairo_set_source_rgba(cr2, r, g, b, a);
|
||||
|
||||
cairo_rectangle(cr2, 0, i, PATTERN_LENGTH, 1);
|
||||
cairo_fill(cr2);
|
||||
}
|
||||
|
||||
cairo_destroy(cr2);
|
||||
}
|
||||
|
||||
setPattern(cr, this->top, x + shadowTopLeftSize, y - shadowTopLeftSize);
|
||||
cairo_rectangle(cr, x + shadowTopLeftSize, y - shadowTopLeftSize, width - 2 * shadowTopLeftSize, shadowTopLeftSize);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
void Shadow::drawShadowLeft(cairo_t* cr, int x, int y, int height, double r, double g, double b) {
|
||||
if (!this->left) {
|
||||
this->left = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, shadowTopLeftSize, PATTERN_LENGTH);
|
||||
cairo_t* cr2 = cairo_create(this->left);
|
||||
|
||||
double a = NAN;
|
||||
|
||||
// draw shadow on top and left
|
||||
for (int i = 0; i < shadowTopLeftSize; i++) {
|
||||
a = shadowTopLeft[i] / 255.0;
|
||||
cairo_set_source_rgba(cr2, r, g, b, a);
|
||||
|
||||
cairo_rectangle(cr2, i, 0, 1, PATTERN_LENGTH);
|
||||
cairo_fill(cr2);
|
||||
}
|
||||
|
||||
cairo_destroy(cr2);
|
||||
}
|
||||
|
||||
setPattern(cr, this->left, x - shadowTopLeftSize, y + shadowTopLeftSize);
|
||||
cairo_rectangle(cr, x - shadowTopLeftSize, y + shadowTopLeftSize, shadowTopLeftSize,
|
||||
height - 2 * shadowTopLeftSize);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
void Shadow::drawShadowRight(cairo_t* cr, int x, int y, int height, double r, double g, double b) {
|
||||
if (!this->right) {
|
||||
this->right = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, shadowBottomRightSize, PATTERN_LENGTH);
|
||||
cairo_t* cr2 = cairo_create(this->right);
|
||||
|
||||
double a = NAN;
|
||||
|
||||
// draw shadow on top and left
|
||||
for (int i = 0; i < shadowBottomRightSize; i++) {
|
||||
a = shadowBottomRight[i] / 255.0;
|
||||
cairo_set_source_rgba(cr2, r, g, b, a);
|
||||
|
||||
cairo_rectangle(cr2, i, 0, 1, PATTERN_LENGTH);
|
||||
cairo_fill(cr2);
|
||||
}
|
||||
|
||||
cairo_destroy(cr2);
|
||||
}
|
||||
|
||||
setPattern(cr, this->right, x, y + shadowBottomRightSize);
|
||||
cairo_rectangle(cr, x, y + shadowBottomRightSize, shadowBottomRightSize, height - 2 * shadowBottomRightSize);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
void Shadow::drawShadowBottom(cairo_t* cr, int x, int y, int width, double r, double g, double b) {
|
||||
if (!this->bottom) {
|
||||
this->bottom = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PATTERN_LENGTH, shadowBottomRightSize);
|
||||
cairo_t* cr2 = cairo_create(this->bottom);
|
||||
|
||||
|
||||
// draw shadow on top and left
|
||||
for (int i = 0; i < shadowBottomRightSize; i++) {
|
||||
auto a = shadowBottomRight[i] / 255.0;
|
||||
cairo_set_source_rgba(cr2, r, g, b, a);
|
||||
|
||||
cairo_rectangle(cr2, 0, i, PATTERN_LENGTH, 1);
|
||||
cairo_fill(cr2);
|
||||
}
|
||||
|
||||
cairo_destroy(cr2);
|
||||
}
|
||||
|
||||
setPattern(cr, this->bottom, x + shadowBottomRightSize, y);
|
||||
cairo_rectangle(cr, x + shadowBottomRightSize, y, width - 2 * shadowBottomRightSize, shadowBottomRightSize);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
void Shadow::drawShadowImpl(cairo_t* cr, int x, int y, int width, int height) {
|
||||
const int sBrSize = shadowBottomRightSize;
|
||||
const int sTlSize = shadowTopLeftSize;
|
||||
|
||||
drawShadowTop(cr, x, y, width - 3, 0, 0, 0);
|
||||
drawShadowLeft(cr, x, y, height - 3, 0, 0, 0);
|
||||
drawShadowRight(cr, x + width, y - 4, height + 5, 0, 0, 0);
|
||||
drawShadowBottom(cr, x - 4, y + height, width + 5, 0, 0, 0);
|
||||
|
||||
paintEdge(cr, this->edgeTopLeft, x, y, sTlSize, sTlSize);
|
||||
paintEdge(cr, this->edgeTopRight, x + width + 1, y, sBrSize, sTlSize);
|
||||
paintEdge(cr, this->edgeBottomLeft, x, y + height + 1, sTlSize, sBrSize);
|
||||
paintEdge(cr, this->edgeBottomRight, x + width + 1, y + height + 1, sBrSize, sBrSize);
|
||||
}
|
||||
|
||||
void Shadow::drawShadow(cairo_t* cr, int x, int y, int width, int height) {
|
||||
Shadow::instance->drawShadowImpl(cr, x, y, width, height);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h> // for cairo_surface_t, cairo_t
|
||||
|
||||
class Shadow {
|
||||
private:
|
||||
Shadow();
|
||||
virtual ~Shadow();
|
||||
|
||||
static void drawShadowEdge(cairo_t* cr, int x, int y, int width, int height, const unsigned char* edge, double r,
|
||||
double g, double b);
|
||||
void drawShadowImpl(cairo_t* cr, int x, int y, int width, int height);
|
||||
|
||||
static void paintEdge(cairo_t* cr, cairo_surface_t* image, int x, int y, int width, int height);
|
||||
|
||||
void drawShadowTop(cairo_t* cr, int x, int y, int width, double r, double g, double b);
|
||||
void drawShadowLeft(cairo_t* cr, int x, int y, int height, double r, double g, double b);
|
||||
void drawShadowRight(cairo_t* cr, int x, int y, int height, double r, double g, double b);
|
||||
void drawShadowBottom(cairo_t* cr, int x, int y, int width, double r, double g, double b);
|
||||
|
||||
public:
|
||||
/**
|
||||
* This is the public interface of this class
|
||||
*/
|
||||
static void drawShadow(cairo_t* cr, int x, int y, int width, int height);
|
||||
static int getShadowBottomRightSize();
|
||||
static int getShadowTopLeftSize();
|
||||
|
||||
private:
|
||||
static Shadow* instance;
|
||||
|
||||
cairo_surface_t* edgeTopLeft = nullptr;
|
||||
cairo_surface_t* edgeTopRight = nullptr;
|
||||
cairo_surface_t* edgeBottomLeft = nullptr;
|
||||
cairo_surface_t* edgeBottomRight = nullptr;
|
||||
|
||||
cairo_surface_t* top = nullptr;
|
||||
cairo_surface_t* right = nullptr;
|
||||
cairo_surface_t* bottom = nullptr;
|
||||
cairo_surface_t* left = nullptr;
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
///////////////////////////////////////////////
|
||||
// Auto-generated from image source
|
||||
const int shadowTopLeftSize = 8;
|
||||
const int shadowBottomRightSize = 12;
|
||||
const unsigned char shadowTopLeft[] = {0, 1, 3, 6, 12, 22, 36, 57};
|
||||
const unsigned char shadowBottomRight[] = {171, 142, 111, 82, 56, 37, 22, 12, 6, 3, 1, 0};
|
||||
const unsigned char shadowEdge1[16][16] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
|
||||
{0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6},
|
||||
{0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11},
|
||||
{0, 0, 0, 1, 1, 2, 3, 5, 7, 10, 12, 15, 17, 19, 20, 21},
|
||||
{0, 0, 0, 1, 2, 3, 5, 8, 12, 16, 20, 24, 28, 31, 33, 34},
|
||||
{0, 0, 1, 1, 3, 5, 8, 13, 18, 25, 32, 39, 44, 49, 52, 54},
|
||||
{0, 0, 1, 2, 4, 7, 12, 18, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1, 3, 5, 9, 16, 25, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 1, 3, 7, 12, 20, 32, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 4, 8, 15, 25, 38, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 9, 17, 28, 44, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 10, 18, 31, 49, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 11, 20, 33, 52, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 6, 11, 21, 35, 54, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
|
||||
};
|
||||
const unsigned char shadowEdge2[16][24] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||
{12, 12, 12, 12, 12, 12, 12, 11, 11, 10, 9, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0},
|
||||
{22, 22, 22, 22, 22, 22, 21, 21, 20, 19, 17, 15, 12, 10, 7, 5, 3, 2, 1, 1, 0, 0, 0, 0},
|
||||
{36, 36, 36, 36, 36, 36, 35, 34, 33, 31, 28, 24, 20, 16, 12, 8, 5, 3, 2, 1, 0, 0, 0, 0},
|
||||
{57, 57, 57, 57, 57, 56, 56, 54, 52, 49, 44, 39, 32, 25, 18, 13, 8, 5, 3, 1, 1, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 46, 36, 27, 18, 12, 7, 4, 2, 1, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 63, 49, 36, 25, 16, 10, 5, 3, 1, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 80, 63, 46, 32, 20, 12, 7, 3, 1, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 96, 76, 56, 38, 25, 15, 8, 4, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 111, 87, 64, 44, 28, 17, 9, 5, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 122, 96, 71, 49, 31, 19, 10, 5, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 131, 102, 75, 52, 33, 20, 11, 5, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 136, 107, 79, 54, 35, 21, 11, 6, 3, 1, 0, 0},
|
||||
|
||||
};
|
||||
const unsigned char shadowEdge3[24][24] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 143, 112, 83, 57, 37, 22, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 142, 112, 82, 57, 36, 22, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 141, 111, 82, 56, 36, 21, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 140, 109, 81, 56, 36, 21, 12, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 136, 107, 79, 54, 35, 21, 11, 6, 3, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 131, 102, 75, 52, 33, 20, 11, 5, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 123, 96, 71, 49, 31, 19, 10, 5, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 111, 87, 64, 44, 28, 17, 9, 5, 2, 1, 0, 0},
|
||||
{173, 173, 173, 173, 172, 171, 169, 165, 158, 148, 134, 117, 97, 76, 56, 39, 25, 15, 8, 4, 2, 1, 0, 0},
|
||||
{143, 143, 143, 143, 142, 142, 140, 136, 131, 123, 111, 97, 80, 63, 46, 32, 20, 12, 7, 3, 1, 1, 0, 0},
|
||||
{112, 112, 112, 112, 112, 111, 109, 107, 103, 96, 87, 76, 63, 49, 36, 25, 16, 10, 5, 3, 1, 0, 0, 0},
|
||||
{83, 83, 83, 83, 83, 82, 81, 79, 76, 71, 65, 56, 47, 36, 27, 19, 12, 7, 4, 2, 1, 0, 0, 0},
|
||||
{57, 57, 57, 57, 57, 56, 56, 54, 52, 49, 44, 39, 32, 25, 18, 13, 8, 5, 3, 1, 1, 0, 0, 0},
|
||||
{37, 37, 37, 37, 37, 37, 36, 35, 34, 32, 29, 25, 21, 16, 12, 8, 5, 3, 2, 1, 0, 0, 0, 0},
|
||||
{22, 22, 22, 22, 22, 22, 21, 21, 20, 19, 17, 15, 12, 10, 7, 5, 3, 2, 1, 1, 0, 0, 0, 0},
|
||||
{12, 12, 12, 12, 12, 12, 12, 11, 11, 10, 9, 8, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0},
|
||||
{6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
|
||||
};
|
||||
const unsigned char shadowEdge4[24][16] = {
|
||||
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 12, 22, 36, 57, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 12, 21, 36, 56, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 6, 11, 21, 36, 55, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 6, 11, 21, 35, 54, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 11, 20, 33, 52, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 10, 19, 31, 49, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 9, 17, 28, 44, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 4, 8, 15, 25, 39, 56, 76, 97, 117, 134, 148, 158, 165},
|
||||
{0, 1, 1, 3, 7, 12, 20, 32, 46, 63, 80, 97, 111, 123, 131, 136},
|
||||
{0, 0, 1, 3, 5, 9, 16, 25, 36, 49, 63, 76, 87, 96, 102, 107},
|
||||
{0, 0, 1, 2, 4, 7, 12, 18, 27, 36, 47, 56, 64, 71, 76, 79},
|
||||
{0, 0, 1, 1, 3, 5, 8, 13, 18, 25, 32, 39, 44, 49, 52, 54},
|
||||
{0, 0, 0, 1, 2, 3, 5, 8, 12, 16, 21, 25, 29, 32, 34, 35},
|
||||
{0, 0, 0, 1, 1, 2, 3, 5, 7, 10, 12, 15, 17, 19, 20, 21},
|
||||
{0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11},
|
||||
{0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6},
|
||||
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
|
||||
};
|
||||
///////////////////////////////////////////////
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The definitions for the possibel toolbars
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* The name in the glade.xml file
|
||||
*/
|
||||
const char* guiName;
|
||||
|
||||
/**
|
||||
* The name in the .ini file
|
||||
*/
|
||||
const char* propName;
|
||||
|
||||
/**
|
||||
* if horizontal (true) or vertical (false)
|
||||
*/
|
||||
bool horizontal;
|
||||
} ToolbarEntryDefintion;
|
||||
|
||||
const static ToolbarEntryDefintion TOOLBAR_DEFINITIONS[] = {
|
||||
{"tbTop1", "toolbarTop1", true}, {"tbTop2", "toolbarTop2", true},
|
||||
{"tbLeft1", "toolbarLeft1", false}, {"tbLeft2", "toolbarLeft2", false},
|
||||
{"tbRight1", "toolbarRight1", false}, {"tbRight2", "toolbarRight2", false},
|
||||
{"tbBottom1", "toolbarBottom1", true}, {"tbBottom2", "toolbarBottom2", true},
|
||||
{"tbFloat1", "toolbarFloat1", true}, // define this index below as TBFloatFirst
|
||||
{"tbFloat2", "toolbarFloat2", true}, {"tbFloat3", "toolbarFloat3", true},
|
||||
{"tbFloat4", "toolbarFloat4", true} // define this index below as TBFloatLast
|
||||
};
|
||||
|
||||
|
||||
const static int TOOLBAR_DEFINITIONS_LEN = G_N_ELEMENTS(TOOLBAR_DEFINITIONS);
|
||||
|
||||
#define TBFloatFirst 8
|
||||
#define TBFloatLast 11
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "ToolitemDragDrop.h"
|
||||
|
||||
#include <glib-object.h> // for g_object_...
|
||||
#include <glib.h> // for g_malloc
|
||||
|
||||
#include "gui/toolbarMenubar/AbstractToolItem.h" // for AbstractT...
|
||||
#include "gui/toolbarMenubar/icon/ToolbarSeparatorImage.h" // for getNewToo...
|
||||
|
||||
struct NamedColor;
|
||||
|
||||
const char* ATTACH_DRAG_DROP_DATA = "XOJ_DRAG_DROP_DATA";
|
||||
|
||||
void ToolitemDragDrop::attachMetadata(GtkWidget* w, int id, AbstractToolItem* ait) {
|
||||
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
|
||||
d->identify = ToolItemDragDropData_Identify;
|
||||
d->id = id;
|
||||
d->item = ait;
|
||||
d->type = TOOL_ITEM_ITEM;
|
||||
d->paletteColorIndex = 0;
|
||||
|
||||
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
|
||||
}
|
||||
|
||||
auto ToolitemDragDrop::ToolItemDragDropData_new(AbstractToolItem* item) -> std::unique_ptr<ToolItemDragDropData> {
|
||||
auto d = std::make_unique<ToolItemDragDropData>();
|
||||
d->identify = ToolItemDragDropData_Identify;
|
||||
d->id = -1;
|
||||
d->item = item;
|
||||
d->type = TOOL_ITEM_ITEM;
|
||||
d->paletteColorIndex = 0;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void ToolitemDragDrop::attachMetadata(GtkWidget* w, int id, ToolItemType type) {
|
||||
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
|
||||
d->identify = ToolItemDragDropData_Identify;
|
||||
d->id = id;
|
||||
d->item = nullptr;
|
||||
d->type = type;
|
||||
d->paletteColorIndex = 0;
|
||||
|
||||
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
|
||||
}
|
||||
|
||||
void ToolitemDragDrop::attachMetadataColor(GtkWidget* w, int id, size_t paletteColorIndex, AbstractToolItem* item) {
|
||||
ToolItemDragDropData* d = g_new(ToolItemDragDropData, 1);
|
||||
d->identify = ToolItemDragDropData_Identify;
|
||||
d->id = id;
|
||||
d->item = item;
|
||||
d->type = TOOL_ITEM_COLOR;
|
||||
d->paletteColorIndex = paletteColorIndex;
|
||||
|
||||
g_object_set_data_full(G_OBJECT(w), ATTACH_DRAG_DROP_DATA, d, static_cast<GDestroyNotify>(g_free));
|
||||
}
|
||||
|
||||
auto ToolitemDragDrop::getIcon(ToolItemDragDropData* data) -> GtkWidget* {
|
||||
if (data->type == TOOL_ITEM_ITEM || data->type == TOOL_ITEM_COLOR) {
|
||||
return data->item->getNewToolIcon();
|
||||
}
|
||||
if (data->type == TOOL_ITEM_SEPARATOR) {
|
||||
return ToolbarSeparatorImage::newImage(SeparatorType::SEPARATOR);
|
||||
}
|
||||
if (data->type == TOOL_ITEM_SPACER) {
|
||||
return ToolbarSeparatorImage::newImage(SeparatorType::SPACER);
|
||||
}
|
||||
|
||||
g_warning("ToolitemDragDrop::getIcon unhandled type: %i\n", data->type);
|
||||
return gtk_image_new();
|
||||
}
|
||||
|
||||
auto ToolitemDragDrop::checkToolItemDragDropData(ToolItemDragDropData const* d) -> bool {
|
||||
return d->identify == ToolItemDragDropData_Identify;
|
||||
}
|
||||
|
||||
auto ToolitemDragDrop::metadataGetMetadata(GtkWidget* w) -> ToolItemDragDropData* {
|
||||
void* ptr = g_object_get_data(G_OBJECT(w), ATTACH_DRAG_DROP_DATA);
|
||||
if (ptr == nullptr) {
|
||||
g_warning("ToolitemDragDrop::metadataGetMetadata Could not get Metadata %s from %s\n", ATTACH_DRAG_DROP_DATA,
|
||||
g_type_name(G_TYPE_FROM_INSTANCE(w)));
|
||||
return nullptr;
|
||||
}
|
||||
if (!checkToolItemDragDropData(static_cast<ToolItemDragDropData const*>(ptr))) {
|
||||
g_warning("ToolitemDragDrop::metadataGetMetadata data is not an instance of ToolItemDragDropData!");
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<ToolItemDragDropData*>(ptr);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Helper for Toolbar Drag & Drop implementation
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h> // for GdkPixbuf
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
class AbstractToolItem;
|
||||
struct NamedColor;
|
||||
|
||||
enum ToolItemType { TOOL_ITEM_SEPARATOR = 0, TOOL_ITEM_SPACER, TOOL_ITEM_ITEM, TOOL_ITEM_COLOR };
|
||||
|
||||
#define ToolItemDragDropData_Identify 0xFA090201
|
||||
|
||||
struct ToolItemDragDropData {
|
||||
unsigned int identify;
|
||||
ToolItemType type;
|
||||
int id;
|
||||
AbstractToolItem* item;
|
||||
size_t paletteColorIndex;
|
||||
};
|
||||
|
||||
class ToolitemDragDrop {
|
||||
private:
|
||||
ToolitemDragDrop();
|
||||
virtual ~ToolitemDragDrop();
|
||||
|
||||
public:
|
||||
static void attachMetadata(GtkWidget* w, int id, AbstractToolItem* ait);
|
||||
static void attachMetadata(GtkWidget* w, int id, ToolItemType type);
|
||||
static void attachMetadataColor(GtkWidget* w, int id, size_t paletteColorIndex, AbstractToolItem* item);
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ToolItemDragDropData> ToolItemDragDropData_new(AbstractToolItem* item);
|
||||
static bool checkToolItemDragDropData(ToolItemDragDropData const* d);
|
||||
|
||||
static ToolItemDragDropData* metadataGetMetadata(GtkWidget* w);
|
||||
|
||||
/**
|
||||
* Returns: (transfer floating)
|
||||
*/
|
||||
static GtkWidget* getIcon(ToolItemDragDropData* data);
|
||||
|
||||
public:
|
||||
static void removeFromToolbarForMove(GtkWidget* widget);
|
||||
};
|
||||
@@ -1,861 +0,0 @@
|
||||
#include "XournalView.h"
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <iterator> // for begin
|
||||
#include <memory> // for unique_ptr, make_unique
|
||||
#include <optional> // for optional
|
||||
#include <unordered_set>
|
||||
|
||||
#include <gdk/gdk.h> // for GdkEventKey, GDK_SHIF...
|
||||
#include <gdk/gdkkeysyms.h> // for GDK_KEY_Page_Down
|
||||
#include <glib-object.h> // for g_object_ref_sink
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/PdfCache.h" // for PdfCache
|
||||
#include "control/ScrollHandler.h" // for ScrollHandler
|
||||
#include "control/ToolHandler.h" // for ToolHandler
|
||||
#include "control/actions/ActionDatabase.h" // for ActionDatabase
|
||||
#include "control/jobs/XournalScheduler.h" // for XournalScheduler
|
||||
#include "control/settings/MetadataManager.h" // for MetadataManager
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "control/tools/CursorSelectionType.h" // for CURSOR_SELECTION_NONE
|
||||
#include "control/tools/EditSelection.h" // for EditSelection
|
||||
#include "control/zoom/ZoomControl.h" // for ZoomControl
|
||||
#include "gui/MainWindow.h" // for MainWindow
|
||||
#include "gui/PdfFloatingToolbox.h" // for PdfFloatingToolbox
|
||||
#include "gui/inputdevices/GeometryToolInputHandler.h" // for GeometryToolInputHandler
|
||||
#include "gui/inputdevices/HandRecognition.h" // for HandRecognition
|
||||
#include "gui/inputdevices/InputContext.h" // for InputContext
|
||||
#include "gui/toolbarMenubar/ColorToolItem.h" // for ColorToolItem
|
||||
#include "gui/toolbarMenubar/ToolMenuHandler.h" // for ToolMenuHandler
|
||||
#include "gui/widgets/XournalWidget.h" // for gtk_xournal_get_layout
|
||||
#include "model/Document.h" // for Document
|
||||
#include "model/Element.h" // for Element, ELEMENT_STROKE
|
||||
#include "model/PageRef.h" // for PageRef
|
||||
#include "model/Stroke.h" // for Stroke, StrokeTool::E...
|
||||
#include "model/XojPage.h" // for XojPage
|
||||
#include "undo/DeleteUndoAction.h" // for DeleteUndoAction
|
||||
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
|
||||
#include "util/Assert.h" // for xoj_assert
|
||||
#include "util/Point.h" // for Point
|
||||
#include "util/Rectangle.h" // for Rectangle
|
||||
#include "util/Util.h" // for npos
|
||||
#include "util/glib_casts.h" // for wrap_v
|
||||
#include "util/gtk4_helper.h" // for gtk_scrolled_window_set_child
|
||||
#include "util/safe_casts.h" // for round_cast
|
||||
|
||||
#include "Layout.h" // for Layout
|
||||
#include "PageView.h" // for XojPageView
|
||||
#include "RepaintHandler.h" // for RepaintHandler
|
||||
#include "XournalppCursor.h" // for XournalppCursor
|
||||
|
||||
using xoj::util::Rectangle;
|
||||
|
||||
constexpr int REGULAR_MOVE_AMOUNT = 3;
|
||||
constexpr int SMALL_MOVE_AMOUNT = 1;
|
||||
constexpr int LARGE_MOVE_AMOUNT = 10;
|
||||
|
||||
std::pair<size_t, size_t> XournalView::preloadPageBounds(size_t page, size_t maxPage) {
|
||||
const size_t preloadBefore = this->control->getSettings()->getPreloadPagesBefore();
|
||||
const size_t preloadAfter = this->control->getSettings()->getPreloadPagesAfter();
|
||||
const size_t lower = page > preloadBefore ? page - preloadBefore : 0;
|
||||
const size_t upper = std::min(maxPage, page + preloadAfter + 1);
|
||||
return {lower, upper};
|
||||
}
|
||||
|
||||
XournalView::XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling):
|
||||
scrollHandling(scrollHandling), control(control) {
|
||||
Document* doc = control->getDocument();
|
||||
doc->lock_shared();
|
||||
if (doc->getPdfPageCount() != 0) {
|
||||
this->cache = std::make_unique<PdfCache>(doc->getPdfDocument(), control->getSettings());
|
||||
}
|
||||
doc->unlock_shared();
|
||||
|
||||
registerListener(control);
|
||||
|
||||
InputContext* inputContext = new InputContext(this, scrollHandling);
|
||||
this->widget = gtk_xournal_new(this, inputContext);
|
||||
g_object_ref_sink(this->widget); // take ownership without increasing the ref count
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(parent), this->widget);
|
||||
gtk_widget_show(this->widget);
|
||||
|
||||
|
||||
g_signal_connect(getWidget(), "realize", G_CALLBACK(+[](GtkWidget* widget, gpointer) {
|
||||
// Disable event compression
|
||||
if (gtk_widget_get_realized(widget)) {
|
||||
gdk_window_set_event_compression(gtk_widget_get_window(widget), false);
|
||||
} else {
|
||||
g_warning("could not disable event compression");
|
||||
}
|
||||
}),
|
||||
nullptr);
|
||||
|
||||
this->repaintHandler = std::make_unique<RepaintHandler>(this);
|
||||
|
||||
control->getZoomControl()->addZoomListener(this);
|
||||
|
||||
gtk_widget_set_can_default(this->widget, true);
|
||||
gtk_widget_grab_default(this->widget);
|
||||
|
||||
gtk_widget_grab_focus(this->widget);
|
||||
|
||||
this->cleanupTimeout = g_timeout_add_seconds(5, xoj::util::wrap_v<clearMemoryTimer>, this);
|
||||
}
|
||||
|
||||
XournalView::~XournalView() {
|
||||
g_source_remove(this->cleanupTimeout);
|
||||
|
||||
gtk_widget_destroy(this->widget);
|
||||
this->widget = nullptr;
|
||||
}
|
||||
|
||||
|
||||
auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean {
|
||||
widget->cleanupBufferCache();
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
auto XournalView::cleanupBufferCache() -> void {
|
||||
const auto& [pagesLower, pagesUpper] = this->preloadPageBounds(this->currentPage, this->viewPages.size());
|
||||
xoj_assert(pagesLower <= pagesUpper);
|
||||
|
||||
std::unordered_set<size_t> retainedPdfPages;
|
||||
|
||||
for (size_t i = 0; i < this->viewPages.size(); i++) {
|
||||
auto&& page = this->viewPages[i];
|
||||
const bool isPreload = pagesLower <= i && i < pagesUpper;
|
||||
const bool shouldRetain = isPreload || page->isVisible();
|
||||
|
||||
if (shouldRetain) {
|
||||
const size_t pdfPageNo = page->getPage()->getPdfPageNr();
|
||||
if (pdfPageNo != npos) {
|
||||
retainedPdfPages.insert(pdfPageNo);
|
||||
}
|
||||
continue;
|
||||
} else if (page->hasBuffer()) {
|
||||
page->deleteViewBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->cache) {
|
||||
this->cache->evictAllExcept(retainedPdfPages);
|
||||
}
|
||||
}
|
||||
|
||||
auto XournalView::getCurrentPage() const -> size_t { return currentPage; }
|
||||
|
||||
const int scrollKeySize = 30;
|
||||
|
||||
auto XournalView::onKeyPressEvent(const KeyEvent& event) -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p != npos && p < this->viewPages.size()) {
|
||||
auto& v = this->viewPages[p];
|
||||
if (v->onKeyPressEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto keyval = event.keyval;
|
||||
auto state = event.state;
|
||||
if (auto* tool = getControl()->getWindow()->getPdfToolbox(); tool->hasSelection()) {
|
||||
if ((keyval == GDK_KEY_c && state == GDK_CONTROL_MASK) || keyval == GDK_KEY_Copy) {
|
||||
// Shortcut to get selected PDF text.
|
||||
tool->copyTextToClipboard();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* selection = getSelection(); selection) {
|
||||
if (keyval == GDK_KEY_Escape) {
|
||||
clearSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
int d = REGULAR_MOVE_AMOUNT;
|
||||
if (state == GDK_MOD1_MASK) {
|
||||
d = SMALL_MOVE_AMOUNT;
|
||||
} else if (state == GDK_SHIFT_MASK) {
|
||||
d = LARGE_MOVE_AMOUNT;
|
||||
}
|
||||
|
||||
int xdir = 0;
|
||||
int ydir = 0;
|
||||
if (keyval == GDK_KEY_Left) {
|
||||
xdir = -1;
|
||||
} else if (keyval == GDK_KEY_Up) {
|
||||
ydir = -1;
|
||||
} else if (keyval == GDK_KEY_Right) {
|
||||
xdir = 1;
|
||||
} else if (keyval == GDK_KEY_Down) {
|
||||
ydir = 1;
|
||||
}
|
||||
if (xdir != 0 || ydir != 0) {
|
||||
selection->moveSelection(d * xdir, d * ydir, /*addMoveUndo=*/true);
|
||||
selection->ensureWithinVisibleArea();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* geom = GTK_XOURNAL(widget)->input->getGeometryToolInputHandler(); geom && geom->keyPressed(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Layout* layout = this->getLayout();
|
||||
|
||||
if (!state) {
|
||||
if (keyval == GDK_KEY_Page_Down || keyval == GDK_KEY_KP_Page_Down) {
|
||||
control->getScrollHandler()->goToNextPage();
|
||||
return true;
|
||||
}
|
||||
if (keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_KP_Page_Up) {
|
||||
control->getScrollHandler()->goToPreviousPage();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_space) {
|
||||
GtkAllocation alloc = {0};
|
||||
gtk_widget_get_allocation(gtk_widget_get_parent(this->widget), &alloc);
|
||||
int windowHeight = alloc.height - scrollKeySize;
|
||||
|
||||
if (!state) {
|
||||
layout->scrollRelative(0, windowHeight);
|
||||
return true;
|
||||
}
|
||||
if (state == GDK_SHIFT_MASK) {
|
||||
layout->scrollRelative(0, -windowHeight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric keypad always navigates by page
|
||||
if (keyval == GDK_KEY_KP_Up) {
|
||||
this->pageRelativeXY(0, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_KP_Down) {
|
||||
this->pageRelativeXY(0, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_KP_Left) {
|
||||
this->pageRelativeXY(-1, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_KP_Right) {
|
||||
this->pageRelativeXY(1, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (keyval == GDK_KEY_Up || keyval == GDK_KEY_k || keyval == GDK_KEY_K) {
|
||||
if (control->getSettings()->isPresentationMode()) {
|
||||
control->getScrollHandler()->goToPreviousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state == GDK_SHIFT_MASK) {
|
||||
this->pageRelativeXY(0, -1);
|
||||
return true;
|
||||
}
|
||||
if (!state) {
|
||||
layout->scrollRelative(0, -scrollKeySize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_Down || keyval == GDK_KEY_j || keyval == GDK_KEY_J) {
|
||||
if (control->getSettings()->isPresentationMode()) {
|
||||
control->getScrollHandler()->goToNextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state == GDK_SHIFT_MASK) {
|
||||
this->pageRelativeXY(0, 1);
|
||||
return true;
|
||||
}
|
||||
if (!state) {
|
||||
layout->scrollRelative(0, scrollKeySize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_Left || keyval == GDK_KEY_h) {
|
||||
if (state == GDK_SHIFT_MASK) {
|
||||
this->pageRelativeXY(-1, 0);
|
||||
return true;
|
||||
}
|
||||
if (!state) {
|
||||
if (control->getSettings()->isPresentationMode()) {
|
||||
control->getScrollHandler()->goToPreviousPage();
|
||||
} else {
|
||||
layout->scrollRelative(-scrollKeySize, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_Right || keyval == GDK_KEY_l) {
|
||||
if (state == GDK_SHIFT_MASK) {
|
||||
this->pageRelativeXY(1, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
if (control->getSettings()->isPresentationMode()) {
|
||||
control->getScrollHandler()->goToNextPage();
|
||||
} else {
|
||||
layout->scrollRelative(scrollKeySize, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_End || keyval == GDK_KEY_KP_End) {
|
||||
control->getScrollHandler()->goToLastPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyval == GDK_KEY_Home || keyval == GDK_KEY_KP_Home) {
|
||||
control->getScrollHandler()->goToFirstPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Switch color on number key
|
||||
auto& colors = control->getWindow()->getToolMenuHandler()->getColorToolItems();
|
||||
if (!state && (keyval >= GDK_KEY_0) && (keyval < GDK_KEY_0 + std::min((std::size_t)10, colors.size()))) {
|
||||
std::size_t index = std::min(colors.size() - 1, (std::size_t)(9 + (keyval - GDK_KEY_0)) % 10);
|
||||
const auto& colorToolItem = colors.at(index);
|
||||
if (auto* db = control->getActionDatabase(); db->isActionEnabled(Action::TOOL_COLOR)) {
|
||||
control->getActionDatabase()->fireChangeActionState(Action::TOOL_COLOR, colorToolItem->getColor());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto XournalView::getRepaintHandler() const -> RepaintHandler* { return this->repaintHandler.get(); }
|
||||
|
||||
auto XournalView::onKeyReleaseEvent(const KeyEvent& event) -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p != npos && p < this->viewPages.size()) {
|
||||
auto& v = this->viewPages[p];
|
||||
if (v->onKeyReleaseEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void XournalView::onSettingsChanged() {
|
||||
if (this->cache) {
|
||||
this->cache->updateSettings(control->getSettings());
|
||||
}
|
||||
}
|
||||
|
||||
// send the focus back to the appropriate widget
|
||||
void XournalView::requestFocus() { gtk_widget_grab_focus(this->widget); }
|
||||
|
||||
auto XournalView::searchTextOnPage(const std::string& text, size_t pageNumber, size_t index, size_t* occurrences,
|
||||
XojPdfRectangle* matchRect) -> bool {
|
||||
if (pageNumber == npos || pageNumber >= this->viewPages.size()) {
|
||||
return false;
|
||||
}
|
||||
auto& v = this->viewPages[pageNumber];
|
||||
|
||||
return v->searchTextOnPage(text, index, occurrences, matchRect);
|
||||
}
|
||||
|
||||
void XournalView::forceUpdatePagenumbers() {
|
||||
size_t p = this->currentPage;
|
||||
this->currentPage = npos;
|
||||
|
||||
control->firePageSelected(p);
|
||||
}
|
||||
|
||||
auto XournalView::getViewFor(size_t pageNr) const -> XojPageView* {
|
||||
if (pageNr == npos || pageNr >= this->viewPages.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return this->viewPages[pageNr].get();
|
||||
}
|
||||
|
||||
void XournalView::pageSelected(size_t page) {
|
||||
if (this->currentPage == page && this->lastSelectedPage == page) {
|
||||
return;
|
||||
}
|
||||
|
||||
control->getWindow()->getPdfToolbox()->userCancelSelection();
|
||||
|
||||
if (this->lastSelectedPage != npos && this->lastSelectedPage < this->viewPages.size()) {
|
||||
this->viewPages[this->lastSelectedPage]->setSelected(false);
|
||||
}
|
||||
|
||||
endTextAllPages();
|
||||
|
||||
this->currentPage = page;
|
||||
|
||||
size_t pdfPage = npos;
|
||||
|
||||
if (page != npos && page < viewPages.size()) {
|
||||
auto& vp = viewPages[page];
|
||||
vp->setSelected(true);
|
||||
lastSelectedPage = page;
|
||||
pdfPage = vp->getPage()->getPdfPageNr();
|
||||
}
|
||||
|
||||
control->updatePageNumbers(currentPage, pdfPage);
|
||||
|
||||
control->updateBackgroundSizeButton();
|
||||
control->updatePageActions();
|
||||
|
||||
if (control->getSettings()->isEagerPageCleanup()) {
|
||||
this->cleanupBufferCache();
|
||||
}
|
||||
|
||||
// Load surrounding pages if they are not
|
||||
const auto& [pagesLower, pagesUpper] = preloadPageBounds(page, this->viewPages.size());
|
||||
xoj_assert(pagesLower <= pagesUpper);
|
||||
for (size_t i = pagesLower; i < pagesUpper; i++) {
|
||||
if (!this->viewPages[i]->hasBuffer()) {
|
||||
this->viewPages[i]->rerenderPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto XournalView::getControl() const -> Control* { return control; }
|
||||
|
||||
void XournalView::scrollTo(size_t pageNo, XojPdfRectangle rect) {
|
||||
double zoom = getZoom();
|
||||
if (pageNo >= this->viewPages.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& v = this->viewPages[pageNo];
|
||||
|
||||
// Make sure it is visible
|
||||
Layout* layout = this->getLayout();
|
||||
|
||||
int x = v->getX() + round_cast<int>(rect.x1 * zoom);
|
||||
int y = v->getY() + round_cast<int>(rect.y1 * zoom);
|
||||
int width;
|
||||
int height;
|
||||
if (rect.x2 == -1 || rect.y2 == -1) {
|
||||
width = v->getDisplayWidth();
|
||||
height = v->getDisplayHeight();
|
||||
} else {
|
||||
width = round_cast<int>((rect.x2 - rect.x1) * zoom);
|
||||
height = round_cast<int>((rect.y2 - rect.y1) * zoom);
|
||||
}
|
||||
|
||||
layout->ensureRectIsVisible(x, y, width, height);
|
||||
|
||||
// Select the page
|
||||
control->firePageSelected(pageNo);
|
||||
}
|
||||
|
||||
|
||||
void XournalView::pageRelativeXY(int offCol, int offRow) {
|
||||
size_t currPage = getCurrentPage();
|
||||
|
||||
XojPageView* view = getViewFor(currPage);
|
||||
int row = view->getMappedRow();
|
||||
int col = view->getMappedCol();
|
||||
|
||||
Layout* layout = this->getLayout();
|
||||
auto optionalPageIndex = layout->getPageIndexAtGridMap(as_unsigned(row + offRow), as_unsigned(col + offCol));
|
||||
if (optionalPageIndex) {
|
||||
this->scrollTo(*optionalPageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void XournalView::endTextAllPages(XojPageView* except) const {
|
||||
for (auto& v: this->viewPages) {
|
||||
if (except != v.get()) {
|
||||
v->endText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::endSplineAllPages() const {
|
||||
for (auto& v: this->viewPages) {
|
||||
v->endSpline();
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::layerChanged(size_t page) {
|
||||
if (page != npos && page < this->viewPages.size()) {
|
||||
this->viewPages[page]->rerenderPage();
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::getPasteTarget(double& x, double& y) const {
|
||||
size_t pageNo = getCurrentPage();
|
||||
if (pageNo == npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle<double>* rect = getVisibleRect(pageNo);
|
||||
|
||||
if (rect) {
|
||||
x = rect->x + rect->width / 2;
|
||||
y = rect->y + rect->height / 2;
|
||||
delete rect;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rectangle which is visible on screen, in document cooordinates
|
||||
*
|
||||
* Or nullptr if the page is not visible
|
||||
*/
|
||||
auto XournalView::getVisibleRect(size_t page) const -> Rectangle<double>* {
|
||||
if (page == npos || page >= this->viewPages.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& p = this->viewPages[page];
|
||||
|
||||
return getVisibleRect(p.get());
|
||||
}
|
||||
|
||||
auto XournalView::getVisibleRect(const XojPageView* redrawable) const -> Rectangle<double>* {
|
||||
return gtk_xournal_get_visible_area(this->widget, redrawable);
|
||||
}
|
||||
|
||||
void XournalView::recreatePdfCache() {
|
||||
this->cache.reset();
|
||||
|
||||
Document* doc = control->getDocument();
|
||||
doc->lock_shared();
|
||||
if (doc->getPdfPageCount() != 0) {
|
||||
this->cache = std::make_unique<PdfCache>(doc->getPdfDocument(), control->getSettings());
|
||||
}
|
||||
doc->unlock_shared();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Helper class for Touch specific fixes
|
||||
*/
|
||||
auto XournalView::getHandRecognition() const -> HandRecognition* {
|
||||
return GTK_XOURNAL(widget)->input->getHandRecognition();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Scrollbars
|
||||
*/
|
||||
auto XournalView::getScrollHandling() const -> ScrollHandling* { return scrollHandling; }
|
||||
|
||||
auto XournalView::getWidget() const -> GtkWidget* { return widget; }
|
||||
|
||||
void XournalView::ensureRectIsVisible(int x, int y, int width, int height) {
|
||||
Layout* layout = this->getLayout();
|
||||
layout->ensureRectIsVisible(x, y, width, height);
|
||||
}
|
||||
|
||||
void XournalView::zoomChanged() {
|
||||
|
||||
size_t currentPage = this->getCurrentPage();
|
||||
XojPageView* view = getViewFor(currentPage);
|
||||
|
||||
ZoomControl* zoom = control->getZoomControl();
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
layoutPages();
|
||||
|
||||
if (zoom->isZoomPresentationMode()) {
|
||||
scrollTo(currentPage);
|
||||
} else if (zoom->isZoomSequenceActive()) {
|
||||
auto pos = zoom->getScrollPositionAfterZoom();
|
||||
Layout* layout = this->getLayout();
|
||||
layout->scrollAbs(pos.x, pos.y);
|
||||
}
|
||||
|
||||
// Updates the Eraser's cursor icon in order to make it as big as the erasing area
|
||||
control->getCursor()->updateCursor();
|
||||
|
||||
// if we changed the zoom of the page, we should hide the pdf floating toolbox
|
||||
// and if user clicked the selection again, the floating toolbox shows again
|
||||
control->getWindow()->getPdfToolbox()->hide();
|
||||
|
||||
this->control->getScheduler()->blockRerenderZoom();
|
||||
}
|
||||
|
||||
void XournalView::pageSizeChanged(size_t page) {
|
||||
layoutPages();
|
||||
if (page != npos && page < this->viewPages.size()) {
|
||||
this->viewPages[page]->rerenderPage(/* sizeChanged */ true);
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::pageChanged(size_t page) {
|
||||
if (page != npos && page < this->viewPages.size()) {
|
||||
this->viewPages[page]->rerenderPage();
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::pageDeleted(size_t page) {
|
||||
const size_t currentPageNo = control->getCurrentPageNo();
|
||||
|
||||
viewPages.erase(begin(viewPages) + static_cast<long>(page));
|
||||
|
||||
layoutPages();
|
||||
|
||||
if (currentPageNo > page) {
|
||||
control->getScrollHandler()->scrollToPage(currentPageNo - 1);
|
||||
} else {
|
||||
control->getScrollHandler()->scrollToPage(currentPageNo);
|
||||
}
|
||||
}
|
||||
|
||||
auto XournalView::getTextEditor() const -> TextEditor* {
|
||||
for (auto&& page: viewPages) {
|
||||
if (page->getTextEditor()) {
|
||||
return page->getTextEditor();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto XournalView::getCache() const -> PdfCache* { return this->cache.get(); }
|
||||
|
||||
void XournalView::pageInserted(size_t page) {
|
||||
Document* doc = control->getDocument();
|
||||
doc->lock_shared();
|
||||
auto pageView = std::make_unique<XojPageView>(this, doc->getPage(page));
|
||||
doc->unlock_shared();
|
||||
|
||||
viewPages.insert(begin(viewPages) + as_signed(page), std::move(pageView));
|
||||
|
||||
layoutPages();
|
||||
// check which pages are visible and select the most visible page
|
||||
Layout* layout = this->getLayout();
|
||||
layout->updateVisibility();
|
||||
}
|
||||
|
||||
auto XournalView::getZoom() const -> double { return control->getZoomControl()->getZoom(); }
|
||||
|
||||
auto XournalView::getDpiScaleFactor() const -> int { return gtk_widget_get_scale_factor(widget); }
|
||||
|
||||
void XournalView::clearSelection() {
|
||||
EditSelection* sel = GTK_XOURNAL(widget)->selection;
|
||||
GTK_XOURNAL(widget)->selection = nullptr;
|
||||
delete sel;
|
||||
|
||||
control->setClipboardHandlerSelection(getSelection());
|
||||
|
||||
getCursor()->setMouseSelectionType(CURSOR_SELECTION_NONE);
|
||||
control->getToolHandler()->setSelectionEditTools(false, false, false, false);
|
||||
}
|
||||
|
||||
void XournalView::deleteSelection(EditSelection* sel) {
|
||||
if (sel == nullptr) {
|
||||
sel = getSelection();
|
||||
}
|
||||
|
||||
if (sel) {
|
||||
auto undo = std::make_unique<DeleteUndoAction>(sel->getSourcePage(), false);
|
||||
sel->fillUndoItem(undo.get());
|
||||
control->getUndoRedoHandler()->addUndoAction(std::move(undo));
|
||||
|
||||
clearSelection();
|
||||
|
||||
repaintSelection(true);
|
||||
}
|
||||
}
|
||||
|
||||
void XournalView::setSelection(EditSelection* selection) {
|
||||
clearSelection();
|
||||
GTK_XOURNAL(this->widget)->selection = selection;
|
||||
|
||||
control->setClipboardHandlerSelection(getSelection());
|
||||
|
||||
bool canChangeSize = false;
|
||||
bool canChangeColor = false;
|
||||
bool canChangeFill = false;
|
||||
bool canChangeLineStyle = false;
|
||||
|
||||
for (const Element* e: selection->getElementsView()) {
|
||||
switch (e->getType()) {
|
||||
case ELEMENT_TEXT:
|
||||
canChangeColor = true;
|
||||
continue;
|
||||
case ELEMENT_STROKE: {
|
||||
canChangeSize = true;
|
||||
|
||||
const auto* s = dynamic_cast<const Stroke*>(e);
|
||||
if (s->getToolType() == StrokeTool::PEN) {
|
||||
// can change everything, leave loop with break
|
||||
canChangeColor = true;
|
||||
canChangeFill = true;
|
||||
canChangeLineStyle = true;
|
||||
break;
|
||||
}
|
||||
if (s->getToolType() == StrokeTool::HIGHLIGHTER) {
|
||||
canChangeColor = true;
|
||||
canChangeFill = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// leave loop
|
||||
break;
|
||||
}
|
||||
|
||||
control->getToolHandler()->setSelectionEditTools(canChangeColor, canChangeSize, canChangeFill, canChangeLineStyle);
|
||||
|
||||
repaintSelection();
|
||||
}
|
||||
|
||||
void XournalView::repaintSelection(bool evenWithoutSelection) {
|
||||
if (evenWithoutSelection) {
|
||||
gtk_widget_queue_draw(this->widget);
|
||||
return;
|
||||
}
|
||||
|
||||
EditSelection* selection = getSelection();
|
||||
if (selection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// repaint always the whole widget
|
||||
gtk_widget_queue_draw(this->widget);
|
||||
}
|
||||
|
||||
void XournalView::layoutPages() {
|
||||
Layout* layout = this->getLayout();
|
||||
layout->recalculate();
|
||||
|
||||
// Todo (fabian): the following lines are conceptually wrong, the Layout::layoutPages function is meant to be
|
||||
// called by an expose event, but removing it, will break "add page".
|
||||
auto rectangle = layout->getVisibleRect();
|
||||
layout->layoutPages(std::max<int>(layout->getMinimalWidth(), round_cast<int>(rectangle.width)),
|
||||
std::max<int>(layout->getMinimalHeight(), round_cast<int>(rectangle.height)));
|
||||
}
|
||||
|
||||
auto XournalView::getDisplayHeight() const -> int {
|
||||
GtkAllocation allocation = {0};
|
||||
gtk_widget_get_allocation(this->widget, &allocation);
|
||||
return allocation.height;
|
||||
}
|
||||
|
||||
auto XournalView::getDisplayWidth() const -> int {
|
||||
GtkAllocation allocation = {0};
|
||||
gtk_widget_get_allocation(this->widget, &allocation);
|
||||
return allocation.width;
|
||||
}
|
||||
|
||||
auto XournalView::isPageVisible(size_t page, int* visibleHeight) const -> bool {
|
||||
Rectangle<double>* rect = getVisibleRect(page);
|
||||
if (rect) {
|
||||
if (visibleHeight) {
|
||||
*visibleHeight = round_cast<int>(rect->height);
|
||||
}
|
||||
|
||||
delete rect;
|
||||
return true;
|
||||
}
|
||||
if (visibleHeight) {
|
||||
*visibleHeight = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void XournalView::documentChanged(DocumentChangeType type) {
|
||||
if (type != DOCUMENT_CHANGE_CLEARED && type != DOCUMENT_CHANGE_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
XournalScheduler* scheduler = this->control->getScheduler();
|
||||
scheduler->lock();
|
||||
scheduler->removeAllJobs();
|
||||
|
||||
clearSelection();
|
||||
|
||||
recreatePdfCache();
|
||||
|
||||
Document* doc = control->getDocument();
|
||||
doc->lock_shared();
|
||||
|
||||
viewPages.clear();
|
||||
size_t pagecount = doc->getPageCount();
|
||||
viewPages.reserve(pagecount);
|
||||
for (size_t i = 0; i < pagecount; i++) {
|
||||
viewPages.emplace_back(std::make_unique<XojPageView>(this, doc->getPage(i)));
|
||||
}
|
||||
|
||||
doc->unlock_shared();
|
||||
|
||||
layoutPages();
|
||||
scrollTo(0);
|
||||
|
||||
scheduler->unlock();
|
||||
}
|
||||
|
||||
auto XournalView::cut() -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p == npos || p >= viewPages.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& page = viewPages[p];
|
||||
return page->cut();
|
||||
}
|
||||
|
||||
auto XournalView::copy() -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p == npos || p >= viewPages.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& page = viewPages[p];
|
||||
return page->copy();
|
||||
}
|
||||
|
||||
auto XournalView::paste() -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p == npos || p >= viewPages.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& page = viewPages[p];
|
||||
return page->paste();
|
||||
}
|
||||
|
||||
auto XournalView::actionDelete() -> bool {
|
||||
size_t p = getCurrentPage();
|
||||
if (p == npos || p >= viewPages.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& page = viewPages[p];
|
||||
return page->actionDelete();
|
||||
}
|
||||
|
||||
auto XournalView::getDocument() const -> Document* { return control->getDocument(); }
|
||||
|
||||
auto XournalView::getViewPages() const -> std::vector<std::unique_ptr<XojPageView>> const& { return viewPages; }
|
||||
|
||||
auto XournalView::getCursor() const -> XournalppCursor* { return control->getCursor(); }
|
||||
|
||||
auto XournalView::getSelection() const -> EditSelection* {
|
||||
g_return_val_if_fail(this->widget != nullptr, nullptr);
|
||||
g_return_val_if_fail(GTK_IS_XOURNAL(this->widget), nullptr);
|
||||
|
||||
return GTK_XOURNAL(this->widget)->selection;
|
||||
}
|
||||
|
||||
auto XournalView::getLayout() const -> Layout* { return gtk_xournal_get_layout(getWidget()); }
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The widget which displays the PDF and the drawings
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <limits> // for numeric_limits
|
||||
#include <memory> // for unique_ptr
|
||||
#include <string> // for string
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gdk/gdk.h> // for GdkEventKey, GdkEventExpose
|
||||
#include <glib.h> // for gboolean
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkAllocation
|
||||
|
||||
#include "control/zoom/ZoomListener.h" // for ZoomListener
|
||||
#include "gui/inputdevices/InputEvents.h" // for KeyEvent
|
||||
#include "model/DocumentChangeType.h" // for DocumentChangeType
|
||||
#include "model/DocumentListener.h" // for DocumentListener
|
||||
#include "pdf/base/XojPdfPage.h" // for XojPdfRectangle
|
||||
#include "util/Util.h" // for npos
|
||||
|
||||
class Control;
|
||||
class XournalppCursor;
|
||||
class Document;
|
||||
class EditSelection;
|
||||
class XojPageView;
|
||||
class XojPdfRectangle;
|
||||
class PdfCache;
|
||||
class RepaintHandler;
|
||||
class ScrollHandling;
|
||||
class TextEditor;
|
||||
class HandRecognition;
|
||||
class Layout;
|
||||
namespace xoj::util {
|
||||
template <class T>
|
||||
class Rectangle;
|
||||
} // namespace xoj::util
|
||||
|
||||
class XournalView: public DocumentListener, public ZoomListener {
|
||||
public:
|
||||
XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling);
|
||||
~XournalView() override;
|
||||
|
||||
public:
|
||||
// Recalculate the layout width and height amd layout the pages with the updated layout size
|
||||
void layoutPages();
|
||||
|
||||
void scrollTo(size_t pageNo, XojPdfRectangle rect = {0, 0, -1, -1});
|
||||
|
||||
// Relative navigation in current layout:
|
||||
void pageRelativeXY(int offCol, int offRow);
|
||||
|
||||
size_t getCurrentPage() const;
|
||||
|
||||
void clearSelection();
|
||||
|
||||
void layerChanged(size_t page);
|
||||
|
||||
void requestFocus();
|
||||
|
||||
void forceUpdatePagenumbers();
|
||||
|
||||
XojPageView* getViewFor(size_t pageNr) const;
|
||||
|
||||
bool searchTextOnPage(const std::string& text, size_t pageNumber, size_t index, size_t* occurrences,
|
||||
XojPdfRectangle* matchRect);
|
||||
|
||||
bool cut();
|
||||
bool copy();
|
||||
bool paste();
|
||||
|
||||
void getPasteTarget(double& x, double& y) const;
|
||||
|
||||
bool actionDelete();
|
||||
|
||||
void endTextAllPages(XojPageView* except = nullptr) const;
|
||||
|
||||
void endSplineAllPages() const;
|
||||
|
||||
int getDisplayWidth() const;
|
||||
int getDisplayHeight() const;
|
||||
|
||||
bool isPageVisible(size_t page, int* visibleHeight) const;
|
||||
|
||||
void ensureRectIsVisible(int x, int y, int width, int height);
|
||||
|
||||
void setSelection(EditSelection* selection);
|
||||
EditSelection* getSelection() const;
|
||||
void deleteSelection(EditSelection* sel = nullptr);
|
||||
void repaintSelection(bool evenWithoutSelection = false);
|
||||
|
||||
TextEditor* getTextEditor() const;
|
||||
std::vector<std::unique_ptr<XojPageView>> const& getViewPages() const;
|
||||
|
||||
Control* getControl() const;
|
||||
double getZoom() const;
|
||||
int getDpiScaleFactor() const;
|
||||
Document* getDocument() const;
|
||||
PdfCache* getCache() const;
|
||||
RepaintHandler* getRepaintHandler() const;
|
||||
GtkWidget* getWidget() const;
|
||||
XournalppCursor* getCursor() const;
|
||||
Layout* getLayout() const;
|
||||
|
||||
xoj::util::Rectangle<double>* getVisibleRect(size_t page) const;
|
||||
xoj::util::Rectangle<double>* getVisibleRect(const XojPageView* redrawable) const;
|
||||
|
||||
/**
|
||||
* Recreate the PDF cache, for example after the underlying PDF file has changed
|
||||
*/
|
||||
void recreatePdfCache();
|
||||
|
||||
/**
|
||||
* A pen action was detected now, therefore ignore touch events
|
||||
* for a short time
|
||||
*/
|
||||
void penActionDetected();
|
||||
|
||||
/**
|
||||
* @return Helper class for Touch specific fixes
|
||||
*/
|
||||
HandRecognition* getHandRecognition() const;
|
||||
|
||||
/**
|
||||
* @return Scrollbars
|
||||
*/
|
||||
ScrollHandling* getScrollHandling() const;
|
||||
|
||||
public:
|
||||
// ZoomListener interface
|
||||
void zoomChanged() override;
|
||||
|
||||
public:
|
||||
// DocumentListener interface
|
||||
void pageSelected(size_t page) override;
|
||||
void pageSizeChanged(size_t page) override;
|
||||
void pageChanged(size_t page) override;
|
||||
void pageInserted(size_t page) override;
|
||||
void pageDeleted(size_t page) override;
|
||||
void documentChanged(DocumentChangeType type) override;
|
||||
|
||||
public:
|
||||
bool onKeyPressEvent(const KeyEvent& event);
|
||||
bool onKeyReleaseEvent(const KeyEvent& event);
|
||||
|
||||
void onSettingsChanged();
|
||||
|
||||
private:
|
||||
void fireZoomChanged();
|
||||
|
||||
std::pair<size_t, size_t> preloadPageBounds(size_t page, size_t maxPage);
|
||||
|
||||
static auto clearMemoryTimer(XournalView* widget) -> gboolean;
|
||||
|
||||
void cleanupBufferCache();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Scrollbars
|
||||
*/
|
||||
ScrollHandling* scrollHandling = nullptr;
|
||||
|
||||
GtkWidget* widget = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<XojPageView>> viewPages;
|
||||
|
||||
Control* control = nullptr;
|
||||
|
||||
size_t currentPage = 0;
|
||||
size_t lastSelectedPage = npos;
|
||||
|
||||
std::unique_ptr<PdfCache> cache;
|
||||
|
||||
/**
|
||||
* Handler for rerendering pages / repainting pages
|
||||
*/
|
||||
std::unique_ptr<RepaintHandler> repaintHandler;
|
||||
|
||||
/**
|
||||
* Memory cleanup timeout
|
||||
*/
|
||||
guint cleanupTimeout = std::numeric_limits<guint>::max();
|
||||
|
||||
friend class Layout;
|
||||
};
|
||||
@@ -1,648 +0,0 @@
|
||||
#include "XournalppCursor.h"
|
||||
|
||||
#include <cmath> // for cos, sin, M_PI, NAN, fmod
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <initializer_list> // for initializer_list
|
||||
|
||||
#include <cairo.h> // for cairo_move_to, cairo_lin...
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h> // for GdkPixbuf
|
||||
#include <glib-object.h> // for g_object_unref
|
||||
#include <gtk/gtk.h> // for gtk_widget_get_window
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/ToolEnums.h" // for TOOL_HAND, TOOL_PEN, TOO...
|
||||
#include "control/ToolHandler.h" // for ToolHandler
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "control/settings/SettingsEnums.h" // for STYLUS_CURSOR_BIG, STYLU...
|
||||
#include "control/zoom/ZoomControl.h" // for ZoomControl
|
||||
#include "gui/MainWindow.h" // for MainWindow
|
||||
#include "util/Color.h" // for argb_to_GdkRGBA, rgb_to_...
|
||||
#include "util/safe_casts.h" // for ceil_cast
|
||||
|
||||
#include "XournalView.h" // for XournalView
|
||||
|
||||
|
||||
// NOTE: Every cursor change must result in the setting of this->currentCursor to the new cursor type even for custom
|
||||
// cursors.
|
||||
// Custom cursors can also compare then set this->currentCursorFlavour to notice changes in colour or size etc. The
|
||||
// flavour calculation is specific to each cursor type and is calculated differently within each type.
|
||||
|
||||
|
||||
// All the cursors we want to use. WARNING Make sure to set their css names in cssCursors[] below WARNING
|
||||
enum AVAILABLECURSORS {
|
||||
CRSR_nullptr = 0, // <--- Do Not Modify
|
||||
CRSR_BUSY,
|
||||
CRSR_MOVE,
|
||||
CRSR_MOVING,
|
||||
CRSR_GRAB,
|
||||
CRSR_GRABBING,
|
||||
CRSR_TOP_LEFT_CORNER,
|
||||
CRSR_TOP_RIGHT_CORNER,
|
||||
CRSR_BOTTOM_LEFT_CORNER,
|
||||
CRSR_BOTTOM_RIGHT_CORNER,
|
||||
CRSR_SB_H_DOUBLE_ARROW,
|
||||
CRSR_EXCHANGE,
|
||||
CRSR_PIRATE,
|
||||
CRSR_SB_V_DOUBLE_ARROW,
|
||||
CRSR_ARROW,
|
||||
CRSR_BLANK_CURSOR,
|
||||
CRSR_XTERM,
|
||||
CRSR_DEFAULT,
|
||||
CRSR_HAND2,
|
||||
CRSR_TCROSS,
|
||||
CRSR_PENORHIGHLIGHTER,
|
||||
CRSR_ERASER,
|
||||
CRSR_DRAWDIRNONE, // drawdir* keep these consecutive and in order: none,shift,ctrl,shiftctrl
|
||||
CRSR_DRAWDIRSHIFT, // "
|
||||
CRSR_DRAWDIRCTRL, // "
|
||||
CRSR_DRAWDIRSHIFTCTRL, // "
|
||||
CRSR_RESIZE,
|
||||
|
||||
CRSR_END_OF_CURSORS
|
||||
};
|
||||
|
||||
|
||||
struct cursorStruct {
|
||||
const gchar* cssName;
|
||||
const gchar* cssBackupName;
|
||||
};
|
||||
|
||||
|
||||
// our enum mapped to css name and a place to store the cursor pointer.
|
||||
// Note: including enum as error check (for now?)
|
||||
cursorStruct cssCursors[CRSR_END_OF_CURSORS];
|
||||
|
||||
|
||||
XournalppCursor::XournalppCursor(Control* control): control(control) {
|
||||
// clang-format off
|
||||
// NOTE: Go ahead and use a fancy css cursor... but specify a common backup cursor.
|
||||
cssCursors[CRSR_nullptr ] = {"",""};
|
||||
cssCursors[CRSR_BUSY ] = {"wait", "" };
|
||||
cssCursors[CRSR_MOVE ] = {"all-scroll", "" };
|
||||
cssCursors[CRSR_MOVING ] = {"grabbing", "" };
|
||||
cssCursors[CRSR_GRAB ] = {"grab", "" };
|
||||
cssCursors[CRSR_GRABBING ] = {"grabbing", "" };
|
||||
cssCursors[CRSR_TOP_LEFT_CORNER ] = {"nw-resize", "" };
|
||||
cssCursors[CRSR_TOP_RIGHT_CORNER ] = {"ne-resize", "" };
|
||||
cssCursors[CRSR_BOTTOM_LEFT_CORNER ] = {"sw-resize", "" };
|
||||
cssCursors[CRSR_BOTTOM_RIGHT_CORNER ] = {"se-resize", "" };
|
||||
cssCursors[CRSR_SB_H_DOUBLE_ARROW ] = {"ew-resize", "" };
|
||||
cssCursors[CRSR_EXCHANGE ] = {"exchange", "arrow" };
|
||||
cssCursors[CRSR_PIRATE ] = {"pirate", "arrow" };
|
||||
cssCursors[CRSR_SB_V_DOUBLE_ARROW ] = {"ns-resize", "" };
|
||||
cssCursors[CRSR_ARROW ] = {"default", "" };
|
||||
cssCursors[CRSR_BLANK_CURSOR ] = {"none", "" };
|
||||
cssCursors[CRSR_XTERM ] = {"text", "" };
|
||||
cssCursors[CRSR_DEFAULT ] = {"default", "" };
|
||||
cssCursors[CRSR_HAND2 ] = {"hand2", "" };
|
||||
cssCursors[CRSR_TCROSS ] = {"crosshair", "" };
|
||||
cssCursors[CRSR_PENORHIGHLIGHTER ] = {"",""}; // custom cursors - enum used only for check
|
||||
cssCursors[CRSR_ERASER ] = {"",""}; // "
|
||||
cssCursors[CRSR_DRAWDIRNONE ] = {"",""}; // "
|
||||
cssCursors[CRSR_DRAWDIRSHIFT ] = {"",""}; // "
|
||||
cssCursors[CRSR_DRAWDIRCTRL ] = {"",""}; // "
|
||||
cssCursors[CRSR_DRAWDIRSHIFTCTRL ] = {"",""}; // "
|
||||
cssCursors[CRSR_RESIZE ] = {"",""}; // "
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
constexpr auto RESIZE_CURSOR_SIZE = 16;
|
||||
constexpr auto DELTA_ANGLE_ARROW_HEAD = M_PI / 6.0;
|
||||
constexpr auto LENGTH_ARROW_HEAD = 0.7;
|
||||
constexpr auto RESIZE_CURSOR_HASH_PRECISION = 1000;
|
||||
|
||||
|
||||
XournalppCursor::~XournalppCursor() = default;
|
||||
|
||||
|
||||
void XournalppCursor::setInputDeviceClass(InputDeviceClass device) { this->inputDevice = device; }
|
||||
|
||||
|
||||
// pen or hi-light cursor will be a DrawDir cursor instead
|
||||
void XournalppCursor::activateDrawDirCursor(bool enable, bool shift, bool ctrl) {
|
||||
this->drawDirActive = enable;
|
||||
this->drawDirShift = shift;
|
||||
this->drawDirCtrl = ctrl;
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::setMouseDown(bool mouseDown) {
|
||||
if (this->mouseDown == mouseDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->mouseDown = mouseDown;
|
||||
ToolHandler* handler = control->getToolHandler();
|
||||
ToolType type = handler->getToolType();
|
||||
|
||||
// Not always an update is needed
|
||||
if (type == TOOL_HAND || type == TOOL_VERTICAL_SPACE || type == TOOL_ERASER) {
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::setMouseSelectionType(CursorSelectionType selectionType) {
|
||||
if (this->selectionType == selectionType) {
|
||||
return;
|
||||
}
|
||||
this->selectionType = selectionType;
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
void XournalppCursor::setRotationAngle(double angle) { this->angle = angle; }
|
||||
void XournalppCursor::setMirror(bool mirror) { this->mirror = mirror; }
|
||||
|
||||
/*This handles setting the busy cursor for the main window and calls
|
||||
* updateCursor to set the busy cursor for the XournalWidget region.
|
||||
*/
|
||||
void XournalppCursor::setCursorBusy(bool busy) {
|
||||
MainWindow* win = control->getWindow();
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->busy == busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->busy = busy;
|
||||
|
||||
if (busy) {
|
||||
GdkWindow* window = gtk_widget_get_window(win->getWindow());
|
||||
xoj::util::GObjectSPtr<GdkCursor> cursor(
|
||||
gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[CRSR_BUSY].cssName),
|
||||
xoj::util::adopt);
|
||||
gdk_window_set_cursor(window, cursor.get());
|
||||
} else {
|
||||
if (gtk_widget_get_window(win->getWindow())) {
|
||||
gdk_window_set_cursor(gtk_widget_get_window(win->getWindow()), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::setInsidePage(bool insidePage) {
|
||||
if (this->insidePage == insidePage) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->insidePage = insidePage;
|
||||
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::setInvisible(bool invisible) {
|
||||
if (this->invisible == invisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->invisible = invisible;
|
||||
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::updateCursor() {
|
||||
MainWindow* win = control->getWindow();
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
XournalView* xournal = win->getXournal();
|
||||
if (!xournal) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkCursor* cursor = nullptr;
|
||||
|
||||
|
||||
if (this->busy) {
|
||||
setCursor(CRSR_BUSY);
|
||||
} else {
|
||||
ToolHandler* handler = control->getToolHandler();
|
||||
ToolType type = handler->getToolType();
|
||||
|
||||
|
||||
if (type == TOOL_HAND) {
|
||||
if (this->mouseDown) {
|
||||
setCursor(CRSR_GRABBING);
|
||||
} else {
|
||||
setCursor(CRSR_GRAB);
|
||||
}
|
||||
} else if (!this->insidePage) {
|
||||
setCursor(CRSR_DEFAULT);
|
||||
} else if (this->selectionType) {
|
||||
switch (this->selectionType) {
|
||||
case CURSOR_SELECTION_MOVE:
|
||||
if (this->mouseDown) {
|
||||
setCursor(CRSR_MOVING);
|
||||
} else {
|
||||
setCursor(CRSR_MOVE);
|
||||
}
|
||||
break;
|
||||
case CURSOR_SELECTION_TOP_LEFT:
|
||||
[[fallthrough]];
|
||||
case CURSOR_SELECTION_BOTTOM_RIGHT:
|
||||
cursor = getResizeCursor(45);
|
||||
break;
|
||||
case CURSOR_SELECTION_TOP_RIGHT:
|
||||
[[fallthrough]];
|
||||
case CURSOR_SELECTION_BOTTOM_LEFT:
|
||||
cursor = getResizeCursor(135);
|
||||
break;
|
||||
case CURSOR_SELECTION_LEFT:
|
||||
[[fallthrough]];
|
||||
case CURSOR_SELECTION_RIGHT:
|
||||
cursor = getResizeCursor(180);
|
||||
break;
|
||||
case CURSOR_SELECTION_TOP:
|
||||
[[fallthrough]];
|
||||
case CURSOR_SELECTION_BOTTOM:
|
||||
cursor = getResizeCursor(90);
|
||||
break;
|
||||
case CURSOR_SELECTION_ROTATE:
|
||||
setCursor(CRSR_EXCHANGE);
|
||||
break;
|
||||
case CURSOR_SELECTION_DELETE:
|
||||
setCursor(CRSR_PIRATE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER || type == TOOL_LASER_POINTER_PEN ||
|
||||
type == TOOL_LASER_POINTER_HIGHLIGHTER) {
|
||||
if (this->inputDevice == INPUT_DEVICE_MOUSE && !this->mouseDown) {
|
||||
// mouse and not pressed
|
||||
setCursor(CRSR_ARROW);
|
||||
} else {
|
||||
if (type == TOOL_PEN || type == TOOL_LASER_POINTER_PEN) {
|
||||
cursor = getPenCursor();
|
||||
} else // must be: if (type == TOOL_HIGHLIGHTER || type == TOOL_LASER_POINTER_HIGHLIGHTER)
|
||||
{
|
||||
cursor = getHighlighterCursor();
|
||||
}
|
||||
}
|
||||
} else if (type == TOOL_ERASER) {
|
||||
EraserVisibility visibility = control->getSettings()->getEraserVisibility();
|
||||
if ((this->inputDevice == INPUT_DEVICE_PEN || this->inputDevice == INPUT_DEVICE_ERASER) &&
|
||||
(visibility == ERASER_VISIBILITY_NEVER || (visibility == ERASER_VISIBILITY_HOVER && this->mouseDown) ||
|
||||
(visibility == ERASER_VISIBILITY_TOUCH && !this->mouseDown))) {
|
||||
setCursor(CRSR_BLANK_CURSOR);
|
||||
} else {
|
||||
cursor = getEraserCursor();
|
||||
}
|
||||
}
|
||||
|
||||
else if (type == TOOL_TEXT) {
|
||||
if (this->invisible) {
|
||||
setCursor(CRSR_BLANK_CURSOR);
|
||||
} else {
|
||||
setCursor(CRSR_XTERM);
|
||||
}
|
||||
} else if (type == TOOL_IMAGE) {
|
||||
setCursor(CRSR_TCROSS);
|
||||
} else if (type == TOOL_FLOATING_TOOLBOX) {
|
||||
setCursor(CRSR_DEFAULT);
|
||||
} else if (type == TOOL_VERTICAL_SPACE) {
|
||||
setCursor(CRSR_SB_V_DOUBLE_ARROW);
|
||||
} else if (type == TOOL_SELECT_OBJECT) {
|
||||
setCursor(CRSR_DEFAULT);
|
||||
} else if (type == TOOL_PLAY_OBJECT) {
|
||||
setCursor(CRSR_HAND2);
|
||||
} else if (type == TOOL_SELECT_PDF_TEXT_LINEAR) {
|
||||
setCursor(CRSR_XTERM);
|
||||
} else // other selections are handled before anyway, because you can move a selection with every tool
|
||||
{
|
||||
setCursor(CRSR_TCROSS);
|
||||
}
|
||||
}
|
||||
|
||||
GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
|
||||
if (window) {
|
||||
if (cursor != nullptr) {
|
||||
gdk_window_set_cursor(window, cursor);
|
||||
}
|
||||
gtk_widget_set_sensitive(xournal->getWidget(), !this->busy);
|
||||
}
|
||||
// Performance removal!
|
||||
// If this line is ever required, make sure this function is never called, when the cursor is not set.
|
||||
// gdk_display_sync(gdk_display_get_default());
|
||||
|
||||
if (cursor != nullptr) {
|
||||
g_object_unref(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
auto XournalppCursor::getResizeCursor(double deltaAngle) -> GdkCursor* {
|
||||
if (this->mirror) {
|
||||
deltaAngle = -deltaAngle;
|
||||
}
|
||||
gulong flavour = static_cast<gulong>(RESIZE_CURSOR_HASH_PRECISION * fmod(angle + deltaAngle, 180.0));
|
||||
if (CRSR_RESIZE == this->currentCursor && flavour == this->currentCursorFlavour) {
|
||||
return nullptr;
|
||||
}
|
||||
this->currentCursor = CRSR_RESIZE;
|
||||
this->currentCursorFlavour = flavour;
|
||||
|
||||
double a = (this->angle + deltaAngle) * M_PI / 180;
|
||||
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
|
||||
cairo_t* cr = cairo_create(crCursor);
|
||||
cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1);
|
||||
cairo_translate(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
|
||||
cairo_scale(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
|
||||
cairo_set_line_width(cr, 0.2);
|
||||
// draw double headed arrow rotated accordingly
|
||||
cairo_move_to(cr, cos(a), sin(a));
|
||||
cairo_line_to(cr, -cos(a), -sin(a));
|
||||
cairo_stroke(cr);
|
||||
// head and tail
|
||||
for (auto s: {-1, 1}) {
|
||||
cairo_move_to(cr, s * cos(a), s * sin(a));
|
||||
cairo_rel_line_to(cr, s * cos(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
|
||||
s * sin(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
|
||||
cairo_move_to(cr, s * cos(a), s * sin(a));
|
||||
cairo_rel_line_to(cr, s * cos(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
|
||||
s * sin(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
cairo_destroy(cr);
|
||||
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
|
||||
cairo_surface_destroy(crCursor);
|
||||
GdkCursor* cursor =
|
||||
gdk_cursor_new_from_pixbuf(gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf,
|
||||
RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
|
||||
g_object_unref(pixbuf);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
auto XournalppCursor::getEraserCursor() -> GdkCursor* {
|
||||
|
||||
// Eraser's size follow a quadratic increment, so the cursor will do the same
|
||||
double cursorSize = control->getToolHandler()->getThickness() * 2.0 * control->getZoomControl()->getZoom();
|
||||
gulong flavour = static_cast<gulong>(64 * cursorSize);
|
||||
|
||||
if (CRSR_ERASER == this->currentCursor && flavour == this->currentCursorFlavour) {
|
||||
return nullptr; // cursor already set
|
||||
}
|
||||
this->currentCursor = CRSR_ERASER;
|
||||
this->currentCursorFlavour = flavour;
|
||||
|
||||
cairo_surface_t* surface =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceil_cast<int>(cursorSize), ceil_cast<int>(cursorSize));
|
||||
cairo_t* cr = cairo_create(surface);
|
||||
cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
|
||||
cairo_set_source_rgb(cr, 1, 1, 1);
|
||||
cairo_fill(cr);
|
||||
cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
|
||||
cairo_set_source_rgb(cr, 0, 0, 0);
|
||||
cairo_stroke(cr);
|
||||
cairo_destroy(cr);
|
||||
GdkCursor* cursor =
|
||||
gdk_cursor_new_from_surface(gdk_display_get_default(), surface, cursorSize / 2.0, cursorSize / 2.0);
|
||||
cairo_surface_destroy(surface);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
auto XournalppCursor::getHighlighterCursor() -> GdkCursor* {
|
||||
if (this->drawDirActive) {
|
||||
return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
|
||||
}
|
||||
|
||||
return createHighlighterOrPenCursor(120 / 255.0);
|
||||
}
|
||||
|
||||
|
||||
auto XournalppCursor::getPenCursor() -> GdkCursor* {
|
||||
if ((control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_NONE) &&
|
||||
!control->getSettings()->isHighlightPosition()) {
|
||||
setCursor(CRSR_BLANK_CURSOR);
|
||||
return nullptr;
|
||||
}
|
||||
if (control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_ARROW) {
|
||||
setCursor(CRSR_ARROW);
|
||||
return nullptr;
|
||||
}
|
||||
if ((control->getSettings()->getStylusCursorType() != STYLUS_CURSOR_NONE) && this->drawDirActive) {
|
||||
return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
|
||||
}
|
||||
|
||||
return createHighlighterOrPenCursor(1.0);
|
||||
}
|
||||
|
||||
auto XournalppCursor::createHighlighterOrPenCursor(double alpha) -> GdkCursor* {
|
||||
auto irgb = control->getToolHandler()->getColor();
|
||||
if (const auto& params = control->getSettings()->getRecolorParameters(); params.recolorizeMainView) {
|
||||
irgb = params.recolor.convertColor(irgb);
|
||||
}
|
||||
auto drgb = Util::rgb_to_GdkRGBA(irgb);
|
||||
auto cursorType = control->getSettings()->getStylusCursorType();
|
||||
auto cursor = (cursorType == STYLUS_CURSOR_NONE) ? CRSR_BLANK_CURSOR : CRSR_PENORHIGHLIGHTER;
|
||||
bool bright = control->getSettings()->isHighlightPosition();
|
||||
double cursorSize = std::min(90., control->getToolHandler()->getThickness() * control->getZoomControl()->getZoom());
|
||||
int height = ceil_cast<int>(cursorSize);
|
||||
int width = height;
|
||||
|
||||
// create a hash of variables so we notice if one changes despite being the same cursor type:
|
||||
gulong flavour = (cursorType == STYLUS_CURSOR_DOT ? 1U : 0U) | (cursorType == STYLUS_CURSOR_BIG ? 2U : 0U) |
|
||||
(bright ? 4U : 0U) | static_cast<gulong>(64 * alpha) << 3U |
|
||||
static_cast<gulong>(cursorSize) << 10U | static_cast<gulong>(uint32_t(irgb)) << 15U;
|
||||
|
||||
if ((cursor == this->currentCursor) && (flavour == this->currentCursorFlavour)) {
|
||||
return nullptr;
|
||||
}
|
||||
this->currentCursor = cursor;
|
||||
this->currentCursorFlavour = flavour;
|
||||
|
||||
if ((cursorType == STYLUS_CURSOR_BIG) || bright) {
|
||||
height = width = 90;
|
||||
}
|
||||
|
||||
// We change the drawing method, now the center with the colored dot of the pen
|
||||
// is at the center of the cairo surface, and when we load the cursor, we load it
|
||||
// with the relative offset
|
||||
int centerX = width / 2;
|
||||
int centerY = height / 2;
|
||||
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
||||
cairo_t* cr = cairo_create(crCursor);
|
||||
|
||||
if (cursorType == STYLUS_CURSOR_BIG) {
|
||||
// When using highlighter, paint the icon with the current color
|
||||
if (alpha != 1.0) {
|
||||
gdk_cairo_set_source_rgba(cr, &drgb);
|
||||
} else {
|
||||
cairo_set_source_rgb(cr, 1, 1, 1);
|
||||
}
|
||||
cairo_set_line_width(cr, 1.2);
|
||||
|
||||
// Starting point
|
||||
cairo_move_to(cr, centerX + 2, centerY);
|
||||
// Pencil cursor
|
||||
cairo_line_to(cr, centerX + 2, centerY - 4);
|
||||
cairo_line_to(cr, centerX + 15, centerY - 17.5);
|
||||
cairo_line_to(cr, centerX + 19, centerY - 14);
|
||||
cairo_line_to(cr, centerX + 6, centerY);
|
||||
|
||||
cairo_close_path(cr);
|
||||
cairo_fill_preserve(cr);
|
||||
cairo_set_source_rgb(cr, 0, 0, 0);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_fill_preserve(cr);
|
||||
}
|
||||
|
||||
if (bright) {
|
||||
// Highlight cursor with a circle
|
||||
auto&& color = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightColor());
|
||||
gdk_cairo_set_source_rgba(cr, &color);
|
||||
cairo_arc(cr, centerX, centerY, control->getSettings()->getCursorHighlightRadius(), 0, 2 * M_PI);
|
||||
cairo_fill_preserve(cr);
|
||||
auto&& borderColor = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightBorderColor());
|
||||
gdk_cairo_set_source_rgba(cr, &borderColor);
|
||||
cairo_set_line_width(cr, control->getSettings()->getCursorHighlightBorderWidth());
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
if (cursorType != STYLUS_CURSOR_NONE) {
|
||||
auto drgbCopy = drgb;
|
||||
drgbCopy.alpha = alpha;
|
||||
gdk_cairo_set_source_rgba(cr, &drgbCopy);
|
||||
cairo_arc(cr, centerX, centerY, cursorSize / 2., 0, 2. * M_PI);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
cairo_destroy(cr);
|
||||
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
|
||||
cairo_surface_destroy(crCursor);
|
||||
GdkCursor* gdkCursor = gdk_cursor_new_from_pixbuf(
|
||||
gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
|
||||
g_object_unref(pixbuf);
|
||||
return gdkCursor;
|
||||
}
|
||||
|
||||
|
||||
void XournalppCursor::setCursor(guint cursorID) {
|
||||
if (cursorID == this->currentCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindow* win = control->getWindow();
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
XournalView* xournal = win->getXournal();
|
||||
if (!xournal) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
GdkCursor* cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssName);
|
||||
if (cursor == nullptr) // failed to get a cursor, try backup cursor.
|
||||
{
|
||||
if (cursorID != CRSR_nullptr) {
|
||||
cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssBackupName);
|
||||
|
||||
// Null cursor is ok but not wanted ... warn user
|
||||
if (cursor == nullptr) {
|
||||
if (CRSR_nullptr == this->currentCursor) {
|
||||
return; // We've already been here
|
||||
}
|
||||
g_warning("CSS Cursor and backup not valid '%s', '%s'", cssCursors[cursorID].cssName,
|
||||
cssCursors[cursorID].cssBackupName);
|
||||
}
|
||||
}
|
||||
cursorID = cursor == nullptr ? CRSR_nullptr : cursorID;
|
||||
}
|
||||
|
||||
this->currentCursor = cursorID;
|
||||
gdk_window_set_cursor(gtk_widget_get_window(xournal->getWidget()), cursor);
|
||||
gdk_window_set_cursor(window, cursor);
|
||||
if (cursor) {
|
||||
g_object_unref(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto XournalppCursor::createCustomDrawDirCursor(int size, bool shift, bool ctrl) -> GdkCursor* {
|
||||
bool big = control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_BIG;
|
||||
bool bright = control->getSettings()->isHighlightPosition();
|
||||
|
||||
guint newCursorID = CRSR_DRAWDIRNONE + (shift ? 1 : 0) + (ctrl ? 2 : 0);
|
||||
gulong flavour =
|
||||
(big ? 1 : 0) | (bright ? 2 : 0) | static_cast<gulong>(size) << 2; // hash of variables for comparison only
|
||||
|
||||
if (newCursorID == this->currentCursor && flavour == this->currentCursorFlavour) {
|
||||
return nullptr;
|
||||
}
|
||||
this->currentCursor = newCursorID;
|
||||
this->currentCursorFlavour = flavour;
|
||||
|
||||
int height = size;
|
||||
int width = size;
|
||||
int fontSize = 8;
|
||||
if (big || bright) {
|
||||
height = width = 60;
|
||||
fontSize = 12;
|
||||
}
|
||||
int centerX = width - width / 4;
|
||||
int centerY = height - height / 4;
|
||||
|
||||
|
||||
cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
||||
cairo_t* cr = cairo_create(crCursor);
|
||||
cairo_set_line_width(cr, 1.2);
|
||||
|
||||
// Starting point
|
||||
cairo_move_to(cr, centerX, height / 2);
|
||||
cairo_line_to(cr, centerX, height);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_move_to(cr, width / 2, centerY);
|
||||
cairo_line_to(cr, width, centerY);
|
||||
cairo_stroke(cr);
|
||||
|
||||
if (ctrl) {
|
||||
cairo_text_extents_t extents;
|
||||
const char* utf8 = "CONTROL";
|
||||
double x = NAN, y = NAN;
|
||||
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, fontSize);
|
||||
cairo_text_extents(cr, utf8, &extents);
|
||||
x = 0;
|
||||
y = extents.height;
|
||||
cairo_move_to(cr, x, y);
|
||||
cairo_show_text(cr, utf8);
|
||||
}
|
||||
|
||||
if (shift) {
|
||||
cairo_text_extents_t extents;
|
||||
const char* utf8 = "SHIFT";
|
||||
double x = NAN, y = NAN;
|
||||
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, fontSize);
|
||||
cairo_text_extents(cr, utf8, &extents);
|
||||
x = 0;
|
||||
y = extents.height * 2.5;
|
||||
cairo_move_to(cr, x, y);
|
||||
cairo_show_text(cr, utf8);
|
||||
}
|
||||
|
||||
cairo_destroy(cr);
|
||||
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
|
||||
cairo_surface_destroy(crCursor);
|
||||
GdkCursor* cursor = gdk_cursor_new_from_pixbuf(
|
||||
gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
|
||||
g_object_unref(pixbuf);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Handles the XournalppCursor
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gdk/gdk.h> // for GdkCursor
|
||||
#include <glib.h> // for guint, gulong
|
||||
|
||||
#include "control/tools/CursorSelectionType.h" // for CursorSelectionType
|
||||
#include "gui/inputdevices/InputEvents.h" // for InputDeviceClass, INP...
|
||||
|
||||
class Control;
|
||||
|
||||
class XournalppCursor {
|
||||
public:
|
||||
XournalppCursor(Control* control);
|
||||
virtual ~XournalppCursor();
|
||||
|
||||
void setCursorBusy(bool busy);
|
||||
void updateCursor();
|
||||
|
||||
void setMouseSelectionType(CursorSelectionType selectionType);
|
||||
|
||||
void setMouseDown(bool mouseDown);
|
||||
void setInvisible(bool invisible);
|
||||
void setInsidePage(bool insidePage);
|
||||
void activateDrawDirCursor(bool enable, bool shift = false, bool ctrl = false);
|
||||
void setInputDeviceClass(InputDeviceClass inputDevice);
|
||||
void setRotationAngle(double angle);
|
||||
void setMirror(bool mirror);
|
||||
|
||||
private:
|
||||
void setCursor(guint id);
|
||||
|
||||
GdkCursor* getPenCursor();
|
||||
|
||||
GdkCursor* getEraserCursor();
|
||||
GdkCursor* getHighlighterCursor();
|
||||
GdkCursor* getResizeCursor(double deltaAngle);
|
||||
|
||||
GdkCursor* createHighlighterOrPenCursor(double alpha);
|
||||
GdkCursor* createCustomDrawDirCursor(int size, bool shift, bool ctrl);
|
||||
|
||||
private:
|
||||
InputDeviceClass inputDevice = INPUT_DEVICE_MOUSE;
|
||||
|
||||
Control* control = nullptr;
|
||||
bool busy = false;
|
||||
bool insidePage = false;
|
||||
CursorSelectionType selectionType = CURSOR_SELECTION_NONE;
|
||||
|
||||
bool mouseDown = false;
|
||||
bool invisible = false;
|
||||
|
||||
// One shot drawDir custom cursor -drawn instead of pen/stylus then cleared.
|
||||
bool drawDirActive = false;
|
||||
bool drawDirShift = false;
|
||||
bool drawDirCtrl = false;
|
||||
|
||||
// avoid re-assigning same cursor
|
||||
guint currentCursor = 0; // enum AVAILABLECURSORS
|
||||
gulong currentCursorFlavour{}; // for different flavours of a cursor (i.e. drawdir, pen and highlighter custom
|
||||
// cursors)
|
||||
|
||||
// for resizing rotated/mirrored selections
|
||||
double angle = 0;
|
||||
bool mirror = false;
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
#include "AboutDialog.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "util/PlaceholderString.h" // for PlaceholderString
|
||||
#include "util/gtk4_helper.h" // for gtk_box_append
|
||||
#include "util/i18n.h" // for _
|
||||
|
||||
#include "config-git.h" // for GIT_COMMIT_ID
|
||||
#include "config.h" // for PROJECT_VERSION
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "about.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "aboutDialog";
|
||||
constexpr auto GIT_REPO = "https://github.com/xournalpp/xournalpp";
|
||||
constexpr auto WEBSITE = "https://xournalpp.github.io";
|
||||
constexpr auto AUTHORS_LINK = "https://raw.githubusercontent.com/xournalpp/xournalpp/master/AUTHORS";
|
||||
constexpr auto LICENCE_LINK = "https://raw.githubusercontent.com/xournalpp/xournalpp/master/LICENSE";
|
||||
|
||||
static GtkWindow* constructWindow(GladeSearchpath* gladeSearchPath) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
GtkWindow* window = GTK_WINDOW(builder.get(UI_DIALOG_NAME));
|
||||
|
||||
auto insertPropertyKey = [](GtkGrid* grid, std::string&& str, gint top) {
|
||||
auto widget = gtk_label_new(nullptr);
|
||||
gtk_label_set_markup(GTK_LABEL(widget), str.insert(0, "<b>").append("</b>").c_str());
|
||||
gtk_widget_set_halign(widget, GtkAlign::GTK_ALIGN_START);
|
||||
gtk_grid_attach(grid, widget, 0, top, 1, 1);
|
||||
};
|
||||
|
||||
auto insertPropertyValue = [](GtkGrid* grid, std::string const& str, gint top) {
|
||||
auto widget = gtk_label_new(str.c_str());
|
||||
gtk_widget_set_halign(widget, GtkAlign::GTK_ALIGN_START);
|
||||
gtk_grid_attach(grid, widget, 1, top, 1, 1);
|
||||
};
|
||||
|
||||
auto infoGrid = GTK_GRID(builder.get("versionInfoGrid"));
|
||||
|
||||
insertPropertyKey(infoGrid, _("Version"), 0);
|
||||
insertPropertyValue(infoGrid, PROJECT_VERSION, 0);
|
||||
|
||||
insertPropertyKey(infoGrid, _("Built on"), 1);
|
||||
insertPropertyValue(infoGrid, __DATE__ ", " __TIME__, 1);
|
||||
|
||||
insertPropertyKey(infoGrid, _("GTK Version"), 2);
|
||||
auto gtkVersion =
|
||||
FS(FORMAT_STR("{1}.{2}.{3}") % gtk_get_major_version() % gtk_get_minor_version() % gtk_get_micro_version());
|
||||
insertPropertyValue(infoGrid, gtkVersion, 2);
|
||||
|
||||
auto const gitCommitId = std::string{GIT_COMMIT_ID};
|
||||
if (!gitCommitId.empty()) {
|
||||
insertPropertyKey(infoGrid, _("Git commit"), 3);
|
||||
insertPropertyValue(infoGrid, gitCommitId.c_str(), 3);
|
||||
}
|
||||
|
||||
gtk_box_append(GTK_BOX(builder.get("vboxRepo")), gtk_link_button_new(GIT_REPO));
|
||||
gtk_box_append(GTK_BOX(builder.get("vboxWebsite")), gtk_link_button_new(WEBSITE));
|
||||
gtk_box_append(GTK_BOX(builder.get("vboxCommunity")),
|
||||
gtk_link_button_new_with_label(AUTHORS_LINK, _("See the full list of contributors")));
|
||||
gtk_box_append(GTK_BOX(builder.get("vboxLicense")),
|
||||
gtk_link_button_new_with_label(LICENCE_LINK, _("GNU GPLv2 or later")));
|
||||
|
||||
g_signal_connect_swapped(GTK_BUTTON(builder.get("closebutton")), "clicked", G_CALLBACK(gtk_window_close), window);
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
// Widgets are visible by default in gtk4
|
||||
gtk_widget_show_all(builder.get("dialog-main-box"));
|
||||
#endif
|
||||
return window;
|
||||
}
|
||||
|
||||
xoj::popup::AboutDialog::AboutDialog(GladeSearchpath* gladeSearchPath): window(constructWindow(gladeSearchPath)) {}
|
||||
|
||||
xoj::popup::AboutDialog::~AboutDialog() = default;
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The about dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
namespace xoj::popup {
|
||||
class AboutDialog {
|
||||
public:
|
||||
AboutDialog(GladeSearchpath* gladeSearchPath);
|
||||
~AboutDialog();
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "gui/dialog/AbstractLatexDialog.h"
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <memory>
|
||||
|
||||
#include "control/LatexController.h"
|
||||
#include "model/TexImage.h"
|
||||
#include "util/Range.h"
|
||||
|
||||
// Default background color of the preview.
|
||||
const Color DEFAULT_PREVIEW_BACKGROUND = Colors::white;
|
||||
|
||||
AbstractLatexDialog::AbstractLatexDialog(std::unique_ptr<LatexController> ctrl):
|
||||
texCtrl{std::move(ctrl)}, previewBackgroundColor{DEFAULT_PREVIEW_BACKGROUND} {
|
||||
texCtrl->dlg = this;
|
||||
}
|
||||
|
||||
AbstractLatexDialog::~AbstractLatexDialog() { texCtrl->dlg = nullptr; };
|
||||
|
||||
void AbstractLatexDialog::setCompilationStatus(bool isTexValid, bool isCompilationDone,
|
||||
const std::string& compilationOutput) {
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btOk), isTexValid && isCompilationDone);
|
||||
|
||||
// Show error warning only if LaTeX is invalid.
|
||||
gtk_widget_set_opacity(GTK_WIDGET(texErrorLabel), isTexValid ? 0 : 1);
|
||||
|
||||
// Update the output pane.
|
||||
gtk_text_buffer_set_text(compilationOutputTextBuffer, compilationOutput.c_str(), -1);
|
||||
}
|
||||
|
||||
void AbstractLatexDialog::setTempRender(PopplerDocument* pdf) {
|
||||
if (poppler_document_get_n_pages(pdf) < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->previewPdfPage.reset(poppler_document_get_page(pdf, 0), xoj::util::adopt);
|
||||
this->previewMask.reset();
|
||||
|
||||
// Queue rendering the changed preview.
|
||||
gtk_widget_queue_draw(GTK_WIDGET(this->previewDrawingArea));
|
||||
}
|
||||
|
||||
void AbstractLatexDialog::setPreviewBackgroundColor(Color newColor) { this->previewBackgroundColor = newColor; }
|
||||
|
||||
void AbstractLatexDialog::previewDrawFunc(GtkDrawingArea*, cairo_t* cr, int width, int height,
|
||||
AbstractLatexDialog* self) {
|
||||
|
||||
// If we have nothing to render, do nothing!
|
||||
if (!self->previewPdfPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
Range pageRange(0, 0, 0, 0);
|
||||
poppler_page_get_size(self->previewPdfPage.get(), &pageRange.maxX, &pageRange.maxY);
|
||||
|
||||
const double zoom =
|
||||
std::min(static_cast<double>(width) / pageRange.maxX, static_cast<double>(height) / pageRange.maxY);
|
||||
|
||||
|
||||
if (!self->previewMask.isInitialized() || self->previewMask.getZoom() < zoom) {
|
||||
// Need to rerender the preview
|
||||
self->previewMask = xoj::view::Mask(gtk_widget_get_scale_factor(GTK_WIDGET(self->previewDrawingArea)),
|
||||
pageRange, zoom, CAIRO_CONTENT_COLOR_ALPHA);
|
||||
poppler_page_render(self->previewPdfPage.get(), self->previewMask.get());
|
||||
}
|
||||
|
||||
|
||||
Util::cairo_set_source_rgbi(cr, self->previewBackgroundColor);
|
||||
cairo_paint(cr);
|
||||
|
||||
// Center the rendering
|
||||
const double extraHorizontalSpace = std::max(0.0, width - pageRange.maxX * zoom);
|
||||
const double extraVerticalSpace = std::max(0.0, height - pageRange.maxY * zoom);
|
||||
cairo_translate(cr, 0.5 * extraHorizontalSpace, 0.5 * extraVerticalSpace);
|
||||
cairo_scale(cr, zoom, zoom);
|
||||
self->previewMask.paintTo(cr);
|
||||
}
|
||||
|
||||
void AbstractLatexDialog::populateStandardWidgetsFromBuilder(Builder& builder) {
|
||||
previewDrawingArea = GTK_DRAWING_AREA(builder.get("texImage"));
|
||||
btOk = GTK_BUTTON(builder.get("btOk"));
|
||||
btCancel = GTK_BUTTON(builder.get("btCancel"));
|
||||
texErrorLabel = GTK_LABEL(builder.get("texErrorLabel"));
|
||||
compilationOutputTextBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(builder.get("texCommandOutputText")));
|
||||
}
|
||||
|
||||
void AbstractLatexDialog::connectStandardSignals() {
|
||||
g_signal_connect(btCancel, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
|
||||
auto* self = static_cast<AbstractLatexDialog*>(d);
|
||||
self->texCtrl->cancelEditing();
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
g_signal_connect(btOk, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
|
||||
auto* self = static_cast<AbstractLatexDialog*>(d);
|
||||
self->texCtrl->insertTexImage();
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
|
||||
gtk_drawing_area_set_draw_func(this->previewDrawingArea, GtkDrawingAreaDrawFunc(previewDrawFunc), this, nullptr);
|
||||
if (texCtrl->temporaryRender) {
|
||||
// Use preexisting pdf
|
||||
setTempRender(texCtrl->temporaryRender->getPdf());
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Latex implementation
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory> // for unique_ptr
|
||||
|
||||
#include <poppler.h> // for PopplerDocument
|
||||
|
||||
#include "gui/Builder.h" // for Builder
|
||||
#include "util/Color.h" // for Color
|
||||
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
|
||||
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
|
||||
#include "view/Mask.h" // for Mask
|
||||
|
||||
class LatexController;
|
||||
|
||||
class AbstractLatexDialog {
|
||||
public:
|
||||
AbstractLatexDialog() = delete;
|
||||
AbstractLatexDialog(const AbstractLatexDialog& other) = delete;
|
||||
AbstractLatexDialog& operator=(const AbstractLatexDialog& other) = delete;
|
||||
AbstractLatexDialog(std::unique_ptr<LatexController> texCtrl);
|
||||
virtual ~AbstractLatexDialog();
|
||||
|
||||
public:
|
||||
virtual void setCompilationStatus(bool isTexValid, bool isCompilationDone, const std::string& compilationOutput);
|
||||
|
||||
virtual std::string getBufferContents() = 0;
|
||||
|
||||
/**
|
||||
* Set temporary Tex render and queue a re-draw.
|
||||
* @param pdf PDF document with rendered TeX.
|
||||
*/
|
||||
void setTempRender(PopplerDocument* pdf);
|
||||
|
||||
/**
|
||||
* Set TeX preview background color to the given color.
|
||||
* @param color New preview background color.
|
||||
*/
|
||||
void setPreviewBackgroundColor(Color color);
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Preview DrawingArea draw function
|
||||
*/
|
||||
static void previewDrawFunc(GtkDrawingArea* drawing_area, cairo_t* cr, int width, int height,
|
||||
AbstractLatexDialog* self);
|
||||
|
||||
/**
|
||||
* @brief populate the widgets stored in AbstractLatexDialog from the given builder with their
|
||||
* standard names.
|
||||
*/
|
||||
void populateStandardWidgetsFromBuilder(Builder& builder);
|
||||
|
||||
/**
|
||||
* Connect the cancel button to cancelEditing, the Ok button to insertTexImage as well as the
|
||||
* draw handler for the preview area.
|
||||
*/
|
||||
void connectStandardSignals();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<LatexController> texCtrl;
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
GtkButton* btOk;
|
||||
GtkButton* btCancel;
|
||||
|
||||
// Preview
|
||||
GtkDrawingArea* previewDrawingArea;
|
||||
xoj::view::Mask previewMask;
|
||||
|
||||
/**
|
||||
* Source page from which we render the preview.
|
||||
*/
|
||||
xoj::util::GObjectSPtr<PopplerPage> previewPdfPage;
|
||||
|
||||
GtkLabel* texErrorLabel;
|
||||
GtkTextBuffer* compilationOutputTextBuffer;
|
||||
|
||||
/**
|
||||
* Background color for the LaTeX preview.
|
||||
*/
|
||||
Color previewBackgroundColor;
|
||||
};
|
||||
@@ -1,284 +0,0 @@
|
||||
#include "ButtonConfigGui.h"
|
||||
|
||||
#include <memory> // for allocator, allocator_trai...
|
||||
#include <utility> // for pair
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h> // for GDK_TYPE_PIXBUF, GdkPixbuf
|
||||
#include <gdk/gdk.h> // for GdkRGBA
|
||||
#include <glib-object.h> // for g_value_get_int, GValue
|
||||
|
||||
#include "control/DeviceListHelper.h" // for InputDevice, getDeviceList
|
||||
#include "control/settings/ButtonConfig.h" // for ButtonConfig
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/Builder.h"
|
||||
#include "util/Color.h" // for GdkRGBA_to_argb, rgb_to_G...
|
||||
#include "util/gtk4_helper.h"
|
||||
#include "util/i18n.h" // for _
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "settingsButtonConfig.glade";
|
||||
constexpr auto UI_WIDGET_NAME = "mainBox";
|
||||
|
||||
ButtonConfigGui::ToolSizeIndexMap ButtonConfigGui::toolSizeIndexMap = {{0, TOOL_SIZE_NONE}, {1, TOOL_SIZE_VERY_FINE},
|
||||
{2, TOOL_SIZE_FINE}, {3, TOOL_SIZE_MEDIUM},
|
||||
{4, TOOL_SIZE_THICK}, {5, TOOL_SIZE_VERY_THICK}};
|
||||
|
||||
std::string ButtonConfigGui::toolSizeToLabel(ToolSize size) {
|
||||
switch (size) {
|
||||
case TOOL_SIZE_NONE:
|
||||
return _("Thickness - don't change");
|
||||
case TOOL_SIZE_VERY_FINE:
|
||||
return _("Very thin");
|
||||
case TOOL_SIZE_FINE:
|
||||
return _("Thin");
|
||||
case TOOL_SIZE_MEDIUM:
|
||||
return _("Medium");
|
||||
case TOOL_SIZE_THICK:
|
||||
return _("Thick");
|
||||
case TOOL_SIZE_VERY_THICK:
|
||||
return _("Very thick");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ButtonConfigGui::ButtonConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, unsigned int button,
|
||||
bool withDevice):
|
||||
settings(settings), button(button), withDevice(withDevice), iconNameHelper(settings) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
|
||||
gtk_box_append(box, builder.get(UI_WIDGET_NAME)); // box takes ownership of it all!
|
||||
|
||||
this->cbDevice = builder.get("labelDevice");
|
||||
this->cbDisableDrawing = builder.get("cbDisableDrawing");
|
||||
|
||||
if (withDevice) {
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDevice), _("No device"));
|
||||
|
||||
this->deviceList = DeviceListHelper::getDeviceList(this->settings, true);
|
||||
for (InputDevice const& dev: this->deviceList) {
|
||||
std::string txt = dev.getName() + " (" + dev.getType() + ")";
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDevice), txt.c_str());
|
||||
}
|
||||
} else {
|
||||
gtk_widget_hide(builder.get("lbDevice"));
|
||||
gtk_widget_hide(this->cbDevice);
|
||||
gtk_widget_hide(this->cbDisableDrawing);
|
||||
}
|
||||
|
||||
GtkListStore* typeModel = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
|
||||
|
||||
auto addTypeCB = [&](const char* icon, const char* name, ToolType action) {
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(typeModel, &iter);
|
||||
gtk_list_store_set(typeModel, &iter, 0, iconNameHelper.iconName(icon).c_str(), 1, name, 2, action, -1);
|
||||
};
|
||||
|
||||
addTypeCB("transparent", _("Tool - don't change"), TOOL_NONE);
|
||||
addTypeCB("tool-pencil", _("Pen"), TOOL_PEN);
|
||||
addTypeCB("tool-eraser", _("Eraser"), TOOL_ERASER);
|
||||
addTypeCB("tool-highlighter", _("Highlighter"), TOOL_HIGHLIGHTER);
|
||||
addTypeCB("tool-text", _("Text"), TOOL_TEXT);
|
||||
addTypeCB("tool-image", _("Insert image"), TOOL_IMAGE);
|
||||
addTypeCB("laser-pointer", _("Laser Pointer - Pen"), TOOL_LASER_POINTER_PEN);
|
||||
addTypeCB("laser-pointer", _("Laser Pointer - Highlighter"), TOOL_LASER_POINTER_HIGHLIGHTER);
|
||||
addTypeCB("spacer", _("Vertical space"), TOOL_VERTICAL_SPACE);
|
||||
addTypeCB("select-lasso", _("Select region"), TOOL_SELECT_REGION);
|
||||
addTypeCB("select-rect", _("Select rectangle"), TOOL_SELECT_RECT);
|
||||
addTypeCB("select-multilayer-lasso", _("Select multi layer region"), TOOL_SELECT_MULTILAYER_REGION);
|
||||
addTypeCB("select-multilayer-rect", _("Select multi layer rect"), TOOL_SELECT_MULTILAYER_RECT);
|
||||
addTypeCB("hand", _("Hand"), TOOL_HAND);
|
||||
addTypeCB("floating-toolbox", _("Floating Toolbox (experimental)"), TOOL_FLOATING_TOOLBOX);
|
||||
addTypeCB("select-pdf-text-ht", _("Select Text from pdf"), TOOL_SELECT_PDF_TEXT_LINEAR);
|
||||
addTypeCB("select-pdf-text-area", _("Select Area Text from pdf"), TOOL_SELECT_PDF_TEXT_RECT);
|
||||
|
||||
this->cbTool = builder.get("cbTool");
|
||||
gtk_combo_box_set_model(GTK_COMBO_BOX(this->cbTool), GTK_TREE_MODEL(typeModel));
|
||||
g_signal_connect(cbTool, "changed", G_CALLBACK(&cbSelectCallback), this);
|
||||
|
||||
GtkCellRenderer* renderer = gtk_cell_renderer_pixbuf_new();
|
||||
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(this->cbTool), renderer, false);
|
||||
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(this->cbTool), renderer, "icon-name", 0, nullptr);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new();
|
||||
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(this->cbTool), renderer, true);
|
||||
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(this->cbTool), renderer, "text", 1, nullptr);
|
||||
|
||||
this->cbThickness = builder.get("cbThickness");
|
||||
for (auto const& size: toolSizeIndexMap) {
|
||||
gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(cbThickness), size.first,
|
||||
toolSizeToLabel(size.second).c_str());
|
||||
}
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), 0);
|
||||
|
||||
this->colorButton = builder.get("colorButton");
|
||||
|
||||
this->cbDrawingType = builder.get("cbDrawingType");
|
||||
// DRAWING_TYPE_DONT_CHANGE
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Drawing Type - don't change"));
|
||||
// DRAWING_TYPE_DEFAULT
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Normal drawing"));
|
||||
// DRAWING_TYPE_RULER
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Line"));
|
||||
// DRAWING_TYPE_RECTANGLE
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Rectangle"));
|
||||
// DRAWING_TYPE_ELLIPSE
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Ellipse"));
|
||||
// DRAWING_TYPE_ARROW
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Arrow"));
|
||||
// DRAWING_TYPE_DOUBLE_ARROW
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Double Arrow"));
|
||||
// DRAWING_TYPE_COORDINATE_SYSTEM
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Coordinate System"));
|
||||
// DRAWING_TYPE_STROKE_RECOGNIZER
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Shape Recognizer"));
|
||||
// DRAWING_TYPE_SPLINE
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(this->cbDrawingType), _("Draw Spline"));
|
||||
|
||||
|
||||
// Values in glade GUI!
|
||||
this->cbEraserType = builder.get("cbEraserType");
|
||||
|
||||
// Possible values are defined in the .glade file
|
||||
this->cbStrokeType = builder.get("cbStrokeType");
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
void ButtonConfigGui::loadSettings() {
|
||||
ButtonConfig* cfg = settings->getButtonConfig(button);
|
||||
|
||||
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (!gtk_tree_model_get_iter_first(model, &iter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GValue value = {0};
|
||||
int i = 0;
|
||||
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbTool), 0);
|
||||
|
||||
do {
|
||||
gtk_tree_model_get_value(model, &iter, 2, &value);
|
||||
|
||||
int action = g_value_get_int(&value);
|
||||
|
||||
if (action == cfg->action) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbTool), i);
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
g_value_unset(&value);
|
||||
} while (gtk_tree_model_iter_next(model, &iter));
|
||||
|
||||
// select thickness in combo box
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), 0);
|
||||
for (auto const& size: toolSizeIndexMap) {
|
||||
if (size.second == cfg->size) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbThickness), size.first);
|
||||
}
|
||||
}
|
||||
|
||||
GdkRGBA color = Util::rgb_to_GdkRGBA(cfg->color);
|
||||
gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorButton), &color);
|
||||
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(this->cbDrawingType), cfg->drawingType);
|
||||
|
||||
if (cfg->eraserMode == ERASER_TYPE_DEFAULT) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 1);
|
||||
} else if (cfg->eraserMode == ERASER_TYPE_WHITEOUT) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 2);
|
||||
} else if (cfg->eraserMode == ERASER_TYPE_DELETE_STROKE) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 3);
|
||||
} else {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbEraserType), 0);
|
||||
}
|
||||
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbStrokeType), cfg->strokeType);
|
||||
|
||||
if (withDevice) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbDevice), 0);
|
||||
|
||||
int i = 1;
|
||||
for (InputDevice const& dev: this->deviceList) {
|
||||
if (cfg->device == dev.getName()) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbDevice), i);
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(cbDisableDrawing), cfg->disableDrawing);
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonConfigGui::saveSettings() {
|
||||
GtkTreeIter iter;
|
||||
gtk_combo_box_get_active_iter(GTK_COMBO_BOX(cbTool), &iter);
|
||||
|
||||
GValue value = {0};
|
||||
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
|
||||
gtk_tree_model_get_value(model, &iter, 2, &value);
|
||||
|
||||
auto action = static_cast<ToolType>(g_value_get_int(&value));
|
||||
ButtonConfig* cfg = settings->getButtonConfig(button);
|
||||
cfg->action = action;
|
||||
|
||||
int thickness = gtk_combo_box_get_active(GTK_COMBO_BOX(cbThickness));
|
||||
if (toolSizeIndexMap.count(thickness) == 0) {
|
||||
cfg->size = TOOL_SIZE_NONE;
|
||||
} else {
|
||||
cfg->size = toolSizeIndexMap[thickness];
|
||||
}
|
||||
|
||||
GdkRGBA color;
|
||||
gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorButton), &color);
|
||||
cfg->color = Util::GdkRGBA_to_argb(color);
|
||||
|
||||
cfg->drawingType = static_cast<DrawingType>(gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbDrawingType)));
|
||||
|
||||
int eraserMode = gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbEraserType));
|
||||
|
||||
if (eraserMode == 1) {
|
||||
cfg->eraserMode = ERASER_TYPE_DEFAULT;
|
||||
} else if (eraserMode == 2) {
|
||||
cfg->eraserMode = ERASER_TYPE_WHITEOUT;
|
||||
} else if (eraserMode == 3) {
|
||||
cfg->eraserMode = ERASER_TYPE_DELETE_STROKE;
|
||||
} else {
|
||||
cfg->eraserMode = ERASER_TYPE_NONE;
|
||||
}
|
||||
|
||||
cfg->strokeType = static_cast<StrokeType>(gtk_combo_box_get_active(GTK_COMBO_BOX(this->cbStrokeType)));
|
||||
|
||||
if (this->withDevice) {
|
||||
size_t dev = static_cast<size_t>(gtk_combo_box_get_active(GTK_COMBO_BOX(cbDevice)) - 1);
|
||||
cfg->device = (dev < 0 || this->deviceList.size() <= dev) ? "" : this->deviceList[dev].getName();
|
||||
cfg->disableDrawing = gtk_check_button_get_active(GTK_CHECK_BUTTON(cbDisableDrawing));
|
||||
}
|
||||
|
||||
settings->customSettingsChanged();
|
||||
}
|
||||
|
||||
void ButtonConfigGui::cbSelectCallback(GtkComboBox*, ButtonConfigGui* gui) { gui->enableDisableTools(); }
|
||||
|
||||
void ButtonConfigGui::enableDisableTools() {
|
||||
GtkTreeIter iter;
|
||||
gtk_combo_box_get_active_iter(GTK_COMBO_BOX(cbTool), &iter);
|
||||
GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(cbTool));
|
||||
|
||||
GValue value = {0};
|
||||
gtk_tree_model_get_value(model, &iter, 2, &value);
|
||||
auto action = static_cast<ToolType>(g_value_get_int(&value));
|
||||
|
||||
gtk_widget_set_visible(cbThickness, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_SIZE));
|
||||
gtk_widget_set_visible(colorButton, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_COLOR));
|
||||
gtk_widget_set_visible(cbDrawingType, action != TOOL_NONE && xoj::tool::hasCapability(action, TOOL_CAP_RECTANGLE));
|
||||
gtk_widget_set_visible(cbEraserType, action == TOOL_ERASER);
|
||||
gtk_widget_set_visible(cbStrokeType, action == TOOL_PEN);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The configuration for a button in the Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map> // for map, map<>::value_compare
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkComboBox, GtkWindow
|
||||
|
||||
#include "control/DeviceListHelper.h" // for InputDevice
|
||||
#include "control/ToolEnums.h" // for ToolSize
|
||||
#include "gui/IconNameHelper.h" // for IconNameHelper
|
||||
|
||||
class Settings;
|
||||
class GladeSearchpath;
|
||||
|
||||
class ButtonConfigGui {
|
||||
public:
|
||||
ButtonConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, unsigned int button,
|
||||
bool withDevice);
|
||||
|
||||
public:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
static void cbSelectCallback(GtkComboBox* widget, ButtonConfigGui* gui);
|
||||
void enableDisableTools();
|
||||
std::string toolSizeToLabel(ToolSize size);
|
||||
|
||||
private:
|
||||
Settings* settings;
|
||||
|
||||
GtkWidget* cbDevice;
|
||||
GtkWidget* cbDisableDrawing;
|
||||
|
||||
GtkWidget* cbTool;
|
||||
GtkWidget* cbThickness;
|
||||
GtkWidget* colorButton;
|
||||
GtkWidget* cbEraserType;
|
||||
GtkWidget* cbDrawingType;
|
||||
GtkWidget* cbStrokeType;
|
||||
|
||||
std::vector<InputDevice> deviceList;
|
||||
|
||||
unsigned int button = 0;
|
||||
bool withDevice = false;
|
||||
|
||||
typedef std::map<int, ToolSize> ToolSizeIndexMap;
|
||||
static ToolSizeIndexMap toolSizeIndexMap;
|
||||
IconNameHelper iconNameHelper;
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
#include "DeviceClassConfigGui.h"
|
||||
|
||||
#include <string> // for allocator, char_traits
|
||||
|
||||
#include <glib.h> // for g_ascii_strtoll, g_strdu...
|
||||
|
||||
#include "control/DeviceListHelper.h" // for InputDevice
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "control/settings/SettingsEnums.h" // for InputDeviceTypeOption
|
||||
#include "gui/Builder.h" // for Builder
|
||||
#include "util/Assert.h" // for xoj_assert
|
||||
#include "util/gtk4_helper.h" // for gtk_box_append
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "settingsDeviceClassConfig.glade";
|
||||
constexpr auto UI_WIDGET_NAME = "deviceClassBox";
|
||||
|
||||
DeviceClassConfigGui::DeviceClassConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings,
|
||||
bool showApplyBtn):
|
||||
settings(settings) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
gtk_box_append(box, builder.get(UI_WIDGET_NAME)); // box takes ownership of it all!
|
||||
|
||||
this->labelDevice = builder.get("labelDevice");
|
||||
this->cbDeviceClass = builder.get("cbDeviceClass");
|
||||
auto* applyBtn = builder.get("btnApply");
|
||||
|
||||
gtk_widget_set_visible(applyBtn, showApplyBtn);
|
||||
if (showApplyBtn) {
|
||||
g_signal_connect(applyBtn, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
|
||||
static_cast<DeviceClassConfigGui*>(d)->saveSettings();
|
||||
}),
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceClassConfigGui::~DeviceClassConfigGui() = default;
|
||||
|
||||
void DeviceClassConfigGui::setDevice(const InputDevice& d) {
|
||||
this->device = d;
|
||||
gtk_label_set_text(GTK_LABEL(this->labelDevice), (device.getName() + " (" + device.getType() + ")").c_str());
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
void DeviceClassConfigGui::loadSettings() {
|
||||
// Get device class of device if available or
|
||||
InputDeviceTypeOption deviceType =
|
||||
this->settings->getDeviceClassForDevice(this->device.getName(), this->device.getSource());
|
||||
// Use the ID of each option in case the combo box options get rearranged in the future
|
||||
gtk_combo_box_set_active_id(GTK_COMBO_BOX(this->cbDeviceClass),
|
||||
std::to_string(static_cast<int>(deviceType)).c_str());
|
||||
}
|
||||
|
||||
void DeviceClassConfigGui::saveSettings() {
|
||||
const gchar* deviceClassId = gtk_combo_box_get_active_id(GTK_COMBO_BOX(this->cbDeviceClass));
|
||||
xoj_assert(deviceClassId != nullptr);
|
||||
auto deviceClass = static_cast<InputDeviceTypeOption>(g_ascii_strtoll(deviceClassId, nullptr, 10));
|
||||
this->settings->setDeviceClassForDevice(this->device.getName(), this->device.getSource(), deviceClass);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The configuration for a button in the Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkComboBox, GtkWindow
|
||||
|
||||
#include "control/DeviceListHelper.h" // for InputDevice
|
||||
|
||||
class Settings;
|
||||
class GladeSearchpath;
|
||||
|
||||
class DeviceClassConfigGui {
|
||||
public:
|
||||
DeviceClassConfigGui(GladeSearchpath* gladeSearchPath, GtkBox* box, Settings* settings, bool showApplyBtn = false);
|
||||
~DeviceClassConfigGui();
|
||||
|
||||
public:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
/// Set the device represented by the entry.
|
||||
/// Please call saveSettings() beforehand if the settings of the previous device need to be saved.
|
||||
void setDevice(const InputDevice& device);
|
||||
|
||||
inline bool representsDevice(const InputDevice& dev) const { return dev == device; }
|
||||
|
||||
private:
|
||||
Settings* settings;
|
||||
InputDevice device;
|
||||
|
||||
GtkWidget* labelDevice;
|
||||
GtkWidget* cbDeviceClass;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "DeviceListDialog.h"
|
||||
|
||||
#include "control/settings/Settings.h"
|
||||
#include "gui/Builder.h"
|
||||
#include "util/gtk4_helper.h" // for gtk_box_append, ...
|
||||
|
||||
#include "DeviceClassConfigGui.h"
|
||||
|
||||
constexpr auto UI_FILE_NAME = "deviceList.glade";
|
||||
constexpr auto UI_MAIN_ID = "deviceListDialog";
|
||||
constexpr auto UI_CONTENT_BOX = "contentBox";
|
||||
|
||||
DeviceListDialog::DeviceListDialog(GladeSearchpath* gladeSearchPath, Settings* settings): settings(settings) {
|
||||
|
||||
Builder builder(gladeSearchPath, UI_FILE_NAME);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_MAIN_ID)));
|
||||
|
||||
auto deviceList = DeviceListHelper::getDeviceList(settings);
|
||||
auto* box = GTK_BOX(builder.get(UI_CONTENT_BOX));
|
||||
for (const InputDevice& inputDevice: deviceList) {
|
||||
auto& entry = this->deviceClassConfigs.emplace_back(gladeSearchPath, box, settings);
|
||||
entry.setDevice(inputDevice);
|
||||
}
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](DeviceListDialog* self) {
|
||||
self->saveSettings();
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
void DeviceListDialog::saveSettings() {
|
||||
this->settings->transactionStart();
|
||||
for (auto& deviceClassConfigGui: this->deviceClassConfigs) {
|
||||
deviceClassConfigGui.saveSettings();
|
||||
}
|
||||
this->settings->transactionEnd();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Dialog listing known devices
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class Settings;
|
||||
class GladeSearchpath;
|
||||
class DeviceClassConfigGui;
|
||||
|
||||
class DeviceListDialog {
|
||||
public:
|
||||
DeviceListDialog(GladeSearchpath* gladeSearchPath, Settings* settings);
|
||||
|
||||
public:
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
Settings* settings;
|
||||
std::vector<DeviceClassConfigGui> deviceClassConfigs;
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
};
|
||||
@@ -1,425 +0,0 @@
|
||||
#include "DeviceTestingArea.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <sstream>
|
||||
|
||||
#include <config-git.h>
|
||||
#include <config.h>
|
||||
#include <zip.h>
|
||||
|
||||
#include "control/settings/Settings.h"
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/dialog/XojSaveDlg.h"
|
||||
#include "gui/inputdevices/InputContext.h"
|
||||
#include "gui/inputdevices/InputEvents.h"
|
||||
#include "gui/inputdevices/PrintEvent.h"
|
||||
#include "model/Point.h"
|
||||
#include "util/CircularBuffer.h"
|
||||
#include "util/EnumIndexedArray.h"
|
||||
#include "util/PathUtil.h"
|
||||
#include "util/PopupWindowWrapper.h"
|
||||
#include "util/VersionInfo.h"
|
||||
#include "util/glib_casts.h"
|
||||
#include "util/gtk4_helper.h" // for gtk_box_append, ...
|
||||
#include "util/i18n.h"
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
#include "DeviceClassConfigGui.h"
|
||||
#include "DeviceListDialog.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
constexpr auto UI_FILE_NAME = "deviceTestingArea.glade";
|
||||
constexpr auto UI_MAIN_ID = "mainBox";
|
||||
|
||||
static constexpr double PRESSURE_FACTOR = 20;
|
||||
static constexpr size_t CONTACT_BUTTON_INDEX = 1; ///< Corresponds to button == 1 in GdkEvent
|
||||
static constexpr double PRESSURELESS_INDICATOR_HALF_SIZE = 3;
|
||||
|
||||
static constexpr size_t MAX_NB_BUTTONS = 4;
|
||||
static constexpr std::bitset<MAX_NB_BUTTONS> CONSIDERED_FOR_TIP_EMULATION(0b1100);
|
||||
static constexpr std::bitset<MAX_NB_BUTTONS> MOUSE_BUTTONS_MASK(0b1110);
|
||||
|
||||
static constexpr unsigned int IN_USE_RESET_DELAY = 100; ///< in ms
|
||||
|
||||
struct Ev {
|
||||
Point point;
|
||||
std::bitset<MAX_NB_BUTTONS> pressedButtons;
|
||||
DeviceTestingArea::HandlerType type;
|
||||
};
|
||||
|
||||
|
||||
enum class DevCategory : size_t {
|
||||
MOUSE = 0,
|
||||
STYLUS = 1,
|
||||
ERASER = 2,
|
||||
TOUCHSCREEN = 3,
|
||||
ENUMERATOR_COUNT = 4 ///< Used as bound
|
||||
}; /// Used as indices for buttonsStatus
|
||||
|
||||
struct DeviceTestingArea::Data {
|
||||
CircularBuffer<Ev> lastEvs{20};
|
||||
EnumIndexedArray<std::bitset<MAX_NB_BUTTONS>, DevCategory> buttonsStatus;
|
||||
size_t nbTouchSequences;
|
||||
std::stringstream log; ///< all the events as received (after conversion) by the handlers
|
||||
std::stringstream ellipsizedLog; ///< Same as log, but sequences of MOTION_EVENT's are ellipsized
|
||||
std::stringstream allGdkEvents; ///< all the events, as emitted by GDK
|
||||
size_t nbOfConsecutiveMotionEvents = 0;
|
||||
GdkDevice* lastMotionDevice = nullptr;
|
||||
guint32 timeReference = 0; ///< timestamp of DeviceTestingArea's creation
|
||||
};
|
||||
|
||||
static void draw(GtkDrawingArea*, cairo_t* cr, int width, int height, gpointer lastEvs) {
|
||||
cairo_set_source_rgb(cr, 1., 1., 1.);
|
||||
cairo_paint(cr);
|
||||
cairo_set_source_rgb(cr, 0., 0., 0.);
|
||||
cairo_set_line_width(cr, 2);
|
||||
|
||||
// Draw a frame - using CSS seems not to work for GtkDrawingArea's
|
||||
cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
|
||||
cairo_line_to(cr, 0., 0.);
|
||||
cairo_line_to(cr, width, 0.);
|
||||
cairo_line_to(cr, width, height);
|
||||
cairo_line_to(cr, 0., height);
|
||||
cairo_close_path(cr);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
|
||||
|
||||
for (auto&& e: *static_cast<CircularBuffer<Ev>*>(lastEvs)) {
|
||||
if (e.pressedButtons[CONTACT_BUTTON_INDEX] ||
|
||||
(e.type == DeviceTestingArea::MOUSE && (e.pressedButtons & MOUSE_BUTTONS_MASK).any())) {
|
||||
// Contact
|
||||
if (e.point.z == Point::NO_PRESSURE) {
|
||||
cairo_set_source_rgb(cr, 1., 0., 0.);
|
||||
cairo_move_to(cr, e.point.x, e.point.y);
|
||||
cairo_rel_move_to(cr, -PRESSURELESS_INDICATOR_HALF_SIZE, -PRESSURELESS_INDICATOR_HALF_SIZE);
|
||||
cairo_rel_line_to(cr, 2 * PRESSURELESS_INDICATOR_HALF_SIZE, 2 * PRESSURELESS_INDICATOR_HALF_SIZE);
|
||||
cairo_rel_move_to(cr, -2 * PRESSURELESS_INDICATOR_HALF_SIZE, 0);
|
||||
cairo_rel_line_to(cr, 2 * PRESSURELESS_INDICATOR_HALF_SIZE, -2 * PRESSURELESS_INDICATOR_HALF_SIZE);
|
||||
} else {
|
||||
cairo_set_source_rgb(cr, .4, 1., 0.);
|
||||
cairo_arc(cr, e.point.x, e.point.y, PRESSURE_FACTOR * e.point.z, 0, 2. * M_PI);
|
||||
}
|
||||
} else {
|
||||
// No contact: hovering
|
||||
cairo_set_source_rgb(cr, .5, .5, .5);
|
||||
cairo_move_to(cr, e.point.x, e.point.y);
|
||||
cairo_rel_line_to(cr, 0, 0);
|
||||
}
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
}
|
||||
|
||||
static void saveLog(const std::stringstream& fullLog, const std::stringstream& ellipsizedLog,
|
||||
const std::stringstream& gdkeventslog, GtkWidget* parent, Settings* settings) {
|
||||
|
||||
auto pathValidation = [](fs::path& p, const char* filterName) {
|
||||
Util::clearExtensions(p, ".zip");
|
||||
p += ".zip";
|
||||
return true;
|
||||
};
|
||||
|
||||
auto callback = [fullLog = fullLog.str(), ellipsizedLog = ellipsizedLog.str(), gdkeventslog = gdkeventslog.str(),
|
||||
settings = settings->getSettingsFile()](std::optional<fs::path> p) {
|
||||
if (p && !p->empty()) {
|
||||
int errorCode = 0;
|
||||
auto* zip = zip_open(char_cast(p->u8string().c_str()), ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
|
||||
if (!zip) {
|
||||
// TODO handle error
|
||||
// errorCode contains the error
|
||||
return;
|
||||
}
|
||||
auto addSource = [&](const char* name, zip_source_t* source) {
|
||||
if (!source) {
|
||||
// TODO handle error
|
||||
// zip_strerror(zip) returns the error string
|
||||
} else if (zip_file_add(zip, name, source, ZIP_FL_ENC_UTF_8) < 0) {
|
||||
// TODO handler error
|
||||
// zip_strerror(zip) returns the error string
|
||||
zip_source_free(source);
|
||||
}
|
||||
};
|
||||
addSource("ellipsized-logs.txt", zip_source_buffer(zip, ellipsizedLog.data(), ellipsizedLog.length(), 0));
|
||||
addSource("full-logs.txt", zip_source_buffer(zip, fullLog.data(), fullLog.length(), 0));
|
||||
addSource("input-gdk_events.txt", zip_source_buffer(zip, gdkeventslog.data(), gdkeventslog.length(), 0));
|
||||
#ifdef ZIP_LENGTH_TO_END // Only introduced in libzip 1.10.1 - ubuntu 22 has v1.7.3
|
||||
addSource("settings.xml",
|
||||
zip_source_file(zip, char_cast(settings.u8string().c_str()), 0, ZIP_LENGTH_TO_END));
|
||||
#else
|
||||
addSource("settings.xml", zip_source_file(zip, char_cast(settings.u8string().c_str()), 0, -1));
|
||||
#endif
|
||||
auto version = xoj::util::getVersionInfo();
|
||||
addSource("version.txt", zip_source_buffer(zip, version.data(), version.length(), 0));
|
||||
|
||||
errorCode = zip_close(zip);
|
||||
if (errorCode) {
|
||||
// TODO handle error
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto popup = xoj::popup::PopupWindowWrapper<xoj::SaveExportDialog>(
|
||||
nullptr, fs::path(), _("Export Logs"), _("Export"), std::move(pathValidation), std::move(callback));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
|
||||
GtkFileFilter* filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filter, "ZIP archive");
|
||||
gtk_file_filter_add_mime_type(filter, "application/zip");
|
||||
gtk_file_chooser_add_filter(fc, filter);
|
||||
|
||||
popup.show(GTK_WINDOW(gtk_widget_get_ancestor(parent, GTK_TYPE_WINDOW)));
|
||||
}
|
||||
|
||||
DeviceTestingArea::DeviceTestingArea(GladeSearchpath* gladeSearchPath, GtkBox* parent, Settings* settings):
|
||||
settings(settings),
|
||||
gladeSearchPath(gladeSearchPath),
|
||||
emulateTipContactOnButtonPress(settings->getInputSystemTPCButtonEnabled()),
|
||||
inputContext(std::make_unique<InputContext>(settings, *this)),
|
||||
ancestorScrolledWindow(gtk_widget_get_ancestor(GTK_WIDGET(parent), GTK_TYPE_SCROLLED_WINDOW), xoj::util::ref),
|
||||
data(std::make_unique<Data>()) {
|
||||
data->log << std::setprecision(6);
|
||||
data->log.imbue(std::locale::classic());
|
||||
data->allGdkEvents << std::setprecision(6);
|
||||
data->allGdkEvents.imbue(std::locale::classic());
|
||||
data->ellipsizedLog << std::setprecision(6);
|
||||
data->ellipsizedLog.imbue(std::locale::classic());
|
||||
|
||||
data->timeReference = strict_cast<guint32>(as_unsigned(g_get_monotonic_time()) / 1000);
|
||||
|
||||
Builder builder(gladeSearchPath, UI_FILE_NAME);
|
||||
gtk_box_append(parent, builder.get(UI_MAIN_ID));
|
||||
|
||||
struct D {
|
||||
std::stringstream* log;
|
||||
GtkTextBuffer* view;
|
||||
};
|
||||
g_signal_connect_data(builder.get("logpopover"), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto* dd = static_cast<D*>(d);
|
||||
gtk_text_buffer_set_text(dd->view, dd->log->str().c_str(), -1);
|
||||
}),
|
||||
new D{&data->ellipsizedLog, GTK_TEXT_BUFFER(builder.get<GObject>("logbuffer"))},
|
||||
xoj::util::closure_notify_cb<D>, GConnectFlags(0));
|
||||
|
||||
auto* cbEmulateTipContactOnButtonPress = builder.get("cbEmulateTipContactOnButtonPress");
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(cbEmulateTipContactOnButtonPress), emulateTipContactOnButtonPress);
|
||||
g_signal_connect(cbEmulateTipContactOnButtonPress, "toggled",
|
||||
G_CALLBACK(+[]
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
(GtkToggleButton* btn, gpointer d) {
|
||||
static_cast<DeviceTestingArea*>(d)->emulateTipContactOnButtonPress =
|
||||
gtk_toggle_button_get_active(btn);
|
||||
}),
|
||||
#else
|
||||
(GtkCheckButton* btn, gpointer d) {
|
||||
static_cast<DeviceTestingArea*>(d)->emulateTipContactOnButtonPress =
|
||||
gtk_check_button_get_active(btn);
|
||||
}),
|
||||
#endif
|
||||
this);
|
||||
|
||||
g_signal_connect(builder.get("btnExportLogs"), "clicked", G_CALLBACK(+[](GtkButton* btn, gpointer d) {
|
||||
auto* self = static_cast<DeviceTestingArea*>(d);
|
||||
saveLog(self->data->log, self->data->ellipsizedLog, self->data->allGdkEvents, GTK_WIDGET(btn),
|
||||
self->settings);
|
||||
}),
|
||||
this);
|
||||
|
||||
g_signal_connect(builder.get("btnAllDevices"), "clicked", G_CALLBACK(+[](GtkButton* btn, gpointer d) {
|
||||
auto* self = static_cast<DeviceTestingArea*>(d);
|
||||
auto dlg = xoj::popup::PopupWindowWrapper<DeviceListDialog>(self->gladeSearchPath,
|
||||
self->settings);
|
||||
dlg.show(GTK_WINDOW(gtk_widget_get_ancestor(GTK_WIDGET(btn), GTK_TYPE_WINDOW)));
|
||||
}),
|
||||
this);
|
||||
|
||||
|
||||
lastDeviceClassConfig = std::make_unique<DeviceClassConfigGui>(
|
||||
gladeSearchPath, GTK_BOX(builder.get("boxLastDevice")), settings, true);
|
||||
drawingArea.reset(builder.get("testArea"), xoj::util::ref);
|
||||
|
||||
mouseIndicators[0].reset(builder.get("mouse-in-use"), xoj::util::ref);
|
||||
mouseIndicators[GDK_BUTTON_PRIMARY].reset(builder.get("mouse-left"), xoj::util::ref);
|
||||
mouseIndicators[GDK_BUTTON_MIDDLE].reset(builder.get("mouse-middle"), xoj::util::ref);
|
||||
mouseIndicators[GDK_BUTTON_SECONDARY].reset(builder.get("mouse-right"), xoj::util::ref);
|
||||
stylusIndicators[0].reset(builder.get("stylus-hover"), xoj::util::ref);
|
||||
stylusIndicators[1].reset(builder.get("stylus-tip"), xoj::util::ref);
|
||||
stylusIndicators[2].reset(builder.get("stylus-1"), xoj::util::ref);
|
||||
stylusIndicators[3].reset(builder.get("stylus-2"), xoj::util::ref);
|
||||
eraserIndicators[0].reset(builder.get("eraser-hover"), xoj::util::ref);
|
||||
eraserIndicators[1].reset(builder.get("eraser-tip"), xoj::util::ref);
|
||||
touchIndicators[0].reset(builder.get("touch-1"), xoj::util::ref);
|
||||
touchIndicators[1].reset(builder.get("touch-2"), xoj::util::ref);
|
||||
touchIndicators[2].reset(builder.get("touch-3"), xoj::util::ref);
|
||||
touchIndicators[3].reset(builder.get("touch-4"), xoj::util::ref);
|
||||
touchIndicators[4].reset(builder.get("touch-5"), xoj::util::ref);
|
||||
|
||||
inputContext->connect(drawingArea.get(), /* connectKeyboardHandler */ false, [data = data.get()](GdkEvent* e) {
|
||||
xoj::input::printGdkEvent(data->allGdkEvents, e, data->timeReference);
|
||||
});
|
||||
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(drawingArea.get()), draw, &data->lastEvs, nullptr);
|
||||
}
|
||||
|
||||
DeviceTestingArea::~DeviceTestingArea() = default;
|
||||
|
||||
|
||||
static constexpr const char* handlerTypeToString[] = {"MOUSE", "STYLUS", "TOUCH"};
|
||||
|
||||
|
||||
bool DeviceTestingArea::handle(const InputEvent& e, HandlerType handlerType) {
|
||||
if (e.deviceClass == INPUT_DEVICE_TOUCHSCREEN) {
|
||||
/*
|
||||
* kinetic scrolling will capture touch event if the sequence looks like a scroll.
|
||||
* We do not want to scroll when testing touch devices in the testing area, so we disable it
|
||||
*/
|
||||
if (e.type == ENTER_EVENT) {
|
||||
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(ancestorScrolledWindow.get()), false);
|
||||
} else if (e.type == LEAVE_EVENT) {
|
||||
gtk_scrolled_window_set_kinetic_scrolling(GTK_SCROLLED_WINDOW(ancestorScrolledWindow.get()), true);
|
||||
}
|
||||
}
|
||||
data->log << "Handler " << handlerTypeToString[handlerType] << " received:\n";
|
||||
xoj::input::printEvent(data->log, e, data->timeReference);
|
||||
|
||||
if (e.type != MOTION_EVENT || e.device != data->lastMotionDevice) {
|
||||
if (data->nbOfConsecutiveMotionEvents > 1) {
|
||||
data->ellipsizedLog << " ⦙\n " << data->nbOfConsecutiveMotionEvents - 1
|
||||
<< " motion events omitted\n ⦙\n";
|
||||
}
|
||||
if (e.type == MOTION_EVENT) {
|
||||
data->nbOfConsecutiveMotionEvents = 1;
|
||||
data->lastMotionDevice = e.device;
|
||||
} else {
|
||||
data->nbOfConsecutiveMotionEvents = 0;
|
||||
data->lastMotionDevice = nullptr;
|
||||
}
|
||||
data->ellipsizedLog << "Handler " << handlerTypeToString[handlerType] << " received:\n";
|
||||
xoj::input::printEvent(data->ellipsizedLog, e, data->timeReference);
|
||||
} else {
|
||||
if (data->nbOfConsecutiveMotionEvents == 0) {
|
||||
data->ellipsizedLog << "Handler " << handlerTypeToString[handlerType] << " received:\n";
|
||||
xoj::input::printEvent(data->ellipsizedLog, e, data->timeReference);
|
||||
data->lastMotionDevice = e.device;
|
||||
}
|
||||
data->nbOfConsecutiveMotionEvents++;
|
||||
}
|
||||
|
||||
auto queueEvent = [&]() {
|
||||
auto category = [&]() {
|
||||
switch (e.deviceClass) {
|
||||
case INPUT_DEVICE_MOUSE:
|
||||
return DevCategory::MOUSE;
|
||||
case INPUT_DEVICE_PEN:
|
||||
return DevCategory::STYLUS;
|
||||
case INPUT_DEVICE_ERASER:
|
||||
return DevCategory::ERASER;
|
||||
case INPUT_DEVICE_TOUCHSCREEN:
|
||||
return DevCategory::TOUCHSCREEN;
|
||||
default:
|
||||
return DevCategory::MOUSE;
|
||||
}
|
||||
}();
|
||||
data->lastEvs.push_front(
|
||||
Ev{Point(e.relative.x, e.relative.y, e.pressure), data->buttonsStatus[category], handlerType});
|
||||
gtk_widget_queue_draw(drawingArea.get());
|
||||
};
|
||||
|
||||
// The mouse rarely issues LEAVE_EVENT. We turn off the "in-use" indicator after a short time of inactivity
|
||||
auto resetInUseTimer = [](xoj::util::GSourceURef& timer, GtkWidget* indicator) {
|
||||
if (!timer) {
|
||||
gtk_widget_add_css_class(indicator, "pressed");
|
||||
}
|
||||
struct cbData {
|
||||
GtkWidget* indicator;
|
||||
xoj::util::GSourceURef* timer;
|
||||
};
|
||||
timer = g_timeout_add_full(
|
||||
G_PRIORITY_DEFAULT, IN_USE_RESET_DELAY,
|
||||
+[](gpointer d) {
|
||||
auto* data = static_cast<cbData*>(d);
|
||||
gtk_widget_remove_css_class(data->indicator, "pressed");
|
||||
data->timer->consume();
|
||||
return G_SOURCE_REMOVE;
|
||||
},
|
||||
new cbData{indicator, &timer}, xoj::util::destroy_cb<cbData>);
|
||||
};
|
||||
|
||||
if (e.type == BUTTON_PRESS_EVENT) {
|
||||
lastDeviceClassConfig->setDevice(InputDevice(e.device));
|
||||
if (handlerType == MOUSE && e.button < mouseIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::MOUSE].set(e.button);
|
||||
gtk_widget_add_css_class(mouseIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
|
||||
} else if (handlerType == STYLUS) {
|
||||
if (e.deviceClass == INPUT_DEVICE_PEN && e.button < stylusIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::STYLUS].set(e.button);
|
||||
gtk_widget_add_css_class(stylusIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
|
||||
if (e.button != CONTACT_BUTTON_INDEX && emulateTipContactOnButtonPress &&
|
||||
CONSIDERED_FOR_TIP_EMULATION[e.button]) {
|
||||
data->buttonsStatus[DevCategory::STYLUS].set(CONTACT_BUTTON_INDEX);
|
||||
gtk_widget_add_css_class(stylusIndicators[CONTACT_BUTTON_INDEX].get(), "pressed");
|
||||
}
|
||||
} else if (e.deviceClass == INPUT_DEVICE_ERASER && e.button < eraserIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::ERASER].set(e.button);
|
||||
gtk_widget_add_css_class(eraserIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
|
||||
}
|
||||
} else if (handlerType == TOUCH) {
|
||||
data->nbTouchSequences++;
|
||||
if (data->nbTouchSequences <= 5) {
|
||||
gtk_widget_add_css_class(touchIndicators[data->nbTouchSequences - 1].get(), "pressed");
|
||||
}
|
||||
data->buttonsStatus[DevCategory::TOUCHSCREEN].set(CONTACT_BUTTON_INDEX);
|
||||
}
|
||||
queueEvent();
|
||||
} else if (e.type == BUTTON_RELEASE_EVENT) {
|
||||
if (handlerType == MOUSE && e.button < mouseIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::MOUSE].reset(e.button);
|
||||
gtk_widget_remove_css_class(mouseIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
|
||||
} else if (handlerType == TOUCH) {
|
||||
if (data->nbTouchSequences == 0) {
|
||||
data->log << "ERROR: Terminating more touch sequences than had been started" << std::endl;
|
||||
} else if (data->nbTouchSequences <= 5) {
|
||||
gtk_widget_remove_css_class(touchIndicators[data->nbTouchSequences - 1].get(), "pressed");
|
||||
if (--data->nbTouchSequences == 0) {
|
||||
data->buttonsStatus[DevCategory::TOUCHSCREEN].reset(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (e.deviceClass == INPUT_DEVICE_PEN && e.button < stylusIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::STYLUS].reset(e.button);
|
||||
gtk_widget_remove_css_class(stylusIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
|
||||
if (e.button != CONTACT_BUTTON_INDEX && emulateTipContactOnButtonPress) {
|
||||
data->buttonsStatus[DevCategory::STYLUS].reset(CONTACT_BUTTON_INDEX);
|
||||
gtk_widget_remove_css_class(stylusIndicators[CONTACT_BUTTON_INDEX].get(), "pressed");
|
||||
}
|
||||
} else if (e.deviceClass == INPUT_DEVICE_ERASER && e.button < eraserIndicators.size()) {
|
||||
data->buttonsStatus[DevCategory::ERASER].reset(e.button);
|
||||
gtk_widget_remove_css_class(eraserIndicators[e.button].get(), "pressed");
|
||||
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
|
||||
}
|
||||
}
|
||||
queueEvent();
|
||||
} else if (e.type == MOTION_EVENT) {
|
||||
if (handlerType == MOUSE) {
|
||||
resetInUseTimer(mouseInUseTimer, mouseIndicators[0].get());
|
||||
} else if (handlerType == STYLUS) {
|
||||
if (e.deviceClass == INPUT_DEVICE_PEN) {
|
||||
resetInUseTimer(stylusInUseTimer, stylusIndicators[0].get());
|
||||
} else {
|
||||
resetInUseTimer(eraserInUseTimer, eraserIndicators[0].get());
|
||||
}
|
||||
}
|
||||
queueEvent();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceTestingArea::saveSettings() const {
|
||||
this->settings->setInputSystemTPCButtonEnabled(this->emulateTipContactOnButtonPress);
|
||||
this->lastDeviceClassConfig->saveSettings();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* An area to test input devices
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, ...
|
||||
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
#include "util/raii/GSourceURef.h"
|
||||
|
||||
class Settings;
|
||||
class InputContext;
|
||||
struct InputEvent;
|
||||
class DeviceClassConfigGui;
|
||||
class GladeSearchpath;
|
||||
|
||||
class DeviceTestingArea {
|
||||
public:
|
||||
DeviceTestingArea(GladeSearchpath* path, GtkBox* parent, Settings* settings);
|
||||
~DeviceTestingArea();
|
||||
|
||||
enum HandlerType { MOUSE = 0, STYLUS = 1, TOUCH = 2 };
|
||||
bool handle(const InputEvent& e, HandlerType t);
|
||||
|
||||
void saveSettings() const;
|
||||
|
||||
private:
|
||||
Settings* settings;
|
||||
GladeSearchpath* gladeSearchPath;
|
||||
|
||||
bool emulateTipContactOnButtonPress;
|
||||
|
||||
std::unique_ptr<InputContext> inputContext;
|
||||
xoj::util::WidgetSPtr drawingArea;
|
||||
|
||||
/// Indicators for button status
|
||||
std::array<xoj::util::WidgetSPtr, 4> mouseIndicators; ///< 0 = in use, 1 = left, 2 = middle, 3 = right
|
||||
std::array<xoj::util::WidgetSPtr, 4> stylusIndicators; ///< 0 = in use, 1 = tip, 2 = button-1, 3 = button-2
|
||||
std::array<xoj::util::WidgetSPtr, 2> eraserIndicators; ///< 0 = in use, 1 = tip
|
||||
std::array<xoj::util::WidgetSPtr, 5> touchIndicators;
|
||||
|
||||
/// Mouse "in use" timer: the mouse stays in use 100ms after its last event
|
||||
xoj::util::GSourceURef mouseInUseTimer;
|
||||
xoj::util::GSourceURef stylusInUseTimer;
|
||||
xoj::util::GSourceURef eraserInUseTimer;
|
||||
|
||||
/// Used to disable kinetic scrolling in order to properly test touchscreens
|
||||
xoj::util::WidgetSPtr ancestorScrolledWindow;
|
||||
|
||||
/// Show and edit the configuration of the last device used
|
||||
std::unique_ptr<DeviceClassConfigGui> lastDeviceClassConfig;
|
||||
|
||||
struct Data;
|
||||
std::unique_ptr<Data> data;
|
||||
};
|
||||
|
||||
|
||||
template <typename Handler>
|
||||
class TestingHandler: public Handler {
|
||||
public:
|
||||
explicit TestingHandler(InputContext* context, DeviceTestingArea::HandlerType t, DeviceTestingArea& testing):
|
||||
Handler(context), deviceType(t), testing(testing) {}
|
||||
~TestingHandler() = default;
|
||||
|
||||
bool handleImpl(const InputEvent& e) override { return testing.handle(e, deviceType); }
|
||||
|
||||
private:
|
||||
const DeviceTestingArea::HandlerType deviceType;
|
||||
DeviceTestingArea& testing;
|
||||
};
|
||||
@@ -1,192 +0,0 @@
|
||||
#include "ExportDialog.h"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <stdexcept> // for invalid_argument
|
||||
#include <string> // for string, to_string, operator+
|
||||
#include <vector> // for allocator
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "util/ElementRange.h" // for parse, PageRangeVector
|
||||
#include "util/gtk4_helper.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "exportSettings.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "exportDialog";
|
||||
|
||||
using namespace xoj::popup;
|
||||
|
||||
ExportDialog::ExportDialog(GladeSearchpath* gladeSearchPath, ExportGraphicsFormat format, size_t currentPage,
|
||||
size_t pageCount, bool hasPdfBackground,
|
||||
std::function<void(const ExportDialog&)> callbackFun):
|
||||
currentPage(currentPage), pageCount(pageCount), builder(gladeSearchPath, UI_FILE), callbackFun(callbackFun) {
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbAllPagesInfo")), ("1 - " + std::to_string(pageCount)).c_str());
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbCurrentPage")), std::to_string(currentPage).c_str());
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
// Widgets are visible by default in gtk4
|
||||
gtk_widget_show_all(builder.get("dialog-main-box"));
|
||||
#endif
|
||||
|
||||
auto removeQualitySetting = [&builder = this->builder]() {
|
||||
gtk_widget_hide(builder.get("lbQuality"));
|
||||
gtk_widget_hide(builder.get("boxQuality"));
|
||||
gtk_widget_hide(builder.get("cbQuality"));
|
||||
};
|
||||
|
||||
if (format == EXPORT_GRAPHICS_PDF) {
|
||||
removeQualitySetting();
|
||||
auto* cbt = GTK_COMBO_BOX_TEXT(builder.get("cbtPdfExportBackend"));
|
||||
for (auto&& [id, name]: ExportBackend::getPrettyNamesOfAvailableBackends()) {
|
||||
gtk_combo_box_text_append(cbt, id, name);
|
||||
}
|
||||
gtk_combo_box_set_active_id(GTK_COMBO_BOX(cbt), ExportBackend::DEFAULT_ID_STRING);
|
||||
if (!hasPdfBackground) {
|
||||
// No need for backend selection if no pdf background is present.
|
||||
gtk_widget_set_sensitive(builder.get("boxPdfBackend"), false);
|
||||
}
|
||||
} else if (format == EXPORT_GRAPHICS_PNG) {
|
||||
gtk_widget_hide(builder.get("cbProgressiveMode"));
|
||||
gtk_widget_hide(builder.get("boxPdfBackend"));
|
||||
} else { // (format == EXPORT_GRAPHICS_SVG)
|
||||
removeQualitySetting();
|
||||
gtk_widget_hide(builder.get("cbProgressiveMode"));
|
||||
gtk_widget_hide(builder.get("boxPdfBackend"));
|
||||
}
|
||||
|
||||
// rdRangePages toggled signal handler
|
||||
//
|
||||
// Sets and unsets the sensitivity of the text form, OK button and
|
||||
// displays an error when the text form is selected and contains
|
||||
// invalid user data. The error goes away when the text form is de-selected.
|
||||
auto toggledHandler = G_CALLBACK(+[](GtkCheckButton* button, ExportDialog* self) {
|
||||
auto active = gtk_check_button_get_active(button);
|
||||
auto btOk = self->builder.get("btOk");
|
||||
auto txtPages = self->builder.get("txtPages");
|
||||
gtk_widget_set_sensitive(txtPages, active);
|
||||
if (active) {
|
||||
const std::string text_form(gtk_editable_get_text(GTK_EDITABLE(txtPages)));
|
||||
try {
|
||||
ElementRange::parse(text_form, self->pageCount);
|
||||
gtk_widget_remove_css_class(txtPages, "error");
|
||||
gtk_widget_set_sensitive(btOk, true);
|
||||
} catch (const std::invalid_argument&) {
|
||||
gtk_widget_add_css_class(txtPages, "error");
|
||||
gtk_widget_set_sensitive(btOk, false);
|
||||
}
|
||||
} else {
|
||||
gtk_widget_remove_css_class(txtPages, "error");
|
||||
gtk_widget_set_sensitive(btOk, true);
|
||||
}
|
||||
});
|
||||
// txtPages changed signal handler
|
||||
//
|
||||
// Displays an error when the user inputs invalid page ranges in the text form,
|
||||
// and sets the sensitivity of the OK button to FALSE. These actions are
|
||||
// reversed when the text form contains a valid page range.
|
||||
auto changedHandler = G_CALLBACK(+[](GtkEditable* txtPages, ExportDialog* self) {
|
||||
const std::string text_form(gtk_editable_get_text(txtPages));
|
||||
auto btOk = self->builder.get("btOk");
|
||||
try {
|
||||
ElementRange::parse(text_form, self->pageCount);
|
||||
gtk_widget_remove_css_class(GTK_WIDGET(txtPages), "error");
|
||||
gtk_widget_set_sensitive(btOk, true);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
gtk_widget_add_css_class(GTK_WIDGET(txtPages), "error");
|
||||
gtk_widget_set_sensitive(btOk, false);
|
||||
}
|
||||
});
|
||||
|
||||
g_signal_connect(builder.get("rdRangePages"), "toggled", toggledHandler, this);
|
||||
g_signal_connect(builder.get("cbQuality"), "changed", G_CALLBACK(ExportDialog::selectQualityCriterion), this);
|
||||
g_signal_connect(builder.get("txtPages"), "changed", changedHandler, this);
|
||||
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(ExportDialog::onSuccessCallback), this);
|
||||
|
||||
/**
|
||||
* By calling this->callbackFun() here, we make sure that `control->unblock()` is run even if the user clicks on
|
||||
* the close-window button.
|
||||
*
|
||||
* The callback returns `false` so that the PopupWindowManager callback deleting `this` gets called as well.
|
||||
*/
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
g_signal_connect_swapped(window.get(), "delete-event", G_CALLBACK(+[](ExportDialog* self) {
|
||||
self->callbackFun(*self);
|
||||
return false;
|
||||
}),
|
||||
this);
|
||||
#else
|
||||
g_signal_connect_swapped(window.get(), "close-request", G_CALLBACK(+[](ExportDialog* self) {
|
||||
self->callbackFun(*self);
|
||||
return false;
|
||||
}),
|
||||
this);
|
||||
#endif
|
||||
}
|
||||
|
||||
ExportDialog::~ExportDialog() = default;
|
||||
|
||||
void ExportDialog::selectQualityCriterion(GtkComboBox* comboBox, ExportDialog* self) {
|
||||
int activeCriterion = gtk_combo_box_get_active(comboBox);
|
||||
switch (activeCriterion) {
|
||||
case EXPORT_QUALITY_DPI:
|
||||
gtk_label_set_text(GTK_LABEL(self->builder.get("lbQualityUnit")), "dpi");
|
||||
gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue")),
|
||||
GTK_ADJUSTMENT(self->builder.get<GObject>("adjustmentDpi")));
|
||||
break;
|
||||
case EXPORT_QUALITY_WIDTH:
|
||||
case EXPORT_QUALITY_HEIGHT:
|
||||
gtk_label_set_text(GTK_LABEL(self->builder.get("lbQualityUnit")), "px");
|
||||
gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue")),
|
||||
GTK_ADJUSTMENT(self->builder.get<GObject>("adjustmentHeightWidth")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::onSuccessCallback(ExportDialog* self) {
|
||||
self->confirmed = true;
|
||||
self->progressiveMode = gtk_check_button_get_active(GTK_CHECK_BUTTON(self->builder.get("cbProgressiveMode")));
|
||||
self->backgroundType = static_cast<ExportBackgroundType>(
|
||||
gtk_combo_box_get_active(GTK_COMBO_BOX(self->builder.get("cbBackgroundType"))));
|
||||
self->pageRanges = [self]() {
|
||||
GtkWidget* rdRangeCurrent = self->builder.get("rdRangeCurrent");
|
||||
GtkWidget* rdRangePages = self->builder.get("rdRangePages");
|
||||
|
||||
if (gtk_check_button_get_active(GTK_CHECK_BUTTON(rdRangePages))) {
|
||||
return ElementRange::parse(gtk_editable_get_text(GTK_EDITABLE(self->builder.get("txtPages"))),
|
||||
self->pageCount);
|
||||
}
|
||||
if (gtk_check_button_get_active(GTK_CHECK_BUTTON(rdRangeCurrent))) {
|
||||
PageRangeVector range;
|
||||
range.emplace_back(self->currentPage - 1, self->currentPage - 1);
|
||||
return range;
|
||||
}
|
||||
|
||||
PageRangeVector range;
|
||||
range.emplace_back(0, self->pageCount - 1);
|
||||
return range;
|
||||
}();
|
||||
self->pdfExportBackend = ExportBackend::fromString(
|
||||
gtk_combo_box_get_active_id(GTK_COMBO_BOX(self->builder.get("cbtPdfExportBackend"))));
|
||||
self->qualityParameter = RasterImageQualityParameter(
|
||||
static_cast<ExportQualityCriterion>(
|
||||
gtk_combo_box_get_active(GTK_COMBO_BOX(self->builder.get("cbQuality")))),
|
||||
gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(self->builder.get("sbQualityValue"))));
|
||||
gtk_window_close(self->window.get());
|
||||
}
|
||||
|
||||
auto ExportDialog::getPngQualityParameter() const -> RasterImageQualityParameter { return qualityParameter; }
|
||||
|
||||
auto ExportDialog::isConfirmed() const -> bool { return this->confirmed; }
|
||||
|
||||
auto ExportDialog::progressiveModeSelected() const -> bool { return this->progressiveMode; }
|
||||
|
||||
auto ExportDialog::getBackgroundType() const -> ExportBackgroundType { return backgroundType; }
|
||||
|
||||
auto ExportDialog::getRange() const -> const PageRangeVector& { return pageRanges; }
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Dialog with export settings
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional>
|
||||
|
||||
#include <gtk/gtk.h> // for GtkComboBox, GtkWindow
|
||||
|
||||
#include "control/jobs/BaseExportJob.h" // for ExportBackgroundType
|
||||
#include "control/jobs/ImageExport.h" // for RasterImageQualityParameter
|
||||
#include "gui/Builder.h"
|
||||
#include "pdf/base/PdfExportBackend.h"
|
||||
#include "util/ElementRange.h" // for PageRangeVector
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
namespace xoj::popup {
|
||||
class ExportDialog {
|
||||
public:
|
||||
ExportDialog(GladeSearchpath* gladeSearchPath, ExportGraphicsFormat format, size_t currentPage, size_t pageCount,
|
||||
bool hasPdfBackground, std::function<void(const ExportDialog&)> callbackFun);
|
||||
~ExportDialog();
|
||||
|
||||
public:
|
||||
bool isConfirmed() const;
|
||||
const PageRangeVector& getRange() const;
|
||||
bool progressiveModeSelected() const;
|
||||
ExportBackgroundType getBackgroundType() const;
|
||||
inline ExportBackend getPdfExportBackend() const { return pdfExportBackend; }
|
||||
|
||||
/**
|
||||
* @brief Reads the quality parameter from the dialog
|
||||
*
|
||||
* @return The selected quality parameter
|
||||
*/
|
||||
RasterImageQualityParameter getPngQualityParameter() const;
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Handler for changes in combobox cbQuality
|
||||
*/
|
||||
static void selectQualityCriterion(GtkComboBox* comboBox, ExportDialog* self);
|
||||
|
||||
static void onSuccessCallback(ExportDialog* self);
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
|
||||
size_t currentPage = 0;
|
||||
size_t pageCount = 0;
|
||||
|
||||
bool confirmed = false;
|
||||
bool progressiveMode;
|
||||
ExportBackgroundType backgroundType;
|
||||
ExportBackend pdfExportBackend;
|
||||
RasterImageQualityParameter qualityParameter;
|
||||
PageRangeVector pageRanges;
|
||||
|
||||
Builder builder;
|
||||
|
||||
std::function<void(const ExportDialog&)> callbackFun;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
@@ -1,192 +0,0 @@
|
||||
#include "ExtEdLatexDialog.h"
|
||||
|
||||
#include <fstream> // for ofstream
|
||||
#include <system_error>
|
||||
|
||||
#include <gio/gio.h> // for g_subprocess*
|
||||
#include <glib-object.h> // for g_object_unref, g_clear_object
|
||||
#include <glib.h>
|
||||
#include <gtk/gtk.h> // for gtk_widget_set_sensitive
|
||||
|
||||
#include "control/Control.h"
|
||||
#include "control/LatexController.h"
|
||||
#include "control/settings/LatexSettings.h"
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/GladeSearchpath.h"
|
||||
#include "util/PathUtil.h" // for readString
|
||||
#include "util/XojMsgBox.h" // for XojMsgBox
|
||||
#include "util/i18n.h" // for FS, _, _F, N_
|
||||
#include "util/raii/GLibGuards.h" // for GErrorGuard, GStrvGuard
|
||||
|
||||
#include "AbstractLatexDialog.h"
|
||||
|
||||
constexpr auto UI_FILE_NAME = "extEdTexDialog.glade";
|
||||
constexpr auto UI_DIALOG_ID = "extEdTexDialog";
|
||||
|
||||
ExtEdLatexDialog::ExtEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> ctrl):
|
||||
AbstractLatexDialog(std::move(ctrl)) {
|
||||
Builder builder(gladeSearchPath, UI_FILE_NAME);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_ID)));
|
||||
populateStandardWidgetsFromBuilder(builder);
|
||||
btContEdit = GTK_BUTTON(builder.get("btContEdit"));
|
||||
connectStandardSignals();
|
||||
|
||||
tempf = texCtrl->texTmpDir / "edit.";
|
||||
|
||||
// Append file extension.
|
||||
tempf += texCtrl->settings.temporaryFileExt;
|
||||
|
||||
// Write initial content
|
||||
std::ofstream os{tempf};
|
||||
if (!texCtrl->initialTex.empty()) {
|
||||
os << texCtrl->initialTex;
|
||||
} else {
|
||||
os << texCtrl->settings.defaultText;
|
||||
}
|
||||
os.close();
|
||||
|
||||
g_signal_connect(getWindow(), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto self = static_cast<ExtEdLatexDialog*>(d);
|
||||
if (!self->texCtrl->temporaryRender) {
|
||||
// Trigger an asynchronous compilation if we are not using a preexisting TexImage
|
||||
// Keep this after popup.show() so that if an error message is to be displayed (e.g.
|
||||
// missing Tex executable), it'll appear on top of the LatexDialog.
|
||||
LatexController::handleTexChanged(self->texCtrl.get());
|
||||
}
|
||||
|
||||
self->openEditor();
|
||||
}),
|
||||
this);
|
||||
g_signal_connect(btContEdit, "clicked", G_CALLBACK(+[](GtkButton*, gpointer d) {
|
||||
auto* self = static_cast<ExtEdLatexDialog*>(d);
|
||||
self->openEditor();
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
ExtEdLatexDialog::~ExtEdLatexDialog() {
|
||||
try {
|
||||
fs::remove(tempf);
|
||||
} catch (const std::system_error& e) {
|
||||
g_warning("Failed to remove LaTeX edit file: %s", e.what());
|
||||
}
|
||||
if (editorCancellable) {
|
||||
g_cancellable_cancel(editorCancellable);
|
||||
g_object_unref(editorCancellable);
|
||||
}
|
||||
};
|
||||
|
||||
void ExtEdLatexDialog::setCompilationStatus(bool isTexValid, bool isCompilationDone,
|
||||
const std::string& compilationOutput) {
|
||||
if (texCtrl->settings.externalEditorAutoConfirm && !editorCancellable && isTexValid && isCompilationDone) {
|
||||
texCtrl->insertTexImage();
|
||||
gtk_window_close(getWindow());
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractLatexDialog::setCompilationStatus(isTexValid, isCompilationDone, compilationOutput);
|
||||
};
|
||||
|
||||
void ExtEdLatexDialog::openEditor() {
|
||||
if (editorCancellable) {
|
||||
return;
|
||||
}
|
||||
|
||||
xoj::util::GErrorGuard err{};
|
||||
xoj::util::GStrvGuard argv{};
|
||||
|
||||
std::string editorCmd = texCtrl->settings.externalEditorCmd;
|
||||
if (editorCmd.empty()) {
|
||||
auto maybeEditor = g_getenv("EDITOR");
|
||||
if (!maybeEditor) {
|
||||
XojMsgBox::showErrorToUser(
|
||||
getWindow(),
|
||||
"No external editor configured and no $EDITOR environment variable set. Please configure an "
|
||||
"external editor command or disable the external editor functionality to use a built-in one.");
|
||||
return;
|
||||
}
|
||||
editorCmd = maybeEditor;
|
||||
}
|
||||
editorCmd += " '";
|
||||
editorCmd += char_cast(tempf.u8string());
|
||||
editorCmd += "'";
|
||||
|
||||
if (!g_shell_parse_argv(editorCmd.c_str(), nullptr, xoj::util::out_ptr(argv), xoj::util::out_ptr(err))) {
|
||||
XojMsgBox::showErrorToUser(getWindow(), FS(_F("Failed to parse external editor command: {1}") % err->message));
|
||||
return;
|
||||
}
|
||||
|
||||
xoj::util::GObjectSPtr<GSubprocessLauncher> launcher(g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE),
|
||||
xoj::util::adopt);
|
||||
|
||||
Color textColor = texCtrl->control->getToolHandler()->getTool(TOOL_TEXT).getColor();
|
||||
std::string colorStr = Util::rgb_to_hex_string(textColor).substr(1);
|
||||
|
||||
g_subprocess_launcher_setenv(launcher.get(), "XPP_TEXT_COLOR", colorStr.c_str(), TRUE);
|
||||
|
||||
xoj::util::GObjectSPtr<GSubprocess> process(
|
||||
g_subprocess_launcher_spawnv(launcher.get(), argv.get(), xoj::util::out_ptr(err)), xoj::util::adopt);
|
||||
|
||||
if (err) {
|
||||
XojMsgBox::showErrorToUser(getWindow(), FS(_F("Could not spawn editor: {1}") % err->message));
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(btContEdit), false);
|
||||
|
||||
editorCancellable = g_cancellable_new();
|
||||
g_subprocess_wait_async(process.get(), editorCancellable, reinterpret_cast<GAsyncReadyCallback>(editorWaitCallback),
|
||||
this);
|
||||
}
|
||||
|
||||
void ExtEdLatexDialog::editorWaitCallback(GObject* processObj, GAsyncResult* res, ExtEdLatexDialog* self) {
|
||||
auto process = G_SUBPROCESS(processObj);
|
||||
|
||||
xoj::util::GErrorGuard err;
|
||||
if (!g_subprocess_wait_finish(process, res, xoj::util::out_ptr(err))) {
|
||||
// If we cancelled the operation, that's because the user closed the popup while the editor was still open. We
|
||||
// must not interact with self now, because that's likely already been destroyed. We kill the editor and then
|
||||
// return.
|
||||
if (g_error_matches(err.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
#ifdef G_OS_UNIX
|
||||
g_subprocess_send_signal(process, SIGTERM);
|
||||
#else
|
||||
// The g_subprocess_send_signal API is unavailable on non-unix platforms, so we use the cross-platform
|
||||
// g_subprocess_force_exit function. We don't use it on supported platforms so we can send a "nicer" exit
|
||||
// signal if available, as g_subprocess_force_exit sends SIGKILL on Unices.
|
||||
g_subprocess_force_exit(process);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
XojMsgBox::showErrorToUser(self->getWindow(), FS(_F("Could not wait for editor: {1}") % err->message));
|
||||
return;
|
||||
}
|
||||
|
||||
g_clear_object(&self->editorCancellable);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(self->btContEdit), true);
|
||||
|
||||
auto status = g_subprocess_get_status(process);
|
||||
if (status != 0) {
|
||||
XojMsgBox::showErrorToUser(self->getWindow(), FS(_F("Editor exited with bad status {1}") % status));
|
||||
return;
|
||||
}
|
||||
|
||||
auto content = self->getBufferContents();
|
||||
|
||||
self->texCtrl->triggerImageUpdate(content);
|
||||
}
|
||||
|
||||
std::string ExtEdLatexDialog::getBufferContents() {
|
||||
auto content = Util::readString(tempf);
|
||||
if (!content) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Remove a possible trailing newline
|
||||
if (!content->empty() && content->back() == '\n') {
|
||||
content->pop_back();
|
||||
}
|
||||
|
||||
return *content;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Latex dialog for an external editor
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <glib.h> // for gpointer
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
#include "gui/GladeSearchpath.h"
|
||||
#include "gui/dialog/AbstractLatexDialog.h"
|
||||
|
||||
class LatexController;
|
||||
|
||||
class ExtEdLatexDialog final: public AbstractLatexDialog {
|
||||
public:
|
||||
ExtEdLatexDialog() = delete;
|
||||
ExtEdLatexDialog(const ExtEdLatexDialog& other) = delete;
|
||||
ExtEdLatexDialog& operator=(const ExtEdLatexDialog& other) = delete;
|
||||
ExtEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> texCtrl);
|
||||
~ExtEdLatexDialog() override;
|
||||
|
||||
std::string getBufferContents() override;
|
||||
|
||||
private:
|
||||
void setCompilationStatus(bool isTexValid, bool isCompilationDone, const std::string& compilationOutput) override;
|
||||
|
||||
/**
|
||||
* @brief a callback for the editor exiting
|
||||
*/
|
||||
static void editorWaitCallback(GObject* processObj, GAsyncResult* res, ExtEdLatexDialog* self);
|
||||
|
||||
/**
|
||||
* @brief Open the editor or do nothing if it's already open.
|
||||
*/
|
||||
void openEditor();
|
||||
|
||||
private:
|
||||
GtkButton* btContEdit;
|
||||
fs::path tempf;
|
||||
GCancellable* editorCancellable = nullptr;
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "FileChooserFiltersHelper.h"
|
||||
|
||||
#include "util/i18n.h"
|
||||
|
||||
namespace xoj {
|
||||
static void addMimeTypeFilter(GtkFileChooser* fc, const char* name, const char* mime) {
|
||||
GtkFileFilter* filterPdf = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterPdf, name);
|
||||
gtk_file_filter_add_mime_type(filterPdf, mime);
|
||||
gtk_file_chooser_add_filter(fc, filterPdf);
|
||||
}
|
||||
|
||||
void addFilterAllFiles(GtkFileChooser* fc) {
|
||||
GtkFileFilter* filterAll = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterAll, _("All files"));
|
||||
gtk_file_filter_add_pattern(filterAll, "*");
|
||||
gtk_file_chooser_add_filter(fc, filterAll);
|
||||
}
|
||||
|
||||
void addFilterSupported(GtkFileChooser* fc) {
|
||||
GtkFileFilter* filterSupported = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterSupported, _("Supported files"));
|
||||
gtk_file_filter_add_mime_type(filterSupported, "application/x-xojpp");
|
||||
gtk_file_filter_add_mime_type(filterSupported, "application/x-xopp");
|
||||
gtk_file_filter_add_mime_type(filterSupported, "application/x-xopt");
|
||||
gtk_file_filter_add_mime_type(filterSupported, "application/pdf");
|
||||
gtk_file_filter_add_pattern(filterSupported, "*.moj"); // MrWriter
|
||||
gtk_file_chooser_add_filter(fc, filterSupported);
|
||||
}
|
||||
|
||||
void addFilterPdf(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("PDF files"), "application/pdf"); }
|
||||
void addFilterXoj(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal files"), "application/x-xojpp"); }
|
||||
void addFilterXopp(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal++ files"), "application/x-xopp"); }
|
||||
void addFilterXopt(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("Xournal++ template"), "application/x-xopt"); }
|
||||
void addFilterSvg(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("SVG graphics"), "image/svg+xml"); }
|
||||
void addFilterPng(GtkFileChooser* fc) { addMimeTypeFilter(fc, _("PNG graphics"), "image/png"); }
|
||||
|
||||
void addFilterImages(GtkFileChooser* fc) {
|
||||
GtkFileFilter* filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filter, _("Image files"));
|
||||
gtk_file_filter_add_pixbuf_formats(filter);
|
||||
gtk_file_chooser_add_filter(fc, filter);
|
||||
}
|
||||
}; // namespace xoj
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Helper functions to add filters to GtkFileChooserDialogs
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace xoj {
|
||||
void addFilterAllFiles(GtkFileChooser* fc);
|
||||
void addFilterSupported(GtkFileChooser* fc);
|
||||
void addFilterPdf(GtkFileChooser* fc);
|
||||
void addFilterXoj(GtkFileChooser* fc);
|
||||
void addFilterXopp(GtkFileChooser* fc);
|
||||
void addFilterXopt(GtkFileChooser* fc);
|
||||
void addFilterSvg(GtkFileChooser* fc);
|
||||
void addFilterPng(GtkFileChooser* fc);
|
||||
void addFilterImages(GtkFileChooser* fc); ///< All images supported by GdkPixbuf
|
||||
}; // namespace xoj
|
||||
@@ -1,216 +0,0 @@
|
||||
#include "FormatDialog.h"
|
||||
|
||||
#include <string> // for operator==, string, basic_string
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
#include <glib.h> // for g_list_free, GList
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/PaperFormatUtils.h" // for PaperFormatUtils
|
||||
#include "model/FormatDefinitions.h" // for FormatUnits, XOJ_UNITS, XOJ_U...
|
||||
#include "util/GListView.h" // for GListView, GListView<>::GList...
|
||||
#include "util/StringUtils.h" // for StringUtils
|
||||
#include "util/i18n.h" // for _
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "pageFormat.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "pageFormatDialog";
|
||||
|
||||
using namespace xoj::popup;
|
||||
using xoj::util::GtkPaperSizeUPtr;
|
||||
|
||||
FormatDialog::FormatDialog(GladeSearchpath* gladeSearchPath, Settings* settings, double width, double height,
|
||||
std::function<void(double, double)> callback):
|
||||
settings(settings),
|
||||
selectedScale(settings->getSizeUnitIndex()),
|
||||
scale(XOJ_UNITS[selectedScale].scale),
|
||||
origWidth(width),
|
||||
origHeight(height),
|
||||
callbackFun(callback) {
|
||||
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
|
||||
paperTemplatesCombo = GTK_COMBO_BOX(builder.get("cbTemplate"));
|
||||
landscapeButton = GTK_TOGGLE_BUTTON(builder.get("btLandscape"));
|
||||
portraitButton = GTK_TOGGLE_BUTTON(builder.get("btPortrait"));
|
||||
widthSpin = GTK_SPIN_BUTTON(builder.get("spinWidth"));
|
||||
heightSpin = GTK_SPIN_BUTTON(builder.get("spinHeight"));
|
||||
|
||||
GtkWidget* cbUnit = builder.get("cbUnit");
|
||||
|
||||
for (int i = 0; i < XOJ_UNIT_COUNT; i++) {
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbUnit), XOJ_UNITS[i].name);
|
||||
}
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(cbUnit), this->selectedScale);
|
||||
|
||||
|
||||
PaperFormatUtils::loadDefaultPaperSizes(paperSizes);
|
||||
paperSizes.emplace_back(_("Custom"));
|
||||
PaperFormatUtils::fillPaperFormatDropDown(paperSizes, paperTemplatesCombo);
|
||||
paperSizes.pop_back(); // Remove the custom option to prevent it from being matched in the
|
||||
// spinValueChangedCb function, as its presence would lead to errors
|
||||
|
||||
reset(this);
|
||||
|
||||
g_signal_connect(landscapeButton, "toggled", G_CALLBACK(landscapeSelectedCb), this);
|
||||
g_signal_connect(portraitButton, "toggled", G_CALLBACK(portraitSelectedCb), this);
|
||||
g_signal_connect(paperTemplatesCombo, "changed", G_CALLBACK(cbFormatChangedCb), this);
|
||||
g_signal_connect(cbUnit, "changed", G_CALLBACK(cbUnitChanged), this);
|
||||
|
||||
g_signal_connect_swapped(widthSpin, "value-changed", G_CALLBACK(spinValueChangedCb), this);
|
||||
g_signal_connect_swapped(heightSpin, "value-changed", G_CALLBACK(spinValueChangedCb), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btReset"), "clicked", G_CALLBACK(reset), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](FormatDialog* self) {
|
||||
self->settings->setSizeUnitIndex(self->selectedScale);
|
||||
self->callbackFun(gtk_spin_button_get_value(self->widthSpin) * self->scale,
|
||||
gtk_spin_button_get_value(self->heightSpin) * self->scale);
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
void FormatDialog::setOrientation(Orientation orientation) {
|
||||
if (this->orientation == orientation) {
|
||||
return;
|
||||
}
|
||||
this->orientation = orientation;
|
||||
|
||||
gtk_toggle_button_set_active(portraitButton, orientation == ORIENTATION_PORTRAIT);
|
||||
gtk_toggle_button_set_active(landscapeButton, orientation == ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
void FormatDialog::spinValueChangedCb(FormatDialog* dlg) {
|
||||
if (dlg->ignoreSpinChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
double width = gtk_spin_button_get_value(dlg->widthSpin) * dlg->scale;
|
||||
double height = gtk_spin_button_get_value(dlg->heightSpin) * dlg->scale;
|
||||
|
||||
if (width < height) {
|
||||
dlg->setOrientation(ORIENTATION_PORTRAIT);
|
||||
} else if (width > height) {
|
||||
dlg->setOrientation(ORIENTATION_LANDSCAPE);
|
||||
} else {
|
||||
dlg->setOrientation(ORIENTATION_NOT_DEFINED);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (auto& size: dlg->paperSizes) {
|
||||
xoj_assert(std::holds_alternative<GtkPaperSizeUPtr>(size));
|
||||
double w = gtk_paper_size_get_width(std::get<GtkPaperSizeUPtr>(size).get(), GTK_UNIT_POINTS);
|
||||
double h = gtk_paper_size_get_height(std::get<GtkPaperSizeUPtr>(size).get(), GTK_UNIT_POINTS);
|
||||
|
||||
if ((static_cast<int>(w - width) == 0 && static_cast<int>(h - height) == 0) ||
|
||||
(static_cast<int>(h - width) == 0 && static_cast<int>(w - height) == 0)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
gtk_combo_box_set_active(dlg->paperTemplatesCombo, i);
|
||||
}
|
||||
|
||||
void FormatDialog::cbUnitChanged(GtkComboBox* widget, FormatDialog* dlg) {
|
||||
int selectd = gtk_combo_box_get_active(widget);
|
||||
if (dlg->selectedScale == selectd) {
|
||||
return;
|
||||
}
|
||||
|
||||
double width = gtk_spin_button_get_value(dlg->widthSpin) * dlg->scale;
|
||||
double height = gtk_spin_button_get_value(dlg->heightSpin) * dlg->scale;
|
||||
|
||||
dlg->selectedScale = selectd;
|
||||
dlg->scale = XOJ_UNITS[dlg->selectedScale].scale;
|
||||
|
||||
dlg->setSpinValues(width / dlg->scale, height / dlg->scale);
|
||||
}
|
||||
|
||||
void FormatDialog::cbFormatChangedCb(GtkComboBox* widget, FormatDialog* dlg) {
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (!gtk_combo_box_get_active_iter(widget, &iter)) {
|
||||
return;
|
||||
}
|
||||
GtkTreeModel* model = gtk_combo_box_get_model(widget);
|
||||
|
||||
GValue value = {0};
|
||||
gtk_tree_model_get_value(model, &iter, 1, &value);
|
||||
|
||||
if (!G_VALUE_HOLDS_POINTER(&value)) {
|
||||
return;
|
||||
}
|
||||
auto* s = static_cast<GtkPaperSize*>(g_value_get_pointer(&value));
|
||||
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
double width = gtk_paper_size_get_width(s, GTK_UNIT_POINTS) / dlg->scale;
|
||||
double height = gtk_paper_size_get_height(s, GTK_UNIT_POINTS) / dlg->scale;
|
||||
|
||||
if (dlg->orientation == ORIENTATION_LANDSCAPE) {
|
||||
if (width < height) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
} else {
|
||||
if (width > height) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
|
||||
dlg->setOrientation(ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
dlg->setSpinValues(width, height);
|
||||
}
|
||||
|
||||
void FormatDialog::portraitSelectedCb(GtkToggleButton* bt, FormatDialog* dlg) {
|
||||
bool activated = gtk_toggle_button_get_active(bt);
|
||||
|
||||
if (activated) {
|
||||
gtk_toggle_button_set_active(dlg->landscapeButton, false);
|
||||
dlg->orientation = ORIENTATION_PORTRAIT;
|
||||
|
||||
double width = gtk_spin_button_get_value(dlg->widthSpin);
|
||||
double height = gtk_spin_button_get_value(dlg->heightSpin);
|
||||
|
||||
if (width > height) {
|
||||
// Exchange width and height
|
||||
dlg->setSpinValues(height, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FormatDialog::landscapeSelectedCb(GtkToggleButton* bt, FormatDialog* dlg) {
|
||||
bool activated = gtk_toggle_button_get_active(bt);
|
||||
|
||||
if (activated) {
|
||||
gtk_toggle_button_set_active(dlg->portraitButton, false);
|
||||
dlg->orientation = ORIENTATION_LANDSCAPE;
|
||||
|
||||
double width = gtk_spin_button_get_value(dlg->widthSpin);
|
||||
double height = gtk_spin_button_get_value(dlg->heightSpin);
|
||||
|
||||
if (width < height) {
|
||||
// Exchange width and height
|
||||
dlg->setSpinValues(height, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FormatDialog::reset(FormatDialog* self) {
|
||||
self->setSpinValues(self->origWidth / self->scale, self->origHeight / self->scale);
|
||||
spinValueChangedCb(self);
|
||||
}
|
||||
|
||||
void FormatDialog::setSpinValues(double width, double height) {
|
||||
ignoreSpinChange = true;
|
||||
gtk_spin_button_set_value(widthSpin, width);
|
||||
gtk_spin_button_set_value(heightSpin, height);
|
||||
ignoreSpinChange = false;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The page format dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory> // for unique_ptr
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for gtk_paper_size_free, GtkComboBox, GtkToggl...
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/PaperFormatUtils.h" // for PaperFormatMenuOptionVector
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
class Settings;
|
||||
|
||||
namespace xoj::popup {
|
||||
class FormatDialog {
|
||||
public:
|
||||
FormatDialog(GladeSearchpath* gladeSearchPath, Settings* settings, double width, double height,
|
||||
std::function<void(double w, double h)> callback);
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
enum Orientation { ORIENTATION_NOT_DEFINED, ORIENTATION_LANDSCAPE, ORIENTATION_PORTRAIT };
|
||||
|
||||
void setOrientation(Orientation orientation);
|
||||
void setSpinValues(double width, double heigth);
|
||||
|
||||
static void portraitSelectedCb(GtkToggleButton* bt, FormatDialog* dlg);
|
||||
static void landscapeSelectedCb(GtkToggleButton* bt, FormatDialog* dlg);
|
||||
static void cbFormatChangedCb(GtkComboBox* widget, FormatDialog* dlg);
|
||||
static void cbUnitChanged(GtkComboBox* widget, FormatDialog* dlg);
|
||||
static void spinValueChangedCb(FormatDialog* dlg);
|
||||
static void reset(FormatDialog* self);
|
||||
|
||||
private:
|
||||
Settings* settings = nullptr;
|
||||
|
||||
PaperFormatUtils::PaperFormatMenuOptionVector paperSizes;
|
||||
|
||||
Orientation orientation = ORIENTATION_NOT_DEFINED;
|
||||
int selectedScale = 0;
|
||||
double scale = 0;
|
||||
|
||||
double origWidth = 0;
|
||||
double origHeight = 0;
|
||||
|
||||
bool ignoreSpinChange = false;
|
||||
|
||||
|
||||
GtkComboBox* paperTemplatesCombo;
|
||||
GtkSpinButton* widthSpin;
|
||||
GtkSpinButton* heightSpin;
|
||||
GtkToggleButton* landscapeButton;
|
||||
GtkToggleButton* portraitButton;
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
std::function<void(double w, double h)> callbackFun;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "GotoDialog.h"
|
||||
|
||||
#include <memory> // for allocator
|
||||
|
||||
#include "gui/Builder.h"
|
||||
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "goto.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "gotoDialog";
|
||||
|
||||
using namespace xoj::popup;
|
||||
|
||||
GotoDialog::GotoDialog(GladeSearchpath* gladeSearchPath, size_t initialPage, size_t maxPage,
|
||||
std::function<void(size_t)> callback):
|
||||
callback(callback) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
this->spinButton = GTK_SPIN_BUTTON(builder.get("spinPage"));
|
||||
gtk_spin_button_set_range(this->spinButton, 1, static_cast<double>(maxPage));
|
||||
gtk_spin_button_set_value(this->spinButton, static_cast<double>(initialPage + 1));
|
||||
gtk_editable_select_region(GTK_EDITABLE(this->spinButton), 0, -1); // Select the entire content
|
||||
|
||||
auto onSuccessCallback = G_CALLBACK(+[](GotoDialog* self) {
|
||||
self->callback(static_cast<size_t>(gtk_spin_button_get_value(GTK_SPIN_BUTTON(self->spinButton))));
|
||||
gtk_window_close(self->window.get());
|
||||
});
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", onSuccessCallback, this);
|
||||
}
|
||||
|
||||
GotoDialog::~GotoDialog() = default;
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Goto-Page dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
namespace xoj::popup {
|
||||
class GotoDialog {
|
||||
public:
|
||||
GotoDialog(GladeSearchpath* gladeSearchPath, size_t initialPage, size_t maxPage,
|
||||
std::function<void(size_t)> callback);
|
||||
~GotoDialog();
|
||||
|
||||
public:
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
GtkSpinButton* spinButton = nullptr;
|
||||
|
||||
std::function<void(size_t)> callback;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Latex implementation
|
||||
*
|
||||
* @author W Brenna
|
||||
* http://wbrenna.ca
|
||||
*
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#include "IntEdLatexDialog.h"
|
||||
|
||||
#include <sstream> // for operator<<, basic_ostream
|
||||
#include <utility> // for move
|
||||
|
||||
#include <glib.h> // for g_free, gpointer, guint
|
||||
#include <poppler-document.h> // for poppler_document_get_n_p...
|
||||
#include <poppler-page.h> // for poppler_page_get_size
|
||||
|
||||
#include "AbstractLatexDialog.h"
|
||||
#include "config-features.h"
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
#include <gtksourceview/gtksource.h> // for GTK_SOURCE_VIEW, gtk_sou...
|
||||
#endif
|
||||
|
||||
#include "control/LatexController.h"
|
||||
#include "control/settings/LatexSettings.h" // for LatexSettings
|
||||
#include "gui/Builder.h"
|
||||
#include "model/Font.h" // for XojFont
|
||||
#include "util/StringUtils.h" // for replace_pair, StringUtils
|
||||
#include "util/raii/CStringWrapper.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE_NAME = "intEdTexDialog.glade";
|
||||
constexpr auto UI_DIALOG_ID = "intEdTexDialog";
|
||||
|
||||
constexpr auto TEX_BOX_WIDGET_NAME = "texBox";
|
||||
|
||||
IntEdLatexDialog::IntEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> ctrl):
|
||||
AbstractLatexDialog(std::move(ctrl)), cssProvider(gtk_css_provider_new(), xoj::util::adopt) {
|
||||
|
||||
Builder builder(gladeSearchPath, UI_FILE_NAME);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_ID)));
|
||||
|
||||
populateStandardWidgetsFromBuilder(builder);
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
this->texBox = gtk_source_view_new();
|
||||
#else
|
||||
this->texBox = gtk_text_view_new();
|
||||
#endif
|
||||
|
||||
gtk_widget_set_name(this->texBox, TEX_BOX_WIDGET_NAME);
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(this->texBox), true);
|
||||
|
||||
this->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(this->texBox));
|
||||
const auto& settings = texCtrl->settings;
|
||||
if (texCtrl->initialTex.empty()) {
|
||||
gtk_text_buffer_set_text(this->textBuffer, settings.defaultText.c_str(), -1);
|
||||
// Preselect the default
|
||||
GtkTextIter start, end;
|
||||
gtk_text_buffer_get_bounds(this->textBuffer, &start, &end);
|
||||
gtk_text_buffer_select_range(this->textBuffer, &start, &end);
|
||||
} else {
|
||||
gtk_text_buffer_set_text(this->textBuffer, texCtrl->initialTex.c_str(), -1);
|
||||
}
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
// Widgets are visible by default in gtk4
|
||||
gtk_widget_show_all(GTK_WIDGET(texBox));
|
||||
#endif
|
||||
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(builder.get("texBoxContainer")), this->texBox);
|
||||
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
// We own neither the languageManager, the styleSchemeManager, nor the sourceLanguage.
|
||||
// Do not attempt to free them.
|
||||
GtkSourceStyleSchemeManager* styleSchemeManager = gtk_source_style_scheme_manager_get_default();
|
||||
GtkSourceLanguageManager* lm = gtk_source_language_manager_get_default();
|
||||
|
||||
// Select the TeX highlighting scheme
|
||||
GtkSourceLanguage* lang = gtk_source_language_manager_guess_language(lm, "file.tex", nullptr);
|
||||
std::string styleSchemeId = settings.sourceViewThemeId;
|
||||
GtkSourceStyleScheme* styleScheme =
|
||||
gtk_source_style_scheme_manager_get_scheme(styleSchemeManager, styleSchemeId.c_str());
|
||||
|
||||
if (settings.sourceViewSyntaxHighlight) {
|
||||
gtk_source_buffer_set_language(GTK_SOURCE_BUFFER(this->textBuffer), lang);
|
||||
}
|
||||
|
||||
gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewAutoIndent);
|
||||
gtk_source_view_set_indent_on_tab(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewAutoIndent);
|
||||
|
||||
gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(this->texBox), settings.sourceViewShowLineNumbers);
|
||||
|
||||
if (styleScheme) {
|
||||
gtk_source_buffer_set_style_scheme(GTK_SOURCE_BUFFER(this->textBuffer), styleScheme);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Enable/disable word-wrap.
|
||||
GtkWrapMode texBoxWrapMode = GTK_WRAP_NONE;
|
||||
if (settings.editorWordWrap) {
|
||||
texBoxWrapMode = GTK_WRAP_WORD_CHAR;
|
||||
}
|
||||
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(this->texBox), texBoxWrapMode);
|
||||
|
||||
std::stringstream texBoxCssBuilder;
|
||||
if (settings.useCustomEditorFont) {
|
||||
std::string fontName = settings.editorFont.getName();
|
||||
|
||||
// Escape "'" and "\" characters in the fontName
|
||||
StringUtils::replaceAllChars(fontName, {replace_pair('\\', "\\\\")});
|
||||
StringUtils::replaceAllChars(fontName, {replace_pair('\'', "\\'")});
|
||||
|
||||
texBoxCssBuilder << "#" << TEX_BOX_WIDGET_NAME << " {";
|
||||
texBoxCssBuilder << " font-size: " << settings.editorFont.getSize() << "pt;";
|
||||
texBoxCssBuilder << " font-family: '" << fontName << "';";
|
||||
texBoxCssBuilder << " } ";
|
||||
|
||||
gtk_css_provider_load_from_data(this->cssProvider.get(), texBoxCssBuilder.str().c_str(), -1, nullptr);
|
||||
|
||||
// Apply the CSS to both the texBox and the drawing area.
|
||||
gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(this->previewDrawingArea)),
|
||||
GTK_STYLE_PROVIDER(this->cssProvider.get()),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(this->texBox)),
|
||||
GTK_STYLE_PROVIDER(this->cssProvider.get()),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
connectStandardSignals();
|
||||
|
||||
/*
|
||||
* Connect handleTexChanged() to the text buffer containing the latex code, to trigger rebuilds upon code changes.
|
||||
*/
|
||||
g_signal_connect(this->getTextBuffer(), "changed", G_CALLBACK(+[](GtkTextBuffer*, gpointer ctrl) {
|
||||
LatexController::handleTexChanged(static_cast<LatexController*>(ctrl));
|
||||
}),
|
||||
texCtrl.get());
|
||||
|
||||
g_signal_connect(this->getWindow(), "delete-event", G_CALLBACK(+[](GtkWidget*, GdkEvent*, gpointer d) -> gboolean {
|
||||
auto self = static_cast<IntEdLatexDialog*>(d);
|
||||
/**
|
||||
* dlg->getTextBuffer() may survive the dialog. When copying all of its content, the
|
||||
* clipboard owns a ref to the GtkTextBuffer. We must disconnect the signal to avoid
|
||||
* dereferencing `self` after its destruction
|
||||
*/
|
||||
g_signal_handlers_disconnect_by_data(self->getTextBuffer(), self->texCtrl.get());
|
||||
return false; // Let the callback from PopupWindowWrapper delete the dialog
|
||||
}),
|
||||
this);
|
||||
|
||||
g_signal_connect(this->getWindow(), "show", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto texCtrl = static_cast<LatexController*>(d);
|
||||
if (!texCtrl->temporaryRender) {
|
||||
// Trigger an asynchronous compilation if we are not using a preexisting TexImage
|
||||
// Keep this after popup.show() so that if an error message is to be displayed (e.g.
|
||||
// missing Tex executable), it'll appear on top of the LatexDialog.
|
||||
LatexController::handleTexChanged(texCtrl);
|
||||
}
|
||||
}),
|
||||
texCtrl.get());
|
||||
}
|
||||
|
||||
IntEdLatexDialog::~IntEdLatexDialog() = default;
|
||||
|
||||
|
||||
auto IntEdLatexDialog::getTextBuffer() -> GtkTextBuffer* { return this->textBuffer; }
|
||||
|
||||
auto IntEdLatexDialog::getBufferContents() -> std::string {
|
||||
GtkTextIter start, end;
|
||||
gtk_text_buffer_get_bounds(this->textBuffer, &start, &end);
|
||||
auto content =
|
||||
xoj::util::OwnedCString::assumeOwnership(gtk_text_buffer_get_text(this->textBuffer, &start, &end, false));
|
||||
return content.get();
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Latex dialog for the internal editor
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string> // for string
|
||||
|
||||
#include <cairo.h> // for cairo_t
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkTextBuffer, GtkWindow
|
||||
#include <gtk/gtkcssprovider.h> // for GtkCssProvider
|
||||
#include <poppler.h> // for PopplerDocument, PopplerPage
|
||||
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
#include "AbstractLatexDialog.h"
|
||||
|
||||
class LatexController;
|
||||
class LatexSettings;
|
||||
class GladeSearchpath;
|
||||
|
||||
class IntEdLatexDialog final: public AbstractLatexDialog {
|
||||
public:
|
||||
IntEdLatexDialog() = delete;
|
||||
IntEdLatexDialog(const IntEdLatexDialog& other) = delete;
|
||||
IntEdLatexDialog& operator=(const IntEdLatexDialog& other) = delete;
|
||||
IntEdLatexDialog(GladeSearchpath* gladeSearchPath, std::unique_ptr<LatexController> texCtrl);
|
||||
virtual ~IntEdLatexDialog();
|
||||
|
||||
public:
|
||||
// Necessary for the controller in order to connect the 'text-changed'
|
||||
// signal handler
|
||||
GtkTextBuffer* getTextBuffer();
|
||||
std::string getBufferContents() override;
|
||||
|
||||
private:
|
||||
// Text field
|
||||
xoj::util::GObjectSPtr<GtkCssProvider> cssProvider; ///< For tex code display font and size
|
||||
GtkWidget* texBox;
|
||||
GtkTextBuffer* textBuffer;
|
||||
|
||||
/**
|
||||
* The final LaTeX string to save once the dialog is closed.
|
||||
*/
|
||||
std::string finalLatex;
|
||||
|
||||
std::function<void()> callback;
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "LanguageConfigGui.h"
|
||||
|
||||
#include <algorithm> // for find, lower_bound, sort
|
||||
#include <cstdlib> // for setenv
|
||||
#include <memory> // for allocator, __alloc_traits<>::...
|
||||
|
||||
#include <glib-object.h> // for G_TYPE_STRING
|
||||
#include <glib.h> // for g_warning, gint
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "util/PathUtil.h" // for getGettextFilepath, getLocale...
|
||||
#include "util/StringUtils.h" // for StringUtils
|
||||
#include "util/XojMsgBox.h" // for XojMsgBox
|
||||
#include "util/i18n.h" // for _
|
||||
#include "util/safe_casts.h" // for as_unsigned
|
||||
|
||||
#include "config.h" // for GETTEXT_PACKAGE
|
||||
#include "filesystem.h" // for directory_iterator, operator/
|
||||
|
||||
LanguageConfigGui::LanguageConfigGui(GtkBox* parent, Settings* settings):
|
||||
comboBox(gtk_combo_box_text_new(), xoj::util::adopt), settings(settings) {
|
||||
gtk_box_append(parent, comboBox.get());
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
gtk_widget_show_all(comboBox.get());
|
||||
#endif
|
||||
|
||||
// Fetch available locales
|
||||
try {
|
||||
fs::path baseLocaleDir = Util::getGettextFilepath(Util::getLocalePath());
|
||||
for (auto const& d: fs::directory_iterator(baseLocaleDir)) {
|
||||
if (auto mofile = (d.path() / "LC_MESSAGES" / GETTEXT_PACKAGE) += ".mo"; fs::exists(mofile)) {
|
||||
auto u8path = d.path().filename().u8string();
|
||||
auto casted = char_cast(u8path);
|
||||
availableLocales.emplace_back(casted.begin(), casted.end());
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
g_warning("Failed to fetch available locales:\n%s", e.what());
|
||||
}
|
||||
std::sort(availableLocales.begin(), availableLocales.end());
|
||||
|
||||
// No pot file for English
|
||||
if (auto enPos = std::lower_bound(availableLocales.begin(), availableLocales.end(), "en");
|
||||
enPos != availableLocales.end() && !StringUtils::startsWith(*enPos, "en")) {
|
||||
availableLocales.insert(enPos, "en");
|
||||
}
|
||||
|
||||
// Default
|
||||
availableLocales.insert(availableLocales.begin(), _("System Default"));
|
||||
|
||||
auto gtkAvailableLocales = gtk_list_store_new(1, G_TYPE_STRING);
|
||||
for (auto const& i: availableLocales) {
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(gtkAvailableLocales, &iter);
|
||||
gtk_list_store_set(gtkAvailableLocales, &iter, 0, i.c_str(), -1);
|
||||
}
|
||||
gtk_combo_box_set_model(GTK_COMBO_BOX(comboBox.get()), GTK_TREE_MODEL(gtkAvailableLocales));
|
||||
|
||||
|
||||
// Set the current locale if previously selected
|
||||
auto prefPos = [&]() {
|
||||
if (auto preferred = settings->getPreferredLocale(); !preferred.empty()) {
|
||||
if (auto &&endi = availableLocales.end(), prefPos = std::find(availableLocales.begin(), endi, preferred);
|
||||
prefPos != endi) {
|
||||
return prefPos;
|
||||
}
|
||||
XojMsgBox::showErrorToUser(nullptr, _("Previously selected language not available anymore!"));
|
||||
}
|
||||
// Use system default
|
||||
return availableLocales.begin();
|
||||
}();
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(comboBox.get()),
|
||||
static_cast<gint>(std::distance(availableLocales.begin(), prefPos)));
|
||||
}
|
||||
|
||||
|
||||
void LanguageConfigGui::saveSettings() {
|
||||
gint pos = gtk_combo_box_get_active(GTK_COMBO_BOX(comboBox.get()));
|
||||
auto pref = (pos == 0) ? "" : availableLocales[as_unsigned(pos)];
|
||||
|
||||
settings->setPreferredLocale(pref);
|
||||
settings->customSettingsChanged();
|
||||
#ifdef _WIN32
|
||||
_putenv_s("LANGUAGE", this->settings->getPreferredLocale().c_str());
|
||||
#else
|
||||
setenv("LANGUAGE", this->settings->getPreferredLocale().c_str(), 1);
|
||||
#endif
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The configuration for a language in the Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkBox
|
||||
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
|
||||
class Settings;
|
||||
class GladeSearchpath;
|
||||
|
||||
class LanguageConfigGui {
|
||||
public:
|
||||
LanguageConfigGui(GtkBox* parent, Settings* settings);
|
||||
|
||||
public:
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
std::vector<std::string> availableLocales;
|
||||
|
||||
xoj::util::WidgetSPtr comboBox;
|
||||
|
||||
Settings* settings;
|
||||
};
|
||||
@@ -1,205 +0,0 @@
|
||||
#include "LatexSettingsPanel.h"
|
||||
|
||||
#include <fstream> // for ifstream, basic_istream
|
||||
#include <iterator> // for istreambuf_iterator, ope...
|
||||
#include <string> // for allocator, string
|
||||
#include <variant> // for get_if
|
||||
|
||||
#include <gio/gio.h> // for GSubprocess, g_subproces...
|
||||
#include <glib-object.h> // for g_object_unref, g_object...
|
||||
#include <glib.h> // for g_error_free, GError
|
||||
|
||||
#include "config-features.h" // for ENABLE_GTK_SOURCEVIEW
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
#include <gtksourceview/gtksource.h> // for gtk_source_style_scheme_...
|
||||
#endif
|
||||
|
||||
#include "control/latex/LatexGenerator.h" // for LatexGenerator::GenError
|
||||
#include "control/settings/LatexSettings.h" // for LatexSettings
|
||||
#include "gui/Builder.h"
|
||||
#include "model/Font.h" // for XojFont
|
||||
#include "util/Color.h" // for Color
|
||||
#include "util/PathUtil.h" // for fromGFile, getTmpDir...
|
||||
#include "util/PlaceholderString.h" // for PlaceholderString
|
||||
#include "util/XojMsgBox.h"
|
||||
#include "util/gtk4_helper.h"
|
||||
#include "util/i18n.h" // for FS, _F, _
|
||||
#include "util/raii/CStringWrapper.h"
|
||||
|
||||
#include "filesystem.h" // for path, is_regular_file
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "latexSettings.glade";
|
||||
constexpr auto UI_PANEL_NAME = "latexSettingsPanel";
|
||||
|
||||
LatexSettingsPanel::LatexSettingsPanel(GladeSearchpath* gladeSearchPath):
|
||||
builder(gladeSearchPath, UI_FILE),
|
||||
panel(GTK_SCROLLED_WINDOW(builder.get(UI_PANEL_NAME))),
|
||||
cbAutoDepCheck(GTK_CHECK_BUTTON(builder.get("latexSettingsRunCheck"))),
|
||||
// Todo(gtk4): replace this GtkFileChooserButton (by what?)
|
||||
globalTemplateChooser(GTK_FILE_CHOOSER(builder.get("latexSettingsTemplateFile"))),
|
||||
cbUseSystemFont(GTK_CHECK_BUTTON(builder.get("cbUseSystemFont"))) {
|
||||
|
||||
g_signal_connect_swapped(builder.get("latexSettingsTestBtn"), "clicked",
|
||||
G_CALLBACK(+[](LatexSettingsPanel* self) { self->checkDeps(); }), this);
|
||||
g_signal_connect_swapped(this->cbUseSystemFont, "toggled",
|
||||
G_CALLBACK(+[](LatexSettingsPanel* self) { self->updateWidgetSensitivity(); }), this);
|
||||
g_signal_connect_swapped(builder.get("cbUseExternalEditor"), "toggled",
|
||||
G_CALLBACK(+[](LatexSettingsPanel* self) { self->updateWidgetSensitivity(); }), this);
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
GtkBox* themeSelectionBox = GTK_BOX(builder.get("bxThemeSelectionContainer"));
|
||||
this->sourceViewThemeSelector = gtk_source_style_scheme_chooser_button_new();
|
||||
gtk_box_append(themeSelectionBox, sourceViewThemeSelector);
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbSourceviewSettingsDescription")), _("LaTeX editor theme:"));
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
// Widgets are visible by default in gtk4
|
||||
gtk_widget_show(sourceViewThemeSelector);
|
||||
#endif
|
||||
#else
|
||||
this->sourceViewThemeSelector = nullptr;
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbSourceviewSettingsDescription")),
|
||||
_("GtkSourceView was disabled when building Xournal++! "
|
||||
"Some options will not be available."));
|
||||
#endif
|
||||
}
|
||||
|
||||
void LatexSettingsPanel::load(const LatexSettings& settings) {
|
||||
gtk_check_button_set_active(this->cbAutoDepCheck, settings.autoCheckDependencies);
|
||||
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexDefaultEntry")), settings.defaultText.c_str());
|
||||
if (!settings.globalTemplatePath.empty()) {
|
||||
gtk_file_chooser_set_file(this->globalTemplateChooser, Util::toGFile(settings.globalTemplatePath).get(),
|
||||
nullptr);
|
||||
}
|
||||
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexSettingsGenCmd")), settings.genCmd.c_str());
|
||||
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
std::string themeId = settings.sourceViewThemeId;
|
||||
GtkSourceStyleSchemeManager* themeManager = gtk_source_style_scheme_manager_get_default();
|
||||
GtkSourceStyleScheme* theme = gtk_source_style_scheme_manager_get_scheme(themeManager, themeId.c_str());
|
||||
|
||||
if (theme) {
|
||||
gtk_source_style_scheme_chooser_set_style_scheme(GTK_SOURCE_STYLE_SCHEME_CHOOSER(this->sourceViewThemeSelector),
|
||||
theme);
|
||||
}
|
||||
#endif
|
||||
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbShowLineNumbers")), settings.sourceViewShowLineNumbers);
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbAutoIndent")), settings.sourceViewAutoIndent);
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbSyntaxHighlight")), settings.sourceViewSyntaxHighlight);
|
||||
|
||||
// Editor font
|
||||
std::string editorFontDescription{settings.editorFont.asString()};
|
||||
gtk_font_chooser_set_font(GTK_FONT_CHOOSER(builder.get("selBtnEditorFont")), editorFontDescription.c_str());
|
||||
|
||||
// Should we use the custom editor font?
|
||||
gtk_check_button_set_active(this->cbUseSystemFont, !settings.useCustomEditorFont);
|
||||
|
||||
// Editor word wrap.
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbWordWrap")), settings.editorWordWrap);
|
||||
|
||||
// External editor
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")), settings.useExternalEditor);
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(builder.get("cbExternalEditorAutoConfirm")),
|
||||
settings.externalEditorAutoConfirm);
|
||||
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexExternalEditorCmd")), settings.externalEditorCmd.c_str());
|
||||
gtk_editable_set_text(GTK_EDITABLE(builder.get("latexTemporaryFileExt")), settings.temporaryFileExt.c_str());
|
||||
|
||||
this->updateWidgetSensitivity();
|
||||
}
|
||||
|
||||
void LatexSettingsPanel::save(LatexSettings& settings) {
|
||||
settings.autoCheckDependencies = gtk_check_button_get_active(this->cbAutoDepCheck);
|
||||
settings.defaultText = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexDefaultEntry")));
|
||||
settings.globalTemplatePath = Util::fromGFile(
|
||||
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(this->globalTemplateChooser), xoj::util::adopt)
|
||||
.get());
|
||||
settings.genCmd = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexSettingsGenCmd")));
|
||||
|
||||
#ifdef ENABLE_GTK_SOURCEVIEW
|
||||
GtkSourceStyleScheme* theme = gtk_source_style_scheme_chooser_get_style_scheme(
|
||||
GTK_SOURCE_STYLE_SCHEME_CHOOSER(this->sourceViewThemeSelector));
|
||||
settings.sourceViewThemeId = gtk_source_style_scheme_get_id(theme);
|
||||
#endif
|
||||
|
||||
settings.sourceViewShowLineNumbers =
|
||||
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbShowLineNumbers")));
|
||||
settings.sourceViewAutoIndent = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbAutoIndent")));
|
||||
settings.sourceViewSyntaxHighlight =
|
||||
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbSyntaxHighlight")));
|
||||
|
||||
GtkFontChooser* fontSelector = GTK_FONT_CHOOSER(builder.get("selBtnEditorFont"));
|
||||
settings.editorFont = xoj::util::OwnedCString::assumeOwnership(gtk_font_chooser_get_font(fontSelector)).get();
|
||||
settings.useCustomEditorFont = !gtk_check_button_get_active(this->cbUseSystemFont);
|
||||
settings.editorWordWrap = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbWordWrap")));
|
||||
|
||||
settings.useExternalEditor = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")));
|
||||
settings.externalEditorAutoConfirm =
|
||||
gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbExternalEditorAutoConfirm")));
|
||||
settings.externalEditorCmd = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexExternalEditorCmd")));
|
||||
settings.temporaryFileExt = gtk_editable_get_text(GTK_EDITABLE(builder.get("latexTemporaryFileExt")));
|
||||
}
|
||||
|
||||
void LatexSettingsPanel::checkDeps() {
|
||||
LatexSettings settings;
|
||||
this->save(settings);
|
||||
std::string msg;
|
||||
bool fail = false;
|
||||
|
||||
if (fs::is_regular_file(settings.globalTemplatePath)) {
|
||||
// Assume the file is encoded as UTF-8 (open in binary mode to avoid surprises)
|
||||
std::ifstream is(settings.globalTemplatePath, std::ios_base::binary);
|
||||
if (!is.is_open()) {
|
||||
msg = FS(_F("Unable to open global template file at {1}. Does it exist?") %
|
||||
settings.globalTemplatePath.u8string());
|
||||
fail = true;
|
||||
} else {
|
||||
std::string templ(std::istreambuf_iterator<char>(is), {});
|
||||
std::string sample = LatexGenerator::templateSub(settings.defaultText, templ, Colors::black);
|
||||
auto const& tmpDir = Util::getTmpDirSubfolder("tex");
|
||||
auto result = LatexGenerator(settings).asyncRun(tmpDir, sample);
|
||||
if (auto* proc = std::get_if<GSubprocess*>(&result)) {
|
||||
GError* err = nullptr;
|
||||
if (g_subprocess_wait_check(*proc, nullptr, &err)) {
|
||||
msg = _("Sample LaTeX file generated successfully.");
|
||||
} else {
|
||||
msg = FS(_F("Error: {1}. Please check the contents of {2}") % err->message % tmpDir.u8string());
|
||||
g_error_free(err);
|
||||
fail = true;
|
||||
}
|
||||
g_object_unref(*proc);
|
||||
} else if (auto* err = std::get_if<LatexGenerator::GenError>(&result)) {
|
||||
msg = err->message;
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg = FS(_F("Error: {1} is not a regular file. Please check your LaTeX template file settings. ") %
|
||||
settings.globalTemplatePath.u8string());
|
||||
fail = true;
|
||||
}
|
||||
|
||||
GtkWindow* win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(panel)));
|
||||
XojMsgBox::showMessageToUser(win, msg, fail ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO);
|
||||
}
|
||||
|
||||
void LatexSettingsPanel::updateWidgetSensitivity() {
|
||||
bool useSystemFont = gtk_check_button_get_active(this->cbUseSystemFont);
|
||||
|
||||
// Only select a custom font if we're not using the system's.
|
||||
gtk_widget_set_sensitive(builder.get("boxCustomFontOptions"), !useSystemFont);
|
||||
|
||||
#ifndef ENABLE_GTK_SOURCEVIEW
|
||||
gtk_widget_set_sensitive(builder.get("bxGtkSourceviewSettings"), false);
|
||||
#endif
|
||||
|
||||
auto useExternalEditor = gtk_check_button_get_active(GTK_CHECK_BUTTON(builder.get("cbUseExternalEditor")));
|
||||
gtk_widget_set_sensitive(builder.get("cbExternalEditorAutoConfirm"), useExternalEditor);
|
||||
gtk_widget_set_sensitive(builder.get("latexExternalEditorCmd"), useExternalEditor);
|
||||
gtk_widget_set_sensitive(builder.get("latexTemporaryFileExt"), useExternalEditor);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Latex settings panel
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h> // for GtkToggleButton, GtkFileChooser, GtkWidget
|
||||
|
||||
#include "gui/Builder.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
class LatexSettings;
|
||||
|
||||
class LatexSettingsPanel {
|
||||
public:
|
||||
explicit LatexSettingsPanel(GladeSearchpath*);
|
||||
LatexSettingsPanel(const LatexSettingsPanel&) = delete;
|
||||
LatexSettingsPanel& operator=(const LatexSettingsPanel&) = delete;
|
||||
LatexSettingsPanel(const LatexSettingsPanel&&) = delete;
|
||||
LatexSettingsPanel& operator=(const LatexSettingsPanel&&) = delete;
|
||||
|
||||
void load(const LatexSettings& settings);
|
||||
void save(LatexSettings& settings);
|
||||
|
||||
inline GtkWidget* getPanel() const { return GTK_WIDGET(panel); }
|
||||
|
||||
private:
|
||||
void checkDeps();
|
||||
|
||||
/**
|
||||
* @brief Update whether options accept input ("grayed-out" or not)
|
||||
* based on the current panel state.
|
||||
*/
|
||||
void updateWidgetSensitivity();
|
||||
|
||||
Builder builder;
|
||||
GtkScrolledWindow* panel;
|
||||
|
||||
GtkCheckButton* cbAutoDepCheck;
|
||||
GtkFileChooser* globalTemplateChooser;
|
||||
GtkWidget* sourceViewThemeSelector;
|
||||
GtkCheckButton* cbUseSystemFont;
|
||||
};
|
||||
@@ -1,224 +0,0 @@
|
||||
#include "PageTemplateDialog.h"
|
||||
|
||||
#include <cstdio> // for sprintf
|
||||
#include <ctime> // for localtime, strftime, time
|
||||
#include <fstream> // for ofstream, basic_ostream
|
||||
#include <memory> // for allocator, unique_ptr
|
||||
#include <optional> // for optional
|
||||
#include <string> // for string, operator<<
|
||||
|
||||
#include <gdk/gdk.h> // for GdkRGBA
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_c...
|
||||
|
||||
#include "control/pagetype/PageTypeHandler.h" // for PageTypeInfo, PageType...
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/Builder.h" // for Builder
|
||||
#include "gui/dialog/XojOpenDlg.h" // for XojOpenDlg
|
||||
#include "gui/menus/popoverMenus/PageTypeSelectionPopoverGridOnly.h"
|
||||
#include "gui/toolbarMenubar/ToolMenuHandler.h"
|
||||
#include "model/FormatDefinitions.h" // for FormatUnits, XOJ_UNITS
|
||||
#include "model/PageType.h" // for PageType
|
||||
#include "util/Color.h" // for GdkRGBA_to_argb, rgb_t...
|
||||
#include "util/PathUtil.h" // for fromGFile, readString
|
||||
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
|
||||
#include "util/XojMsgBox.h" // for XojMsgBox
|
||||
#include "util/i18n.h" // for _
|
||||
#include "util/serdesstream.h" // for serdes_stream
|
||||
|
||||
#include "FormatDialog.h" // for FormatDialog
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "pageTemplate.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "templateDialog";
|
||||
|
||||
using namespace xoj::popup;
|
||||
|
||||
PageTemplateDialog::PageTemplateDialog(GladeSearchpath* gladeSearchPath, Settings* settings, ToolMenuHandler* toolmenu,
|
||||
PageTypeHandler* types):
|
||||
gladeSearchPath(gladeSearchPath), settings(settings), toolMenuHandler(toolmenu), types(types) {
|
||||
model.parse(settings->getPageTemplate());
|
||||
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
|
||||
// Needs to be initialized after this->window
|
||||
pageTypeSelectionMenu = std::make_unique<PageTypeSelectionPopoverGridOnly>(types, settings, this);
|
||||
gtk_menu_button_set_popup(GTK_MENU_BUTTON(builder.get("btBackgroundDropdown")),
|
||||
pageTypeSelectionMenu->getPopover());
|
||||
|
||||
pageSizeLabel = GTK_LABEL(builder.get("lbPageSize"));
|
||||
backgroundTypeLabel = GTK_LABEL(builder.get("lbBackgroundType"));
|
||||
backgroundColorChooser = GTK_COLOR_CHOOSER(builder.get("cbBackgroundButton"));
|
||||
copyLastPageButton = GTK_TOGGLE_BUTTON(builder.get("cbCopyLastPage"));
|
||||
copyLastPageSizeButton = GTK_TOGGLE_BUTTON(builder.get("cbCopyLastPageSize"));
|
||||
|
||||
|
||||
g_signal_connect_swapped(builder.get("btChangePaperSize"), "clicked",
|
||||
G_CALLBACK(+[](PageTemplateDialog* self) { self->showPageSizeDialog(); }), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btLoad"), "clicked",
|
||||
G_CALLBACK(+[](PageTemplateDialog* self) { self->loadFromFile(); }), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btSave"), "clicked",
|
||||
G_CALLBACK(+[](PageTemplateDialog* self) { self->saveToFile(); }), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->getWindow());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](PageTemplateDialog* self) {
|
||||
self->saveToModel();
|
||||
self->settings->setPageTemplate(self->model.toString());
|
||||
self->toolMenuHandler->setDefaultNewPageType(self->model.getPageInsertType());
|
||||
self->toolMenuHandler->setDefaultNewPaperSize(
|
||||
self->model.isCopyLastPageSize() ? std::nullopt :
|
||||
std::optional(PaperSize(self->model)));
|
||||
gtk_window_close(self->getWindow());
|
||||
}),
|
||||
this);
|
||||
|
||||
updateDataFromModel();
|
||||
}
|
||||
|
||||
PageTemplateDialog::~PageTemplateDialog() = default;
|
||||
|
||||
void PageTemplateDialog::updateDataFromModel() {
|
||||
GdkRGBA color = Util::rgb_to_GdkRGBA(model.getBackgroundColor());
|
||||
gtk_color_chooser_set_rgba(backgroundColorChooser, &color);
|
||||
|
||||
updatePageSize();
|
||||
|
||||
pageTypeSelectionMenu->setSelectedPT(model.getBackgroundType());
|
||||
changeCurrentPageBackground(types->getInfoOn(model.getBackgroundType()));
|
||||
|
||||
gtk_toggle_button_set_active(copyLastPageButton, model.isCopyLastPageSettings());
|
||||
gtk_toggle_button_set_active(copyLastPageSizeButton, model.isCopyLastPageSize());
|
||||
}
|
||||
|
||||
void PageTemplateDialog::changeCurrentPageBackground(const PageTypeInfo* info) {
|
||||
model.setBackgroundType(info->page);
|
||||
|
||||
gtk_label_set_text(backgroundTypeLabel, info->name.c_str());
|
||||
}
|
||||
|
||||
void PageTemplateDialog::saveToModel() {
|
||||
model.setCopyLastPageSettings(gtk_toggle_button_get_active(copyLastPageButton));
|
||||
model.setCopyLastPageSize(gtk_toggle_button_get_active(copyLastPageSizeButton));
|
||||
|
||||
GdkRGBA color;
|
||||
gtk_color_chooser_get_rgba(backgroundColorChooser, &color);
|
||||
model.setBackgroundColor(Util::GdkRGBA_to_argb(color));
|
||||
}
|
||||
|
||||
void PageTemplateDialog::saveToFile() {
|
||||
saveToModel();
|
||||
|
||||
GtkWidget* dialog =
|
||||
gtk_file_chooser_dialog_new(_("Save File"), this->getWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"),
|
||||
GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, nullptr);
|
||||
|
||||
GtkFileFilter* filterXoj = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterXoj, _("Xournal++ template"));
|
||||
gtk_file_filter_add_mime_type(filterXoj, "application/x-xopt");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterXoj);
|
||||
|
||||
if (!settings->getLastSavePath().empty()) {
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(settings->getLastSavePath()).get(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
time_t curtime = time(nullptr);
|
||||
char stime[128];
|
||||
strftime(stime, sizeof(stime), "%F-Template-%H-%M.xopt", localtime(&curtime));
|
||||
fs::path saveFilename = stime; // There may be an issue here, if the C and C++ locales do not use the same encoding
|
||||
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), Util::toGFilename(saveFilename).c_str());
|
||||
|
||||
class FileDlg final {
|
||||
public:
|
||||
FileDlg(GtkDialog* dialog, PageTemplateDialog* parent): window(GTK_WINDOW(dialog)), parent(parent) {
|
||||
this->signalId = g_signal_connect(
|
||||
dialog, "response", G_CALLBACK((+[](GtkDialog* dialog, int response, gpointer data) {
|
||||
FileDlg* self = static_cast<FileDlg*>(data);
|
||||
if (response == GTK_RESPONSE_OK) {
|
||||
auto file = Util::fromGFile(
|
||||
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)),
|
||||
xoj::util::adopt)
|
||||
.get());
|
||||
|
||||
auto saveTemplate = [self, dialog](const fs::path& file) {
|
||||
// Closing the window causes another "response" signal, which we want to ignore
|
||||
g_signal_handler_disconnect(dialog, self->signalId);
|
||||
gtk_window_close(GTK_WINDOW(dialog));
|
||||
self->parent->settings->setLastSavePath(file.parent_path());
|
||||
|
||||
auto out = serdes_stream<std::ofstream>(file);
|
||||
out << self->parent->model.toString();
|
||||
};
|
||||
XojMsgBox::replaceFileQuestion(GTK_WINDOW(dialog), std::move(file),
|
||||
std::move(saveTemplate));
|
||||
} else {
|
||||
// Closing the window causes another "response" signal, which we want to ignore
|
||||
g_signal_handler_disconnect(dialog, self->signalId);
|
||||
gtk_window_close(GTK_WINDOW(dialog)); // Deletes self, don't do anything after this
|
||||
}
|
||||
})),
|
||||
this);
|
||||
}
|
||||
~FileDlg() = default;
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
PageTemplateDialog* parent;
|
||||
gulong signalId;
|
||||
};
|
||||
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(GTK_DIALOG(dialog), this);
|
||||
popup.show(GTK_WINDOW(this->getWindow()));
|
||||
}
|
||||
|
||||
void PageTemplateDialog::loadFromFile() {
|
||||
xoj::OpenDlg::showOpenTemplateDialog(this->getWindow(), settings, [this](fs::path path) {
|
||||
auto contents = Util::readString(path);
|
||||
if (!contents.has_value()) {
|
||||
return;
|
||||
}
|
||||
model.parse(*contents);
|
||||
|
||||
updateDataFromModel();
|
||||
});
|
||||
}
|
||||
|
||||
void PageTemplateDialog::updatePageSize() {
|
||||
const FormatUnits* formatUnit = &XOJ_UNITS[settings->getSizeUnitIndex()];
|
||||
|
||||
char buffer[64];
|
||||
sprintf(buffer, "%0.2lf", model.getPageWidth() / formatUnit->scale);
|
||||
std::string pageSize = buffer;
|
||||
pageSize += formatUnit->name;
|
||||
pageSize += " x ";
|
||||
|
||||
sprintf(buffer, "%0.2lf", model.getPageHeight() / formatUnit->scale);
|
||||
pageSize += buffer;
|
||||
pageSize += formatUnit->name;
|
||||
|
||||
gtk_label_set_text(pageSizeLabel, pageSize.c_str());
|
||||
}
|
||||
|
||||
void PageTemplateDialog::showPageSizeDialog() {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<xoj::popup::FormatDialog>(gladeSearchPath, settings,
|
||||
model.getPageWidth(), model.getPageHeight(),
|
||||
[dlg = this](double width, double height) {
|
||||
dlg->model.setPageWidth(width);
|
||||
dlg->model.setPageHeight(height);
|
||||
|
||||
dlg->updatePageSize();
|
||||
});
|
||||
popup.show(this->getWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* The dialog was confirmed / saved
|
||||
*/
|
||||
auto PageTemplateDialog::isSaved() const -> bool { return saved; }
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Dialog to configure page template for new pages
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory> // for unique_ptr
|
||||
|
||||
#include "control/settings/PageTemplateSettings.h" // for PageTemplateSettings
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class PageTypeHandler;
|
||||
class PageTypeInfo;
|
||||
class GladeSearchpath;
|
||||
class Settings;
|
||||
class ToolMenuHandler;
|
||||
class PageTypeSelectionPopoverGridOnly;
|
||||
|
||||
namespace xoj::popup {
|
||||
class PageTemplateDialog final {
|
||||
public:
|
||||
PageTemplateDialog(GladeSearchpath* gladeSearchPath, Settings* settings, ToolMenuHandler* toolmenu,
|
||||
PageTypeHandler* types);
|
||||
PageTemplateDialog(PageTemplateDialog&) = delete;
|
||||
PageTemplateDialog(PageTemplateDialog&&) = delete;
|
||||
PageTemplateDialog& operator=(PageTemplateDialog&) = delete;
|
||||
PageTemplateDialog&& operator=(PageTemplateDialog&&) = delete;
|
||||
~PageTemplateDialog();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The dialog was confirmed / saved
|
||||
*/
|
||||
bool isSaved() const;
|
||||
|
||||
void changeCurrentPageBackground(const PageTypeInfo* info);
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void showPageSizeDialog();
|
||||
void updatePageSize();
|
||||
void saveToFile();
|
||||
void loadFromFile();
|
||||
void updateDataFromModel();
|
||||
void saveToModel();
|
||||
|
||||
private:
|
||||
GladeSearchpath* gladeSearchPath; // For opening subdialogs as needed
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
|
||||
Settings* settings;
|
||||
ToolMenuHandler* toolMenuHandler;
|
||||
PageTypeHandler* types;
|
||||
|
||||
PageTemplateSettings model;
|
||||
|
||||
std::unique_ptr<PageTypeSelectionPopoverGridOnly> pageTypeSelectionMenu;
|
||||
|
||||
/**
|
||||
* The dialog was confirmed / saved
|
||||
*/
|
||||
bool saved = false;
|
||||
|
||||
private:
|
||||
// Sub-widgets, owned by this->window
|
||||
GtkLabel* pageSizeLabel;
|
||||
GtkLabel* backgroundTypeLabel;
|
||||
GtkColorChooser* backgroundColorChooser;
|
||||
GtkToggleButton* copyLastPageButton;
|
||||
GtkToggleButton* copyLastPageSizeButton;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "PluginDialog.h"
|
||||
|
||||
#ifdef ENABLE_PLUGINS
|
||||
|
||||
#include <algorithm>
|
||||
#include <string> // for string
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/dialog/PluginDialogEntry.h" // for PluginDialogEntry
|
||||
#include "plugin/PluginController.h" // for PluginController
|
||||
|
||||
class GladeSearchpath;
|
||||
class Plugin;
|
||||
|
||||
constexpr auto UI_FILE = "plugin.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "pluginDialog";
|
||||
|
||||
PluginDialog::PluginDialog(GladeSearchpath* gladeSearchPath, Settings* settings,
|
||||
const std::vector<std::unique_ptr<Plugin>>& pluginList):
|
||||
settings(settings) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
|
||||
GtkBox* pluginBox = GTK_BOX(builder.get("pluginBox"));
|
||||
std::transform(pluginList.begin(), pluginList.end(), std::back_inserter(this->plugins),
|
||||
[&](auto& p) { return std::make_unique<PluginDialogEntry>(p.get(), gladeSearchPath, pluginBox); });
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](PluginDialog* self) {
|
||||
self->saveSettings();
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
void PluginDialog::saveSettings() {
|
||||
std::string pluginEnabled;
|
||||
std::string pluginDisabled;
|
||||
|
||||
// Save plugin settings
|
||||
for (auto&& bcg: this->plugins) { bcg->saveSettings(pluginEnabled, pluginDisabled); }
|
||||
|
||||
settings->setPluginEnabled(pluginEnabled);
|
||||
settings->setPluginDisabled(pluginDisabled);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Plugin manage dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config-features.h"
|
||||
|
||||
#ifdef ENABLE_PLUGINS
|
||||
|
||||
#include <memory> // for unique_ptr
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
#include "PluginDialogEntry.h" // for PluginDialogEntry
|
||||
|
||||
class Plugin;
|
||||
class PluginController;
|
||||
class Settings;
|
||||
class GladeSearchpath;
|
||||
|
||||
class PluginDialog {
|
||||
public:
|
||||
PluginDialog(GladeSearchpath* gladeSearchPath, Settings* settings,
|
||||
const std::vector<std::unique_ptr<Plugin>>& plugins);
|
||||
|
||||
public:
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
Settings* settings;
|
||||
|
||||
std::vector<std::unique_ptr<PluginDialogEntry>> plugins;
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "PluginDialogEntry.h"
|
||||
|
||||
#ifdef ENABLE_PLUGINS
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "plugin/Plugin.h"
|
||||
#include "util/gtk4_helper.h"
|
||||
#include "util/i18n.h"
|
||||
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "pluginEntry.glade";
|
||||
constexpr auto UI_WIDGET_NAME = "pluginMainBox";
|
||||
|
||||
PluginDialogEntry::PluginDialogEntry(Plugin* plugin, GladeSearchpath* gladeSearchPath, GtkBox* box): plugin(plugin) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
gtk_box_append(box, builder.get(UI_WIDGET_NAME));
|
||||
stateButton = GTK_CHECK_BUTTON(builder.get("cbEnabled"));
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("pluginName")), plugin->getName().c_str());
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbAuthor")), plugin->getAuthor().c_str());
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbVersion")), plugin->getVersion().c_str());
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbDescription")), plugin->getDescription().c_str());
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbPath")), char_cast(plugin->getPath().u8string().c_str()));
|
||||
gtk_label_set_text(GTK_LABEL(builder.get("lbDefaultText")),
|
||||
plugin->isDefaultEnabled() ? _("default enabled") : _("default disabled"));
|
||||
gtk_check_button_set_active(stateButton, plugin->isEnabled());
|
||||
}
|
||||
|
||||
void PluginDialogEntry::saveSettings(std::string& pluginEnabled, std::string& pluginDisabled) {
|
||||
bool state = gtk_check_button_get_active(stateButton);
|
||||
|
||||
if (state == plugin->isDefaultEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
if (!pluginEnabled.empty()) {
|
||||
pluginEnabled += ",";
|
||||
}
|
||||
pluginEnabled.append(char_cast(plugin->getPath().u8string()));
|
||||
} else {
|
||||
if (!pluginDisabled.empty()) {
|
||||
pluginDisabled += ",";
|
||||
}
|
||||
pluginDisabled.append(char_cast(plugin->getPath().u8string()));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Configuration of a single plugin
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config-features.h"
|
||||
|
||||
#ifdef ENABLE_PLUGINS
|
||||
#include <string> // for string
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
class Plugin;
|
||||
class GladeSearchpath;
|
||||
|
||||
class PluginDialogEntry {
|
||||
public:
|
||||
PluginDialogEntry(Plugin* plugin, GladeSearchpath* gladeSearchPath, GtkBox* box);
|
||||
|
||||
public:
|
||||
void saveSettings(std::string& pluginEnabled, std::string& pluginDisabled);
|
||||
|
||||
|
||||
private:
|
||||
/**
|
||||
* Plugin instance
|
||||
*/
|
||||
Plugin* plugin;
|
||||
|
||||
GtkCheckButton* stateButton;
|
||||
};
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "RenameLayerDialog.h"
|
||||
|
||||
#include <memory> // for allocator, make_unique
|
||||
#include <string> // for string
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
|
||||
#include "control/layer/LayerController.h" // for LayerController
|
||||
#include "gui/Builder.h"
|
||||
#include "undo/LayerRenameUndoAction.h" // for LayerRenameUndoAction
|
||||
#include "undo/UndoRedoHandler.h" // for UndoRedoHandler
|
||||
#include "util/gtk4_helper.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
class Layer;
|
||||
|
||||
constexpr auto UI_FILE = "renameLayerDialog.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "renameLayerDialog";
|
||||
|
||||
RenameLayerDialog::RenameLayerDialog(GladeSearchpath* gladeSearchPath, UndoRedoHandler* undo, LayerController* lc,
|
||||
Layer* l):
|
||||
lc(lc), undo(undo), l(l) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
layerNameEntry = GTK_ENTRY(builder.get("layerNameEntry"));
|
||||
|
||||
gtk_editable_set_text(GTK_EDITABLE(layerNameEntry), lc->getCurrentLayerName().c_str());
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect_swapped(builder.get("btOk"), "clicked", G_CALLBACK(+[](RenameLayerDialog* self) {
|
||||
std::string newName = gtk_editable_get_text(GTK_EDITABLE(self->layerNameEntry));
|
||||
|
||||
self->undo->addUndoAction(std::make_unique<LayerRenameUndoAction>(
|
||||
self->lc, self->l, newName, self->lc->getCurrentLayerName()));
|
||||
|
||||
self->lc->setCurrentLayerName(newName);
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The rename layer dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
class Layer;
|
||||
class LayerController;
|
||||
class UndoRedoHandler;
|
||||
|
||||
class RenameLayerDialog {
|
||||
public:
|
||||
RenameLayerDialog(GladeSearchpath* gladeSearchPath, UndoRedoHandler* undo, LayerController* lc, Layer* l);
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
LayerController* lc;
|
||||
UndoRedoHandler* undo;
|
||||
Layer* l;
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
GtkEntry* layerNameEntry;
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
#include "SelectBackgroundColorDialog.h"
|
||||
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <string> // for allocator
|
||||
|
||||
#include <glib.h> // for g_free, g_strdup_printf
|
||||
|
||||
#include "control/Control.h" // for Control
|
||||
#include "control/settings/Settings.h" // for SElement, Settings
|
||||
#include "util/Color.h"
|
||||
#include "util/i18n.h" // for _
|
||||
#include "util/safe_casts.h" // for as_unsigned
|
||||
|
||||
static inline std::array<GdkRGBA, 9> background1 = {
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_lightpink), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_bisque), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_lemonchifon), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_palegoldenrod), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_lavender), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_gainsboro03), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_antiquewhite), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_gainsboro), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_snow) //
|
||||
};
|
||||
|
||||
static inline std::array<GdkRGBA, 6> backgroundXournal = {
|
||||
Util::rgb_to_GdkRGBA(Colors::white), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_paleturqoise), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_aquamarine), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_pink), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_lightsalmon), //
|
||||
Util::rgb_to_GdkRGBA(Colors::xopp_khaki) //
|
||||
};
|
||||
|
||||
SelectBackgroundColorDialog::SelectBackgroundColorDialog(Control* control, std::function<void(Color)> callback):
|
||||
control(control), callback(callback) {
|
||||
lastBackgroundColors.fill(Util::rgb_to_GdkRGBA(Colors::white));
|
||||
Settings* settings = control->getSettings();
|
||||
SElement& el = settings->getCustomElement("lastUsedPageBgColor");
|
||||
|
||||
int count = 0;
|
||||
el.getInt("count", count);
|
||||
|
||||
auto index = 0ULL;
|
||||
|
||||
for (int i = 0; i < count && index < LAST_BACKGROUND_COLOR_COUNT; i++) {
|
||||
int iColor{};
|
||||
char* settingName = g_strdup_printf("color%02i", i);
|
||||
bool read = el.getInt(settingName, iColor);
|
||||
g_free(settingName);
|
||||
|
||||
if (!read) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lastBackgroundColors[index] = Util::rgb_to_GdkRGBA(Color(as_unsigned(iColor)));
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
|
||||
window.reset(GTK_WINDOW(gtk_color_chooser_dialog_new(_("Select background color"), nullptr)));
|
||||
GtkColorChooser* chooser = GTK_COLOR_CHOOSER(window.get());
|
||||
gtk_color_chooser_set_use_alpha(chooser, false);
|
||||
|
||||
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, background1.size(), background1.data());
|
||||
|
||||
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, backgroundXournal.size(),
|
||||
backgroundXournal.data());
|
||||
|
||||
// Last used colors (only by background, the general last used are shared with the
|
||||
// pen and highlighter etc.)
|
||||
gtk_color_chooser_add_palette(chooser, GTK_ORIENTATION_HORIZONTAL, 9, static_cast<int>(lastBackgroundColors.size()),
|
||||
lastBackgroundColors.data());
|
||||
|
||||
g_signal_connect(window.get(), "response",
|
||||
G_CALLBACK(+[](GtkColorChooser*, int response, SelectBackgroundColorDialog* self) {
|
||||
if (response == GTK_RESPONSE_DELETE_EVENT) {
|
||||
// Ignore DELETE_EVENT, as this is caught by the PopupWindowWrapper
|
||||
return;
|
||||
}
|
||||
if (response == GTK_RESPONSE_OK) {
|
||||
GdkRGBA color;
|
||||
gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(self->window.get()), &color);
|
||||
self->storeLastUsedValuesInSettings(color);
|
||||
self->callback(Util::GdkRGBA_to_argb(color));
|
||||
}
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
void SelectBackgroundColorDialog::storeLastUsedValuesInSettings(GdkRGBA color) {
|
||||
for (auto& lastBackgroundColor: lastBackgroundColors) {
|
||||
if (gdk_rgba_equal(&lastBackgroundColor, &color)) {
|
||||
// The new color is already in the list, do not save
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Settings* settings = control->getSettings();
|
||||
SElement& el = settings->getCustomElement("lastUsedPageBgColor");
|
||||
|
||||
// Move all colors one step back
|
||||
for (size_t i = LAST_BACKGROUND_COLOR_COUNT - 1; i > 0; i--) {
|
||||
lastBackgroundColors[i] = lastBackgroundColors[i - 1];
|
||||
}
|
||||
|
||||
lastBackgroundColors[0] = color;
|
||||
|
||||
el.setInt("count", LAST_BACKGROUND_COLOR_COUNT);
|
||||
for (size_t i = 0; i < LAST_BACKGROUND_COLOR_COUNT; i++) {
|
||||
char* settingName = g_strdup_printf("color%02zu", i);
|
||||
el.setIntHex(settingName, int(uint32_t(Util::GdkRGBA_to_argb(lastBackgroundColors[i]))));
|
||||
g_free(settingName);
|
||||
}
|
||||
|
||||
settings->customSettingsChanged();
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Dialog for selecting a background color
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // for array
|
||||
#include <functional>
|
||||
|
||||
#include <gdk/gdk.h> // for GdkRGBA
|
||||
#include <gtk/gtk.h> // for GtkWindow
|
||||
|
||||
#include "util/Color.h"
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class Control;
|
||||
|
||||
/**
|
||||
* Count of last background colors
|
||||
*/
|
||||
const int LAST_BACKGROUND_COLOR_COUNT = 9;
|
||||
|
||||
|
||||
class SelectBackgroundColorDialog {
|
||||
public:
|
||||
SelectBackgroundColorDialog(Control* control, std::function<void(Color)> callback);
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void storeLastUsedValuesInSettings(GdkRGBA color);
|
||||
|
||||
private:
|
||||
Control* control{nullptr};
|
||||
|
||||
/**
|
||||
* Last used background colors (stored in settings)
|
||||
*/
|
||||
std::array<GdkRGBA, LAST_BACKGROUND_COLOR_COUNT> lastBackgroundColors{};
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
std::function<void(Color)> callback;
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
#include "SelectOpacityDialog.h"
|
||||
|
||||
#include <cairo.h> // for cairo_set_operator, cairo_rectangle, cairo_...
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
#include <glib.h> // for gdouble
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "util/Stacktrace.h"
|
||||
#include "util/i18n.h"
|
||||
#include "util/raii/CairoWrappers.h"
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
#include "util/safe_casts.h" // for round_cast
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "selectOpacity.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "selectOpacityDialog";
|
||||
|
||||
static int percentToByte(double percent) { return round_cast<int>(percent * 2.55); }
|
||||
static double byteToPercent(int byte) { return byte / 2.55; }
|
||||
|
||||
static inline void buildLabel(Builder& builder, OpacityFeature opacityFeature) {
|
||||
// Used to set the label of the dialog in the form of:
|
||||
// <b>{toolOptionsDesc}</b>\n
|
||||
// Select opacity for: {opacityFeatureDesc}
|
||||
std::string toolOptionsDesc;
|
||||
std::string selectOpacityFor = _("Select opacity for: ");
|
||||
std::string opacityFeatureDesc;
|
||||
|
||||
switch (opacityFeature) {
|
||||
case OPACITY_FILL_HIGHLIGHTER:
|
||||
toolOptionsDesc = _("Highlighter Options");
|
||||
opacityFeatureDesc = _("Fill color");
|
||||
break;
|
||||
case OPACITY_FILL_PEN:
|
||||
toolOptionsDesc = _("Pen Options");
|
||||
opacityFeatureDesc = _("Fill color");
|
||||
break;
|
||||
case OPACITY_SELECT_PDF_TEXT_MARKER:
|
||||
toolOptionsDesc = _("PDF Text Options");
|
||||
opacityFeatureDesc = _("PDF Text Marker");
|
||||
break;
|
||||
default:
|
||||
g_warning("No opacityFeature description set for '%s'", opacityFeatureToString(opacityFeature).data());
|
||||
Stacktrace::printStacktrace();
|
||||
break;
|
||||
}
|
||||
gtk_label_set_label(GTK_LABEL(builder.get("label1")),
|
||||
FC(_F("<b>{1}</b>\n{2}{3}") % toolOptionsDesc % selectOpacityFor % opacityFeatureDesc));
|
||||
}
|
||||
|
||||
xoj::popup::SelectOpacityDialog::SelectOpacityDialog(GladeSearchpath* gladeSearchPath, int alpha,
|
||||
OpacityFeature feature,
|
||||
std::function<void(int, OpacityFeature)> callback):
|
||||
opacityFeature(feature), callback(callback) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
|
||||
buildLabel(builder, opacityFeature);
|
||||
previewImage = GTK_IMAGE(builder.get("imgPreview"));
|
||||
alphaRange = GTK_RANGE(builder.get("scaleAlpha"));
|
||||
|
||||
gtk_range_set_value(alphaRange, byteToPercent(alpha));
|
||||
|
||||
setPreviewImage(alpha);
|
||||
|
||||
|
||||
g_signal_connect(alphaRange, "value-changed", G_CALLBACK(+[](GtkRange* range, gpointer self) {
|
||||
static_cast<SelectOpacityDialog*>(self)->setPreviewImage(
|
||||
percentToByte(round_cast<int>(gtk_range_get_value(range))));
|
||||
}),
|
||||
this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
g_signal_connect(builder.get("btOk"), "clicked", G_CALLBACK(+[](GtkButton*, SelectOpacityDialog* self) {
|
||||
self->callback(percentToByte(gtk_range_get_value(self->alphaRange)), self->opacityFeature);
|
||||
gtk_window_close(self->window.get());
|
||||
}),
|
||||
this);
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
// Widgets are visible by default in gtk4
|
||||
gtk_widget_show_all(builder.get("dialog-main-box"));
|
||||
#endif
|
||||
}
|
||||
|
||||
xoj::popup::SelectOpacityDialog::~SelectOpacityDialog() = default;
|
||||
|
||||
const int PREVIEW_WIDTH = 70;
|
||||
const int PREVIEW_HEIGTH = 50;
|
||||
const int PREVIEW_BORDER = 10;
|
||||
|
||||
void xoj::popup::SelectOpacityDialog::setPreviewImage(int alpha) {
|
||||
xoj::util::CairoSurfaceSPtr surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PREVIEW_WIDTH, PREVIEW_HEIGTH),
|
||||
xoj::util::adopt);
|
||||
xoj::util::CairoSPtr cairo(cairo_create(surface.get()), xoj::util::adopt);
|
||||
cairo_t* cr = cairo.get();
|
||||
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_set_source_rgb(cr, 255, 255, 255);
|
||||
cairo_paint(cr);
|
||||
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
||||
cairo_set_source_rgba(cr, 0, 0x80 / 255.0, 0, alpha / 255.0);
|
||||
cairo_rectangle(cr, PREVIEW_BORDER, PREVIEW_BORDER, PREVIEW_WIDTH - PREVIEW_BORDER * 2,
|
||||
PREVIEW_HEIGTH - PREVIEW_BORDER * 2);
|
||||
cairo_fill(cr);
|
||||
|
||||
if (opacityFeature == OPACITY_FILL_PEN) {
|
||||
cairo_set_line_width(cr, 5);
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_set_source_rgb(cr, 0, 0x80 / 255.0, 0);
|
||||
cairo_rectangle(cr, PREVIEW_BORDER, PREVIEW_BORDER, PREVIEW_WIDTH - PREVIEW_BORDER * 2,
|
||||
PREVIEW_HEIGTH - PREVIEW_BORDER * 2);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
xoj::util::GObjectSPtr<GdkPixbuf> pixbuf(
|
||||
gdk_pixbuf_get_from_surface(surface.get(), 0, 0, PREVIEW_WIDTH, PREVIEW_HEIGTH), xoj::util::adopt);
|
||||
gtk_image_set_from_pixbuf(previewImage, pixbuf.get());
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* The about dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "control/ToolEnums.h"
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
namespace xoj::popup {
|
||||
class SelectOpacityDialog {
|
||||
public:
|
||||
SelectOpacityDialog(GladeSearchpath* gladeSearchPath, int alpha, OpacityFeature type,
|
||||
std::function<void(int, OpacityFeature)> callback);
|
||||
~SelectOpacityDialog();
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void setPreviewImage(int alpha);
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
GtkImage* previewImage;
|
||||
GtkRange* alphaRange;
|
||||
|
||||
OpacityFeature opacityFeature;
|
||||
std::function<void(int, OpacityFeature)> callback;
|
||||
};
|
||||
}; // namespace xoj::popup
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Settings Dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkWindow
|
||||
|
||||
#include "audio/DeviceInfo.h" // for DeviceInfo
|
||||
#include "control/tools/StrokeStabilizerEnum.h" // for AveragingMethod, Pre...
|
||||
#include "gui/Builder.h"
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
#include "ButtonConfigGui.h"
|
||||
#include "LanguageConfigGui.h"
|
||||
#include "LatexSettingsPanel.h"
|
||||
#include "SettingsDialogPaletteTab.h"
|
||||
#include "config-features.h" // for ENABLE_AUDIO
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class Control;
|
||||
class Settings;
|
||||
class DeviceTestingArea;
|
||||
|
||||
struct Palette;
|
||||
|
||||
class SettingsDialog {
|
||||
public:
|
||||
SettingsDialog(GladeSearchpath* gladeSearchPath, Settings* settings, Control* control,
|
||||
const std::vector<fs::path>& paletteDirectories, std::function<void()> callback);
|
||||
~SettingsDialog();
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
void save();
|
||||
void setDpi(int dpi);
|
||||
|
||||
/**
|
||||
* Set active regions
|
||||
*/
|
||||
void enableWithCheckbox(const std::string& checkbox, const std::string& widget);
|
||||
void disableWithCheckbox(const std::string& checkbox, const std::string& widget);
|
||||
void enableWithEnabledCheckbox(const std::string& checkbox, const std::string& widget);
|
||||
|
||||
/*
|
||||
* Listeners for changes to settings.
|
||||
*/
|
||||
void customHandRecognitionToggled();
|
||||
void customStylusIconTypeChanged();
|
||||
|
||||
/**
|
||||
* Update whether options can be selected, tooltips, etc. for
|
||||
* pressure sensitivity options (e.g. pressure multiplier).
|
||||
*/
|
||||
void updatePressureSensitivityOptions();
|
||||
|
||||
private:
|
||||
void load();
|
||||
void loadCheckbox(const char* name, bool value);
|
||||
bool getCheckbox(const char* name);
|
||||
|
||||
void loadSlider(const char* name, double value);
|
||||
double getSlider(const char* name);
|
||||
|
||||
void initMouseButtonEvents(GladeSearchpath* gladeSearchPath);
|
||||
|
||||
void showStabilizerAvMethodOptions(StrokeStabilizer::AveragingMethod method);
|
||||
void showStabilizerPreprocessorOptions(StrokeStabilizer::Preprocessor preprocessor);
|
||||
|
||||
private:
|
||||
Settings* settings = nullptr;
|
||||
Control* control = nullptr;
|
||||
GtkWidget* callib = nullptr;
|
||||
int dpi = 72;
|
||||
|
||||
#ifdef ENABLE_AUDIO
|
||||
std::vector<DeviceInfo> audioInputDevices;
|
||||
std::vector<DeviceInfo> audioOutputDevices;
|
||||
#endif
|
||||
|
||||
Builder builder;
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
|
||||
std::unique_ptr<DeviceTestingArea> deviceTestingArea;
|
||||
|
||||
LanguageConfigGui languageConfig;
|
||||
std::vector<std::unique_ptr<ButtonConfigGui>> buttonConfigs;
|
||||
|
||||
LatexSettingsPanel latexPanel;
|
||||
SettingsDialogPaletteTab paletteTab;
|
||||
|
||||
std::function<void()> callback;
|
||||
};
|
||||
@@ -1,213 +0,0 @@
|
||||
#include "SettingsDialogPaletteTab.h"
|
||||
|
||||
#include "gui/toolbarMenubar/icon/ColorIcon.h"
|
||||
#include "util/PathUtil.h"
|
||||
#include "util/XojMsgBox.h"
|
||||
#include "util/gtk4_helper.h"
|
||||
#include "util/i18n.h"
|
||||
|
||||
static const char* const G_OBJECT_PALETTE_PATH = "xournalpp.palettePath";
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// HELPER
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
auto colorize(const std::string& text, const std::string& color) -> std::string {
|
||||
return std::string{"<span foreground=\""} + color + std::string{"\">"} + text + std::string{"</span>"};
|
||||
}
|
||||
|
||||
void setGObjectPalettePath(GObject* gObject, const fs::path& path) {
|
||||
g_object_set_data(gObject, G_OBJECT_PALETTE_PATH, (gpointer)&path);
|
||||
}
|
||||
|
||||
auto getGObjectPalettePath(GObject* gObject) -> fs::path {
|
||||
auto* pathPointer = (fs::path*)g_object_get_data(gObject, G_OBJECT_PALETTE_PATH);
|
||||
return *pathPointer;
|
||||
}
|
||||
|
||||
auto getDisplayPalettePath(const fs::path& path) -> std::string {
|
||||
if (path.parent_path() == Util::getBuiltInPaletteDirectoryPath()) {
|
||||
return std::string{"└─ "} + "<i>" + FS(_F("Built-in palettes")) + "/</i> " +
|
||||
char_cast(path.filename().u8string().c_str());
|
||||
} else if (path.parent_path() == Util::getCustomPaletteDirectoryPath()) {
|
||||
return std::string{"└─ "} + "<i>" + FS(_F("User palettes")) + "/</i> " +
|
||||
char_cast(path.filename().u8string().c_str());
|
||||
} else {
|
||||
g_warning("Unexpected type of palette path encountered %s", char_cast(path.parent_path().u8string().c_str()));
|
||||
return std::string{"└─ "} + char_cast(path.u8string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
constexpr auto UI_FILE = "paletteSettings.glade";
|
||||
constexpr auto UI_PANEL_NAME = "paletteSettingsPanel";
|
||||
|
||||
SettingsDialogPaletteTab::SettingsDialogPaletteTab(GladeSearchpath* gladeSearchPath,
|
||||
const std::vector<fs::path>& paletteDirectories):
|
||||
builder(gladeSearchPath, UI_FILE) {
|
||||
colorPaletteExplainLabel = GTK_LABEL(builder.get("colorPaletteExplainLabel"));
|
||||
paletteListBox = GTK_LIST_BOX(builder.get("paletteListBox"));
|
||||
panel = GTK_SCROLLED_WINDOW(builder.get(UI_PANEL_NAME));
|
||||
renderColorPaletteExplainLabel();
|
||||
setAllPaletteFilePaths(paletteDirectories);
|
||||
}
|
||||
|
||||
void SettingsDialogPaletteTab::renderPaletteTab(const fs::path& currentlySetPalettePath) {
|
||||
GtkListBox* lb = paletteListBox;
|
||||
if (allPaletteFilePaths.empty()) {
|
||||
renderNoPaletteFoundDisclaimer(lb);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const fs::path& p: allPaletteFilePaths) {
|
||||
GtkWidget* listBoxRow = renderPaletteListBoxRow(lb, p);
|
||||
|
||||
if (p == currentlySetPalettePath) {
|
||||
gtk_list_box_select_row(GTK_LIST_BOX(lb), GTK_LIST_BOX_ROW(listBoxRow));
|
||||
}
|
||||
}
|
||||
gtk_widget_show_all(GTK_WIDGET(lb));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
auto SettingsDialogPaletteTab::renderPaletteListBoxRow(GtkListBox* lb, const fs::path& p) -> GtkWidget* {
|
||||
Palette palette{p};
|
||||
GtkWidget* listBoxRow = nullptr;
|
||||
|
||||
try {
|
||||
palette.load();
|
||||
listBoxRow = newPaletteListBoxRow(palette);
|
||||
} catch (const std::exception& e) {
|
||||
listBoxRow = newErrorListBoxRow(p, e.what());
|
||||
}
|
||||
setGObjectPalettePath(G_OBJECT(listBoxRow), p);
|
||||
|
||||
gtk_list_box_prepend(lb, listBoxRow);
|
||||
return listBoxRow;
|
||||
}
|
||||
|
||||
void SettingsDialogPaletteTab::renderColorPaletteExplainLabel() const {
|
||||
gtk_label_set_label(colorPaletteExplainLabel, FS(_F("<i>The palettes shown below are obtained from the "
|
||||
"<a href=\"{1}\">Built-in palettes</a> and "
|
||||
"<a href=\"{2}\">User palettes</a> directories:</i>\n") %
|
||||
Util::toUri(Util::getBuiltInPaletteDirectoryPath()).value() %
|
||||
Util::toUri(Util::getCustomPaletteDirectoryPath()).value())
|
||||
.c_str());
|
||||
gtk_label_set_wrap(colorPaletteExplainLabel, true);
|
||||
gtk_label_set_use_markup(colorPaletteExplainLabel, true);
|
||||
g_signal_connect(colorPaletteExplainLabel, "activate-link", G_CALLBACK(+[](GtkLabel*, gchar* uri, gpointer) {
|
||||
XojMsgBox::openURL(nullptr, uri);
|
||||
return true; // default handler does not work on Windows
|
||||
}),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void SettingsDialogPaletteTab::renderNoPaletteFoundDisclaimer(GtkListBox* lb) {
|
||||
GtkWidget* listBoxRow = gtk_list_box_row_new();
|
||||
GtkWidget* label = gtk_label_new(("<span foreground=\"red\">" +
|
||||
FS(_F("No palette files (i.e. with extension .gpl) could be found.\n"
|
||||
"Using the default until another palette is configured.")) +
|
||||
"</span>")
|
||||
.c_str());
|
||||
gtk_label_set_use_markup(GTK_LABEL(label), true);
|
||||
|
||||
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), label);
|
||||
gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(listBoxRow), FALSE);
|
||||
gtk_list_box_row_set_selectable(GTK_LIST_BOX_ROW(listBoxRow), FALSE);
|
||||
|
||||
gtk_list_box_prepend(lb, listBoxRow);
|
||||
}
|
||||
|
||||
// use list of fs::path as input
|
||||
void SettingsDialogPaletteTab::setAllPaletteFilePaths(const std::vector<fs::path>& paletteDirectories) {
|
||||
for (const fs::path& paletteDirectory: paletteDirectories) {
|
||||
std::vector<fs::path> const files = Util::listFilesSorted(paletteDirectory);
|
||||
for (const fs::path& paletteFile: files) {
|
||||
if (paletteFile.extension() == ".gpl") {
|
||||
allPaletteFilePaths.push_back(paletteFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto SettingsDialogPaletteTab::getSelectedPalette() const -> std::optional<fs::path> {
|
||||
GtkListBoxRow* selected_listbox_row = gtk_list_box_get_selected_row(paletteListBox);
|
||||
if (allPaletteFilePaths.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!G_IS_OBJECT(selected_listbox_row)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return getGObjectPalettePath(G_OBJECT(selected_listbox_row));
|
||||
}
|
||||
|
||||
auto SettingsDialogPaletteTab::newErrorListBoxRow(const fs::path& palettePath, const std::string& error) -> GtkWidget* {
|
||||
GtkWidget* listBoxRow = gtk_list_box_row_new();
|
||||
GtkWidget* rowContent = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
|
||||
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), rowContent);
|
||||
|
||||
std::string const formattedError = colorize(FS(_F("Error")) + ": " + error, "red");
|
||||
GtkWidget* text = newPaletteTextBox(formattedError, palettePath);
|
||||
gtk_box_append(GTK_BOX(rowContent), text);
|
||||
|
||||
return listBoxRow;
|
||||
}
|
||||
|
||||
auto SettingsDialogPaletteTab::newPaletteListBoxRow(Palette& palette) -> GtkWidget* {
|
||||
GtkWidget* listBoxRow = gtk_list_box_row_new();
|
||||
GtkWidget* rowContent = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
|
||||
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(listBoxRow), rowContent);
|
||||
|
||||
std::string const paletteName = palette.getHeader(std::string{FS(_F("Name"))});
|
||||
GtkWidget* text = nullptr;
|
||||
if (paletteName.empty()) {
|
||||
text = newPaletteTextBox(std::string{"<i>" + FS(_F("Palette has no Name")) + "</i>"}, palette.getFilePath());
|
||||
} else {
|
||||
text = newPaletteTextBox(paletteName, palette.getFilePath().u8string());
|
||||
}
|
||||
gtk_box_append(GTK_BOX(rowContent), text);
|
||||
|
||||
GtkWidget* colorIcons = newPaletteColorIconsBox(palette);
|
||||
gtk_widget_set_hexpand(colorIcons, true);
|
||||
gtk_box_append(GTK_BOX(rowContent), colorIcons);
|
||||
|
||||
return listBoxRow;
|
||||
}
|
||||
|
||||
auto SettingsDialogPaletteTab::newPaletteTextBox(const std::string& mainContent, const fs::path& path) -> GtkWidget* {
|
||||
GtkWidget* textBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3);
|
||||
|
||||
GtkWidget* mainLabel = gtk_label_new(mainContent.c_str());
|
||||
gtk_widget_set_halign(mainLabel, GTK_ALIGN_START);
|
||||
gtk_label_set_use_markup(GTK_LABEL(mainLabel), true);
|
||||
gtk_box_append(GTK_BOX(textBox), mainLabel);
|
||||
|
||||
GtkWidget* secondaryLabel = gtk_label_new(getDisplayPalettePath(path).c_str());
|
||||
gtk_widget_set_halign(secondaryLabel, GTK_ALIGN_START);
|
||||
gtk_label_set_use_markup(GTK_LABEL(secondaryLabel), true);
|
||||
gtk_box_append(GTK_BOX(textBox), secondaryLabel);
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
auto SettingsDialogPaletteTab::newPaletteColorIconsBox(const Palette& palette) -> GtkWidget* {
|
||||
GtkWidget* colors = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
|
||||
for (unsigned long i = 0; i < palette.size(); i++) {
|
||||
const NamedColor& namedColor = palette.getColorAt(i);
|
||||
const Color c = namedColor.getColor();
|
||||
GtkWidget* icon = ColorIcon::newGtkImage(c, 16, true);
|
||||
gtk_box_append(GTK_BOX(colors), icon);
|
||||
}
|
||||
gtk_widget_set_halign(colors, GTK_ALIGN_END);
|
||||
return colors;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/GladeGui.h"
|
||||
#include "gui/toolbarMenubar/model/ColorPalette.h"
|
||||
class SettingsDialogPaletteTab {
|
||||
public:
|
||||
SettingsDialogPaletteTab(GladeSearchpath* gladeSearchPath, const std::vector<fs::path>& paletteDirectories);
|
||||
void renderPaletteTab(const fs::path& currentlySetPalettePath);
|
||||
auto getSelectedPalette() const -> std::optional<fs::path>;
|
||||
inline GtkWidget* getPanel() const { return GTK_WIDGET(panel); }
|
||||
|
||||
private:
|
||||
std::vector<fs::path> allPaletteFilePaths;
|
||||
GtkLabel* colorPaletteExplainLabel;
|
||||
GtkListBox* paletteListBox;
|
||||
Builder builder;
|
||||
GtkScrolledWindow* panel;
|
||||
|
||||
void setAllPaletteFilePaths(const std::vector<fs::path>& paletteDirectories);
|
||||
void renderColorPaletteExplainLabel() const;
|
||||
|
||||
static auto newErrorListBoxRow(const fs::path& palettePath, const std::string& error) -> GtkWidget*;
|
||||
static auto newPaletteTextBox(const std::string& mainContent, const fs::path& path) -> GtkWidget*;
|
||||
static auto newPaletteColorIconsBox(const Palette& palette) -> GtkWidget*;
|
||||
static auto newPaletteListBoxRow(Palette& palette) -> GtkWidget*;
|
||||
static void renderNoPaletteFoundDisclaimer(GtkListBox* lb);
|
||||
static auto renderPaletteListBoxRow(GtkListBox* lb, const fs::path& p) -> GtkWidget*;
|
||||
};
|
||||
@@ -1,187 +0,0 @@
|
||||
#include "ToolbarManageDialog.h"
|
||||
|
||||
#include <string> // for allocator, string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <glib-object.h> // for g_signal_connect
|
||||
#include <pango/pango.h> // for PANGO_WEIGHT_NORMAL
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "gui/toolbarMenubar/model/ToolbarData.h" // for ToolbarData
|
||||
#include "gui/toolbarMenubar/model/ToolbarModel.h" // for ToolbarModel
|
||||
#include "util/Assert.h" // for xoj_assert
|
||||
#include "util/i18n.h" // for _
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
constexpr auto UI_FILE = "toolbarManageDialog.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "manageToolbarsDialog";
|
||||
|
||||
enum { COLUMN_STRING, COLUMN_BOLD, COLUMN_POINTER, COLUMN_EDITABLE, N_COLUMNS };
|
||||
|
||||
static xoj::util::GObjectSPtr<GtkListStore> createModel(ToolbarModel* tb) {
|
||||
xoj_assert(tb);
|
||||
GtkTreeIter iter;
|
||||
GtkListStore* model = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_BOOLEAN);
|
||||
gtk_list_store_append(model, &iter);
|
||||
gtk_list_store_set(model, &iter, COLUMN_STRING, _("Predefined"), COLUMN_BOLD, PANGO_WEIGHT_BOLD, COLUMN_POINTER,
|
||||
nullptr, COLUMN_EDITABLE, false, -1);
|
||||
|
||||
for (const auto& data: tb->getToolbars()) {
|
||||
if (data->isPredefined()) {
|
||||
gtk_list_store_append(model, &iter);
|
||||
gtk_list_store_set(model, &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD, PANGO_WEIGHT_NORMAL,
|
||||
COLUMN_POINTER, data.get(), COLUMN_EDITABLE, false, -1);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_list_store_append(model, &iter);
|
||||
gtk_list_store_set(model, &iter, COLUMN_STRING, _("Customized"), COLUMN_BOLD, PANGO_WEIGHT_BOLD, COLUMN_POINTER,
|
||||
nullptr, COLUMN_EDITABLE, false, -1);
|
||||
|
||||
for (const auto& data: tb->getToolbars()) {
|
||||
if (!data->isPredefined()) {
|
||||
gtk_list_store_append(model, &iter);
|
||||
gtk_list_store_set(model, &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD, PANGO_WEIGHT_NORMAL,
|
||||
COLUMN_POINTER, data.get(), COLUMN_EDITABLE, true, -1);
|
||||
}
|
||||
}
|
||||
return xoj::util::GObjectSPtr<GtkListStore>(model, xoj::util::adopt);
|
||||
}
|
||||
|
||||
ToolbarManageDialog::ToolbarManageDialog(GladeSearchpath* gladeSearchPath, ToolbarModel* tbModel,
|
||||
std::function<void()> callback):
|
||||
tbModel(tbModel), model(createModel(tbModel)), callback(callback) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
tree = GTK_TREE_VIEW(builder.get("toolbarList"));
|
||||
copyButton = builder.get("btCopy");
|
||||
deleteButton = builder.get("btDelete");
|
||||
|
||||
gtk_tree_view_set_model(tree, GTK_TREE_MODEL(this->model.get()));
|
||||
|
||||
GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
|
||||
GtkTreeViewColumn* column =
|
||||
gtk_tree_view_column_new_with_attributes(_("Toolbars"), renderer, "text", COLUMN_STRING, "weight",
|
||||
COLUMN_BOLD, "editable", COLUMN_EDITABLE, nullptr);
|
||||
|
||||
gtk_tree_view_append_column(tree, column);
|
||||
|
||||
GtkTreeSelection* select = gtk_tree_view_get_selection(tree);
|
||||
gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
|
||||
g_signal_connect(select, "changed", G_CALLBACK(treeSelectionChangedCallback), this);
|
||||
|
||||
g_signal_connect(renderer, "edited", G_CALLBACK(treeCellEditedCallback), this);
|
||||
|
||||
g_signal_connect(builder.get("btNew"), "clicked", G_CALLBACK(buttonNewCallback), this);
|
||||
g_signal_connect(deleteButton, "clicked", G_CALLBACK(buttonDeleteCallback), this);
|
||||
g_signal_connect(copyButton, "clicked", G_CALLBACK(buttonCopyCallback), this);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btClose"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
}
|
||||
|
||||
ToolbarManageDialog::~ToolbarManageDialog() { this->callback(); }
|
||||
|
||||
void ToolbarManageDialog::buttonNewCallback(GtkButton* button, ToolbarManageDialog* dlg) {
|
||||
auto data = std::make_unique<ToolbarData>(false);
|
||||
data->setName(_("New"));
|
||||
data->setId("custom");
|
||||
dlg->tbModel->initCopyNameId(data.get());
|
||||
dlg->addToolbarData(std::move(data));
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::buttonDeleteCallback(GtkButton* button, ToolbarManageDialog* dlg) {
|
||||
ToolbarData* selected = dlg->getSelectedEntry();
|
||||
if (selected == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkTreeModel* model = GTK_TREE_MODEL(dlg->model.get());
|
||||
GtkTreeIter iter;
|
||||
if (gtk_tree_model_get_iter_first(model, &iter)) {
|
||||
do {
|
||||
ToolbarData* data = nullptr;
|
||||
gtk_tree_model_get(model, &iter, COLUMN_POINTER, &data, -1);
|
||||
|
||||
if (data == selected) {
|
||||
gtk_list_store_remove(dlg->model.get(), &iter);
|
||||
break;
|
||||
}
|
||||
} while (gtk_tree_model_iter_next(model, &iter));
|
||||
}
|
||||
|
||||
dlg->tbModel->remove(selected);
|
||||
dlg->updateSelectionData();
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::buttonCopyCallback(GtkButton* button, ToolbarManageDialog* dlg) {
|
||||
ToolbarData* selected = dlg->getSelectedEntry();
|
||||
if (selected == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = std::make_unique<ToolbarData>(*selected);
|
||||
dlg->tbModel->initCopyNameId(data.get());
|
||||
dlg->addToolbarData(std::move(data));
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::addToolbarData(std::unique_ptr<ToolbarData> tbd) {
|
||||
auto* data = this->tbModel->add(std::move(tbd));
|
||||
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(this->model.get(), &iter);
|
||||
gtk_list_store_set(this->model.get(), &iter, COLUMN_STRING, data->getName().c_str(), COLUMN_BOLD,
|
||||
PANGO_WEIGHT_NORMAL, COLUMN_POINTER, data, COLUMN_EDITABLE, true, -1);
|
||||
|
||||
GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(this->model.get()), &iter);
|
||||
GtkTreeViewColumn* column = gtk_tree_view_get_column(tree, 0);
|
||||
|
||||
gtk_tree_view_set_cursor(tree, path, column, true);
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::treeCellEditedCallback(GtkCellRendererText* renderer, gchar* pathString, gchar* newText,
|
||||
ToolbarManageDialog* dlg) {
|
||||
GtkTreeIter iter;
|
||||
ToolbarData* data = nullptr;
|
||||
|
||||
gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dlg->model.get()), &iter, pathString);
|
||||
gtk_tree_model_get(GTK_TREE_MODEL(dlg->model.get()), &iter, COLUMN_POINTER, &data, -1);
|
||||
if (data) {
|
||||
gtk_list_store_set(dlg->model.get(), &iter, COLUMN_STRING, newText, -1);
|
||||
data->setName(newText);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::entrySelected(ToolbarData* data) {
|
||||
if (data == nullptr) {
|
||||
gtk_widget_set_sensitive(copyButton, false);
|
||||
gtk_widget_set_sensitive(deleteButton, false);
|
||||
} else {
|
||||
gtk_widget_set_sensitive(copyButton, true);
|
||||
gtk_widget_set_sensitive(deleteButton, !data->isPredefined());
|
||||
}
|
||||
}
|
||||
|
||||
auto ToolbarManageDialog::getSelectedEntry() -> ToolbarData* {
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel* model = nullptr;
|
||||
ToolbarData* data = nullptr;
|
||||
|
||||
GtkTreeSelection* selection = gtk_tree_view_get_selection(tree);
|
||||
if (selection == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
|
||||
gtk_tree_model_get(model, &iter, COLUMN_POINTER, &data, -1);
|
||||
return data;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ToolbarManageDialog::updateSelectionData() { entrySelected(getSelectedEntry()); }
|
||||
|
||||
void ToolbarManageDialog::treeSelectionChangedCallback(GtkTreeSelection* selection, ToolbarManageDialog* dlg) {
|
||||
dlg->updateSelectionData();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Toolbar edit dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <glib.h> // for gchar
|
||||
#include <gtk/gtk.h> // for GtkButton, GtkCellRendererText, GtkListStore
|
||||
|
||||
#include "util/raii/GObjectSPtr.h"
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class ToolbarData;
|
||||
class ToolbarModel;
|
||||
class GladeSearchpath;
|
||||
|
||||
class ToolbarManageDialog {
|
||||
public:
|
||||
ToolbarManageDialog(GladeSearchpath* gladeSearchPath, ToolbarModel* model, std::function<void()> callback);
|
||||
~ToolbarManageDialog();
|
||||
|
||||
public:
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
static void treeSelectionChangedCallback(GtkTreeSelection* selection, ToolbarManageDialog* dlg);
|
||||
static void treeCellEditedCallback(GtkCellRendererText* renderer, gchar* pathString, gchar* newText,
|
||||
ToolbarManageDialog* dlg);
|
||||
|
||||
static void buttonNewCallback(GtkButton* button, ToolbarManageDialog* dlg);
|
||||
static void buttonDeleteCallback(GtkButton* button, ToolbarManageDialog* dlg);
|
||||
static void buttonCopyCallback(GtkButton* button, ToolbarManageDialog* dlg);
|
||||
|
||||
void addToolbarData(std::unique_ptr<ToolbarData> tbd);
|
||||
void entrySelected(ToolbarData* data);
|
||||
|
||||
void updateSelectionData();
|
||||
|
||||
ToolbarData* getSelectedEntry();
|
||||
|
||||
private:
|
||||
ToolbarModel* tbModel;
|
||||
xoj::util::GObjectSPtr<GtkListStore> model;
|
||||
|
||||
GtkTreeView* tree;
|
||||
GtkWidget* copyButton;
|
||||
GtkWidget* deleteButton;
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
std::function<void()> callback;
|
||||
};
|
||||
@@ -1,211 +0,0 @@
|
||||
#include "XojOpenDlg.h"
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "util/PathUtil.h" // for fromGFile, toGFile
|
||||
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
|
||||
#include "util/Util.h"
|
||||
#include "util/gtk4_helper.h" // for gtk_file_chooser_set_current_folder
|
||||
#include "util/i18n.h" // for _
|
||||
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
|
||||
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
|
||||
|
||||
#include "FileChooserFiltersHelper.h"
|
||||
|
||||
static void addlastSavePathShortcut(GtkFileChooser* fc, Settings* settings) {
|
||||
auto lastSavePath = settings->getLastSavePath();
|
||||
if (!lastSavePath.empty()) {
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
gtk_file_chooser_add_shortcut_folder(fc, char_cast(lastSavePath.u8string().c_str()), nullptr);
|
||||
#else
|
||||
gtk_file_chooser_add_shortcut_folder(fc, Util::toGFile(lastSavePath).get(), nullptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void setCurrentFolderToLastOpenPath(GtkFileChooser* fc, Settings* settings) {
|
||||
xoj::util::GObjectSPtr<GFile> currentFolder;
|
||||
if (settings && !settings->getLastOpenPath().empty()) {
|
||||
currentFolder = Util::toGFile(settings->getLastOpenPath());
|
||||
} else {
|
||||
currentFolder.reset(g_file_new_for_path(g_get_home_dir()), xoj::util::adopt);
|
||||
}
|
||||
gtk_file_chooser_set_current_folder(fc, currentFolder.get(), nullptr);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
static std::function<void(fs::path, Args...)> addSetLastSavePathToCallback(
|
||||
std::function<void(fs::path, Args...)> callback, Settings* settings) {
|
||||
return [cb = std::move(callback), settings](fs::path path, Args... args) {
|
||||
if (settings && !path.empty()) {
|
||||
settings->setLastOpenPath(path.parent_path());
|
||||
}
|
||||
cb(std::move(path), std::forward<Args>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
constexpr auto ATTACH_CHOICE_ID = "attachPdfChoice";
|
||||
static void addAttachChoice(GtkFileChooser* fc) {
|
||||
gtk_file_chooser_add_choice(fc, ATTACH_CHOICE_ID, _("Attach file to the journal"), nullptr, nullptr);
|
||||
gtk_file_chooser_set_choice(fc, ATTACH_CHOICE_ID, "false");
|
||||
}
|
||||
|
||||
// Helper class, for a single open dialog
|
||||
class FileDlg {
|
||||
public:
|
||||
/**
|
||||
* Creates an open file dialog. The callback is only called if a file is actually chosen
|
||||
* @param callback(path, attachPdf)
|
||||
*/
|
||||
FileDlg(const char* title, std::function<void(fs::path, bool)> callback);
|
||||
/**
|
||||
* Creates an open file dialog. The callback is only called if a file is actually chosen
|
||||
* @param callback(path)
|
||||
*/
|
||||
FileDlg(const char* title, std::function<void(fs::path)> callback);
|
||||
~FileDlg() = default;
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
|
||||
std::function<void(fs::path, bool)> callback;
|
||||
gulong signalId{};
|
||||
};
|
||||
|
||||
static GtkWindow* makeWindow(const char* title) {
|
||||
// Todo(maybe)
|
||||
// Restore previews using https://discourse.gnome.org/t/file-chooser-gtk-4-image-preview/11510/2
|
||||
auto* win = gtk_file_chooser_dialog_new(title, nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"),
|
||||
GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_OK, nullptr);
|
||||
return GTK_WINDOW(win);
|
||||
}
|
||||
|
||||
FileDlg::FileDlg(const char* title, std::function<void(fs::path, bool)> callback):
|
||||
window(makeWindow(title)), callback(std::move(callback)) {
|
||||
this->signalId = g_signal_connect(
|
||||
window.get(), "response", G_CALLBACK(+[](GtkDialog* win, int response, gpointer data) {
|
||||
auto* self = static_cast<FileDlg*>(data);
|
||||
|
||||
if (response == GTK_RESPONSE_OK) {
|
||||
auto path =
|
||||
Util::fromGFile(xoj::util::GObjectSPtr<GFile>(
|
||||
gtk_file_chooser_get_file(GTK_FILE_CHOOSER(win)), xoj::util::adopt)
|
||||
.get());
|
||||
|
||||
bool attach = false;
|
||||
if (const char* choice = gtk_file_chooser_get_choice(GTK_FILE_CHOOSER(win), ATTACH_CHOICE_ID);
|
||||
choice) {
|
||||
attach = std::strcmp(choice, "true") == 0;
|
||||
}
|
||||
|
||||
// We need to call gtk_window_close() before invoking the callback, because if the callback pops up
|
||||
// another dialog, the first one won't close...
|
||||
// So we postpone the callback
|
||||
Util::execInUiThread([cb = std::move(self->callback), path = std::move(path), attach]() {
|
||||
cb(std::move(path), attach);
|
||||
});
|
||||
}
|
||||
// Closing the window causes another "response" signal, which we want to ignore
|
||||
g_signal_handler_disconnect(win, self->signalId);
|
||||
gtk_window_close(self->getWindow()); // Destroys *self. Beware!
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
FileDlg::FileDlg(const char* title, std::function<void(fs::path)> callback):
|
||||
FileDlg(title, [cb = std::move(callback)](fs::path path, bool) { cb(std::move(path)); }) {}
|
||||
|
||||
|
||||
void xoj::OpenDlg::showOpenTemplateDialog(GtkWindow* parent, Settings* settings,
|
||||
std::function<void(fs::path)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open template file"),
|
||||
addSetLastSavePathToCallback(std::move(callback), settings));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
xoj::addFilterAllFiles(fc);
|
||||
xoj::addFilterXopt(fc);
|
||||
setCurrentFolderToLastOpenPath(fc, settings);
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
|
||||
|
||||
void xoj::OpenDlg::showOpenFileDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open file"),
|
||||
addSetLastSavePathToCallback(std::move(callback), settings));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
xoj::addFilterSupported(fc);
|
||||
xoj::addFilterXoj(fc);
|
||||
xoj::addFilterXopt(fc);
|
||||
xoj::addFilterXopp(fc);
|
||||
xoj::addFilterPdf(fc);
|
||||
xoj::addFilterAllFiles(fc);
|
||||
|
||||
addlastSavePathShortcut(fc, settings);
|
||||
setCurrentFolderToLastOpenPath(fc, settings);
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
|
||||
void xoj::OpenDlg::showAnnotatePdfDialog(GtkWindow* parent, Settings* settings,
|
||||
std::function<void(fs::path, bool)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Annotate Pdf file"),
|
||||
addSetLastSavePathToCallback(std::move(callback), settings));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
|
||||
xoj::addFilterPdf(fc);
|
||||
xoj::addFilterAllFiles(fc);
|
||||
|
||||
addlastSavePathShortcut(fc, settings);
|
||||
setCurrentFolderToLastOpenPath(fc, settings);
|
||||
|
||||
addAttachChoice(fc);
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
|
||||
void xoj::OpenDlg::showOpenImageDialog(GtkWindow* parent, Settings* settings,
|
||||
std::function<void(fs::path, bool)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Choose image file"),
|
||||
[cb = std::move(callback), settings](fs::path p, bool attach) {
|
||||
if (auto folder = p.parent_path(); !folder.empty()) {
|
||||
settings->setLastImagePath(folder);
|
||||
}
|
||||
cb(std::move(p), attach);
|
||||
});
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
|
||||
xoj::addFilterImages(fc);
|
||||
xoj::addFilterAllFiles(fc);
|
||||
|
||||
if (!settings->getLastImagePath().empty()) {
|
||||
gtk_file_chooser_set_current_folder(fc, Util::toGFile(settings->getLastImagePath()).get(), nullptr);
|
||||
}
|
||||
|
||||
addAttachChoice(fc);
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
|
||||
void xoj::OpenDlg::showMultiFormatDialog(GtkWindow* parent, std::vector<std::string> formats,
|
||||
std::function<void(fs::path)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<FileDlg>(_("Open file"), std::move(callback));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
|
||||
if (formats.size() > 0) {
|
||||
GtkFileFilter* filterSupported = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterSupported, _("Supported files"));
|
||||
for (std::string format: formats) {
|
||||
gtk_file_filter_add_pattern(filterSupported, format.c_str());
|
||||
}
|
||||
gtk_file_chooser_add_filter(fc, filterSupported);
|
||||
}
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* GTK Open dialog to select XOJ (or PDF) file with preview
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWindow
|
||||
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class Settings;
|
||||
|
||||
namespace xoj::OpenDlg {
|
||||
void showOpenFileDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback);
|
||||
/// @param callback(path, attachPdf)
|
||||
void showAnnotatePdfDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path, bool)> callback);
|
||||
void showOpenTemplateDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path)> callback);
|
||||
|
||||
/// @param callback(path, attachImg)
|
||||
void showOpenImageDialog(GtkWindow* parent, Settings* settings, std::function<void(fs::path, bool)> callback);
|
||||
|
||||
void showMultiFormatDialog(GtkWindow* parent, std::vector<std::string> formats, std::function<void(fs::path)> callback);
|
||||
}; // namespace xoj::OpenDlg
|
||||
@@ -1,91 +0,0 @@
|
||||
#include "XojSaveDlg.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "control/settings/Settings.h"
|
||||
#include "util/PathUtil.h" // for fromGFile, toGFile
|
||||
#include "util/PopupWindowWrapper.h" // for PopupWindowWrapper
|
||||
#include "util/Util.h"
|
||||
#include "util/XojMsgBox.h"
|
||||
#include "util/gtk4_helper.h" // for gtk_file_chooser_set_current_folder
|
||||
#include "util/i18n.h" // for _
|
||||
#include "util/raii/GObjectSPtr.h" // for GObjectSPtr
|
||||
#include "util/raii/GtkWindowUPtr.h" // for GtkWindowUPtr
|
||||
|
||||
#include "FileChooserFiltersHelper.h"
|
||||
|
||||
static GtkWindow* makeWindow(Settings* settings, fs::path suggestedPath, const char* windowTitle,
|
||||
const char* buttonLabel) {
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(windowTitle, nullptr, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"),
|
||||
GTK_RESPONSE_CANCEL, buttonLabel, GTK_RESPONSE_OK, nullptr);
|
||||
|
||||
if (!suggestedPath.empty()) {
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(suggestedPath.parent_path()).get(),
|
||||
nullptr);
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
|
||||
Util::toGFilename(suggestedPath.filename()).c_str());
|
||||
}
|
||||
if (settings) {
|
||||
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), Util::toGFile(settings->getLastOpenPath()).get(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
return GTK_WINDOW(dialog);
|
||||
}
|
||||
|
||||
xoj::SaveExportDialog::SaveExportDialog(Settings* settings, fs::path suggestedPath, const char* windowTitle,
|
||||
const char* buttonLabel,
|
||||
std::function<bool(fs::path&, const char* filterName)> pathValidation,
|
||||
std::function<void(std::optional<fs::path>)> callback):
|
||||
window(makeWindow(settings, std::move(suggestedPath), windowTitle, buttonLabel)),
|
||||
callback(std::move(callback)),
|
||||
pathValidation(std::move(pathValidation)) {
|
||||
this->signalId = g_signal_connect(
|
||||
window.get(), "response", G_CALLBACK(+[](GtkDialog* win, int response, gpointer data) {
|
||||
auto* self = static_cast<SaveExportDialog*>(data);
|
||||
auto* fc = GTK_FILE_CHOOSER(win);
|
||||
if (response == GTK_RESPONSE_OK) {
|
||||
auto file = Util::fromGFile(
|
||||
xoj::util::GObjectSPtr<GFile>(gtk_file_chooser_get_file(fc), xoj::util::adopt).get());
|
||||
|
||||
if (self->pathValidation(file, gtk_file_filter_get_name(gtk_file_chooser_get_filter(fc)))) {
|
||||
XojMsgBox::replaceFileQuestion(
|
||||
GTK_WINDOW(win), std::move(file),
|
||||
std::bind(&SaveExportDialog::close, self, std::placeholders::_1));
|
||||
} // else the dialog stays on until a suitable destination is found or cancel is hit.
|
||||
} else {
|
||||
self->close(std::nullopt);
|
||||
}
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
void xoj::SaveExportDialog::close(std::optional<fs::path> path) {
|
||||
// We need to call gtk_window_close() before invoking the callback, because if the callback pops up another dialog,
|
||||
// the first one won't close...
|
||||
// But since gtk_window_close() triggers the destruction of *this, we first move the callback
|
||||
auto cb = std::move(this->callback);
|
||||
|
||||
// Closing the window causes another "response" signal, which we want to ignore
|
||||
g_signal_handler_disconnect(window.get(), signalId);
|
||||
gtk_window_close(window.get()); // Destroys *this. Don't do anything after this call
|
||||
|
||||
cb(std::move(path));
|
||||
}
|
||||
|
||||
static bool xoppPathValidation(fs::path& p, const char*) {
|
||||
Util::clearExtensions(p);
|
||||
p += ".xopp";
|
||||
return true;
|
||||
}
|
||||
|
||||
void xoj::SaveExportDialog::showSaveFileDialog(GtkWindow* parent, Settings* settings, fs::path suggestedPath,
|
||||
std::function<void(std::optional<fs::path>)> callback) {
|
||||
auto popup = xoj::popup::PopupWindowWrapper<SaveExportDialog>(settings, std::move(suggestedPath), _("Save File"),
|
||||
_("Save"), xoppPathValidation, std::move(callback));
|
||||
|
||||
auto* fc = GTK_FILE_CHOOSER(popup.getPopup()->getWindow());
|
||||
xoj::addFilterXopp(fc);
|
||||
|
||||
popup.show(parent);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* GTK Save/Export dialog to select destination file
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWindow
|
||||
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
#include "filesystem.h" // for path
|
||||
|
||||
class Settings;
|
||||
|
||||
namespace xoj {
|
||||
/// Helper class, for a single dialog
|
||||
class SaveExportDialog {
|
||||
public:
|
||||
/**
|
||||
* Shows a save file dialog. The callback is called with std::nullopt if no path were selected
|
||||
*/
|
||||
static void showSaveFileDialog(GtkWindow* parent, Settings* settings, fs::path suggestedPath,
|
||||
std::function<void(std::optional<fs::path>)> callback);
|
||||
|
||||
/**
|
||||
* Creates a save or export file dialog. The callback is called with std::nullopt if no path were selected
|
||||
* @param pathValidation May modify the given path. Returns true if the path is (now) valid for saving/exporting.
|
||||
* @param callback(path)
|
||||
*/
|
||||
SaveExportDialog(Settings* settings, fs::path suggestedPath, const char* windowTitle, const char* buttonLabel,
|
||||
std::function<bool(fs::path&, const char* filterName)> pathValidation,
|
||||
std::function<void(std::optional<fs::path>)> callback);
|
||||
~SaveExportDialog() = default;
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
private:
|
||||
/// Closes the dialog and calls the callback on `path`
|
||||
void close(std::optional<fs::path> path);
|
||||
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
std::function<void(std::optional<fs::path>)> callback;
|
||||
std::function<bool(fs::path&, const char* filterName)> pathValidation;
|
||||
gulong signalId{};
|
||||
};
|
||||
}; // namespace xoj
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
#include "BackgroundSelectDialogBase.h"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <utility> // for move
|
||||
|
||||
#include <gdk/gdk.h> // for GDK_EXPOSURE_MASK
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
|
||||
#include "gui/Builder.h"
|
||||
#include "util/Util.h" // for paintBackgroundWhite
|
||||
#include "util/gtk4_helper.h"
|
||||
#include "util/safe_casts.h" // for round_cast
|
||||
|
||||
#include "BaseElementView.h" // for BaseElementView
|
||||
|
||||
class GladeSearchpath;
|
||||
|
||||
|
||||
constexpr auto UI_FILE = "backgroundSelection.glade";
|
||||
constexpr auto UI_DIALOG_NAME = "Dialog";
|
||||
|
||||
BackgroundSelectDialogBase::BackgroundSelectDialogBase(GladeSearchpath* gladeSearchPath, Document* doc,
|
||||
Settings* settings, const char* title):
|
||||
settings(settings), doc(doc) {
|
||||
Builder builder(gladeSearchPath, UI_FILE);
|
||||
this->window.reset(GTK_WINDOW(builder.get(UI_DIALOG_NAME)));
|
||||
gtk_window_set_title(window.get(), title);
|
||||
|
||||
this->container = GTK_FIXED(gtk_fixed_new());
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(this->container), true);
|
||||
this->okButton = builder.get("btOk");
|
||||
this->vbox = GTK_BOX(builder.get("vbox"));
|
||||
|
||||
this->scrolledWindow = GTK_SCROLLED_WINDOW(builder.get("scrollContents"));
|
||||
gtk_scrolled_window_set_child(scrolledWindow, GTK_WIDGET(container));
|
||||
|
||||
g_signal_connect(gtk_scrolled_window_get_hadjustment(scrolledWindow), "notify::page-size",
|
||||
G_CALLBACK(+[](GObject*, GParamSpec*, gpointer d) {
|
||||
static_cast<BackgroundSelectDialogBase*>(d)->layout();
|
||||
}),
|
||||
this);
|
||||
|
||||
gtk_window_set_default_size(this->window.get(), 800, 600);
|
||||
gtk_widget_set_sensitive(this->okButton, false);
|
||||
|
||||
g_signal_connect_swapped(builder.get("btCancel"), "clicked", G_CALLBACK(gtk_window_close), this->window.get());
|
||||
|
||||
g_signal_connect(this->container, "realize", G_CALLBACK(+[](GtkWidget*, gpointer self) {
|
||||
static_cast<BackgroundSelectDialogBase*>(self)->layout();
|
||||
}),
|
||||
this);
|
||||
}
|
||||
|
||||
BackgroundSelectDialogBase::~BackgroundSelectDialogBase() = default;
|
||||
|
||||
auto BackgroundSelectDialogBase::getSettings() -> Settings* { return this->settings; }
|
||||
|
||||
void BackgroundSelectDialogBase::layout() {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int row_height = 0;
|
||||
|
||||
auto width = gtk_adjustment_get_page_size(gtk_scrolled_window_get_hadjustment(scrolledWindow));
|
||||
|
||||
for (const auto& p: this->entries) {
|
||||
if (!gtk_widget_get_visible(p->getWidget())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x + p->getWidth() > width) {
|
||||
y += row_height;
|
||||
x = 0;
|
||||
row_height = 0;
|
||||
}
|
||||
gtk_fixed_move(this->container, p->getWidget(), x, y);
|
||||
|
||||
row_height = std::max(row_height, p->getHeight());
|
||||
x += p->getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSelectDialogBase::populate() {
|
||||
for (const auto& e: entries) {
|
||||
gtk_fixed_put(this->container, e->getWidget(), 0, 0);
|
||||
}
|
||||
|
||||
if (!entries.empty()) {
|
||||
setSelected(0);
|
||||
}
|
||||
|
||||
layout();
|
||||
}
|
||||
|
||||
void BackgroundSelectDialogBase::setSelected(size_t selected) {
|
||||
if (this->selected == selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t lastSelected = this->selected;
|
||||
if (lastSelected < entries.size()) {
|
||||
entries[lastSelected]->setSelected(false);
|
||||
}
|
||||
|
||||
if (selected < entries.size()) {
|
||||
entries[selected]->setSelected(true);
|
||||
this->selected = selected;
|
||||
gtk_widget_set_sensitive(okButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSelectDialogBase::ok() { gtk_button_clicked(GTK_BUTTON(okButton)); }
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Base class for Background selection dialog
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <gtk/gtk.h> // for GtkWidget, GtkWindow
|
||||
|
||||
#include "util/Util.h" // for npos
|
||||
#include "util/raii/GtkWindowUPtr.h"
|
||||
|
||||
class Document;
|
||||
class Settings;
|
||||
class BaseElementView;
|
||||
class GladeSearchpath;
|
||||
|
||||
class BackgroundSelectDialogBase {
|
||||
protected:
|
||||
BackgroundSelectDialogBase(GladeSearchpath* gladeSearchPath, Document* doc, Settings* settings, const char* title);
|
||||
~BackgroundSelectDialogBase();
|
||||
|
||||
public:
|
||||
Settings* getSettings();
|
||||
void setSelected(size_t selected);
|
||||
|
||||
void ok(); ///< As if the Ok button was clicked
|
||||
|
||||
inline GtkWindow* getWindow() const { return window.get(); }
|
||||
|
||||
protected:
|
||||
/// Adds the entry widgets
|
||||
void populate();
|
||||
void layout();
|
||||
|
||||
protected:
|
||||
xoj::util::GtkWindowUPtr window;
|
||||
|
||||
Settings* settings = nullptr;
|
||||
GtkScrolledWindow* scrolledWindow = nullptr;
|
||||
GtkFixed* container = nullptr; ///< Area where miniatures are layed out
|
||||
GtkWidget* okButton = nullptr;
|
||||
GtkBox* vbox = nullptr; ///< Vertical GtkBox containing all.
|
||||
|
||||
Document* doc = nullptr;
|
||||
|
||||
/// Selection confirmed
|
||||
bool confirmed = false;
|
||||
|
||||
/// Selected entry, none if npos
|
||||
size_t selected = npos;
|
||||
|
||||
/// Entries to display
|
||||
std::vector<std::unique_ptr<BaseElementView>> entries;
|
||||
};
|
||||
@@ -1,145 +0,0 @@
|
||||
#include "BaseElementView.h"
|
||||
|
||||
#include <glib-object.h> // for G_CALLBACK, g_signal_connect
|
||||
|
||||
#include "control/settings/Settings.h" // for Settings
|
||||
#include "gui/Shadow.h" // for Shadow
|
||||
#include "util/Color.h" // for cairo_set_source_rgbi
|
||||
#include "util/gtk4_helper.h"
|
||||
|
||||
#include "BackgroundSelectDialogBase.h" // for BackgroundSelectDialogBase
|
||||
|
||||
constexpr int BORDER_WIDTH = 2;
|
||||
|
||||
BaseElementView::BaseElementView(size_t id, BackgroundSelectDialogBase* dlg): dlg(dlg), id(id) {
|
||||
this->widget = gtk_drawing_area_new();
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget),
|
||||
GtkDrawingAreaDrawFunc(+[](GtkWidget*, cairo_t* cr, int, int, gpointer element) {
|
||||
static_cast<BaseElementView*>(element)->paint(cr);
|
||||
}),
|
||||
this, nullptr);
|
||||
|
||||
#if GTK_MAJOR_VERSION == 3
|
||||
gtk_widget_show(this->widget);
|
||||
gtk_widget_add_events(widget, GDK_BUTTON_PRESS_MASK);
|
||||
g_signal_connect(this->widget, "button-press-event", G_CALLBACK(+[](GtkWidget*, GdkEventButton* e, gpointer d) {
|
||||
auto* element = static_cast<BaseElementView*>(d);
|
||||
if (e->type == GDK_BUTTON_PRESS) {
|
||||
element->dlg->setSelected(element->id);
|
||||
} else if (e->type == GDK_2BUTTON_PRESS) {
|
||||
element->dlg->ok();
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
this);
|
||||
#else
|
||||
auto* ctrl = gtk_gesture_click_new();
|
||||
gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(ctrl));
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(ctrl), GDK_BUTTON_PRIMARY);
|
||||
g_signal_connect(ctrl, "pressed",
|
||||
G_CALLBACK(+[](GtkGestureClick* g, gint n_press, gdouble x, gdouble y, gpointer d) {
|
||||
auto* element = static_cast<BaseElementView*>(d);
|
||||
if (n_press == 1) {
|
||||
element->dlg->setSelected(element->id);
|
||||
} else if (n_press == 2) {
|
||||
element->dlg->ok();
|
||||
}
|
||||
}),
|
||||
this);
|
||||
#endif
|
||||
}
|
||||
|
||||
BaseElementView::~BaseElementView() {
|
||||
gtk_widget_destroy(this->widget);
|
||||
|
||||
if (this->crBuffer) {
|
||||
cairo_surface_destroy(this->crBuffer);
|
||||
this->crBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseElementView::setSelected(bool selected) {
|
||||
if (this->selected == selected) {
|
||||
return;
|
||||
}
|
||||
this->selected = selected;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void BaseElementView::repaint() {
|
||||
if (this->crBuffer) {
|
||||
cairo_surface_destroy(this->crBuffer);
|
||||
this->crBuffer = nullptr;
|
||||
}
|
||||
gtk_widget_queue_draw(this->widget);
|
||||
}
|
||||
|
||||
void BaseElementView::paint(cairo_t* cr) {
|
||||
GtkAllocation alloc;
|
||||
gtk_widget_get_allocation(this->widget, &alloc);
|
||||
|
||||
if (this->crBuffer == nullptr) {
|
||||
this->crBuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, alloc.width, alloc.height);
|
||||
|
||||
int width = getContentWidth();
|
||||
int height = getContentHeight();
|
||||
|
||||
cairo_t* cr2 = cairo_create(this->crBuffer);
|
||||
|
||||
cairo_save(cr2);
|
||||
cairo_translate(cr2, Shadow::getShadowTopLeftSize() + BORDER_WIDTH,
|
||||
Shadow::getShadowTopLeftSize() + BORDER_WIDTH);
|
||||
paintContents(cr2);
|
||||
cairo_restore(cr2);
|
||||
|
||||
cairo_set_operator(cr2, CAIRO_OPERATOR_OVER);
|
||||
|
||||
if (this->selected) {
|
||||
// Draw border
|
||||
Util::cairo_set_source_rgbi(cr2, dlg->getSettings()->getBorderColor());
|
||||
cairo_set_line_width(cr2, BORDER_WIDTH);
|
||||
cairo_set_line_cap(cr2, CAIRO_LINE_CAP_BUTT);
|
||||
cairo_set_line_join(cr2, CAIRO_LINE_JOIN_BEVEL);
|
||||
|
||||
cairo_rectangle(cr2, Shadow::getShadowTopLeftSize() + BORDER_WIDTH / 2,
|
||||
Shadow::getShadowTopLeftSize() + BORDER_WIDTH / 2, width + BORDER_WIDTH,
|
||||
height + BORDER_WIDTH);
|
||||
|
||||
cairo_stroke(cr2);
|
||||
|
||||
Shadow::drawShadow(cr2, Shadow::getShadowTopLeftSize(), Shadow::getShadowTopLeftSize(),
|
||||
width + 2 * BORDER_WIDTH, height + 2 * BORDER_WIDTH);
|
||||
} else {
|
||||
Shadow::drawShadow(cr2, Shadow::getShadowTopLeftSize() + BORDER_WIDTH,
|
||||
Shadow::getShadowTopLeftSize() + BORDER_WIDTH, width, height);
|
||||
}
|
||||
|
||||
cairo_destroy(cr2);
|
||||
}
|
||||
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
||||
cairo_set_source_surface(cr, this->crBuffer, 0, 0);
|
||||
cairo_paint(cr);
|
||||
}
|
||||
|
||||
auto BaseElementView::getWidget() -> GtkWidget* {
|
||||
updateSize();
|
||||
return this->widget;
|
||||
}
|
||||
|
||||
auto BaseElementView::getWidth() -> int {
|
||||
calcSize();
|
||||
return getContentWidth() + Shadow::getShadowBottomRightSize() + Shadow::getShadowTopLeftSize() + 2 * BORDER_WIDTH;
|
||||
}
|
||||
|
||||
auto BaseElementView::getHeight() -> int {
|
||||
calcSize();
|
||||
return getContentHeight() + Shadow::getShadowBottomRightSize() + Shadow::getShadowTopLeftSize() + 2 * BORDER_WIDTH;
|
||||
}
|
||||
|
||||
void BaseElementView::calcSize() {
|
||||
// Not implemented in the base class
|
||||
}
|
||||
|
||||
void BaseElementView::updateSize() { gtk_widget_set_size_request(this->widget, this->getWidth(), this->getHeight()); }
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Base class for Background selection dialog entry
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h> // for cairo_t, cairo_surface_t
|
||||
#include <gdk/gdk.h> // for GdkEventButton
|
||||
#include <glib.h> // for gboolean
|
||||
#include <gtk/gtk.h> // for GtkWidget
|
||||
|
||||
#include "util/Util.h" // for npos
|
||||
|
||||
class BackgroundSelectDialogBase;
|
||||
|
||||
class BaseElementView {
|
||||
public:
|
||||
BaseElementView(size_t id, BackgroundSelectDialogBase* dlg);
|
||||
virtual ~BaseElementView();
|
||||
|
||||
public:
|
||||
GtkWidget* getWidget();
|
||||
int getWidth();
|
||||
int getHeight();
|
||||
|
||||
/**
|
||||
* Select / unselect this entry
|
||||
*/
|
||||
void setSelected(bool selected);
|
||||
|
||||
/**
|
||||
* Repaint this widget
|
||||
*/
|
||||
void repaint();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Apply the size to the Widget
|
||||
*/
|
||||
void updateSize();
|
||||
|
||||
/**
|
||||
* Paint the whole widget
|
||||
*/
|
||||
void paint(cairo_t* cr);
|
||||
|
||||
/**
|
||||
* Paint the contents (without border / selection)
|
||||
*/
|
||||
virtual void paintContents(cairo_t* cr) = 0;
|
||||
|
||||
/**
|
||||
* Get the width in pixel, without shadow / border
|
||||
*/
|
||||
virtual int getContentWidth() = 0;
|
||||
|
||||
/**
|
||||
* Get the height in pixel, without shadow / border
|
||||
*/
|
||||
virtual int getContentHeight() = 0;
|
||||
|
||||
/**
|
||||
* Will be called before getContentWidth() / getContentHeight(), can be overwritten
|
||||
*/
|
||||
virtual void calcSize();
|
||||
|
||||
protected:
|
||||
BackgroundSelectDialogBase* dlg;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Element ID
|
||||
*/
|
||||
size_t id = npos;
|
||||
|
||||
bool selected = false;
|
||||
|
||||
GtkWidget* widget = nullptr;
|
||||
cairo_surface_t* crBuffer = nullptr;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "ImageElementView.h"
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h> // for gdk_pixbuf_...
|
||||
#include <gdk/gdk.h> // for gdk_cairo_s...
|
||||
|
||||
#include "gui/Shadow.h" // for Shadow
|
||||
#include "gui/dialog/backgroundSelect/BaseElementView.h" // for BaseElement...
|
||||
#include "util/safe_casts.h" // for ceil_cast
|
||||
|
||||
class BackgroundSelectDialogBase;
|
||||
|
||||
constexpr int MINIATURE_PIXEL_SIZE = 128;
|
||||
|
||||
ImageElementView::ImageElementView(size_t id, BackgroundSelectDialogBase* dlg): BaseElementView(id, dlg) {}
|
||||
|
||||
ImageElementView::~ImageElementView() = default;
|
||||
|
||||
void ImageElementView::calcSize() {
|
||||
if (this->width == -1) {
|
||||
GdkPixbuf* p = backgroundImage.getPixbuf();
|
||||
this->width = gdk_pixbuf_get_width(p);
|
||||
this->height = gdk_pixbuf_get_height(p);
|
||||
|
||||
if (this->width < this->height) {
|
||||
zoom = static_cast<double>(MINIATURE_PIXEL_SIZE) / this->height;
|
||||
this->height = MINIATURE_PIXEL_SIZE;
|
||||
this->width = floor_cast<int>(this->width * zoom);
|
||||
} else {
|
||||
zoom = static_cast<double>(MINIATURE_PIXEL_SIZE) / this->width;
|
||||
this->width = MINIATURE_PIXEL_SIZE;
|
||||
this->height = floor_cast<int>(this->height * zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageElementView::paintContents(cairo_t* cr) {
|
||||
cairo_scale(cr, this->zoom, this->zoom);
|
||||
|
||||
GdkPixbuf* p = this->backgroundImage.getPixbuf();
|
||||
gdk_cairo_set_source_pixbuf(cr, p, 0, 0);
|
||||
cairo_paint(cr);
|
||||
}
|
||||
|
||||
auto ImageElementView::getContentWidth() -> int { return width; }
|
||||
|
||||
auto ImageElementView::getContentHeight() -> int { return height; }
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Xournal++
|
||||
*
|
||||
* Image view
|
||||
*
|
||||
* @author Xournal++ Team
|
||||
* https://github.com/xournalpp/xournalpp
|
||||
*
|
||||
* @license GNU GPLv2 or later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h> // for cairo_t
|
||||
|
||||
#include "model/BackgroundImage.h" // for BackgroundImage
|
||||
#include "util/Util.h" // for npos
|
||||
|
||||
#include "BaseElementView.h" // for BaseElementView
|
||||
|
||||
class BackgroundSelectDialogBase;
|
||||
|
||||
|
||||
class ImageElementView: public BaseElementView {
|
||||
public:
|
||||
ImageElementView(size_t id, BackgroundSelectDialogBase* dlg);
|
||||
~ImageElementView() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Paint the contents (without border / selection)
|
||||
*/
|
||||
void paintContents(cairo_t* cr) override;
|
||||
|
||||
/**
|
||||
* Get the width in pixel, without shadow / border
|
||||
*/
|
||||
int getContentWidth() override;
|
||||
|
||||
/**
|
||||
* Get the height in pixel, without shadow / border
|
||||
*/
|
||||
int getContentHeight() override;
|
||||
|
||||
/**
|
||||
* Will be called before getContentWidth() / getContentHeight(), can be overwritten
|
||||
*/
|
||||
void calcSize() override;
|
||||
|
||||
private:
|
||||
double zoom = 1;
|
||||
|
||||
BackgroundImage backgroundImage;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
|
||||
friend class ImagesDialog;
|
||||
};
|
||||
Loaded 100 of 341 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user