🔥 删除gui相关

This commit is contained in:
shufeng2012 committed 2026-08-16 19:44:05 +08:00
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) {}
-42
View File
@@ -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;
};
-432
View File
@@ -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);
}
-175
View File
@@ -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", &gtkdark, 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
-323
View File
@@ -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;
};
-217
View File
@@ -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);
}
-52
View File
@@ -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
@@ -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