diff --git a/src/core/control/Control.cpp b/src/core/control/Control.cpp index aa7e78d3de92..473d22031c26 100644 --- a/src/core/control/Control.cpp +++ b/src/core/control/Control.cpp @@ -86,6 +86,7 @@ #include "undo/InsertDeletePageUndoAction.h" // for Inse... #include "undo/InsertUndoAction.h" // for Inse... #include "undo/MoveSelectionToLayerUndoAction.h" // for Move... +#include "undo/SwapUndoAction.h" // for SwapUndoAction #include "undo/UndoAction.h" // for Undo... #include "util/Assert.h" // for xoj_assert #include "util/Color.h" // for oper... @@ -523,7 +524,7 @@ auto Control::firePageSelected(const PageRef& page) -> size_t { return npos; } - DocumentHandler::firePageSelected(pageId); + firePageSelected(pageId); return pageId; } @@ -649,12 +650,14 @@ void Control::addDefaultPage(string pageTemplate) { this->doc->lock(); this->doc->addPage(std::move(page)); this->doc->unlock(); - - updateDeletePageButton(); } -void Control::updateDeletePageButton() { - this->actionDB->enableAction(Action::DELETE_PAGE, this->doc->getPageCount() > 1); +void Control::updatePageActions() { + auto currentPage = getCurrentPageNo(); + auto nbPages = this->doc->getPageCount(); + this->actionDB->enableAction(Action::DELETE_PAGE, nbPages > 1); + this->actionDB->enableAction(Action::MOVE_PAGE_TOWARDS_BEGINNING, currentPage != 0); + this->actionDB->enableAction(Action::MOVE_PAGE_TOWARDS_END, currentPage < nbPages - 1); } void Control::deletePage() { @@ -692,7 +695,6 @@ void Control::deletePage() { doc->deletePage(pNr); this->doc->unlock(); - updateDeletePageButton(); this->undoRedo->addUndoAction(std::make_unique(page, pNr, false)); if (pNr >= this->doc->getPageCount()) { @@ -713,6 +715,106 @@ void Control::duplicatePage() { insertPage(pageCopy, getCurrentPageNo() + 1); } +void Control::movePageTowardsBeginning() { + auto currentPageNo = this->getCurrentPageNo(); + if (currentPageNo < 1) { + g_warning("Control::movePageTowardsBeginning() called on the first page"); + return; + } + if (currentPageNo == npos) { + g_warning("Control::movePageTowardsBeginning() called with current page selected"); + return; + } + + auto lock = std::unique_lock(*this->doc); + PageRef page = this->doc->getPage(currentPageNo); + PageRef otherPage = doc->getPage(currentPageNo - 1); + if (!page || !otherPage) { + g_warning("Control::movePageTowardsBeginning() called but no page %zu or %zu", currentPageNo, + currentPageNo - 1); + return; + } + + doc->deletePage(currentPageNo); + doc->insertPage(page, currentPageNo - 1); + lock.unlock(); + + UndoRedoHandler* undo = this->getUndoRedoHandler(); + undo->addUndoAction(std::make_unique(currentPageNo - 1, true, page, otherPage)); + + this->firePageDeleted(currentPageNo); + this->firePageInserted(currentPageNo - 1); + this->firePageSelected(currentPageNo - 1); + + this->getScrollHandler()->scrollToPage(currentPageNo - 1); +} + + +void Control::movePageTowardsEnd() { + auto currentPageNo = this->getCurrentPageNo(); + if (currentPageNo == npos) { + g_warning("Control::movePageTowardsEnd() called with current page selected"); + return; + } + + auto lock = std::unique_lock(*this->doc); + auto nbPage = this->doc->getPageCount(); + if (currentPageNo >= nbPage - 1) { + g_warning("Control::movePageTowardsEnd() called on last page"); + return; + } + + PageRef page = this->doc->getPage(currentPageNo); + PageRef otherPage = doc->getPage(currentPageNo + 1); + if (!page || !otherPage) { + g_warning("Control::movePageTowardsEnd() called but no page %zu or %zu", currentPageNo, currentPageNo + 1); + return; + } + + doc->deletePage(currentPageNo); + doc->insertPage(page, currentPageNo + 1); + lock.unlock(); + + this->undoRedo->addUndoAction(std::make_unique(currentPageNo, false, page, otherPage)); + + this->firePageDeleted(currentPageNo); + this->firePageInserted(currentPageNo + 1); + this->firePageSelected(currentPageNo + 1); + + this->getScrollHandler()->scrollToPage(currentPageNo + 1); +} + +void Control::askInsertPdfPage(size_t pdfPage) { + using Responses = enum { CANCEL = 1, AFTER = 2, END = 3 }; + std::vector buttons = {{_("Cancel"), Responses::CANCEL}, + {_("Insert after current page"), Responses::AFTER}, + {_("Insert at end"), Responses::END}}; + + XojMsgBox::askQuestion(this->getGtkWindow(), + FC(_F("Your current document does not contain PDF Page no {1}\n" + "Would you like to insert this page?\n\n" + "Tip: You can select Journal → Paper Background → PDF Background " + "to insert a PDF page.") % + static_cast(pdfPage + 1)), + "", buttons, [ctrl = this, pdfPage](int response) { + if (response == Responses::AFTER || response == Responses::END) { + Document* doc = ctrl->getDocument(); + + doc->lock(); + size_t position = response == Responses::AFTER ? ctrl->getCurrentPageNo() + 1 : + doc->getPageCount(); + XojPdfPageSPtr pdf = doc->getPdfPage(pdfPage); + doc->unlock(); + + if (pdf) { + auto page = std::make_shared(pdf->getWidth(), pdf->getHeight()); + page->setBackgroundPdfPageNr(pdfPage); + ctrl->insertPage(page, position); + } + } + }); +} + void Control::insertNewPage(size_t position, bool shouldScrollToPage) { pageBackgroundChangeController->insertNewPage(position, shouldScrollToPage); } @@ -771,8 +873,7 @@ void Control::insertPage(const PageRef& page, size_t position, bool shouldScroll firePageSelected(position); } - - updateDeletePageButton(); + updatePageActions(); undoRedo->addUndoAction(std::make_unique(page, position, true)); } @@ -1462,7 +1563,7 @@ void Control::fileLoaded(int scrollToPage) { updateWindowTitle(); win->getXournal()->forceUpdatePagenumbers(); getCursor()->updateCursor(); - updateDeletePageButton(); + updatePageActions(); } enum class MissingPdfDialogOptions : gint { USE_PROPOSED, SELECT_OTHER, REMOVE, CANCEL }; diff --git a/src/core/control/Control.h b/src/core/control/Control.h index 8c5afd066a66..9ee52fb5a9c1 100644 --- a/src/core/control/Control.h +++ b/src/core/control/Control.h @@ -224,11 +224,19 @@ class Control: void appendNewPdfPages(); void insertPage(const PageRef& page, size_t position, bool shouldScrollToPage = true); void deletePage(); + void movePageTowardsBeginning(); + void movePageTowardsEnd(); /** - * Disable / enable delete page button + * Ask the user whether a page with the given id + * should be added to the document. */ - void updateDeletePageButton(); + void askInsertPdfPage(size_t pdfPage); + + /** + * Disable / enable page action buttons + */ + void updatePageActions(); // selection handling void clearSelection(); diff --git a/src/core/control/ScrollHandler.cpp b/src/core/control/ScrollHandler.cpp index 1b6e78c9624f..5f8d9a79a7f0 100644 --- a/src/core/control/ScrollHandler.cpp +++ b/src/core/control/ScrollHandler.cpp @@ -68,7 +68,6 @@ void ScrollHandler::scrollToPage(size_t page, XojPdfRectangle rect) { void ScrollHandler::scrollToLinkDest(const LinkDestination& dest) { size_t pdfPage = dest.getPdfPage(); - Sidebar* sidebar = control->getSidebar(); if (pdfPage != npos) { Document* doc = control->getDocument(); @@ -77,7 +76,7 @@ void ScrollHandler::scrollToLinkDest(const LinkDestination& dest) { doc->unlock(); if (page == npos) { - sidebar->askInsertPdfPage(pdfPage); + control->askInsertPdfPage(pdfPage); } else { if (dest.shouldChangeTop()) { control->getScrollHandler()->scrollToPage(page, {dest.getLeft(), dest.getTop(), -1, -1}); diff --git a/src/core/control/actions/ActionProperties.h b/src/core/control/actions/ActionProperties.h index 1c85b5558de3..d2ed79fe9caf 100644 --- a/src/core/control/actions/ActionProperties.h +++ b/src/core/control/actions/ActionProperties.h @@ -500,6 +500,15 @@ template <> struct ActionProperties { static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->duplicatePage(); } }; +template <> +struct ActionProperties { + static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->movePageTowardsBeginning(); } +}; +template <> +struct ActionProperties { + static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->movePageTowardsEnd(); } +}; + template <> struct ActionProperties { static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->appendNewPdfPages(); } @@ -930,6 +939,26 @@ template <> struct ActionProperties { static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->getLayerController()->addNewLayer(); } }; + +template <> +struct ActionProperties { + static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->getLayerController()->copyCurrentLayer(); } +}; + +template <> +struct ActionProperties { + static void callback(GSimpleAction*, GVariant*, Control* ctrl) { + ctrl->getLayerController()->moveCurrentLayer(true); + } +}; + +template <> +struct ActionProperties { + static void callback(GSimpleAction*, GVariant*, Control* ctrl) { + ctrl->getLayerController()->moveCurrentLayer(false); + } +}; + template <> struct ActionProperties { static void callback(GSimpleAction*, GVariant*, Control* ctrl) { ctrl->getLayerController()->deleteCurrentLayer(); } diff --git a/src/core/control/jobs/PreviewJob.cpp b/src/core/control/jobs/PreviewJob.cpp index 97bc247c4fa6..9db14e98f7f0 100644 --- a/src/core/control/jobs/PreviewJob.cpp +++ b/src/core/control/jobs/PreviewJob.cpp @@ -34,37 +34,21 @@ auto PreviewJob::getSource() -> void* { return this->sidebarPreview; } auto PreviewJob::getType() -> JobType { return JOB_TYPE_PREVIEW; } void PreviewJob::initGraphics() { - GtkAllocation alloc; - gtk_widget_get_allocation(this->sidebarPreview->widget, &alloc); - crBuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, alloc.width, alloc.height); - zoom = this->sidebarPreview->sidebar->getZoom(); - cr2 = cairo_create(crBuffer); -} - -void PreviewJob::drawBorder() { - cairo_translate(cr2, Shadow::getShadowTopLeftSize() + 2, Shadow::getShadowTopLeftSize() + 2); - cairo_scale(cr2, zoom, zoom); + auto w = this->sidebarPreview->imageWidth; + auto h = this->sidebarPreview->imageHeight; + auto DPIscaling = this->sidebarPreview->DPIscaling; + buffer.reset(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h), xoj::util::adopt); + cairo_surface_set_device_scale(buffer.get(), DPIscaling, DPIscaling); + cr.reset(cairo_create(buffer.get()), xoj::util::adopt); + double zoom = this->sidebarPreview->sidebar->getZoom(); + cairo_translate(cr.get(), Shadow::getShadowTopLeftSize() + 2, Shadow::getShadowTopLeftSize() + 2); + cairo_scale(cr.get(), zoom, zoom); } void PreviewJob::finishPaint() { - this->sidebarPreview->drawingMutex.lock(); - - if (this->sidebarPreview->crBuffer) { - cairo_surface_destroy(this->sidebarPreview->crBuffer); - } - this->sidebarPreview->crBuffer = crBuffer; - - // The preview widget can be referenced after this is deleted. - // Only it should be referenced in the callback. - GtkWidget* previewWidget = this->sidebarPreview->widget; - g_object_ref(previewWidget); - - Util::execInUiThread([previewWidget]() { - gtk_widget_queue_draw(previewWidget); - g_object_unref(previewWidget); - }); - - this->sidebarPreview->drawingMutex.unlock(); + auto lock = std::lock_guard(this->sidebarPreview->drawingMutex); + this->sidebarPreview->buffer = std::move(this->buffer); + Util::execInUiThread([btn = this->sidebarPreview->button]() { gtk_widget_queue_draw(btn.get()); }); } void PreviewJob::drawPage() { @@ -82,17 +66,17 @@ void PreviewJob::drawPage() { layer = (dynamic_cast(this->sidebarPreview))->getLayer(); } - auto context = xoj::view::Context::createDefault(cr2); + auto context = xoj::view::Context::createDefault(cr.get()); switch (type) { case RENDER_TYPE_PAGE_PREVIEW: // render all layers - view.drawPage(page, cr2, true); + view.drawPage(page, cr.get(), true); break; case RENDER_TYPE_PAGE_LAYER: // render single layer - view.initDrawing(page, cr2, true); + view.initDrawing(page, cr.get(), true); if (layer == 0) { view.drawBackground(xoj::view::BACKGROUND_SHOW_ALL); } else { @@ -105,7 +89,7 @@ void PreviewJob::drawPage() { case RENDER_TYPE_PAGE_LAYERSTACK: // render all layers up to layer - view.initDrawing(page, cr2, true); + view.initDrawing(page, cr.get(), true); view.drawBackground(xoj::view::BACKGROUND_SHOW_ALL); for (Layer::Index i = 0; i < layer; i++) { Layer* drawLayer = (*page->getLayers())[i]; @@ -120,15 +104,14 @@ void PreviewJob::drawPage() { break; } - cairo_destroy(cr2); doc->unlock(); } void PreviewJob::clipToPage() { // Only render within the preview page. Without this, the when preview jobs attempt // to clear the display, we fill a region larger than the inside of the preview page! - cairo_rectangle(cr2, 0, 0, this->sidebarPreview->page->getWidth(), this->sidebarPreview->page->getHeight()); - cairo_clip(cr2); + cairo_rectangle(cr.get(), 0, 0, this->sidebarPreview->page->getWidth(), this->sidebarPreview->page->getHeight()); + cairo_clip(cr.get()); } void PreviewJob::run() { @@ -137,7 +120,6 @@ void PreviewJob::run() { } initGraphics(); - drawBorder(); clipToPage(); drawPage(); finishPaint(); diff --git a/src/core/control/jobs/PreviewJob.h b/src/core/control/jobs/PreviewJob.h index ef17a77769d8..834009f57dbe 100644 --- a/src/core/control/jobs/PreviewJob.h +++ b/src/core/control/jobs/PreviewJob.h @@ -13,6 +13,8 @@ #include // for cairo_surface_t, cairo_t +#include "util/raii/CairoWrappers.h" + #include "Job.h" // for Job, JobType class SidebarPreviewBaseEntry; @@ -38,7 +40,6 @@ class PreviewJob: public Job { private: void initGraphics(); void clipToPage(); - void drawBorder(); void finishPaint(); void drawPage(); @@ -46,12 +47,12 @@ class PreviewJob: public Job { /** * Graphics buffer */ - cairo_surface_t* crBuffer = nullptr; + xoj::util::CairoSurfaceSPtr buffer; /** * Graphics drawing */ - cairo_t* cr2 = nullptr; + xoj::util::CairoSPtr cr; /** * Zoom factor diff --git a/src/core/enums/Action.enum.h b/src/core/enums/Action.enum.h index ae7574e69509..c73cf0c5d745 100644 --- a/src/core/enums/Action.enum.h +++ b/src/core/enums/Action.enum.h @@ -91,6 +91,8 @@ enum class Action : size_t { NEW_PAGE_AFTER, NEW_PAGE_AT_END, DUPLICATE_PAGE, + MOVE_PAGE_TOWARDS_BEGINNING, + MOVE_PAGE_TOWARDS_END, APPEND_NEW_PDF_PAGES, CONFIGURE_PAGE_TEMPLATE, DELETE_PAGE, @@ -153,6 +155,9 @@ enum class Action : size_t { LAYER_SHOW_ALL, LAYER_HIDE_ALL, LAYER_NEW, + LAYER_COPY, + LAYER_MOVE_UP, + LAYER_MOVE_DOWN, LAYER_DELETE, LAYER_MERGE_DOWN, LAYER_RENAME, diff --git a/src/core/enums/generated/Action.NameMap.generated.h b/src/core/enums/generated/Action.NameMap.generated.h index 6945a0355eaa..9be0a1b3b043 100644 --- a/src/core/enums/generated/Action.NameMap.generated.h +++ b/src/core/enums/generated/Action.NameMap.generated.h @@ -57,6 +57,8 @@ constexpr const char* ACTION_NAMES[] = { // Action to string conversion map "new-page-after", "new-page-at-end", "duplicate-page", + "move-page-towards-beginning", + "move-page-towards-end", "append-new-pdf-pages", "configure-page-template", "delete-page", @@ -103,6 +105,9 @@ constexpr const char* ACTION_NAMES[] = { // Action to string conversion map "layer-show-all", "layer-hide-all", "layer-new", + "layer-copy", + "layer-move-up", + "layer-move-down", "layer-delete", "layer-merge-down", "layer-rename", diff --git a/src/core/gui/XournalView.cpp b/src/core/gui/XournalView.cpp index b945f63a3b8e..e972f542b275 100644 --- a/src/core/gui/XournalView.cpp +++ b/src/core/gui/XournalView.cpp @@ -400,6 +400,7 @@ void XournalView::pageSelected(size_t page) { control->updatePageNumbers(currentPage, pdfPage); control->updateBackgroundSizeButton(); + control->updatePageActions(); if (control->getSettings()->isEagerPageCleanup()) { this->cleanupBufferCache(); diff --git a/src/core/gui/sidebar/AbstractSidebarPage.cpp b/src/core/gui/sidebar/AbstractSidebarPage.cpp index d41808d00e02..72a8f24f523c 100644 --- a/src/core/gui/sidebar/AbstractSidebarPage.cpp +++ b/src/core/gui/sidebar/AbstractSidebarPage.cpp @@ -3,13 +3,9 @@ #include // for gdk_cursor_new_for_display, gdk_display_get... #include // for g_object_unref -AbstractSidebarPage::AbstractSidebarPage(Control* control, SidebarToolbar* toolbar): - control(control), toolbar(toolbar) {} +AbstractSidebarPage::AbstractSidebarPage(Control* control): control(control) {} -AbstractSidebarPage::~AbstractSidebarPage() { - this->control = nullptr; - this->toolbar = nullptr; -} +AbstractSidebarPage::~AbstractSidebarPage() = default; void AbstractSidebarPage::selectPageNr(size_t page, size_t pdfPage) {} diff --git a/src/core/gui/sidebar/AbstractSidebarPage.h b/src/core/gui/sidebar/AbstractSidebarPage.h index 47dcbf3ab7cf..a2b0adca7182 100644 --- a/src/core/gui/sidebar/AbstractSidebarPage.h +++ b/src/core/gui/sidebar/AbstractSidebarPage.h @@ -16,14 +16,13 @@ #include // for GtkToolItem -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarToolbar... #include "model/DocumentListener.h" // for DocumentListener class Control; -class AbstractSidebarPage: public DocumentListener, public SidebarToolbarActionListener { +class AbstractSidebarPage: public DocumentListener { public: - AbstractSidebarPage(Control* control, SidebarToolbar* toolbar); + AbstractSidebarPage(Control* control); ~AbstractSidebarPage() override; public: @@ -74,14 +73,9 @@ class AbstractSidebarPage: public DocumentListener, public SidebarToolbarActionL */ Control* control = nullptr; - /** - * The Toolbar to move, copy & delete pages - */ - SidebarToolbar* toolbar = nullptr; - public: /** * The Sidebar button */ - GtkToolItem* tabButton = nullptr; + GtkWidget* tabButton = nullptr; }; diff --git a/src/core/gui/sidebar/Sidebar.cpp b/src/core/gui/sidebar/Sidebar.cpp index 7f1adf9a9ca5..c8d0981041b8 100644 --- a/src/core/gui/sidebar/Sidebar.cpp +++ b/src/core/gui/sidebar/Sidebar.cpp @@ -9,148 +9,96 @@ #include // for G_CALLBACK, g_s... #include // for gtk_toggle_tool_button_new... -#include "control/Control.h" // for Control -#include "control/settings/Settings.h" // for Settings -#include "gui/GladeGui.h" // for GladeGui -#include "gui/sidebar/AbstractSidebarPage.h" // for AbstractSidebar... -#include "gui/sidebar/indextree/SidebarIndexPage.h" // for SidebarIndexPage -#include "model/Document.h" // for Document -#include "model/XojPage.h" // for XojPage -#include "pdf/base/XojPdfPage.h" // for XojPdfPageSPtr -#include "previews/layer/SidebarLayersContextMenu.h" // for SidebarLayersCo... -#include "previews/layer/SidebarPreviewLayers.h" // for SidebarPreviewL... -#include "previews/page/SidebarPreviewPages.h" // for SidebarPreviewP... -#include "util/Util.h" // for npos -#include "util/XojMsgBox.h" // for askQuestion -#include "util/glib_casts.h" // for closure_notify_cb -#include "util/i18n.h" // for _, FC, _F - -Sidebar::Sidebar(GladeGui* gui, Control* control): control(control), gui(gui), toolbar(this, gui) { - this->tbSelectPage = GTK_TOOLBAR(gui->get("tbSelectSidebarPage")); +#include "control/Control.h" // for Control +#include "control/settings/Settings.h" // for Settings +#include "gui/GladeGui.h" // for GladeGui +#include "gui/sidebar/AbstractSidebarPage.h" // for AbstractSidebar... +#include "gui/sidebar/indextree/SidebarIndexPage.h" // for SidebarIndexPage +#include "model/Document.h" // for Document +#include "model/XojPage.h" // for XojPage +#include "pdf/base/XojPdfPage.h" // for XojPdfPageSPtr +#include "previews/layer/SidebarPreviewLayers.h" // for SidebarPreviewL... +#include "previews/page/SidebarPreviewPages.h" // for SidebarPreviewP... +#include "util/Util.h" // for npos +#include "util/glib_casts.h" // for closure_notify_cb +#include "util/gtk4_helper.h" // +#include "util/i18n.h" // for _, FC, _F + +Sidebar::Sidebar(GladeGui* gui, Control* control): control(control) { + this->tbSelectTab = GTK_BOX(gui->get("bxSidebarTopActions")); this->buttonCloseSidebar = gui->get("buttonCloseSidebar"); this->sidebarContents = gui->get("sidebarContents"); - this->initPages(sidebarContents, gui); + this->initTabs(sidebarContents); registerListener(control); } -void Sidebar::initPages(GtkWidget* sidebarContents, GladeGui* gui) { - addPage(std::make_unique(this->control, &this->toolbar)); - addPage(std::make_unique(this->control, this->gui, &this->toolbar)); - auto layersContextMenu = std::make_shared(this->gui, &this->toolbar); - addPage(std::make_unique(this->control, this->gui, &this->toolbar, false, layersContextMenu)); - addPage(std::make_unique(this->control, this->gui, &this->toolbar, true, layersContextMenu)); +void Sidebar::initTabs(GtkWidget* sidebarContents) { + addTab(std::make_unique(this->control)); + addTab(std::make_unique(this->control)); + addTab(std::make_unique(this->control, false)); + addTab(std::make_unique(this->control, true)); // Init toolbar with icons size_t i = 0; - for (auto&& p: this->pages) { - GtkToolItem* it = gtk_toggle_tool_button_new(); - p->tabButton = it; + for (auto&& p: this->tabs) { + GtkWidget* btn = gtk_toggle_button_new(); + p->tabButton = btn; - gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(it), gtk_image_new_from_icon_name(p->getIconName().c_str(), - GTK_ICON_SIZE_SMALL_TOOLBAR)); - g_signal_connect_data(it, "clicked", G_CALLBACK(&buttonClicked), new SidebarPageButton(this, i, p.get()), - xoj::util::closure_notify_cb, GConnectFlags(0)); - gtk_tool_item_set_tooltip_text(it, p->getName().c_str()); - gtk_tool_button_set_label(GTK_TOOL_BUTTON(it), p->getName().c_str()); - - gtk_toolbar_insert(tbSelectPage, it, -1); + gtk_button_set_icon_name(GTK_BUTTON(btn), p->getIconName().c_str()); + g_signal_connect_data(btn, "clicked", G_CALLBACK(&buttonClicked), new SidebarTabButton(this, i, p.get()), + xoj::util::closure_notify_cb, GConnectFlags(0)); + gtk_widget_set_tooltip_text(btn, p->getName().c_str()); + gtk_box_append(tbSelectTab, btn); // Add widget to sidebar - gtk_box_pack_start(GTK_BOX(sidebarContents), p->getWidget(), true, true, 0); + gtk_widget_set_vexpand(p->getWidget(), true); + gtk_box_append(GTK_BOX(sidebarContents), p->getWidget()); i++; } - gtk_widget_show_all(GTK_WIDGET(this->tbSelectPage)); - updateVisibleTabs(); } -void Sidebar::buttonClicked(GtkToolButton* toolbutton, SidebarPageButton* buttonData) { - if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toolbutton))) { - if (buttonData->sidebar->visiblePage != buttonData->page->getWidget()) { - buttonData->sidebar->setSelectedPage(buttonData->index); +void Sidebar::buttonClicked(GtkButton* button, SidebarTabButton* buttonData) { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) { + if (buttonData->sidebar->visibleTab != buttonData->page->getWidget()) { + buttonData->sidebar->setSelectedTab(buttonData->index); } - } else if (buttonData->sidebar->visiblePage == buttonData->page->getWidget()) { - gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolbutton), true); + } else if (buttonData->sidebar->visibleTab == buttonData->page->getWidget()) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), true); } } -void Sidebar::addPage(std::unique_ptr page) { this->pages.push_back(std::move(page)); } - -void Sidebar::askInsertPdfPage(size_t pdfPage) { - using Responses = enum { CANCEL = 1, AFTER = 2, END = 3 }; - std::vector buttons = {{_("Cancel"), Responses::CANCEL}, - {_("Insert after current page"), Responses::AFTER}, - {_("Insert at end"), Responses::END}}; - - XojMsgBox::askQuestion(control->getGtkWindow(), - FC(_F("Your current document does not contain PDF Page no {1}\n" - "Would you like to insert this page?\n\n" - "Tip: You can select Journal → Paper Background → PDF Background " - "to insert a PDF page.") % - static_cast(pdfPage + 1)), - "", buttons, [ctrl = this->control, pdfPage](int response) { - if (response == Responses::AFTER || response == Responses::END) { - Document* doc = ctrl->getDocument(); - - doc->lock(); - size_t position = response == Responses::AFTER ? ctrl->getCurrentPageNo() + 1 : - doc->getPageCount(); - XojPdfPageSPtr pdf = doc->getPdfPage(pdfPage); - doc->unlock(); - - if (pdf) { - auto page = std::make_shared(pdf->getWidth(), pdf->getHeight()); - page->setBackgroundPdfPageNr(pdfPage); - ctrl->insertPage(page, position); - } - } - }); -} +void Sidebar::addTab(std::unique_ptr tab) { this->tabs.push_back(std::move(tab)); } Sidebar::~Sidebar() = default; -/** - * Called when an action is performed - */ -void Sidebar::actionPerformed(SidebarActions action) { - if (this->currentPageIdx >= this->pages.size()) { - return; - } - - this->pages.at(this->currentPageIdx)->actionPerformed(action); -} - void Sidebar::selectPageNr(size_t page, size_t pdfPage) { - for (auto&& p: this->pages) { + for (auto&& p: this->tabs) { p->selectPageNr(page, pdfPage); } } -size_t Sidebar::getNumberOfPages() { return this->pages.size(); } - -size_t Sidebar::getSelectedPage() { return this->currentPageIdx; } - -void Sidebar::setSelectedPage(size_t page) { - this->visiblePage = nullptr; +void Sidebar::setSelectedTab(size_t tab) { + this->visibleTab = nullptr; size_t i = 0; - for (auto&& p: this->pages) { - if (page == i) { - gtk_widget_show(p->getWidget()); - gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(p->tabButton), true); - this->visiblePage = p->getWidget(); - this->currentPageIdx = i; - p->enableSidebar(); + for (auto&& t: this->tabs) { + if (tab == i) { + gtk_widget_show(t->getWidget()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(t->tabButton), true); + this->visibleTab = t->getWidget(); + this->currentTabIdx = i; + t->enableSidebar(); } else { - p->disableSidebar(); - gtk_widget_hide(p->getWidget()); - gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(p->tabButton), false); + t->disableSidebar(); + gtk_widget_hide(t->getWidget()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(t->tabButton), false); } i++; @@ -161,7 +109,7 @@ void Sidebar::updateVisibleTabs() { size_t i = 0; size_t selected = npos; - for (auto&& p: this->pages) { + for (auto&& p: this->tabs) { gtk_widget_set_visible(GTK_WIDGET(p->tabButton), p->hasData()); if (p->hasData() && selected == npos) { @@ -171,15 +119,15 @@ void Sidebar::updateVisibleTabs() { i++; } - setSelectedPage(selected); + setSelectedTab(selected); } void Sidebar::setTmpDisabled(bool disabled) { gtk_widget_set_sensitive(this->buttonCloseSidebar, !disabled); - gtk_widget_set_sensitive(GTK_WIDGET(this->tbSelectPage), !disabled); + gtk_widget_set_sensitive(GTK_WIDGET(this->tbSelectTab), !disabled); - for (auto&& p: this->pages) { - p->setTmpDisabled(disabled); + for (auto&& t: this->tabs) { + t->setTmpDisabled(disabled); } gdk_display_sync(gdk_display_get_default()); @@ -192,7 +140,9 @@ void Sidebar::saveSize() { this->control->getSettings()->setSidebarWidth(alloc.width); } -auto Sidebar::getToolbar() -> SidebarToolbar* { return &this->toolbar; } +size_t Sidebar::getNumberOfTabs() const { return this->tabs.size(); } + +size_t Sidebar::getSelectedTab() const { return this->currentTabIdx; } auto Sidebar::getControl() -> Control* { return this->control; } @@ -202,11 +152,11 @@ void Sidebar::documentChanged(DocumentChangeType type) { } } -SidebarPageButton::SidebarPageButton(Sidebar* sidebar, size_t index, AbstractSidebarPage* page): +SidebarTabButton::SidebarTabButton(Sidebar* sidebar, size_t index, AbstractSidebarPage* page): sidebar(sidebar), index(index), page(page) {} void Sidebar::layout() { - for (auto&& page: this->pages) { - page->layout(); + for (auto&& tab: this->tabs) { + tab->layout(); } } diff --git a/src/core/gui/sidebar/Sidebar.h b/src/core/gui/sidebar/Sidebar.h index e3eb8ecc7b90..e0a2af4ab6b8 100644 --- a/src/core/gui/sidebar/Sidebar.h +++ b/src/core/gui/sidebar/Sidebar.h @@ -17,30 +17,22 @@ #include // for GtkWidget, Gtk... -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarActions #include "model/DocumentChangeType.h" // for DocumentChange... #include "model/DocumentListener.h" // for DocumentListener class AbstractSidebarPage; class Control; class GladeGui; -class SidebarPageButton; +class SidebarTabButton; -class Sidebar: public DocumentListener, public SidebarToolbarActionListener { +class Sidebar: public DocumentListener { public: Sidebar(GladeGui* gui, Control* control); ~Sidebar() override; private: - void initPages(GtkWidget* sidebarContents, GladeGui* gui); - void addPage(std::unique_ptr page); - - // SidebarToolbarActionListener -public: - /** - * Called when an action is performed - */ - void actionPerformed(SidebarActions action) override; + void initTabs(GtkWidget* sidebarContents); + void addTab(std::unique_ptr tab); public: /** @@ -55,16 +47,6 @@ class Sidebar: public DocumentListener, public SidebarToolbarActionListener { Control* getControl(); - /** - * Sets the current selected page - */ - void setSelectedPage(size_t page); - - /** - * Show/hide tabs based on whether they have content. Select first active tab (page). - */ - void updateVisibleTabs(); - /** * Temporary disable Sidebar (e.g. while saving) */ @@ -76,25 +58,25 @@ class Sidebar: public DocumentListener, public SidebarToolbarActionListener { void saveSize(); /** - * Gets the sidebar toolbar - */ - SidebarToolbar* getToolbar(); - - /** - * Ask the user whether a page with the given id - * should be added to the document. + * Get how many pages are contained in this sidebar + * + * Todo: it makes no sense to expose this, as the caller has no way of knowing what those pages correspond to */ - void askInsertPdfPage(size_t pdfPage); + [[deprecated]] size_t getNumberOfTabs() const; /** - * Get how many pages are contained in this sidebar + * Get index of the currently selected page + * + * Todo: it makes no sense to expose this, as the caller has no way of knowing what this number corresponds to */ - size_t getNumberOfPages(); + [[deprecated]] size_t getSelectedTab() const; /** - * Get index of the currently selected page + * Sets the current selected tab + * + * Todo: this should be private */ - size_t getSelectedPage(); + void setSelectedTab(size_t tab); public: // DocumentListener interface @@ -104,22 +86,26 @@ class Sidebar: public DocumentListener, public SidebarToolbarActionListener { /** * Page selected */ - static void buttonClicked(GtkToolButton* toolbutton, SidebarPageButton* buttonData); + static void buttonClicked(GtkButton* button, SidebarTabButton* buttonData); + + /** + * Show/hide tabs based on whether they have content. Select first active tab (page). + */ + void updateVisibleTabs(); + private: Control* control = nullptr; - GladeGui* gui = nullptr; - /** * The sidebar pages */ - std::vector> pages; + std::vector> tabs; /** - * The Toolbar with the pages + * The bar with the tab selection */ - GtkToolbar* tbSelectPage = nullptr; + GtkBox* tbSelectTab = nullptr; /** * The close button of the sidebar @@ -129,27 +115,22 @@ class Sidebar: public DocumentListener, public SidebarToolbarActionListener { /** * The current visible page in the sidebar */ - GtkWidget* visiblePage = nullptr; + GtkWidget* visibleTab = nullptr; /** * Current active page */ - size_t currentPageIdx{0}; + size_t currentTabIdx{0}; /** * The sidebarContents widget */ GtkWidget* sidebarContents = nullptr; - - /** - * Sidebar toolbar - */ - SidebarToolbar toolbar; }; -class SidebarPageButton { +class SidebarTabButton { public: - SidebarPageButton(Sidebar* sidebar, size_t index, AbstractSidebarPage* page); + SidebarTabButton(Sidebar* sidebar, size_t index, AbstractSidebarPage* page); public: Sidebar* sidebar = nullptr; diff --git a/src/core/gui/sidebar/indextree/SidebarIndexPage.cpp b/src/core/gui/sidebar/indextree/SidebarIndexPage.cpp index e0063be35bd8..cf916bc0ea89 100644 --- a/src/core/gui/sidebar/indextree/SidebarIndexPage.cpp +++ b/src/core/gui/sidebar/indextree/SidebarIndexPage.cpp @@ -5,18 +5,18 @@ #include // for g_object_unref #include // for PangoLogAttr -#include "control/Control.h" // for Control -#include "control/ScrollHandler.h" // for ScrollHandler -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarToolbar -#include "model/Document.h" // for Document -#include "model/LinkDestination.h" // for XojLinkDest -#include "util/Assert.h" // for xoj_assert -#include "util/glib_casts.h" // for wrap_v -#include "util/i18n.h" // for _ -#include "util/safe_casts.h" // for as_unsigned - -SidebarIndexPage::SidebarIndexPage(Control* control, SidebarToolbar* toolbar): - AbstractSidebarPage(control, toolbar), iconNameHelper(control->getSettings()) { +#include "control/Control.h" // for Control +#include "control/ScrollHandler.h" // for ScrollHandler +#include "model/Document.h" // for Document +#include "model/LinkDestination.h" // for XojLinkDest +#include "util/Assert.h" // for xoj_assert +#include "util/glib_casts.h" // for wrap_v +#include "util/gtk4_helper.h" // +#include "util/i18n.h" // for _ +#include "util/safe_casts.h" // for as_unsigned + +SidebarIndexPage::SidebarIndexPage(Control* control): + AbstractSidebarPage(control), iconNameHelper(control->getSettings()) { this->treeViewBookmarks = gtk_tree_view_new(); g_object_ref(this->treeViewBookmarks); @@ -26,16 +26,15 @@ SidebarIndexPage::SidebarIndexPage(Control* control, SidebarToolbar* toolbar): reinterpret_cast(treeSearchFunction), this, nullptr); - this->scrollBookmarks = gtk_scrolled_window_new(nullptr, nullptr); + this->scrollBookmarks = gtk_scrolled_window_new(); g_object_ref(this->scrollBookmarks); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollBookmarks), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollBookmarks), GTK_SHADOW_IN); GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeViewBookmarks)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeViewBookmarks), false); - gtk_container_add(GTK_CONTAINER(scrollBookmarks), treeViewBookmarks); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollBookmarks), treeViewBookmarks); GtkTreeViewColumn* column = gtk_tree_view_column_new(); @@ -72,7 +71,7 @@ SidebarIndexPage::~SidebarIndexPage() { g_object_unref(this->scrollBookmarks); } -void SidebarIndexPage::enableSidebar() { toolbar->setHidden(true); } +void SidebarIndexPage::enableSidebar() {} void SidebarIndexPage::disableSidebar() { // Nothing to do at the moment diff --git a/src/core/gui/sidebar/indextree/SidebarIndexPage.h b/src/core/gui/sidebar/indextree/SidebarIndexPage.h index 16d6088dfdbf..f7e88683ccff 100644 --- a/src/core/gui/sidebar/indextree/SidebarIndexPage.h +++ b/src/core/gui/sidebar/indextree/SidebarIndexPage.h @@ -26,7 +26,7 @@ class SidebarToolbar; class SidebarIndexPage: public AbstractSidebarPage { public: - SidebarIndexPage(Control* control, SidebarToolbar* toolbar); + SidebarIndexPage(Control* control); ~SidebarIndexPage() override; public: diff --git a/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.cpp b/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.cpp deleted file mode 100644 index b065c8a99811..000000000000 --- a/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "gui/sidebar/previews/base/SidebarBaseContextMenu.h" - -#include // for g_object_unref, g_signal_handler_disconnect - -SidebarBaseContextMenu::SidebarBaseContextMenu(GtkWidget* contextMenu): contextMenu(contextMenu) {} - -SidebarBaseContextMenu::~SidebarBaseContextMenu() { - for (const auto& [widget, handlerId, _]: this->contextMenuSignals) { - if (g_signal_handler_is_connected(widget, handlerId)) { - g_signal_handler_disconnect(widget, handlerId); - } - g_object_unref(widget); - } -} - - -void SidebarBaseContextMenu::open() { - gtk_menu_popup(GTK_MENU(this->contextMenu), nullptr, nullptr, nullptr, nullptr, 3, gtk_get_current_event_time()); -} diff --git a/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.h b/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.h deleted file mode 100644 index 12b5b2fcfac0..000000000000 --- a/src/core/gui/sidebar/previews/base/SidebarBaseContextMenu.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Xournal++ - * - * Abstract base class for sidebar context menus - * - * @author Xournal++ Team - * https://github.com/xournalpp/xournalpp - * - * @license GNU GPLv2 or later - */ - -#pragma once - -#include // for unique_ptr -#include // for tuple -#include // for vector - -#include // for gulong -#include // for GtkWidget - -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarActions - -class SidebarBaseContextMenu { -public: - SidebarBaseContextMenu(GtkWidget* contextMenu); - virtual ~SidebarBaseContextMenu(); - - SidebarBaseContextMenu(SidebarBaseContextMenu&&) = delete; - SidebarBaseContextMenu(const SidebarBaseContextMenu&) = delete; - SidebarBaseContextMenu& operator=(SidebarBaseContextMenu&&) = delete; - SidebarBaseContextMenu& operator=(const SidebarBaseContextMenu&) = delete; - - /** - * Sets the sensitivity of the actions in the menu according to the actions - * specified by the passed `actions`. - * - * If the context menu had widgets (entries) for the actions A, B, C, D - * and this was passed the actions value (A|C) then it would sensitize - * (make clickable) the widgets for the actions A and C, and desensitize the - * others. - */ - virtual void setActionsSensitivity(SidebarActions actions) = 0; - - /** - * Opens the context menu. - */ - virtual void open(); - -protected: - /** - * The context menu to display on right click. - */ - GtkWidget* const contextMenu = nullptr; - - /** - * The data passed to the menu item callbacks. - */ - struct ContextMenuData { - SidebarToolbar* toolbar; - SidebarActions actions; - }; - - /** - * The signals connected to the context menu items. This must be kept track - * of so the data can be deallocated safely. - */ - std::vector>> contextMenuSignals; - -private: -}; diff --git a/src/core/gui/sidebar/previews/base/SidebarLayout.cpp b/src/core/gui/sidebar/previews/base/SidebarLayout.cpp index f1edba019c18..752f5a3f2570 100644 --- a/src/core/gui/sidebar/previews/base/SidebarLayout.cpp +++ b/src/core/gui/sidebar/previews/base/SidebarLayout.cpp @@ -1,20 +1,16 @@ #include "SidebarLayout.h" #include // for max -#include // for list, operator!=, _List_iterator #include // for vector -#include // for GTK_LAYOUT, gtk_layout_move +#include // for GTK_FIXED, gtk_fixed_move +#include "util/gtk4_helper.h" #include "util/safe_casts.h" // for as_unsigned #include "SidebarPreviewBase.h" // for SidebarPreviewBase #include "SidebarPreviewBaseEntry.h" // for SidebarPreviewBaseEntry -SidebarLayout::SidebarLayout() = default; - -SidebarLayout::~SidebarLayout() = default; - class SidebarRow { public: explicit SidebarRow(int width) { @@ -50,7 +46,7 @@ class SidebarRow { auto getWidth() const -> int { return this->currentWidth; } - auto placeAt(int y, GtkLayout* layout) -> int { + auto placeAt(int y, GtkFixed* layout) -> int { int height = 0; int x = 0; @@ -60,7 +56,7 @@ class SidebarRow { for (SidebarPreviewBaseEntry* p: this->list) { int currentY = (height - p->getHeight()) / 2; - gtk_layout_move(layout, p->getWidget(), x, y + currentY); + gtk_fixed_move(layout, p->getWidget(), x, y + currentY); x += p->getWidth(); } @@ -73,24 +69,23 @@ class SidebarRow { int width; int currentWidth; - std::list list; + std::vector list; }; void SidebarLayout::layout(SidebarPreviewBase* sidebar) { int y = 0; int width = 0; - GtkAllocation alloc; - - gtk_widget_get_allocation(sidebar->scrollPreview.get(), &alloc); + int sidebarWidth = gtk_widget_get_width(sidebar->scrollableBox.get()); - SidebarRow row(alloc.width); + SidebarRow row(sidebarWidth); + GtkFixed* w = sidebar->miniaturesContainer.get(); - for (auto &p: sidebar->previews) { + for (auto& p: sidebar->previews) { if (row.isSpaceFor(p.get())) { row.add(p.get()); } else { - y += row.placeAt(y, GTK_LAYOUT(sidebar->iconViewPreview.get())); + y += row.placeAt(y, w); width = std::max(width, row.getWidth()); @@ -100,12 +95,13 @@ void SidebarLayout::layout(SidebarPreviewBase* sidebar) { } if (row.getCount() != 0) { - y += row.placeAt(y, GTK_LAYOUT(sidebar->iconViewPreview.get())); + y += row.placeAt(y, w); width = std::max(width, row.getWidth()); row.clear(); } - gtk_layout_set_size(GTK_LAYOUT(sidebar->iconViewPreview.get()), as_unsigned(width), as_unsigned(y)); + gtk_widget_set_size_request(GTK_WIDGET(w), width, y); + gtk_widget_show_all(GTK_WIDGET(w)); } diff --git a/src/core/gui/sidebar/previews/base/SidebarLayout.h b/src/core/gui/sidebar/previews/base/SidebarLayout.h index b7c10adc3fe3..5ca6a1f6ba46 100644 --- a/src/core/gui/sidebar/previews/base/SidebarLayout.h +++ b/src/core/gui/sidebar/previews/base/SidebarLayout.h @@ -15,8 +15,8 @@ class SidebarPreviewBase; class SidebarLayout { public: - SidebarLayout(); - virtual ~SidebarLayout(); + SidebarLayout() = delete; + ~SidebarLayout() = delete; public: /** diff --git a/src/core/gui/sidebar/previews/base/SidebarPreviewBase.cpp b/src/core/gui/sidebar/previews/base/SidebarPreviewBase.cpp index 99c302e97f65..e47ce35043d5 100644 --- a/src/core/gui/sidebar/previews/base/SidebarPreviewBase.cpp +++ b/src/core/gui/sidebar/previews/base/SidebarPreviewBase.cpp @@ -7,22 +7,28 @@ #include "control/Control.h" // for Control #include "control/PdfCache.h" // for PdfCache +#include "gui/Builder.h" // for Builder #include "gui/MainWindow.h" // for MainWindow #include "model/Document.h" // for Document #include "util/Util.h" // for npos #include "util/glib_casts.h" // for wrap_for_once_v +#include "util/gtk4_helper.h" #include "SidebarLayout.h" // for SidebarLayout #include "SidebarPreviewBaseEntry.h" // for SidebarPreviewBaseEntry -class GladeGui; +constexpr auto XML_FILE = "sidebar.ui"; - -SidebarPreviewBase::SidebarPreviewBase(Control* control, GladeGui* gui, SidebarToolbar* toolbar): - AbstractSidebarPage(control, toolbar), - scrollPreview(gtk_scrolled_window_new(nullptr, nullptr), xoj::util::adopt), - iconViewPreview(gtk_layout_new(nullptr, nullptr), xoj::util::adopt) { - this->layoutmanager = new SidebarLayout(); +SidebarPreviewBase::SidebarPreviewBase(Control* control, const char* menuId, const char* toolbarId): + AbstractSidebarPage(control), + scrollableBox(gtk_scrolled_window_new(), xoj::util::adopt), + mainBox(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0), xoj::util::adopt), + miniaturesContainer(GTK_FIXED(gtk_fixed_new()), xoj::util::adopt) { + gtk_box_append(GTK_BOX(mainBox.get()), scrollableBox.get()); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollableBox.get()), GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollableBox.get()), GTK_WIDGET(miniaturesContainer.get())); + gtk_widget_set_vexpand(scrollableBox.get(), true); Document* doc = this->control->getDocument(); doc->lock(); @@ -31,43 +37,39 @@ SidebarPreviewBase::SidebarPreviewBase(Control* control, GladeGui* gui, SidebarT } doc->unlock(); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(this->scrollPreview.get()), GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(this->scrollPreview.get()), GTK_SHADOW_IN); - - gtk_container_add(GTK_CONTAINER(this->scrollPreview.get()), this->iconViewPreview.get()); registerListener(this->control); this->control->addChangedDocumentListener(this); - g_signal_connect(this->scrollPreview.get(), "size-allocate", G_CALLBACK(sizeChanged), this); - - gtk_widget_show_all(this->scrollPreview.get()); -} + auto* adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrollableBox.get())); + g_signal_connect( + adj, "notify::page-size", G_CALLBACK(+[](GObject* adj, GParamSpec*, gpointer d) { + static_cast(d)->newWidth(gtk_adjustment_get_page_size(GTK_ADJUSTMENT(adj))); + }), + this); -SidebarPreviewBase::~SidebarPreviewBase() { - delete this->layoutmanager; - this->layoutmanager = nullptr; + Builder builder(control->getGladeSearchPath(), XML_FILE); + GMenuModel* menu = G_MENU_MODEL(builder.get(menuId)); + contextMenu.reset(GTK_MENU(gtk_menu_new_from_model(menu)), xoj::util::adopt); + gtk_menu_attach_to_widget(contextMenu.get(), mainBox.get(), nullptr); - this->control->removeChangedDocumentListener(this); + gtk_box_append(GTK_BOX(mainBox.get()), builder.get(toolbarId)); - this->previews.clear(); + gtk_widget_show_all(mainBox.get()); } +SidebarPreviewBase::~SidebarPreviewBase() { this->control->removeChangedDocumentListener(this); } + void SidebarPreviewBase::enableSidebar() { enabled = true; } void SidebarPreviewBase::disableSidebar() { enabled = false; } -void SidebarPreviewBase::sizeChanged(GtkWidget* widget, GtkAllocation* allocation, SidebarPreviewBase* sidebar) { - static int lastWidth = -1; - - if (lastWidth == -1) { - lastWidth = allocation->width; - } +void SidebarPreviewBase::newWidth(double width) { + static constexpr double TRIGGER = 20.; - if (std::abs(lastWidth - allocation->width) > 20) { - sidebar->layout(); - lastWidth = allocation->width; + if (std::abs(lastWidth - width) > TRIGGER) { + this->layout(); + lastWidth = width; } } @@ -79,7 +81,7 @@ void SidebarPreviewBase::layout() { SidebarLayout::layout(this); } auto SidebarPreviewBase::hasData() -> bool { return true; } -auto SidebarPreviewBase::getWidget() -> GtkWidget* { return this->scrollPreview.get(); } +auto SidebarPreviewBase::getWidget() -> GtkWidget* { return this->mainBox.get(); } void SidebarPreviewBase::documentChanged(DocumentChangeType type) { if (type == DOCUMENT_CHANGE_COMPLETE || type == DOCUMENT_CHANGE_CLEARED) { @@ -114,8 +116,7 @@ auto SidebarPreviewBase::scrollToPreview(SidebarPreviewBase* sidebar) -> bool { auto& p = sidebar->previews[sidebar->selectedEntry]; // scroll to preview - GtkAdjustment* hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(sidebar->scrollPreview.get())); - GtkAdjustment* vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sidebar->scrollPreview.get())); + GtkAdjustment* vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sidebar->scrollableBox.get())); GtkWidget* widget = p->getWidget(); GtkAllocation allocation; @@ -129,7 +130,6 @@ auto SidebarPreviewBase::scrollToPreview(SidebarPreviewBase* sidebar) -> bool { } gtk_adjustment_clamp_page(vadj, y, y + allocation.height); - gtk_adjustment_clamp_page(hadj, x, x + allocation.width); } return false; } @@ -137,3 +137,7 @@ auto SidebarPreviewBase::scrollToPreview(SidebarPreviewBase* sidebar) -> bool { void SidebarPreviewBase::pageDeleted(size_t page) {} void SidebarPreviewBase::pageInserted(size_t page) {} + +void SidebarPreviewBase::openPreviewContextMenu(GdkEvent* currentEvent) { + gtk_menu_popup_at_pointer(contextMenu.get(), currentEvent); +} diff --git a/src/core/gui/sidebar/previews/base/SidebarPreviewBase.h b/src/core/gui/sidebar/previews/base/SidebarPreviewBase.h index 643db79aec5c..d259c13b5219 100644 --- a/src/core/gui/sidebar/previews/base/SidebarPreviewBase.h +++ b/src/core/gui/sidebar/previews/base/SidebarPreviewBase.h @@ -25,13 +25,11 @@ class PdfCache; class SidebarLayout; class SidebarPreviewBaseEntry; -class SidebarToolbar; class Control; -class GladeGui; class SidebarPreviewBase: public AbstractSidebarPage { public: - SidebarPreviewBase(Control* control, GladeGui* gui, SidebarToolbar* toolbar); + SidebarPreviewBase(Control* control, const char* menuId, const char* toolbarId); ~SidebarPreviewBase() override; public: @@ -80,52 +78,52 @@ class SidebarPreviewBase: public AbstractSidebarPage { */ static bool scrollToPreview(SidebarPreviewBase* sidebar); - /** - * The size of the sidebar has chnaged - */ - static void sizeChanged(GtkWidget* widget, GtkAllocation* allocation, SidebarPreviewBase* sidebar); + /// The width of the sidebar has changed + void newWidth(double width); public: /** * Opens a context menu, at the current cursor position. */ - virtual void openPreviewContextMenu() = 0; + void openPreviewContextMenu(GdkEvent* currentEvent); private: - /** - * The scrollbar with the icons - */ - xoj::util::WidgetSPtr scrollPreview; - /** * The Zoom of the previews */ double zoom = 0.15; + /// last recorded width of the sidebar + double lastWidth = -1; + /** * For preview rendering */ std::unique_ptr cache; +protected: + /// The scrollable area with the miniatures + xoj::util::WidgetSPtr scrollableBox; + + /// Main box, containing the scrollable area and the toolbar. + xoj::util::WidgetSPtr mainBox; + + /// The widget within the scrollable area with the page miniatures + xoj::util::GObjectSPtr miniaturesContainer; + /** - * The layouting class for the prviews + * The context menu to display when a miniature is right-clicked. + * This must be populated by the derived classes constructors. + * It must be a GtkPopover parented (gtk_widget_set_parent()) by this->miniaturesContainer */ - SidebarLayout* layoutmanager = nullptr; + xoj::util::GObjectSPtr contextMenu; - - // Members also used by subclasses -protected: /** * The currently selected entry in the sidebar, starting from 0 * -1 means no valid selection */ size_t selectedEntry = npos; - /** - * The widget within the scrollarea with the page icons - */ - xoj::util::WidgetSPtr iconViewPreview; - /** * The previews */ diff --git a/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.cpp b/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.cpp index 6f9d5a60781e..aef92f90f2e8 100644 --- a/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.cpp +++ b/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.cpp @@ -1,9 +1,8 @@ #include "SidebarPreviewBaseEntry.h" -#include // for __shared_ptr_access - #include // for GdkEvent, GDK_BUTTON_PRESS #include // for G_CALLBACK, g_object_ref +#include // #include "control/Control.h" // for Control #include "control/jobs/XournalScheduler.h" // for XournalScheduler @@ -11,53 +10,43 @@ #include "gui/Shadow.h" // for Shadow #include "model/XojPage.h" // for XojPage #include "util/Color.h" // for cairo_set_source_rgbi +#include "util/gtk4_helper.h" // #include "util/i18n.h" // for _ -#include "util/safe_casts.h" // for ceil_cast, round_cast +#include "util/safe_casts.h" // for floor_cast #include "SidebarPreviewBase.h" // for SidebarPreviewBase SidebarPreviewBaseEntry::SidebarPreviewBaseEntry(SidebarPreviewBase* sidebar, const PageRef& page): - sidebar(sidebar), page(page) { - this->widget = GTK_WIDGET(g_object_ref_sink(gtk_button_new())); - gtk_widget_show(this->widget); + sidebar(sidebar), page(page), button(gtk_button_new(), xoj::util::adopt) { updateSize(); - gtk_widget_set_events(widget, GDK_EXPOSURE_MASK); + gtk_widget_set_events(this->button.get(), GDK_EXPOSURE_MASK); - g_signal_connect(this->widget, "draw", G_CALLBACK(drawCallback), this); + g_signal_connect(this->button.get(), "draw", G_CALLBACK(drawCallback), this); - g_signal_connect(this->widget, "clicked", G_CALLBACK(+[](GtkWidget* widget, SidebarPreviewBaseEntry* self) { - self->mouseButtonPressCallback(); + g_signal_connect(this->button.get(), "clicked", G_CALLBACK(+[](GtkButton*, gpointer self) { + static_cast(self)->mouseButtonPressCallback(); return true; }), this); - const auto clickCallback = G_CALLBACK(+[](GtkWidget* widget, GdkEvent* event, SidebarPreviewBaseEntry* self) { + const auto clickCallback = G_CALLBACK(+[](GtkWidget*, GdkEvent* event, SidebarPreviewBaseEntry* self) { // Open context menu on right mouse click if (event->type == GDK_BUTTON_PRESS) { auto mouseEvent = reinterpret_cast(event); if (mouseEvent->button == 3) { self->mouseButtonPressCallback(); - self->sidebar->openPreviewContextMenu(); + self->sidebar->openPreviewContextMenu(event); return true; } } return false; }); - g_signal_connect_after(this->widget, "button-press-event", clickCallback, this); + g_signal_connect_after(this->button.get(), "button-press-event", clickCallback, this); } SidebarPreviewBaseEntry::~SidebarPreviewBaseEntry() { this->sidebar->getControl()->getScheduler()->removeSidebar(this); - this->page = nullptr; - - gtk_widget_destroy(this->widget); - this->widget = nullptr; - - if (this->crBuffer) { - cairo_surface_destroy(this->crBuffer); - this->crBuffer = nullptr; - } } auto SidebarPreviewBaseEntry::drawCallback(GtkWidget* widget, cairo_t* cr, SidebarPreviewBaseEntry* preview) @@ -72,20 +61,17 @@ void SidebarPreviewBaseEntry::setSelected(bool selected) { } this->selected = selected; - gtk_widget_queue_draw(this->widget); + gtk_widget_queue_draw(this->button.get()); } void SidebarPreviewBaseEntry::repaint() { sidebar->getControl()->getScheduler()->addRepaintSidebar(this); } void SidebarPreviewBaseEntry::drawLoadingPage() { - GtkAllocation alloc; - gtk_widget_get_allocation(widget, &alloc); - - this->crBuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, alloc.width, alloc.height); + this->buffer.reset(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageWidth, imageHeight), xoj::util::adopt); double zoom = sidebar->getZoom(); - cairo_t* cr2 = cairo_create(this->crBuffer); + cairo_t* cr2 = cairo_create(this->buffer.get()); cairo_matrix_t defaultMatrix = {0}; cairo_get_matrix(cr2, &defaultMatrix); @@ -112,14 +98,16 @@ void SidebarPreviewBaseEntry::paint(cairo_t* cr) { this->drawingMutex.lock(); - if (this->crBuffer == nullptr) { + if (!this->buffer) { drawLoadingPage(); doRepaint = true; } - cairo_set_source_surface(cr, this->crBuffer, 0, 0); + cairo_set_source_surface(cr, this->buffer.get(), 0, 0); cairo_paint(cr); + this->drawingMutex.unlock(); + double height = page->getHeight() * sidebar->getZoom(); double width = page->getWidth() * sidebar->getZoom(); @@ -144,29 +132,23 @@ void SidebarPreviewBaseEntry::paint(cairo_t* cr) { round_cast(width), round_cast(height)); } - this->drawingMutex.unlock(); - if (doRepaint) { repaint(); } } void SidebarPreviewBaseEntry::updateSize() { - gtk_widget_set_size_request(this->widget, getWidgetWidth(), getWidgetHeight()); -} - -auto SidebarPreviewBaseEntry::getWidgetWidth() -> int { - return ceil_cast(page->getWidth() * sidebar->getZoom()) + Shadow::getShadowBottomRightSize() + - Shadow::getShadowTopLeftSize() + 4; -} + this->DPIscaling = gtk_widget_get_scale_factor(this->button.get()); -auto SidebarPreviewBaseEntry::getWidgetHeight() -> int { - return ceil_cast(page->getHeight() * sidebar->getZoom()) + Shadow::getShadowBottomRightSize() + - Shadow::getShadowTopLeftSize() + 4; + const int shadowPadding = Shadow::getShadowBottomRightSize() + Shadow::getShadowTopLeftSize() + 4; + // To avoid having a black line, we use floor rather than ceil + this->imageWidth = floor_cast(page->getWidth() * sidebar->getZoom()) + shadowPadding; + this->imageHeight = floor_cast(page->getHeight() * sidebar->getZoom()) + shadowPadding; + gtk_widget_set_size_request(this->button.get(), imageWidth, imageHeight); } -auto SidebarPreviewBaseEntry::getWidth() -> int { return getWidgetWidth(); } +auto SidebarPreviewBaseEntry::getWidget() const -> GtkWidget* { return this->button.get(); } -auto SidebarPreviewBaseEntry::getHeight() -> int { return getWidgetHeight(); } +auto SidebarPreviewBaseEntry::getWidth() const -> int { return imageWidth; } -auto SidebarPreviewBaseEntry::getWidget() -> GtkWidget* { return this->widget; } +auto SidebarPreviewBaseEntry::getHeight() const -> int { return imageHeight; } diff --git a/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.h b/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.h index 6970cac57eea..6f87e537856c 100644 --- a/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.h +++ b/src/core/gui/sidebar/previews/base/SidebarPreviewBaseEntry.h @@ -18,6 +18,8 @@ #include // for GtkWidget #include "model/PageRef.h" // for PageRef +#include "util/raii/CairoWrappers.h" +#include "util/raii/GObjectSPtr.h" class SidebarPreviewBase; @@ -45,9 +47,9 @@ class SidebarPreviewBaseEntry { virtual ~SidebarPreviewBaseEntry(); public: - virtual GtkWidget* getWidget(); - virtual int getWidth(); - virtual int getHeight(); + virtual GtkWidget* getWidget() const; + virtual int getWidth() const; + virtual int getHeight() const; virtual void setSelected(bool selected); @@ -57,7 +59,7 @@ class SidebarPreviewBaseEntry { /** * @return What should be rendered */ - virtual PreviewRenderType getRenderType() = 0; + virtual PreviewRenderType getRenderType() const = 0; private: static gboolean drawCallback(GtkWidget* widget, cairo_t* cr, SidebarPreviewBaseEntry* preview); @@ -65,19 +67,19 @@ class SidebarPreviewBaseEntry { protected: virtual void mouseButtonPressCallback() = 0; - virtual int getWidgetWidth(); - virtual int getWidgetHeight(); - virtual void drawLoadingPage(); virtual void paint(cairo_t* cr); -private: protected: /** * If this page is currently selected */ bool selected = false; + int imageWidth; + int imageHeight; + int DPIscaling; ///< 1, maybe 2 in HiDPI setups + /** * The sidebar which displays the previews */ @@ -88,20 +90,14 @@ class SidebarPreviewBaseEntry { */ PageRef page; - /** - * Mutex - */ + /// Mutex protecting the buffer std::mutex drawingMutex{}; - /** - * The Widget which is used for drawing - */ - GtkWidget* widget; + /// Buffer because of performance reasons + xoj::util::CairoSurfaceSPtr buffer; - /** - * Buffer because of performance reasons - */ - cairo_surface_t* crBuffer = nullptr; + /// The main widget, containing the miniature + xoj::util::WidgetSPtr button; friend class PreviewJob; }; diff --git a/src/core/gui/sidebar/previews/base/SidebarToolbar.cpp b/src/core/gui/sidebar/previews/base/SidebarToolbar.cpp deleted file mode 100644 index 3eaf09a71fab..000000000000 --- a/src/core/gui/sidebar/previews/base/SidebarToolbar.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "SidebarToolbar.h" - -#include // for G_CALLBACK, g_signal_connect - -#include "gui/GladeGui.h" // for GladeGui - -SidebarToolbar::SidebarToolbar(SidebarToolbarActionListener* listener, GladeGui* gui): listener(listener) { - this->btUp = GTK_BUTTON(gui->get("btUp")); - this->btDown = GTK_BUTTON(gui->get("btDown")); - this->btCopy = GTK_BUTTON(gui->get("btCopy")); - this->btDelete = GTK_BUTTON(gui->get("btDelete")); - - g_signal_connect(this->btUp, "clicked", G_CALLBACK(&btUpClicked), this); - g_signal_connect(this->btDown, "clicked", G_CALLBACK(&btDownClicked), this); - g_signal_connect(this->btCopy, "clicked", G_CALLBACK(&btCopyClicked), this); - g_signal_connect(this->btDelete, "clicked", G_CALLBACK(&btDeleteClicked), this); -} - -SidebarToolbar::~SidebarToolbar() = default; - -void SidebarToolbar::runAction(SidebarActions actions) { this->listener->actionPerformed(actions); } - -void SidebarToolbar::btUpClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar) { - toolbar->runAction(SIDEBAR_ACTION_MOVE_UP); -} - -void SidebarToolbar::btDownClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar) { - toolbar->runAction(SIDEBAR_ACTION_MOVE_DOWN); -} - -void SidebarToolbar::btCopyClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar) { - toolbar->runAction(SIDEBAR_ACTION_COPY); -} - -void SidebarToolbar::btDeleteClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar) { - toolbar->runAction(SIDEBAR_ACTION_DELETE); -} - -void SidebarToolbar::setHidden(bool hidden) { - gtk_widget_set_visible(GTK_WIDGET(this->btUp), !hidden); - gtk_widget_set_visible(GTK_WIDGET(this->btDown), !hidden); - gtk_widget_set_visible(GTK_WIDGET(this->btCopy), !hidden); - gtk_widget_set_visible(GTK_WIDGET(this->btDelete), !hidden); -} - -void SidebarToolbar::setButtonEnabled(SidebarActions enabledActions) { - gtk_widget_set_sensitive(GTK_WIDGET(this->btUp), enabledActions & SIDEBAR_ACTION_MOVE_UP); - gtk_widget_set_sensitive(GTK_WIDGET(this->btDown), enabledActions & SIDEBAR_ACTION_MOVE_DOWN); - gtk_widget_set_sensitive(GTK_WIDGET(this->btCopy), enabledActions & SIDEBAR_ACTION_COPY); - gtk_widget_set_sensitive(GTK_WIDGET(this->btDelete), enabledActions & SIDEBAR_ACTION_DELETE); -} - -void SidebarToolbar::setButtonTooltips(const std::string& tipUp, const std::string& tipDown, const std::string& tipCopy, - const std::string& tipDelete) { - gtk_widget_set_tooltip_text(GTK_WIDGET(this->btUp), tipUp.c_str()); - gtk_widget_set_tooltip_text(GTK_WIDGET(this->btDown), tipDown.c_str()); - gtk_widget_set_tooltip_text(GTK_WIDGET(this->btCopy), tipCopy.c_str()); - gtk_widget_set_tooltip_text(GTK_WIDGET(this->btDelete), tipDelete.c_str()); -} - -SidebarToolbarActionListener::~SidebarToolbarActionListener() = default; - -void SidebarToolbarActionListener::actionPerformed(SidebarActions action) {} diff --git a/src/core/gui/sidebar/previews/base/SidebarToolbar.h b/src/core/gui/sidebar/previews/base/SidebarToolbar.h deleted file mode 100644 index 77f6c015f58f..000000000000 --- a/src/core/gui/sidebar/previews/base/SidebarToolbar.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Xournal++ - * - * Sidebar preview layout - * - * @author Xournal++ Team - * https://github.com/xournalpp/xournalpp - * - * @license GNU GPLv2 or later - */ - -#pragma once - -#include // for string - -#include // for GtkButton, GtkToolButton - -class GladeGui; - - -enum SidebarActions { - SIDEBAR_ACTION_NONE = 0, - SIDEBAR_ACTION_MOVE_UP = 1 << 0, - SIDEBAR_ACTION_MOVE_DOWN = 1 << 1, - SIDEBAR_ACTION_COPY = 1 << 2, - SIDEBAR_ACTION_DELETE = 1 << 3, - SIDEBAR_ACTION_NEW_BEFORE = 1 << 4, - SIDEBAR_ACTION_NEW_AFTER = 1 << 5, - SIDEBAR_ACTION_MERGE_DOWN = 1 << 6, -}; - -class SidebarToolbarActionListener { -public: - virtual ~SidebarToolbarActionListener(); - - /** - * Called when an action is performed - */ - virtual void actionPerformed(SidebarActions action); -}; - -class SidebarToolbar { -public: - SidebarToolbar(SidebarToolbarActionListener* listener, GladeGui* gui); - virtual ~SidebarToolbar(); - -public: - /** - * Sets the button enabled / disabled - */ - void setButtonEnabled(SidebarActions enabledActions); - void setButtonTooltips(const std::string& tipUp, const std::string& tipDown, const std::string& tipCopy, - const std::string& tipDelete); - - void setHidden(bool hidden); - - /** - * Runs the actions directly. - */ - void runAction(SidebarActions actions); - -private: -private: - static void btUpClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar); - static void btDownClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar); - static void btCopyClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar); - static void btDeleteClicked(GtkToolButton* toolbutton, SidebarToolbar* toolbar); - -private: - /** - * Listener for actions - */ - SidebarToolbarActionListener* listener; - - /** - * Button move Page up - */ - GtkButton* btUp; - - /** - * Button move Page down - */ - GtkButton* btDown; - - /** - * Button copy current page - */ - GtkButton* btCopy; - - /** - * Button delete page - */ - GtkButton* btDelete; -}; diff --git a/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.cpp b/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.cpp deleted file mode 100644 index e884f66c3f88..000000000000 --- a/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "gui/sidebar/previews/layer/SidebarLayersContextMenu.h" - -#include // for map, operator!=, _Rb_tree_const_iterator -#include // for unique_ptr, allocator, make_unique -#include // for string, operator==, basic_string, operator< -#include // for tuple -#include // for tuple_element<>::type, move -#include // for vector - -#include // for g_object_ref, G_CALLBACK, g_signal_connect -#include // for gulong - -#include "gui/GladeGui.h" // for GladeGui -#include "util/Assert.h" // for xoj_assert - -SidebarLayersContextMenu::SidebarLayersContextMenu(GladeGui* gui, SidebarToolbar* toolbar): - SidebarBaseContextMenu(gui->get("sidebarPreviewLayersContextMenu")) { - - - constexpr const char* sidebarPreviewLayerMoveUp = "sidebarPreviewLayerMoveUp"; - constexpr const char* sidebarPreviewLayerMoveDown = "sidebarPreviewLayerMoveDown"; - constexpr const char* sidebarPreviewMergeDown = "sidebarPreviewMergeDown"; - constexpr const char* sidebarPreviewLayerDuplicate = "sidebarPreviewLayerDuplicate"; - constexpr const char* sidebarPreviewLayerDelete = "sidebarPreviewLayerDelete"; - - // Connect the context menu actions - const std::map ctxMenuActions = { - {sidebarPreviewLayerMoveUp, SIDEBAR_ACTION_MOVE_UP}, - {sidebarPreviewLayerMoveDown, SIDEBAR_ACTION_MOVE_DOWN}, - {sidebarPreviewMergeDown, SIDEBAR_ACTION_MERGE_DOWN}, - {sidebarPreviewLayerDuplicate, SIDEBAR_ACTION_COPY}, - {sidebarPreviewLayerDelete, SIDEBAR_ACTION_DELETE}, - // TODO(ja-he): add SIDEBAR_ACTION_NEW_BEFORE/AFTER if/when these are defined for layers - }; - - - for (const auto& [name, action]: ctxMenuActions) { - - GtkWidget* const entry = gui->get(name); - xoj_assert(entry != nullptr); - - // Create callbacks, store the entry - using Data = ContextMenuData; - auto userdata = std::make_unique(Data{toolbar, action}); - - const auto callback = - G_CALLBACK(+[](GtkMenuItem* item, Data* data) { data->toolbar->runAction(data->actions); }); - const gulong signalId = g_signal_connect(entry, "activate", callback, userdata.get()); - g_object_ref(entry); - this->contextMenuSignals.emplace_back(entry, signalId, std::move(userdata)); - - if (name == sidebarPreviewLayerMoveUp) { - this->contextMenuMoveUp = entry; - } else if (name == sidebarPreviewLayerMoveDown) { - this->contextMenuMoveDown = entry; - } else if (name == sidebarPreviewMergeDown) { - this->contextMenuMergeDown = entry; - } else if (name == sidebarPreviewLayerDuplicate) { - this->contextMenuDuplicate = entry; - } else if (name == sidebarPreviewLayerDelete) { - this->contextMenuDelete = entry; - } - } -} - -void SidebarLayersContextMenu::setActionsSensitivity(SidebarActions actions) { - gtk_widget_set_sensitive(this->contextMenuMoveUp, actions & SIDEBAR_ACTION_MOVE_UP); - gtk_widget_set_sensitive(this->contextMenuMoveDown, actions & SIDEBAR_ACTION_MOVE_DOWN); - gtk_widget_set_sensitive(this->contextMenuMergeDown, actions & SIDEBAR_ACTION_MERGE_DOWN); - gtk_widget_set_sensitive(this->contextMenuDuplicate, actions & SIDEBAR_ACTION_COPY); - gtk_widget_set_sensitive(this->contextMenuDelete, actions & SIDEBAR_ACTION_DELETE); -} diff --git a/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.h b/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.h deleted file mode 100644 index 125b3dc83cf5..000000000000 --- a/src/core/gui/sidebar/previews/layer/SidebarLayersContextMenu.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Xournal++ - * - * Context menu layers sidebars - * - * @author Xournal++ Team - * https://github.com/xournalpp/xournalpp - * - * @license GNU GPLv2 or later - */ - -#pragma once - -#include // for GtkWidget - -#include "gui/sidebar/previews/base/SidebarBaseContextMenu.h" // for Sideba... -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for Sideba... - -class GladeGui; - -class SidebarLayersContextMenu: public SidebarBaseContextMenu { -public: - SidebarLayersContextMenu(GladeGui* gui, SidebarToolbar* toolbar); - - void setActionsSensitivity(SidebarActions actions) override; - -private: - GtkWidget* contextMenuMoveUp = nullptr; - GtkWidget* contextMenuMoveDown = nullptr; - GtkWidget* contextMenuMergeDown = nullptr; - GtkWidget* contextMenuDuplicate = nullptr; - GtkWidget* contextMenuDelete = nullptr; -}; diff --git a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.cpp b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.cpp index f93d165801b7..d7ee069cff58 100644 --- a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.cpp +++ b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.cpp @@ -4,94 +4,69 @@ #include // for G_CALLBACK, g_signal_connect, g_si... #include "gui/Shadow.h" // for Shadow +#include "util/gtk4_helper.h" #include "SidebarPreviewLayers.h" // for SidebarPreviewLayers SidebarPreviewLayerEntry::SidebarPreviewLayerEntry(SidebarPreviewLayers* sidebar, const PageRef& page, - Layer::Index layerId, const std::string& layerName, size_t index, - bool stacked): + Layer::Index layerId, const std::string& layerName, bool stacked): SidebarPreviewBaseEntry(sidebar, page), sidebar(sidebar), - index(index), layerId(layerId), - box(GTK_WIDGET(g_object_ref_sink(gtk_box_new(GTK_ORIENTATION_VERTICAL, 2)))), + box(gtk_box_new(GTK_ORIENTATION_VERTICAL, 4), xoj::util::adopt), stacked(stacked) { - const auto clickCallback = G_CALLBACK(+[](GtkWidget* widget, GdkEvent* event, SidebarPreviewLayerEntry* self) { - // Open context menu on right mouse click - if (event->type == GDK_BUTTON_PRESS) { - auto mouseEvent = reinterpret_cast(event); - if (mouseEvent->button == 3) { - self->mouseButtonPressCallback(); - self->sidebar->openPreviewContextMenu(); - return true; - } - } - return false; - }); - g_signal_connect_after(this->widget, "button-press-event", clickCallback, this); - - GtkWidget* toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); +#if GTK_CHECK_VERSION(4, 8, 0) + cbVisible = gtk_check_button_new(); + GtkWidget* lbl = gtk_label_new(layerName.c_str()); + gtk_label_set_ellipsize(GTK_LABEL(lbl), PANGO_ELLIPSIZE_END); + gtk_check_button_set_child(GTK_CHECK_BUTTON(cbVisible), lbl); +#else cbVisible = gtk_check_button_new_with_label(layerName.c_str()); - - g_signal_connect(cbVisible, "toggled", G_CALLBACK(+[](GtkToggleButton* source, SidebarPreviewLayerEntry* self) { - self->checkboxToggled(); - }), - this); - - - // Left padding +#endif + + callbackId = g_signal_connect( + cbVisible, "toggled", G_CALLBACK(+[](GtkCheckButton* btn, gpointer d) { + auto* self = static_cast(d); + bool check = gtk_check_button_get_active(btn); + (dynamic_cast(self->sidebar))->layerVisibilityChanged(self->layerId, check); + }), + this); gtk_widget_set_margin_start(cbVisible, Shadow::getShadowTopLeftSize()); - gtk_container_add(GTK_CONTAINER(toolbar), cbVisible); - - gtk_widget_set_vexpand(widget, false); - gtk_container_add(GTK_CONTAINER(box), widget); - - gtk_widget_set_vexpand(toolbar, false); - gtk_container_add(GTK_CONTAINER(box), toolbar); - - gtk_widget_show_all(box); - + // This doesn't really do it. gtk_widget_get_allocated_height() always returns 1... toolbarHeight = gtk_widget_get_allocated_height(cbVisible) + Shadow::getShadowTopLeftSize() + 20; -} + gtk_box_append(GTK_BOX(box.get()), this->button.get()); + gtk_box_append(GTK_BOX(box.get()), cbVisible); -SidebarPreviewLayerEntry::~SidebarPreviewLayerEntry() { - gtk_widget_destroy(this->box); - this->box = nullptr; + gtk_widget_show_all(box.get()); } -void SidebarPreviewLayerEntry::checkboxToggled() { - if (inUpdate) { - return; - } - - bool check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cbVisible)); - (dynamic_cast(sidebar))->layerVisibilityChanged(layerId, check); +SidebarPreviewLayerEntry::~SidebarPreviewLayerEntry() { + GtkWidget* w = this->getWidget(); + gtk_fixed_remove(GTK_FIXED(gtk_widget_get_parent(w)), w); } void SidebarPreviewLayerEntry::mouseButtonPressCallback() { - (dynamic_cast(sidebar))->layerSelected(index); + (dynamic_cast(sidebar))->layerSelected(layerId); } -auto SidebarPreviewLayerEntry::getRenderType() -> PreviewRenderType { +auto SidebarPreviewLayerEntry::getRenderType() const -> PreviewRenderType { return stacked ? RENDER_TYPE_PAGE_LAYERSTACK : RENDER_TYPE_PAGE_LAYER; } -auto SidebarPreviewLayerEntry::getHeight() -> int { return getWidgetHeight() + toolbarHeight; } +auto SidebarPreviewLayerEntry::getHeight() const -> int { return imageHeight + toolbarHeight; } auto SidebarPreviewLayerEntry::getLayer() const -> Layer::Index { return layerId; } -auto SidebarPreviewLayerEntry::getWidget() -> GtkWidget* { return this->box; } +auto SidebarPreviewLayerEntry::getWidget() const -> GtkWidget* { return this->box.get(); } /** * Set the value of the visible checkbox */ void SidebarPreviewLayerEntry::setVisibleCheckbox(bool enabled) { - inUpdate = true; - - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cbVisible), enabled); - - inUpdate = false; + g_signal_handler_block(cbVisible, callbackId); + gtk_check_button_set_active(GTK_CHECK_BUTTON(cbVisible), enabled); + g_signal_handler_unblock(cbVisible, callbackId); } diff --git a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.h b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.h index 5288dde71a1b..3a8ec3afad88 100644 --- a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.h +++ b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayerEntry.h @@ -19,30 +19,31 @@ #include "gui/sidebar/previews/base/SidebarPreviewBaseEntry.h" // for Previ... #include "model/Layer.h" // for Layer #include "model/PageRef.h" // for PageRef +#include "util/raii/GObjectSPtr.h" class SidebarPreviewLayers; class SidebarPreviewLayerEntry: public SidebarPreviewBaseEntry { public: SidebarPreviewLayerEntry(SidebarPreviewLayers* sidebar, const PageRef& page, Layer::Index layerId, - const std::string& layerName, size_t index, bool stacked); + const std::string& layerName, bool stacked); ~SidebarPreviewLayerEntry() override; public: - int getHeight() override; + int getHeight() const override; /** * @return What should be rendered * @override */ - PreviewRenderType getRenderType() override; + PreviewRenderType getRenderType() const override; /** * @return The layer to be rendered */ Layer::Index getLayer() const; - GtkWidget* getWidget() override; + GtkWidget* getWidget() const override; /** * Set the value of the visible checkbox @@ -56,14 +57,7 @@ class SidebarPreviewLayerEntry: public SidebarPreviewBaseEntry { SidebarPreviewLayers* sidebar; private: - /** - * Layer preview index - */ - size_t index; - - /** - * Layer to render - */ + /// Layer to render Layer::Index layerId; /** @@ -71,24 +65,14 @@ class SidebarPreviewLayerEntry: public SidebarPreviewBaseEntry { */ int toolbarHeight = 0; - /** - * Container box for the preview and the button - */ - GtkWidget* box; + /// Container box for the preview and the button + xoj::util::WidgetSPtr box; - /** - * Visible checkbox - */ + /// Visibility checkbox GtkWidget* cbVisible = nullptr; + gulong callbackId = 0; - /** - * Ignore events - */ - bool inUpdate = false; - - /** - * render as stacked - */ + /// render as stacked bool stacked = false; friend class PreviewJob; diff --git a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.cpp b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.cpp index 084696ea6654..53535ceff177 100644 --- a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.cpp +++ b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.cpp @@ -5,73 +5,32 @@ #include // for gtk... -#include "control/Control.h" // for Con... -#include "control/layer/LayerController.h" // for Lay... -#include "gui/sidebar/previews/base/SidebarPreviewBaseEntry.h" // for Sid... -#include "gui/sidebar/previews/layer/SidebarLayersContextMenu.h" // for Sid... -#include "model/PageRef.h" // for Pag... -#include "model/XojPage.h" // for Xoj... -#include "util/Util.h" // for npos -#include "util/i18n.h" // for _ +#include "control/Control.h" // for Con... +#include "control/layer/LayerController.h" // for Lay... +#include "gui/sidebar/previews/base/SidebarPreviewBaseEntry.h" // for Sid... +#include "model/PageRef.h" // for Pag... +#include "model/XojPage.h" // for Xoj... +#include "util/Util.h" // for npos +#include "util/gtk4_helper.h" +#include "util/i18n.h" // for _ #include "SidebarPreviewLayerEntry.h" // for Sid... -class GladeGui; +constexpr auto MENU_ID = "PreviewLayersContextMenu"; +constexpr auto TOOLBAR_ID = "PreviewLayersToolbar"; -SidebarPreviewLayers::SidebarPreviewLayers(Control* control, GladeGui* gui, SidebarToolbar* toolbar, bool stacked, - std::shared_ptr contextMenu): - SidebarPreviewBase(control, gui, toolbar), +SidebarPreviewLayers::SidebarPreviewLayers(Control* control, bool stacked): + SidebarPreviewBase(control, MENU_ID, TOOLBAR_ID), lc(control->getLayerController()), stacked(stacked), - iconNameHelper(control->getSettings()), - contextMenu(contextMenu) { + iconNameHelper(control->getSettings()) { LayerCtrlListener::registerListener(lc); - - this->toolbar->setButtonEnabled(SIDEBAR_ACTION_NONE); - - // initialize the context menu action sensitivity - Layer::Index layerIndex = this->lc->getLayerCount() - this->lc->getCurrentLayerId(); - SidebarActions actions = SidebarPreviewLayers::getViableActions(layerIndex, this->lc->getLayerCount()); - this->contextMenu->setActionsSensitivity(actions); } SidebarPreviewLayers::~SidebarPreviewLayers() = default; -/** - * Called when an action is performed - */ -void SidebarPreviewLayers::actionPerformed(SidebarActions action) { - switch (action) { - case SIDEBAR_ACTION_MOVE_UP: { - control->getLayerController()->moveCurrentLayer(true); - break; - } - case SIDEBAR_ACTION_MOVE_DOWN: { - control->getLayerController()->moveCurrentLayer(false); - break; - } - case SIDEBAR_ACTION_COPY: { - control->getLayerController()->copyCurrentLayer(); - break; - } - case SIDEBAR_ACTION_DELETE: - control->getLayerController()->deleteCurrentLayer(); - break; - case SIDEBAR_ACTION_MERGE_DOWN: { - control->getLayerController()->mergeCurrentLayerDown(); - break; - } - default: - break; - } -} - void SidebarPreviewLayers::enableSidebar() { SidebarPreviewBase::enableSidebar(); - - this->toolbar->setButtonTooltips(_("Swap the current layer with the one above"), - _("Swap the current layer with the one below"), - _("Insert a copy of the current layer below"), _("Delete this layer")); rebuildLayerMenu(); } @@ -115,12 +74,11 @@ void SidebarPreviewLayers::updatePreviews() { auto layerCount = page->getLayerCount(); - size_t index = 0; for (auto i = layerCount + 1; i != 0;) { --i; std::string name = lc->getLayerNameById(i); - auto p = std::make_unique(this, page, i, name, index++, this->stacked); - gtk_layout_put(GTK_LAYOUT(this->iconViewPreview.get()), p->getWidget(), 0, 0); + auto p = std::make_unique(this, page, i, name, this->stacked); + gtk_fixed_put(this->miniaturesContainer.get(), p->getWidget(), 0, 0); this->previews.emplace_back(std::move(p)); } @@ -168,81 +126,13 @@ void SidebarPreviewLayers::updateSelectedLayer() { p->setSelected(true); scrollToPreview(this); } - - int actions = 0; - // Background and top layer cannot be moved up - if (this->selectedEntry < (this->previews.size() - 1) && this->selectedEntry > 0) { - actions |= SIDEBAR_ACTION_MOVE_UP; - } - - // Background and first layer cannot be moved down - if (this->selectedEntry < (this->previews.size() - 2)) { - actions |= SIDEBAR_ACTION_MOVE_DOWN; - } - - // Background cannot be copied - if (this->selectedEntry < (this->previews.size() - 1)) { - actions |= SIDEBAR_ACTION_COPY; - } - - // Background cannot be deleted - if (this->selectedEntry < (this->previews.size() - 1)) { - actions |= SIDEBAR_ACTION_DELETE; - } - - this->toolbar->setHidden(false); - this->toolbar->setButtonEnabled(static_cast(actions)); } -void SidebarPreviewLayers::layerSelected(Layer::Index layerIndex) { - // Layers are in reverse order (top index: 0, but bottom preview is 0) - lc->switchToLay(lc->getLayerCount() - layerIndex); - updateSelectedLayer(); - - auto actions = SidebarPreviewLayers::getViableActions(layerIndex, this->lc->getLayerCount()); - this->contextMenu->setActionsSensitivity(SidebarActions(actions)); -} - -auto SidebarPreviewLayers::getViableActions(Layer::Index layerIndex, Layer::Index layerCount) -> SidebarActions { - /* - * If we have no layers (in which case in the UI an empty background layer - * is still shown) - */ - if (layerCount == 0) { - return SIDEBAR_ACTION_NONE; - } - - /* - * Assuming that we have at least one layer the highest index is the - * background, the second-highest is the bottom-most actual layer and the - * lowest index (i.E. 0) is the topmost layer. - */ - const auto bgIndex = layerCount; - const auto bottomLayerIndex = bgIndex - 1; - - int actions = 0; - - // if we are above the bottom layer, we can merge down - if (layerIndex < bottomLayerIndex) { - actions |= SIDEBAR_ACTION_MERGE_DOWN; +void SidebarPreviewLayers::layerSelected(Layer::Index layerId) { + if (layerId != 0) { // We can't select the background + lc->switchToLay(layerId); + updateSelectedLayer(); } - - // if we are above the bottom layer, we can move down - if (layerIndex < bottomLayerIndex) { - actions |= SIDEBAR_ACTION_MOVE_DOWN; - } - - if (layerIndex > 0 && layerIndex != bgIndex) { - actions |= SIDEBAR_ACTION_MOVE_UP; - } - - // if we are not on the background layer, we can duplicate (aka copy) and delete - if (layerIndex < bgIndex) { - actions |= SIDEBAR_ACTION_COPY; - actions |= SIDEBAR_ACTION_DELETE; - } - - return SidebarActions(actions); } /** @@ -251,5 +141,3 @@ auto SidebarPreviewLayers::getViableActions(Layer::Index layerIndex, Layer::Inde void SidebarPreviewLayers::layerVisibilityChanged(Layer::Index layerIndex, bool enabled) { lc->setLayerVisible(layerIndex, enabled); } - -void SidebarPreviewLayers::openPreviewContextMenu() { this->contextMenu->open(); } diff --git a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.h b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.h index c05b47e15bec..4beb43948f48 100644 --- a/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.h +++ b/src/core/gui/sidebar/previews/layer/SidebarPreviewLayers.h @@ -18,7 +18,6 @@ #include "control/layer/LayerCtrlListener.h" // for LayerCtrlL... #include "gui/IconNameHelper.h" // for IconNameHe... #include "gui/sidebar/previews/base/SidebarPreviewBase.h" // for SidebarPre... -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarAct... #include "model/Layer.h" // for Layer, Lay... class Control; @@ -29,8 +28,7 @@ class SidebarLayersContextMenu; class SidebarPreviewLayers: public SidebarPreviewBase, public LayerCtrlListener { public: - SidebarPreviewLayers(Control* control, GladeGui* gui, SidebarToolbar* toolbar, bool stacked, - std::shared_ptr contextMenu); + SidebarPreviewLayers(Control* control, bool stacked); ~SidebarPreviewLayers() override; @@ -40,11 +38,6 @@ class SidebarPreviewLayers: public SidebarPreviewBase, public LayerCtrlListener void updateSelectedLayer() override; public: - /** - * Called when an action is performed - */ - void actionPerformed(SidebarActions action) override; - void enableSidebar() override; /** @@ -73,23 +66,12 @@ class SidebarPreviewLayers: public SidebarPreviewBase, public LayerCtrlListener */ void layerVisibilityChanged(Layer::Index layerIndex, bool enabled); - /** - * Opens the layer preview context menu, at the current cursor position, for - * the given layer. - */ - void openPreviewContextMenu() override; - public: // DocumentListener interface (only the part which is not handled by SidebarPreviewBase) void pageSizeChanged(size_t page) override; void pageChanged(size_t page) override; private: - /** - * @return things that can reasonably be done to a given layer (e.g. merge down, copy, delete, etc.) - */ - [[nodiscard]] static auto getViableActions(Layer::Index layerIndex, Layer::Index layerCount) -> SidebarActions; - /** * Layer Controller */ @@ -101,6 +83,4 @@ class SidebarPreviewLayers: public SidebarPreviewBase, public LayerCtrlListener bool stacked; IconNameHelper iconNameHelper; - - std::shared_ptr contextMenu; }; diff --git a/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.cpp b/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.cpp index 10d9165b1f0a..38a1a99f6291 100644 --- a/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.cpp +++ b/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.cpp @@ -5,13 +5,22 @@ #include "control/settings/Settings.h" // for Settings #include "gui/PagePreviewDecoration.h" // for Drawing ... #include "gui/sidebar/previews/page/SidebarPreviewPages.h" // for SidebarPr... +#include "util/gtk4_helper.h" SidebarPreviewPageEntry::SidebarPreviewPageEntry(SidebarPreviewPages* sidebar, const PageRef& page, size_t index): - SidebarPreviewBaseEntry(sidebar, page), sidebar(sidebar), index(index) {} + SidebarPreviewBaseEntry(sidebar, page), sidebar(sidebar), index(index) { + if (sidebar->getControl()->getSettings()->getSidebarNumberingStyle() == + SidebarNumberingStyle::NUMBER_BELOW_PREVIEW) { + gtk_widget_set_size_request(this->button.get(), imageWidth, imageHeight + PagePreviewDecoration::MARGIN_BOTTOM); + } +} -SidebarPreviewPageEntry::~SidebarPreviewPageEntry() = default; +SidebarPreviewPageEntry::~SidebarPreviewPageEntry() { + GtkWidget* w = this->getWidget(); + gtk_fixed_remove(GTK_FIXED(gtk_widget_get_parent(w)), w); +} -auto SidebarPreviewPageEntry::getRenderType() -> PreviewRenderType { return RENDER_TYPE_PAGE_PREVIEW; } +auto SidebarPreviewPageEntry::getRenderType() const -> PreviewRenderType { return RENDER_TYPE_PAGE_PREVIEW; } void SidebarPreviewPageEntry::mouseButtonPressCallback() { sidebar->getControl()->getScrollHandler()->scrollToPage(page); @@ -23,25 +32,19 @@ void SidebarPreviewPageEntry::paint(cairo_t* cr) { if (sidebar->getControl()->getSettings()->getSidebarNumberingStyle() == SidebarNumberingStyle::NONE) { return; } - if (sidebar->getControl()->getSettings()->getSidebarNumberingStyle() == - SidebarNumberingStyle::NUMBER_BELOW_PREVIEW) { - gtk_widget_set_size_request(this->widget, getWidgetWidth(), getWidgetHeight()); - } drawEntryNumber(cr); } void SidebarPreviewPageEntry::drawEntryNumber(cairo_t* cr) { - this->drawingMutex.lock(); PagePreviewDecoration::drawDecoration(cr, this, this->sidebar->getControl()); - this->drawingMutex.unlock(); } -int SidebarPreviewPageEntry::getWidgetHeight() { +auto SidebarPreviewPageEntry::getHeight() const -> int { if (sidebar->getControl()->getSettings()->getSidebarNumberingStyle() == SidebarNumberingStyle::NUMBER_BELOW_PREVIEW) { - return SidebarPreviewBaseEntry::getWidgetHeight() + PagePreviewDecoration::MARGIN_BOTTOM; + return imageHeight + PagePreviewDecoration::MARGIN_BOTTOM; } - return SidebarPreviewBaseEntry::getWidgetHeight(); + return imageHeight; } void SidebarPreviewPageEntry::setIndex(size_t index) { this->index = index; } @@ -50,4 +53,4 @@ size_t SidebarPreviewPageEntry::getIndex() const { return this->index; } bool SidebarPreviewPageEntry::isSelected() const { return this->selected; } -double SidebarPreviewPageEntry::getZoom() const { return this->sidebar->getZoom(); } \ No newline at end of file +double SidebarPreviewPageEntry::getZoom() const { return this->sidebar->getZoom(); } diff --git a/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.h b/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.h index 2196b0cb076b..fc5ff99ae415 100644 --- a/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.h +++ b/src/core/gui/sidebar/previews/page/SidebarPreviewPageEntry.h @@ -22,11 +22,9 @@ class SidebarPreviewPageEntry: public SidebarPreviewBaseEntry { ~SidebarPreviewPageEntry() override; public: - /** - * @return What should be rendered - * @override - */ - PreviewRenderType getRenderType() override; + int getHeight() const override; + + PreviewRenderType getRenderType() const override; void setIndex(size_t index); size_t getIndex() const; @@ -38,7 +36,6 @@ class SidebarPreviewPageEntry: public SidebarPreviewBaseEntry { SidebarPreviewPages* sidebar; void mouseButtonPressCallback() override; void paint(cairo_t* cr) override; - int getWidgetHeight() override; private: size_t index; diff --git a/src/core/gui/sidebar/previews/page/SidebarPreviewPages.cpp b/src/core/gui/sidebar/previews/page/SidebarPreviewPages.cpp index 617c3c65b54f..4f35498307a7 100644 --- a/src/core/gui/sidebar/previews/page/SidebarPreviewPages.cpp +++ b/src/core/gui/sidebar/previews/page/SidebarPreviewPages.cpp @@ -8,78 +8,28 @@ #include // for g_obj... #include "control/Control.h" // for Control -#include "control/ScrollHandler.h" // for Scrol... -#include "gui/GladeGui.h" // for GladeGui #include "gui/sidebar/previews/base/SidebarPreviewBaseEntry.h" // for Sideb... -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for Sideb... #include "model/Document.h" // for Document #include "model/PageRef.h" // for PageRef -#include "model/XojPage.h" // for XojPage -#include "undo/SwapUndoAction.h" // for SwapU... -#include "undo/UndoRedoHandler.h" // for UndoR... #include "util/Assert.h" // for xoj_assert #include "util/Util.h" // for npos -#include "util/i18n.h" // for _ -#include "util/safe_casts.h" // for as_signed +#include "util/gtk4_helper.h" +#include "util/i18n.h" // for _ +#include "util/safe_casts.h" // for as_signed #include "SidebarPreviewPageEntry.h" // for Sideb... -SidebarPreviewPages::SidebarPreviewPages(Control* control, GladeGui* gui, SidebarToolbar* toolbar): - SidebarPreviewBase(control, gui, toolbar), - contextMenu(gui->get("sidebarPreviewContextMenu")), - iconNameHelper(control->getSettings()) { - // Connect the context menu actions - const std::map ctxMenuActions = { - {"sidebarPreviewDuplicate", SIDEBAR_ACTION_COPY}, - {"sidebarPreviewDelete", SIDEBAR_ACTION_DELETE}, - {"sidebarPreviewMoveUp", SIDEBAR_ACTION_MOVE_UP}, - {"sidebarPreviewMoveDown", SIDEBAR_ACTION_MOVE_DOWN}, - {"sidebarPreviewNewBefore", SIDEBAR_ACTION_NEW_BEFORE}, - {"sidebarPreviewNewAfter", SIDEBAR_ACTION_NEW_AFTER}, - }; - - for (const auto& pair: ctxMenuActions) { - GtkWidget* const entry = gui->get(pair.first); - xoj_assert(entry != nullptr); - - // Unfortunately, we need a fairly complicated mechanism to keep track - // of which action we want to execute. - using Data = SidebarPreviewPages::ContextMenuData; - auto userdata = std::make_unique(Data{this->toolbar, pair.second}); - - const auto callback = - G_CALLBACK(+[](GtkMenuItem* item, Data* data) { data->toolbar->runAction(data->actions); }); - const gulong signalId = g_signal_connect(entry, "activate", callback, userdata.get()); - g_object_ref(entry); - this->contextMenuSignals.emplace_back(entry, signalId, std::move(userdata)); - - if (pair.first == "sidebarPreviewMoveDown") { - this->contextMenuMoveDown = entry; - } else if (pair.first == "sidebarPreviewMoveUp") { - this->contextMenuMoveUp = entry; - } - } - xoj_assert(this->contextMenuMoveDown != nullptr); - xoj_assert(this->contextMenuMoveUp != nullptr); -} +constexpr auto MENU_ID = "PreviewPagesContextMenu"; +constexpr auto TOOLBAR_ID = "PreviewPagesToolbar"; -SidebarPreviewPages::~SidebarPreviewPages() { - for (const auto& signalTuple: this->contextMenuSignals) { - GtkWidget* const widget = std::get<0>(signalTuple); - const gulong handlerId = std::get<1>(signalTuple); - if (g_signal_handler_is_connected(widget, handlerId)) { - g_signal_handler_disconnect(widget, handlerId); - } - g_object_unref(widget); - } -} +SidebarPreviewPages::SidebarPreviewPages(Control* control): + SidebarPreviewBase(control, MENU_ID, TOOLBAR_ID), iconNameHelper(control->getSettings()) {} + +SidebarPreviewPages::~SidebarPreviewPages() = default; void SidebarPreviewPages::enableSidebar() { SidebarPreviewBase::enableSidebar(); - this->toolbar->setButtonTooltips(_("Swap the current page with the one above"), - _("Swap the current page with the one below"), - _("Insert a copy of the current page below"), _("Delete this page")); pageSelected(this->selectedEntry); } @@ -87,95 +37,6 @@ auto SidebarPreviewPages::getName() -> std::string { return _("Page Preview"); } auto SidebarPreviewPages::getIconName() -> std::string { return this->iconNameHelper.iconName("sidebar-page-preview"); } -/** - * Called when an action is performed - */ -void SidebarPreviewPages::actionPerformed(SidebarActions action) { - switch (action) { - case SIDEBAR_ACTION_MOVE_UP: { - Document* doc = control->getDocument(); - PageRef swappedPage = control->getCurrentPage(); - if (!swappedPage || doc->getPageCount() <= 1) { - return; - } - - doc->lock(); - size_t page = doc->indexOf(swappedPage); - PageRef otherPage = doc->getPage(page - 1); - - if (!otherPage) { - doc->unlock(); - return; - } - - if (page != npos) { - doc->deletePage(page); - doc->insertPage(swappedPage, page - 1); - } - doc->unlock(); - - UndoRedoHandler* undo = control->getUndoRedoHandler(); - undo->addUndoAction(std::make_unique(page - 1, true, swappedPage, otherPage)); - - control->firePageDeleted(page); - control->firePageInserted(page - 1); - control->firePageSelected(page - 1); - - control->getScrollHandler()->scrollToPage(page - 1); - break; - } - case SIDEBAR_ACTION_MOVE_DOWN: { - Document* doc = control->getDocument(); - PageRef swappedPage = control->getCurrentPage(); - if (!swappedPage || doc->getPageCount() <= 1) { - return; - } - - doc->lock(); - size_t page = doc->indexOf(swappedPage); - PageRef otherPage = doc->getPage(page + 1); - - if (!otherPage) { - doc->unlock(); - return; - } - - if (page != npos) { - doc->deletePage(page); - doc->insertPage(swappedPage, page + 1); - } - doc->unlock(); - - UndoRedoHandler* undo = control->getUndoRedoHandler(); - undo->addUndoAction(std::make_unique(page, false, swappedPage, otherPage)); - - control->firePageDeleted(page); - control->firePageInserted(page + 1); - control->firePageSelected(page + 1); - - control->getScrollHandler()->scrollToPage(page + 1); - break; - } - case SIDEBAR_ACTION_COPY: { - control->duplicatePage(); - break; - } - case SIDEBAR_ACTION_DELETE: - control->deletePage(); - break; - case SIDEBAR_ACTION_NEW_BEFORE: - control->insertNewPage(control->getCurrentPageNo()); - break; - case SIDEBAR_ACTION_NEW_AFTER: - control->insertNewPage(control->getCurrentPageNo() + 1); - break; - default: - break; - case SIDEBAR_ACTION_NONE: - break; - } -} - void SidebarPreviewPages::updatePreviews() { this->previews.clear(); @@ -184,12 +45,12 @@ void SidebarPreviewPages::updatePreviews() { size_t len = doc->getPageCount(); for (size_t i = 0; i < len; i++) { auto p = std::make_unique(this, doc->getPage(i), i); - gtk_layout_put(GTK_LAYOUT(this->iconViewPreview.get()), p->getWidget(), 0, 0); + gtk_fixed_put(this->miniaturesContainer.get(), p->getWidget(), 0, 0); this->previews.emplace_back(std::move(p)); } + doc->unlock(); layout(); - doc->unlock(); } void SidebarPreviewPages::pageSizeChanged(size_t page) { @@ -235,7 +96,7 @@ void SidebarPreviewPages::pageInserted(size_t page) { doc->unlock(); - gtk_layout_put(GTK_LAYOUT(this->iconViewPreview.get()), p->getWidget(), 0, 0); + gtk_fixed_put(this->miniaturesContainer.get(), p->getWidget(), 0, 0); this->previews.insert(this->previews.begin() + as_signed(page), std::move(p)); // Unselect page, to prevent double selection displaying @@ -269,36 +130,9 @@ void SidebarPreviewPages::pageSelected(size_t page) { auto& p = this->previews[this->selectedEntry]; p->setSelected(true); scrollToPreview(this); - - int actions = 0; - if (page != 0 && !this->previews.empty()) { - actions |= SIDEBAR_ACTION_MOVE_UP; - } - - if (page != this->previews.size() - 1 && !this->previews.empty()) { - actions |= SIDEBAR_ACTION_MOVE_DOWN; - } - - if (!this->previews.empty()) { - actions |= SIDEBAR_ACTION_COPY; - } - - if (this->previews.size() > 1) { - actions |= SIDEBAR_ACTION_DELETE; - } - - this->toolbar->setHidden(false); - this->toolbar->setButtonEnabled(static_cast(actions)); - - gtk_widget_set_sensitive(this->contextMenuMoveUp, actions & SIDEBAR_ACTION_MOVE_UP); - gtk_widget_set_sensitive(this->contextMenuMoveDown, actions & SIDEBAR_ACTION_MOVE_DOWN); } } -void SidebarPreviewPages::openPreviewContextMenu() { - gtk_menu_popup(GTK_MENU(this->contextMenu), nullptr, nullptr, nullptr, nullptr, 3, gtk_get_current_event_time()); -} - void SidebarPreviewPages::updateIndices() { size_t index = 0; for (auto& preview: this->previews) { diff --git a/src/core/gui/sidebar/previews/page/SidebarPreviewPages.h b/src/core/gui/sidebar/previews/page/SidebarPreviewPages.h index 75014194fd3f..314163bca88c 100644 --- a/src/core/gui/sidebar/previews/page/SidebarPreviewPages.h +++ b/src/core/gui/sidebar/previews/page/SidebarPreviewPages.h @@ -22,7 +22,7 @@ #include "gui/IconNameHelper.h" // for IconNameHe... #include "gui/sidebar/previews/base/SidebarPreviewBase.h" // for SidebarPre... -#include "gui/sidebar/previews/base/SidebarToolbar.h" // for SidebarAct... +#include "util/raii/GObjectSPtr.h" class Control; class GladeGui; @@ -30,15 +30,10 @@ class GladeGui; class SidebarPreviewPages: public SidebarPreviewBase { public: - SidebarPreviewPages(Control* control, GladeGui* gui, SidebarToolbar* toolbar); + SidebarPreviewPages(Control* control); ~SidebarPreviewPages() override; public: - /** - * Called when an action is performed - */ - void actionPerformed(SidebarActions action) override; - void enableSidebar() override; /** @@ -57,12 +52,6 @@ class SidebarPreviewPages: public SidebarPreviewBase { */ void updatePreviews() override; - /** - * Opens the page preview context menu, at the current cursor position, for - * the given page. - */ - void openPreviewContextMenu() override; - public: // DocumentListener interface (only the part which is not handled by SidebarPreviewBase) void pageSizeChanged(size_t page) override; @@ -82,29 +71,6 @@ class SidebarPreviewPages: public SidebarPreviewBase { */ void updateIndices(); - /** - * The context menu to display when a page is right-clicked. - */ - GtkWidget* const contextMenu = nullptr; - - GtkWidget* contextMenuMoveUp = nullptr; - GtkWidget* contextMenuMoveDown = nullptr; - - /** - * The data passed to the menu item callbacks. - */ - struct ContextMenuData { - SidebarToolbar* toolbar; - SidebarActions actions; - }; - - - /** - * The signals connected to the context menu items. This must be kept track - * of so the data can be deallocated safely. - */ - std::vector>> contextMenuSignals; - private: IconNameHelper iconNameHelper; }; diff --git a/src/core/undo/InsertDeletePageUndoAction.cpp b/src/core/undo/InsertDeletePageUndoAction.cpp index e3b66e29893d..be967000d275 100644 --- a/src/core/undo/InsertDeletePageUndoAction.cpp +++ b/src/core/undo/InsertDeletePageUndoAction.cpp @@ -50,7 +50,6 @@ auto InsertDeletePageUndoAction::insertPage(Control* control) -> bool { control->firePageInserted(this->pagePos); control->getCursor()->updateCursor(); control->getScrollHandler()->scrollToPage(this->pagePos); - control->updateDeletePageButton(); return true; } @@ -75,8 +74,6 @@ auto InsertDeletePageUndoAction::deletePage(Control* control) -> bool { control->firePageDeleted(pNr); doc->lock(); doc->deletePage(pNr); - - control->updateDeletePageButton(); doc->unlock(); return true; } diff --git a/src/util/gtk4_helper.cpp b/src/util/gtk4_helper.cpp index 0ee3c0946b45..8675c6fbd316 100644 --- a/src/util/gtk4_helper.cpp +++ b/src/util/gtk4_helper.cpp @@ -162,6 +162,9 @@ gboolean gtk_file_chooser_set_current_folder(GtkFileChooser* chooser, GFile* fil return gtk_file_chooser_set_current_folder_file(chooser, file, error); } +/**** GtkFixed ****/ +void gtk_fixed_remove(GtkFixed* fixed, GtkWidget* child) { gtk_container_remove(GTK_CONTAINER(fixed), child); } + /**** GtkListBox ****/ void gtk_list_box_append(GtkListBox* box, GtkWidget* widget) { gtk_container_add(GTK_CONTAINER(box), widget); } void gtk_list_box_row_set_child(GtkListBoxRow* row, GtkWidget* w) { set_child(GTK_CONTAINER(row), w); } diff --git a/src/util/include/util/gtk4_helper.h b/src/util/include/util/gtk4_helper.h index f4ecae9c8f5b..2df74787c6c9 100644 --- a/src/util/include/util/gtk4_helper.h +++ b/src/util/include/util/gtk4_helper.h @@ -83,6 +83,9 @@ void gtk_im_context_set_client_widget(GtkIMContext* context, GtkWidget* widget); gboolean gtk_file_chooser_add_shortcut_folder(GtkFileChooser* chooser, GFile* file, GError** error); gboolean gtk_file_chooser_set_current_folder(GtkFileChooser* chooser, GFile* file, GError** error); +/**** GtkFixed ****/ +void gtk_fixed_remove(GtkFixed* fixed, GtkWidget* child); + /**** GtkListBox ****/ void gtk_list_box_append(GtkListBox* box, GtkWidget* widget); void gtk_list_box_row_set_child(GtkListBoxRow* row, GtkWidget* w); diff --git a/ui/main.glade b/ui/main.glade index e2540a263d05..f1904e24cfce 100644 --- a/ui/main.glade +++ b/ui/main.glade @@ -214,21 +214,41 @@ False vertical - + True False + - - tbSelectSidebarPage + True False + - True + False True 0 + + + true + true + vertical + + + + True + True + 1 + + buttonCloseSidebar @@ -251,14 +271,9 @@ False True - end - 1 + 2 - False @@ -272,9 +287,6 @@ True False vertical - - - True @@ -282,102 +294,6 @@ 2 - - - True - False - - - btUp - True - True - True - - - True - False - go-up - - - - - False - True - 1 - - - - - btDown - True - True - True - - - True - False - go-down - - - - - False - True - 2 - - - - - btCopy - True - True - True - - - True - False - edit-copy - - - - - False - True - 3 - - - - - btDelete - True - True - True - - - True - False - edit-delete - - - - - False - True - 4 - - - - - - False - True - 2 - 3 - - False @@ -827,129 +743,4 @@ - - sidebarPreviewContextMenu - True - False - - - sidebarPreviewNewBefore - True - False - Insert Page Before - - - - - sidebarPreviewNewAfter - True - False - Insert Page After - - - - - sidebarPreviewDuplicate - True - False - Duplicate - - - - - True - False - - - - - sidebarPreviewMoveUp - True - False - Move Up - - - - - sidebarPreviewMoveDown - True - False - Move Down - - - - - True - False - - - - - sidebarPreviewEdit - True - False - False - Edit (not implemented yet) - - - - - True - False - - - - - sidebarPreviewDelete - True - False - Delete - - - - - sidebarPreviewLayersContextMenu - True - False - - - sidebarPreviewLayerMoveUp - True - False - Move Up - - - - - sidebarPreviewLayerMoveDown - True - False - Move Down - - - - - sidebarPreviewMergeDown - True - False - Merge Down - - - - - sidebarPreviewLayerDuplicate - True - False - Duplicate - - - - - sidebarPreviewLayerDelete - True - False - Delete - - - diff --git a/ui/sidebar.ui b/ui/sidebar.ui new file mode 100644 index 000000000000..181c862edc39 --- /dev/null +++ b/ui/sidebar.ui @@ -0,0 +1,147 @@ + + + + + + + + win.move-page-towards-beginning +10go-up + Swap the current page with the one above + + + + + win.move-page-towards-end +10go-down + Swap the current page with the one below + + + + + win.duplicate-page +10edit-copy + Insert a copy of the current page below + + + + + win.delete-page +10delete + Delete this page + + + + +
+ + Insert Page Before + win.new-page-before + + + Insert Page After + win.new-page-after + + + Duplicate + win.duplicate-page + +
+
+ + Move Up + win.move-page-towards-beginning + + + Move Down + win.move-page-towards-end + +
+
+ + Delete + win.delete-page + +
+
+ + + + + win.layer-move-up + + + True + False + go-up + + + Swap the current layer with the one above + + + + + win.layer-move-down + + + 1 + 0 + go-down + + + Swap the current layer with the one below + + + + + win.layer-copy + + + 1 + 0 + edit-copy + + + Insert a copy of the current layer below + + + + + win.layer-delete +10delete + Delete this layer + + + + +
+ + Move Up + win.layer-move-up + + + Move Down + win.layer-move-down + + + Merge Down + win.layer-merge-down + + + Duplicate + win.layer-copy + +
+
+ + Delete + win.layer-delete + +
+
+
diff --git a/ui/xournalpp.css b/ui/xournalpp.css index a1fbb20a08ce..fefc3a73c6bb 100644 --- a/ui/xournalpp.css +++ b/ui/xournalpp.css @@ -141,6 +141,16 @@ radiobutton.invisible radio { box-shadow:none; } +/* A spacer is a transparent separator with expand == true */ +separator.spacer { + color:transparent; + background-color:transparent; + background-image:none; + outline-style:none; + border-style:none; + box-shadow:none; +} + /* If you want to change the size of toolbar buttons try these: * All toolbars: */ @@ -173,18 +183,6 @@ notebook frame>label { font-weight: bold; } - -/* - Sidebar (page select, layer select, etc) - */ -#sidebarContents { - background-color: white; -} - -window.darkMode #sidebarContents { - background-color: #222; -} - menubar, toolbar { -GtkWidget-window-dragging: false }