diff options
Diffstat (limited to 'libreofficekit/qa')
33 files changed, 6179 insertions, 0 deletions
diff --git a/libreofficekit/qa/data/blank_presentation.odp b/libreofficekit/qa/data/blank_presentation.odp Binary files differnew file mode 100644 index 000000000..fd68d9a9b --- /dev/null +++ b/libreofficekit/qa/data/blank_presentation.odp diff --git a/libreofficekit/qa/data/blank_text.odt b/libreofficekit/qa/data/blank_text.odt Binary files differnew file mode 100644 index 000000000..00b92d785 --- /dev/null +++ b/libreofficekit/qa/data/blank_text.odt diff --git a/libreofficekit/qa/data/calc_sheetnames.ods b/libreofficekit/qa/data/calc_sheetnames.ods Binary files differnew file mode 100644 index 000000000..f6627a058 --- /dev/null +++ b/libreofficekit/qa/data/calc_sheetnames.ods diff --git a/libreofficekit/qa/data/empty.ods b/libreofficekit/qa/data/empty.ods Binary files differnew file mode 100644 index 000000000..a36d1f97c --- /dev/null +++ b/libreofficekit/qa/data/empty.ods diff --git a/libreofficekit/qa/data/impress_slidenames.odp b/libreofficekit/qa/data/impress_slidenames.odp Binary files differnew file mode 100644 index 000000000..d7cb6aeef --- /dev/null +++ b/libreofficekit/qa/data/impress_slidenames.odp diff --git a/libreofficekit/qa/data/join/README b/libreofficekit/qa/data/join/README new file mode 100644 index 000000000..35762e1f0 --- /dev/null +++ b/libreofficekit/qa/data/join/README @@ -0,0 +1,4 @@ +Files to run through tilebench --join to detect problems. + +bin/run tilebench instdir/program libreofficekit/qa/join/<filename> --join + diff --git a/libreofficekit/qa/data/join/calc-100-textjitter.xlsx b/libreofficekit/qa/data/join/calc-100-textjitter.xlsx Binary files differnew file mode 100644 index 000000000..94a3e5254 --- /dev/null +++ b/libreofficekit/qa/data/join/calc-100-textjitter.xlsx diff --git a/libreofficekit/qa/data/join/calc-object-offset.ods b/libreofficekit/qa/data/join/calc-object-offset.ods Binary files differnew file mode 100644 index 000000000..b86ef3107 --- /dev/null +++ b/libreofficekit/qa/data/join/calc-object-offset.ods diff --git a/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx b/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx new file mode 100644 index 000000000..7e2f9f907 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include <memory> + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include "gtv-application-window.hxx" +#include "gtv-main-toolbar.hxx" +#include "gtv-helpers.hxx" +#include "gtv-signal-handlers.hxx" +#include "gtv-lokdocview-signal-handlers.hxx" +#include "gtv-calc-header-bar.hxx" +#include "gtv-comments-sidebar.hxx" +#include "gtv-lok-dialog.hxx" + +#include <boost/property_tree/json_parser.hpp> + +namespace { + +struct GtvApplicationWindowPrivate +{ + GtkWidget* container; + GtkWidget* gridcontainer; + GtkWidget* toolbarcontainer; + GtkWidget* scrolledwindowcontainer; + + bool toolbarBroadcast; + bool partSelectorBroadcast; + + GList* m_pChildWindows; + + // Rendering args; options with which lokdocview was rendered in this window + GtvRenderingArgs* m_pRenderingArgs; +}; + +} + +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE_WITH_PRIVATE(GtvApplicationWindow, gtv_application_window, GTK_TYPE_APPLICATION_WINDOW); +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic pop +#endif +#endif + +static GtvApplicationWindowPrivate* +getPrivate(GtvApplicationWindow* win) +{ + return static_cast<GtvApplicationWindowPrivate*>(gtv_application_window_get_instance_private(win)); +} + +static void +gtv_application_window_init(GtvApplicationWindow* win) +{ + const std::string uiFilePath = GtvHelpers::getDirPath(__FILE__) + std::string(UI_FILE_NAME); + GtvGtkWrapper<GtkBuilder> builder(gtk_builder_new_from_file(uiFilePath.c_str()), + [](GtkBuilder* pBuilder) { + g_object_unref(pBuilder); + }); + GtvApplicationWindowPrivate* priv = getPrivate(win); + + // This is the parent GtkBox holding everything + priv->container = GTK_WIDGET(gtk_builder_get_object(builder.get(), "container")); + // Toolbar container + priv->toolbarcontainer = gtv_main_toolbar_new(); + + // Attach to the toolbar to main window + gtk_box_pack_start(GTK_BOX(priv->container), priv->toolbarcontainer, false, false, false); + gtk_box_reorder_child(GTK_BOX(priv->container), priv->toolbarcontainer, 0); + + priv->gridcontainer = GTK_WIDGET(gtk_builder_get_object(builder.get(), "maingrid")); + // scrolled window containing the main drawing area + win->scrolledwindow = GTK_WIDGET(gtk_builder_get_object(builder.get(), "scrolledwindow")); + // scrolledwindow container + priv->scrolledwindowcontainer = GTK_WIDGET(gtk_builder_get_object(builder.get(), "scrolledwindowcontainer")); + + GtkAdjustment* pHAdjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(win->scrolledwindow)); + g_signal_connect(pHAdjustment, "value-changed", G_CALLBACK(docAdjustmentChanged), win); + GtkAdjustment* pVAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(win->scrolledwindow)); + g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(docAdjustmentChanged), win); + + // calc header row bar + win->cornerarea = gtv_calc_header_bar_new(); + gtv_calc_header_bar_set_type_and_width(GTV_CALC_HEADER_BAR(win->cornerarea), CalcHeaderType::CORNER); + win->rowbar = gtv_calc_header_bar_new(); + gtv_calc_header_bar_set_type_and_width(GTV_CALC_HEADER_BAR(win->rowbar), CalcHeaderType::ROW); + win->columnbar = gtv_calc_header_bar_new(); + gtv_calc_header_bar_set_type_and_width(GTV_CALC_HEADER_BAR(win->columnbar), CalcHeaderType::COLUMN); + + // attach row/column/corner to the container + gtk_grid_attach(GTK_GRID(priv->gridcontainer), win->cornerarea, 0, 0, 1, 1); + gtk_grid_attach(GTK_GRID(priv->gridcontainer), win->rowbar, 0, 1, 1, 1); + gtk_grid_attach(GTK_GRID(priv->gridcontainer), win->columnbar, 1, 0, 1, 1); + + // statusbar + win->statusbar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "statusbar")); + win->redlinelabel = GTK_WIDGET(gtk_builder_get_object(builder.get(), "redlinelabel")); + win->zoomlabel = GTK_WIDGET(gtk_builder_get_object(builder.get(), "zoomlabel")); + + win->findtoolbar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "findtoolbar")); + win->findbarlabel = GTK_WIDGET(gtk_builder_get_object(builder.get(), "findbar_label")); + win->findbarEntry = GTK_WIDGET(gtk_builder_get_object(builder.get(), "findbar_entry")); + win->findAll = GTK_WIDGET(gtk_builder_get_object(builder.get(), "findbar_findall")); + priv->toolbarBroadcast = true; + priv->partSelectorBroadcast = true; + + gtk_container_add(GTK_CONTAINER(win), priv->container); + + priv->m_pChildWindows = nullptr; + priv->m_pRenderingArgs = new GtvRenderingArgs(); +} + +static void +gtv_application_window_dispose(GObject* object) +{ + GtvApplicationWindowPrivate* priv = getPrivate(GTV_APPLICATION_WINDOW(object)); + + delete priv->m_pRenderingArgs; + priv->m_pRenderingArgs = nullptr; + + G_OBJECT_CLASS (gtv_application_window_parent_class)->dispose (object); +} + +static void +gtv_application_window_class_init(GtvApplicationWindowClass* klass) +{ + G_OBJECT_CLASS(klass)->dispose = gtv_application_window_dispose; +} + +/// Helper function to do some tasks after widget is fully loaded (including +/// document load) +static void initWindow(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + GList *focusChain = nullptr; + focusChain = g_list_append( focusChain, window->lokdocview ); + + gtk_container_set_focus_chain ( GTK_CONTAINER (priv->container), focusChain ); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // TODO: Implement progressbar in statusbar + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + if (pDocument) + { + LibreOfficeKitDocumentType eDocType = static_cast<LibreOfficeKitDocumentType>(pDocument->pClass->getDocumentType(pDocument)); + if (eDocType == LOK_DOCTYPE_SPREADSHEET) + { + // Align to top left corner, so the tiles are in sync with the + // row/column bar, even when zooming out enough that not all space is + // used. + gtk_widget_set_halign(GTK_WIDGET(window->lokdocview), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(window->lokdocview), GTK_ALIGN_START); + } + + // By default make the document editable in a new window + lok_doc_view_set_edit(LOK_DOC_VIEW(window->lokdocview), true); + // Let toolbar adjust its button accordingly + gtv_main_toolbar_doc_loaded(GTV_MAIN_TOOLBAR(priv->toolbarcontainer), eDocType, true /* Edit button state */); + } + + // Fill our comments sidebar + gboolean bTiledAnnotations; + g_object_get(G_OBJECT(window->lokdocview), "tiled-annotations", &bTiledAnnotations, nullptr); + if (!bTiledAnnotations && pDocument) + { + window->commentssidebar = gtv_comments_sidebar_new(); + gtk_container_add(GTK_CONTAINER(priv->scrolledwindowcontainer), window->commentssidebar); + // fill the comments sidebar + gtv_comments_sidebar_view_annotations(GTV_COMMENTS_SIDEBAR(window->commentssidebar)); + } +} + +static void +gtv_application_open_document_callback(GObject* source_object, GAsyncResult* res, gpointer /*userdata*/) +{ + LOKDocView* pDocView = LOK_DOC_VIEW (source_object); + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GError* error = nullptr; + if (!lok_doc_view_open_document_finish(pDocView, res, &error)) + { + GtkWidget* pDialog = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Error occurred while opening the document: '%s'", + error->message); + gtk_dialog_run(GTK_DIALOG(pDialog)); + gtk_widget_destroy(pDialog); + + g_error_free(error); + gtk_widget_destroy(GTK_WIDGET(pDocView)); + gtk_main_quit(); + return; + } + + initWindow(window); +} + +/// Get the visible area of the scrolled window +void gtv_application_window_get_visible_area(GtvApplicationWindow* pWindow, GdkRectangle* pArea) +{ + GtkAdjustment* pHAdjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(pWindow->scrolledwindow)); + GtkAdjustment* pVAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(pWindow->scrolledwindow)); + + pArea->x = lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(pWindow->lokdocview), + gtk_adjustment_get_value(pHAdjustment)); + pArea->y = lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(pWindow->lokdocview), + gtk_adjustment_get_value(pVAdjustment)); + pArea->width = lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(pWindow->lokdocview), + gtk_adjustment_get_page_size(pHAdjustment)); + pArea->height = lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(pWindow->lokdocview), + gtk_adjustment_get_page_size(pVAdjustment)); +} + +void gtv_application_window_toggle_findbar(GtvApplicationWindow* window) +{ + if (gtk_widget_get_visible(window->findtoolbar)) + { + gtk_widget_hide(window->findtoolbar); + } + else + { + gtk_widget_show_all(window->findtoolbar); + gtk_widget_grab_focus(window->findtoolbar); + } +} + +GtkToolItem* gtv_application_window_find_tool_by_unocommand(GtvApplicationWindow* window, const std::string& unoCmd) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + GtkToolItem* result = nullptr; + + // Find in the first toolbar + GtkContainer* pToolbar1 = gtv_main_toolbar_get_first_toolbar(GTV_MAIN_TOOLBAR(priv->toolbarcontainer)); + GtvGtkWrapper<GList> pList(gtk_container_get_children(pToolbar1), + [](GList* l) + { + g_list_free(l); + }); + for (GList* l = pList.get(); l != nullptr; l = l->next) + { + if (GTK_IS_TOOL_BUTTON(l->data)) + { + GtkToolButton* pButton = GTK_TOOL_BUTTON(l->data); + const gchar* pLabel = gtk_tool_button_get_label(pButton); + if (g_strcmp0(unoCmd.c_str(), pLabel) == 0) + { + result = GTK_TOOL_ITEM(pButton); + } + } + } + + // Look in second toolbar if not found + GtkContainer* pToolbar2 = gtv_main_toolbar_get_second_toolbar(GTV_MAIN_TOOLBAR(priv->toolbarcontainer)); + pList.reset(gtk_container_get_children(pToolbar2)); + for (GList* l = pList.get(); result == nullptr && l != nullptr; l = l->next) + { + if (GTK_IS_TOOL_BUTTON(l->data)) + { + GtkToolButton* pButton = GTK_TOOL_BUTTON(l->data); + const gchar* pLabel = gtk_tool_button_get_label(pButton); + if (g_strcmp0(unoCmd.c_str(), pLabel) == 0) + { + result = GTK_TOOL_ITEM(pButton); + } + } + } + + return result; +} + +static std::string +createRenderingArgsJSON(const GtvRenderingArgs* pRenderingArgs) +{ + boost::property_tree::ptree aTree; + if (pRenderingArgs->m_bHidePageShadow) + { + aTree.put(boost::property_tree::ptree::path_type(".uno:ShowBorderShadow/type", '/'), "boolean"); + aTree.put(boost::property_tree::ptree::path_type(".uno:ShowBorderShadow/value", '/'), false); + } + if (pRenderingArgs->m_bHideWhiteSpace) + { + aTree.put(boost::property_tree::ptree::path_type(".uno:HideWhitespace/type", '/'), "boolean"); + aTree.put(boost::property_tree::ptree::path_type(".uno:HideWhitespace/value", '/'), true); + } + aTree.put(boost::property_tree::ptree::path_type(".uno:Author/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type(".uno:Author/value", '/'), GtvHelpers::getNextAuthor()); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + return aStream.str(); +} + +static void setupDocView(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + g_object_set(G_OBJECT(window->lokdocview), + "doc-password", true, + "doc-password-to-modify", true, + "tiled-annotations", priv->m_pRenderingArgs->m_bEnableTiledAnnotations, + nullptr); + +#if GLIB_CHECK_VERSION(2,40,0) + g_assert_nonnull(window->lokdocview); +#endif + g_signal_connect(window->lokdocview, "edit-changed", G_CALLBACK(LOKDocViewSigHandlers::editChanged), nullptr); + g_signal_connect(window->lokdocview, "command-changed", G_CALLBACK(LOKDocViewSigHandlers::commandChanged), nullptr); + g_signal_connect(window->lokdocview, "command-result", G_CALLBACK(LOKDocViewSigHandlers::commandResult), nullptr); + g_signal_connect(window->lokdocview, "search-not-found", G_CALLBACK(LOKDocViewSigHandlers::searchNotFound), nullptr); + g_signal_connect(window->lokdocview, "search-result-count", G_CALLBACK(LOKDocViewSigHandlers::searchResultCount), nullptr); + g_signal_connect(window->lokdocview, "part-changed", G_CALLBACK(LOKDocViewSigHandlers::partChanged), nullptr); + g_signal_connect(window->lokdocview, "hyperlink-clicked", G_CALLBACK(LOKDocViewSigHandlers::hyperlinkClicked), nullptr); + g_signal_connect(window->lokdocview, "content-control", + G_CALLBACK(LOKDocViewSigHandlers::contentControl), nullptr); + g_signal_connect(window->lokdocview, "cursor-changed", G_CALLBACK(LOKDocViewSigHandlers::cursorChanged), nullptr); + g_signal_connect(window->lokdocview, "address-changed", G_CALLBACK(LOKDocViewSigHandlers::addressChanged), nullptr); + g_signal_connect(window->lokdocview, "formula-changed", G_CALLBACK(LOKDocViewSigHandlers::formulaChanged), nullptr); + g_signal_connect(window->lokdocview, "password-required", G_CALLBACK(LOKDocViewSigHandlers::passwordRequired), nullptr); + g_signal_connect(window->lokdocview, "comment", G_CALLBACK(LOKDocViewSigHandlers::comment), nullptr); + g_signal_connect(window->lokdocview, "window", G_CALLBACK(LOKDocViewSigHandlers::window), window); + + g_signal_connect(window->lokdocview, "configure-event", G_CALLBACK(LOKDocViewSigHandlers::configureEvent), nullptr); +} + +void +gtv_application_window_create_view_from_window(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + GApplication* app = g_application_get_default(); + + GtvApplicationWindow* newWindow = GTV_APPLICATION_WINDOW(gtv_application_window_new(GTK_APPLICATION(app))); + const std::string aArguments = createRenderingArgsJSON(priv->m_pRenderingArgs); + newWindow->lokdocview = lok_doc_view_new_from_widget(LOK_DOC_VIEW(window->lokdocview), aArguments.c_str()); + setupDocView(newWindow); + + gtk_container_add(GTK_CONTAINER(newWindow->scrolledwindow), newWindow->lokdocview); + gtk_widget_show_all(newWindow->scrolledwindow); + gtk_window_present(GTK_WINDOW(newWindow)); + + initWindow(newWindow); +} + +void +gtv_application_window_load_document(GtvApplicationWindow* window, + const GtvRenderingArgs* aArgs, + const std::string& aDocPath) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + // keep a copy of it; we need to use these for creating new views later + *(priv->m_pRenderingArgs) = *aArgs; + + // setup lokdocview + const char *pUserProfile = priv->m_pRenderingArgs->m_aUserProfile.empty() ? + nullptr : priv->m_pRenderingArgs->m_aUserProfile.c_str(); + + window->lokdocview = GTK_WIDGET( + g_initable_new(LOK_TYPE_DOC_VIEW, nullptr, nullptr, + "lopath", priv->m_pRenderingArgs->m_aLoPath.c_str(), + "unipoll", priv->m_pRenderingArgs->m_bUnipoll, + "userprofileurl", pUserProfile, + "halign", GTK_ALIGN_CENTER, + "valign", GTK_ALIGN_CENTER, + nullptr)); + + gtk_container_add(GTK_CONTAINER(window->scrolledwindow), window->lokdocview); + + setupDocView(window); + + // Create argument JSON + const std::string aArguments = createRenderingArgsJSON(priv->m_pRenderingArgs); + lok_doc_view_open_document(LOK_DOC_VIEW(window->lokdocview), aDocPath.c_str(), + aArguments.c_str(), nullptr, + gtv_application_open_document_callback, window->lokdocview); + + gtk_widget_show_all(GTK_WIDGET(window->scrolledwindow)); +} + +GtvMainToolbar* +gtv_application_window_get_main_toolbar(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + return GTV_MAIN_TOOLBAR(priv->toolbarcontainer); +} + +void +gtv_application_window_set_toolbar_broadcast(GtvApplicationWindow* window, bool broadcast) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + priv->toolbarBroadcast = broadcast; +} + +gboolean +gtv_application_window_get_toolbar_broadcast(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + return priv->toolbarBroadcast; +} + +void +gtv_application_window_set_part_broadcast(GtvApplicationWindow* window, bool broadcast) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + priv->partSelectorBroadcast = broadcast; +} + +gboolean +gtv_application_window_get_part_broadcast(GtvApplicationWindow* window) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + return priv->partSelectorBroadcast; +} + +void +gtv_application_window_register_child_window(GtvApplicationWindow* window, GtkWindow* pChildWin) +{ + guint dialogid = 0; + g_object_get(G_OBJECT(pChildWin), "dialogid", &dialogid, nullptr); + g_debug("Register child window: dialogid [%d] in window[%p]", dialogid, window); + GtvApplicationWindowPrivate* priv = getPrivate(window); + if (pChildWin) + priv->m_pChildWindows = g_list_append(priv->m_pChildWindows, pChildWin); +} + +void +gtv_application_window_unregister_child_window(GtvApplicationWindow* window, GtkWindow* pChildWin) +{ + guint dialogid = 0; + g_object_get(G_OBJECT(pChildWin), "dialogid", &dialogid, nullptr); + g_debug("Unregister child window: dialogid [%d] in window[%p]", dialogid, window); + GtvApplicationWindowPrivate* priv = getPrivate(window); + if (pChildWin) + { + priv->m_pChildWindows = g_list_remove(priv->m_pChildWindows, pChildWin); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + guint dialogId = 0; + g_object_get(G_OBJECT(pChildWin), "dialogid", &dialogId, nullptr); + pDocument->pClass->postWindow(pDocument, dialogId, LOK_WINDOW_CLOSE, nullptr); + } +} + +GtkWindow* +gtv_application_window_get_child_window_by_id(GtvApplicationWindow* window, guint nWinId) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + GList* pIt = nullptr; + GtkWindow* ret = nullptr; + // For now, only dialogs are registered as child window + for (pIt = priv->m_pChildWindows; pIt != nullptr; pIt = pIt->next) + { + guint dialogId = 0; + g_object_get(G_OBJECT(pIt->data), "dialogid", &dialogId, nullptr); + if (dialogId == nWinId) + { + ret = GTK_WINDOW(pIt->data); + break; + } + } + + return ret; +} + +GtkWidget* +gtv_application_window_get_parent(GtvApplicationWindow* window, guint nWinId) +{ + GtvApplicationWindowPrivate* priv = getPrivate(window); + GList* pIt = nullptr; + for (pIt = priv->m_pChildWindows; pIt != nullptr; pIt = pIt->next) + { + if (gtv_lok_dialog_is_parent_of(GTV_LOK_DIALOG(pIt->data), nWinId)) + return GTK_WIDGET(pIt->data); + } + return nullptr; +} + +GtvApplicationWindow* +gtv_application_window_new(GtkApplication* app) +{ + g_return_val_if_fail(GTK_IS_APPLICATION(app), nullptr); + + return GTV_APPLICATION_WINDOW(g_object_new(GTV_TYPE_APPLICATION_WINDOW, + "application", app, + "width-request", 1024, + "height-request", 768, + "title", "LibreOffice GtkTiledViewer", + "window-position", GTK_WIN_POS_CENTER, + "show-menubar", false, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-application-window.hxx b/libreofficekit/qa/gtktiledviewer/gtv-application-window.hxx new file mode 100644 index 000000000..9d3d51962 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-application-window.hxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_APPLICATION_WINDOW_H +#define GTV_APPLICATION_WINDOW_H + +#include <gtk/gtk.h> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include "gtv-main-toolbar.hxx" + +#include <string> + +struct GtvRenderingArgs +{ + std::string m_aLoPath; + std::string m_aUserProfile; + bool m_bEnableTiledAnnotations; + bool m_bUnipoll; + + std::string m_aBackgroundColor; + bool m_bHidePageShadow; + bool m_bHideWhiteSpace; + + GtvRenderingArgs() + : m_bEnableTiledAnnotations(false), + m_bUnipoll(false), + m_bHidePageShadow(false), + m_bHideWhiteSpace(false) + { } +}; + +G_BEGIN_DECLS + +#define GTV_TYPE_APPLICATION_WINDOW (gtv_application_window_get_type()) +#define GTV_APPLICATION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_APPLICATION_WINDOW, GtvApplicationWindow)) +#define GTV_IS_APPLICATION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_APPLICATION_WINDOW)) +#define GTV_APPLICATION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_APPLICATION_WINDOW, GtvApplicationWindowClass)) +#define GTV_IS_APPLICATION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_APPLICATION_WINDOW)) +#define GTV_APPLICATION_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_APPLICATION_WINDOW, GtvApplicationWindowClass)) + +struct GtvApplicationWindow +{ + GtkApplicationWindow parent_instance; + + GtkWidget* scrolledwindow; + GtkWidget* lokdocview; + LibreOfficeKitDocumentType doctype; + + GtkWidget* rowbar; + GtkWidget* columnbar; + GtkWidget* cornerarea; + + GtkWidget* commentssidebar; + GtkWidget* statusbar; + GtkWidget* zoomlabel; + GtkWidget* redlinelabel; + GtkWidget* findbarlabel; + GtkWidget* findbarEntry; + GtkWidget* findAll; + + GtkWidget* findtoolbar; +}; + +struct GtvApplicationWindowClass +{ + GtkApplicationWindowClass parentClass; +}; + +GType gtv_application_window_get_type (void) G_GNUC_CONST; + +GtvApplicationWindow* gtv_application_window_new(GtkApplication* application); + +void gtv_application_window_load_document(GtvApplicationWindow* application, + const GtvRenderingArgs* aArgs, + const std::string& aDocPath); + +void gtv_application_window_create_view_from_window(GtvApplicationWindow* window); + +void gtv_application_window_get_visible_area(GtvApplicationWindow* pWindow, GdkRectangle* pArea); + +void gtv_application_window_toggle_findbar(GtvApplicationWindow* window); + +GtkToolItem* gtv_application_window_find_tool_by_unocommand(GtvApplicationWindow* window, const std::string& unoCmd); + +GtvMainToolbar* gtv_application_window_get_main_toolbar(GtvApplicationWindow* window); + +void gtv_application_window_set_toolbar_broadcast(GtvApplicationWindow* window, bool broadcast); + +gboolean gtv_application_window_get_toolbar_broadcast(GtvApplicationWindow* window); + +void gtv_application_window_set_part_broadcast(GtvApplicationWindow* window, bool broadcast); + +gboolean gtv_application_window_get_part_broadcast(GtvApplicationWindow* window); + +void gtv_application_window_register_child_window(GtvApplicationWindow* window, GtkWindow* pChildWin); + +void gtv_application_window_unregister_child_window(GtvApplicationWindow* window, GtkWindow* pChildWin); + +GtkWindow* gtv_application_window_get_child_window_by_id(GtvApplicationWindow* window, guint nWinId); + +GtkWidget* gtv_application_window_get_parent(GtvApplicationWindow* window, guint nWinId); + +G_END_DECLS + +#endif /* GTV_APPLICATION_WINDOW_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-application.cxx b/libreofficekit/qa/gtktiledviewer/gtv-application.cxx new file mode 100644 index 000000000..4268d4e19 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-application.cxx @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include "gtv-application.hxx" +#include "gtv-application-window.hxx" + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> + +#include <string> + +namespace { + +struct GtvApplicationPrivate +{ + GtvRenderingArgs* m_pRenderingArgs; +}; + +} + +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE_WITH_PRIVATE(GtvApplication, gtv_application, GTK_TYPE_APPLICATION); +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic pop +#endif +#endif + +static GtvApplicationPrivate* +getPrivate(GtvApplication* app) +{ + return static_cast<GtvApplicationPrivate*>(gtv_application_get_instance_private(app)); +} + +static void +gtv_application_activate(GApplication*) +{ + // If this isn't provided, some GTK versions fail to run us at all. +} + +static void +gtv_application_open(GApplication* app, GFile** file, gint nFiles, const gchar* /*hint*/) +{ + for (gint i = 0; i < nFiles; i++) + { + // TODO: add some option to create a new view for existing document + // For now, this just opens a new document + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtv_application_window_new(GTK_APPLICATION(app))); + gtk_window_present(GTK_WINDOW(window)); + + GtvApplicationPrivate* priv = getPrivate(GTV_APPLICATION(app)); + gtv_application_window_load_document(window, priv->m_pRenderingArgs, std::string(g_file_get_path(file[i]))); + } +} + +static void +gtv_application_init(GtvApplication* app) +{ + static const GOptionEntry commandLineOptions[] = + { + { "version", 0, 0, G_OPTION_ARG_NONE, nullptr, "Show LOkit version", nullptr }, + { "lo-path", 0, 0, G_OPTION_ARG_STRING, nullptr, "LO path", nullptr }, + { "unipoll", 0, 0, G_OPTION_ARG_NONE, nullptr, "Enable unified polling loop", nullptr }, + { "user-profile", 0, 0, G_OPTION_ARG_STRING, nullptr, "User profile to use", nullptr }, + { "enable-tiled-annotations", 0, 0, G_OPTION_ARG_NONE, nullptr, "Whether tiled annotations should be enabled", nullptr }, + { "background-color", 0, 0, G_OPTION_ARG_STRING, nullptr, "Background color", nullptr }, + { "hide-page-shadow", 0, 0, G_OPTION_ARG_NONE, nullptr, "Hide page shadow", nullptr }, + { "hide-whitespace", 0, 0, G_OPTION_ARG_NONE, nullptr, "Hide whitespace", nullptr }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }, + }; + + g_application_add_main_option_entries(G_APPLICATION(app), commandLineOptions); + + GtvApplicationPrivate* priv = getPrivate(GTV_APPLICATION(app)); + priv->m_pRenderingArgs = new GtvRenderingArgs(); +} + +static void +gtv_application_dispose (GObject* object) +{ + GtvApplicationPrivate* priv = getPrivate(GTV_APPLICATION(object)); + + delete priv->m_pRenderingArgs; + priv->m_pRenderingArgs = nullptr; + + G_OBJECT_CLASS (gtv_application_parent_class)->dispose (object); +} + +static gint +gtv_application_handle_local_options(GApplication* app, GVariantDict* options) +{ + GtvApplicationPrivate* priv = getPrivate(GTV_APPLICATION(app)); + // This is mandatory + if (g_variant_dict_contains(options, "lo-path")) + { + gchar* loPath = nullptr; + g_variant_dict_lookup(options, "lo-path", "s", &loPath); + if (loPath) + { + priv->m_pRenderingArgs->m_aLoPath = std::string(loPath); + g_free(loPath); + } + } + else + { + g_print("--lo-path= is mandatory. Please provide the path to LO installation.\n"); + return 1; // Cannot afford to continue in absence of this param + } + + if (g_variant_dict_contains(options, "unipoll")) + priv->m_pRenderingArgs->m_bUnipoll = true; + + if (g_variant_dict_contains(options, "version")) + { + if (!priv->m_pRenderingArgs->m_aLoPath.empty()) + { + GtkWidget* pDocView = lok_doc_view_new(priv->m_pRenderingArgs->m_aLoPath.c_str(), nullptr, nullptr); + const gchar* versionInfo = lok_doc_view_get_version_info(LOK_DOC_VIEW(pDocView)); + if (versionInfo) + g_print("LOKit version: %s", versionInfo); + } + + return 1; // exit anyway + } + + // Optional args + if (g_variant_dict_contains(options, "user-profile")) + { + gchar* userProfile = nullptr; + g_variant_dict_lookup(options, "user-profile", "s", &userProfile); + if (userProfile) + { + priv->m_pRenderingArgs->m_aUserProfile = std::string("vnd.sun.star.pathname:") + std::string(userProfile); + g_free(userProfile); + } + } + + if (g_variant_dict_contains(options, "background-color")) + { + gchar* backgroundColor = nullptr; + g_variant_dict_lookup(options, "background-color", "s", &backgroundColor); + if (backgroundColor) + { + priv->m_pRenderingArgs->m_aBackgroundColor = std::string(backgroundColor); + g_free(backgroundColor); + } + } + + if (g_variant_dict_contains(options, "enable-tiled-annotations")) + priv->m_pRenderingArgs->m_bEnableTiledAnnotations = true; + if (g_variant_dict_contains(options, "hide-page-shadow")) + priv->m_pRenderingArgs->m_bHidePageShadow = true; + if (g_variant_dict_contains(options, "hide-whitespace")) + priv->m_pRenderingArgs->m_bHideWhiteSpace = true; + + return -1; +} + +static void +gtv_application_class_init(GtvApplicationClass* klass) +{ + G_APPLICATION_CLASS(klass)->activate = gtv_application_activate; + G_APPLICATION_CLASS(klass)->open = gtv_application_open; + G_APPLICATION_CLASS(klass)->handle_local_options = gtv_application_handle_local_options; + G_OBJECT_CLASS(klass)->dispose = gtv_application_dispose; +} + +GtvApplication* gtv_application_new() +{ + return GTV_APPLICATION(g_object_new(GTV_TYPE_APPLICATION, + "application-id", "org.libreoffice.gtktiledviewer", + "flags", G_APPLICATION_HANDLES_OPEN, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-application.hxx b/libreofficekit/qa/gtktiledviewer/gtv-application.hxx new file mode 100644 index 000000000..660350366 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-application.hxx @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_APPLICATION_H +#define GTV_APPLICATION_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GTV_TYPE_APPLICATION (gtv_application_get_type()) +#define GTV_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_APPLICATION, GtvApplication)) +#define GTV_IS_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_APPLICATION)) +#define GTV_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_APPLICATION, GtvApplicationClass)) +#define GTV_IS_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_APPLICATION)) +#define GTV_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_APPLICATION, GtvApplicationClass)) + +struct GtvApplication +{ + GtkApplication parent; +}; + +struct GtvApplicationClass +{ + GtkApplicationClass parentClass; +}; + +GType gtv_application_get_type (void) G_GNUC_CONST; + +GtvApplication* gtv_application_new(); + +G_END_DECLS + +#endif /* GTV_APPLICATION_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.cxx b/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.cxx new file mode 100644 index 000000000..dc106e58d --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.cxx @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include <cmath> +#include <iostream> + +#include "gtv-calc-header-bar.hxx" + +#include <boost/property_tree/ptree.hpp> +#include <o3tl/unreachable.hxx> + +namespace { + +struct GtvCalcHeaderBarPrivateImpl +{ + /// Stores size and content of a single row header. + struct Header + { + int m_nSize; + std::string m_aText; + Header(int nSize, const std::string& rText) + : m_nSize(nSize), + m_aText(rText) + { } + }; + + std::vector<Header> m_aHeaders; + CalcHeaderType m_eType; + + GtvCalcHeaderBarPrivateImpl() + : m_eType(CalcHeaderType::NONE) + { } +}; + +struct GtvCalcHeaderBarPrivate +{ + GtvCalcHeaderBarPrivateImpl* m_pImpl; + + GtvCalcHeaderBarPrivateImpl* operator->() + { + return m_pImpl; + } +}; + +} + +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE_WITH_PRIVATE(GtvCalcHeaderBar, gtv_calc_header_bar, GTK_TYPE_DRAWING_AREA); +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic pop +#endif +#endif + +const int ROW_HEADER_WIDTH = 50; +const int COLUMN_HEADER_HEIGHT = 20; + +static GtvCalcHeaderBarPrivate& +getPrivate(GtvCalcHeaderBar* headerbar) +{ + return *static_cast<GtvCalcHeaderBarPrivate*>(gtv_calc_header_bar_get_instance_private(headerbar)); +} + +static void +gtv_calc_header_bar_init(GtvCalcHeaderBar* bar) +{ + GtvCalcHeaderBarPrivate& priv = getPrivate(bar); + priv.m_pImpl = new GtvCalcHeaderBarPrivateImpl(); +} + +static void +gtv_calc_header_bar_finalize(GObject* object) +{ + GtvCalcHeaderBarPrivate& priv = getPrivate(GTV_CALC_HEADER_BAR(object)); + + delete priv.m_pImpl; + priv.m_pImpl = nullptr; + + G_OBJECT_CLASS (gtv_calc_header_bar_parent_class)->finalize (object); +} + +static void gtv_calc_header_bar_draw_text(cairo_t* pCairo, const GdkRectangle& rRectangle, const std::string& rText) +{ + cairo_text_extents_t extents; + cairo_text_extents(pCairo, rText.c_str(), &extents); + // Cairo reference point for text is the bottom left corner. + cairo_move_to(pCairo, rRectangle.x + rRectangle.width / 2 - extents.width / 2, rRectangle.y + rRectangle.height / 2 + extents.height / 2); + cairo_show_text(pCairo, rText.c_str()); +} + +static bool gtv_calc_header_bar_draw_impl(GtkWidget* pWidget, cairo_t* pCairo) +{ + GtvCalcHeaderBar* self = GTV_CALC_HEADER_BAR(pWidget); + GtvCalcHeaderBarPrivate& priv = getPrivate(GTV_CALC_HEADER_BAR(self)); + cairo_set_source_rgb(pCairo, 0, 0, 0); + + int nPrevious = 0; + for (const GtvCalcHeaderBarPrivateImpl::Header& rHeader : priv->m_aHeaders) + { + GdkRectangle aRectangle; + if (priv->m_eType == CalcHeaderType::ROW) + { + aRectangle.x = 0; + aRectangle.y = nPrevious; + aRectangle.width = ROW_HEADER_WIDTH - 1; + aRectangle.height = rHeader.m_nSize - nPrevious; + // Left line. + cairo_rectangle(pCairo, aRectangle.x, aRectangle.y, 1, aRectangle.height); + cairo_fill(pCairo); + // Bottom line. + cairo_rectangle(pCairo, aRectangle.x, aRectangle.y + aRectangle.height, aRectangle.width, 1); + cairo_fill(pCairo); + // Right line. + cairo_rectangle(pCairo, aRectangle.width, aRectangle.y, 1, aRectangle.height); + cairo_fill(pCairo); + } + else if (priv->m_eType == CalcHeaderType::COLUMN) + { + aRectangle.x = nPrevious; + aRectangle.y = 0; + aRectangle.width = rHeader.m_nSize - nPrevious; + aRectangle.height = COLUMN_HEADER_HEIGHT - 1; + // Top line. + cairo_rectangle(pCairo, aRectangle.x, aRectangle.y, aRectangle.width, 1); + cairo_fill(pCairo); + // Right line. + cairo_rectangle(pCairo, aRectangle.x + aRectangle.width , aRectangle.y, 1, aRectangle.height); + cairo_fill(pCairo); + // Bottom line. + cairo_rectangle(pCairo, aRectangle.x, aRectangle.height, aRectangle.width, 1); + cairo_fill(pCairo); + } + else + { + O3TL_UNREACHABLE; // should never happen + } + + gtv_calc_header_bar_draw_text(pCairo, aRectangle, rHeader.m_aText); + nPrevious = rHeader.m_nSize; + if (rHeader.m_nSize > self->m_nSizePixel) + break; + } + + if (priv->m_aHeaders.empty() && priv->m_eType == CalcHeaderType::CORNER) + { + GdkRectangle aRectangle; + aRectangle.x = 0; + aRectangle.y = 0; + aRectangle.width = ROW_HEADER_WIDTH - 1; + aRectangle.height = COLUMN_HEADER_HEIGHT - 1; + cairo_rectangle(pCairo, aRectangle.x, aRectangle.y, aRectangle.width, aRectangle.height); + cairo_stroke(pCairo); + } + + return false; +} + +static gboolean +gtv_calc_header_bar_draw(GtkWidget* bar, cairo_t* pCairo) +{ + return gtv_calc_header_bar_draw_impl(bar, pCairo); +} + +static void +gtv_calc_header_bar_class_init(GtvCalcHeaderBarClass* klass) +{ + GTK_WIDGET_CLASS(klass)->draw = gtv_calc_header_bar_draw; + G_OBJECT_CLASS(klass)->finalize = gtv_calc_header_bar_finalize; +} + +void gtv_calc_header_bar_configure(GtvCalcHeaderBar* bar, const boost::property_tree::ptree* values) +{ + GtvCalcHeaderBarPrivate& priv = getPrivate(bar); + priv->m_aHeaders.clear(); + + if (values) + { + boost::property_tree::ptree val = *values; + try + { + for (const boost::property_tree::ptree::value_type& rValue : val) + { + int nSize = std::round(std::atof(rValue.second.get<std::string>("size").c_str())); + if (nSize >= bar->m_nPositionPixel) + { + const int nScrolledSize = nSize - bar->m_nPositionPixel; + GtvCalcHeaderBarPrivateImpl::Header aHeader(nScrolledSize, rValue.second.get<std::string>("text")); + priv->m_aHeaders.push_back(aHeader); + } + } + } + catch (boost::property_tree::ptree_bad_path& rException) + { + std::cerr << "gtv_calc_header_bar_configure: " << rException.what() << std::endl; + } + } + gtk_widget_show(GTK_WIDGET(bar)); + gtk_widget_queue_draw(GTK_WIDGET(bar)); +} + +void +gtv_calc_header_bar_set_type_and_width(GtvCalcHeaderBar* bar, CalcHeaderType eType) +{ + // TODO: Install type property for this class + GtvCalcHeaderBarPrivate& priv = getPrivate(bar); + priv->m_eType = eType; + + if (eType == CalcHeaderType::ROW) + gtk_widget_set_size_request(GTK_WIDGET(bar), ROW_HEADER_WIDTH, -1); + else if (eType == CalcHeaderType::COLUMN) + gtk_widget_set_size_request(GTK_WIDGET(bar), -1, COLUMN_HEADER_HEIGHT); +} + +GtkWidget* +gtv_calc_header_bar_new() +{ + return GTK_WIDGET(g_object_new(GTV_TYPE_CALC_HEADER_BAR, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.hxx b/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.hxx new file mode 100644 index 000000000..a1a4d37a3 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-calc-header-bar.hxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_CALC_HEADER_BAR_H +#define GTV_CALC_HEADER_BAR_H + +#include <gtk/gtk.h> + +#include <boost/property_tree/ptree_fwd.hpp> + +G_BEGIN_DECLS + +#define GTV_TYPE_CALC_HEADER_BAR (gtv_calc_header_bar_get_type()) +#define GTV_CALC_HEADER_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_CALC_HEADER_BAR, GtvCalcHeaderBar)) +#define GTV_IS_CALC_HEADER_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_CALC_HEADER_BAR)) +#define GTV_CALC_HEADER_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_CALC_HEADER_BAR, GtvCalcHeaderBarClass)) +#define GTV_IS_CALC_HEADER_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_CALC_HEADER_BAR)) +#define GTV_CALC_HEADER_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_CALC_HEADER_BAR, GtvCalcHeaderBarClass)) + +struct GtvCalcHeaderBar +{ + GtkDrawingArea parent; + /// Height for row bar, width for column bar. + int m_nSizePixel; + /// Left/top position for the column/row bar -- initially 0, then may grow due to scrolling. + int m_nPositionPixel; +}; + +struct GtvCalcHeaderBarClass +{ + GtkDrawingAreaClass parentClass; +}; + +GType gtv_calc_header_bar_get_type (void) G_GNUC_CONST; + +enum CalcHeaderType { ROW, COLUMN, CORNER, NONE }; + +GtkWidget* gtv_calc_header_bar_new(); + +void gtv_calc_header_bar_configure(GtvCalcHeaderBar* bar, const boost::property_tree::ptree* values); + +int gtv_calc_header_bar_get_pos_pixel(GtvCalcHeaderBar* bar); + +int gtv_calc_header_bar_get_size_pixel(GtvCalcHeaderBar* bar); + +void gtv_calc_header_bar_set_type_and_width(GtvCalcHeaderBar* bar, CalcHeaderType eType); + +G_END_DECLS + +#endif /* GTV_CALC_HEADER_BAR_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.cxx b/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.cxx new file mode 100644 index 000000000..f63e77fd1 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.cxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include <iostream> + +#include "gtv-application-window.hxx" +#include "gtv-helpers.hxx" +#include "gtv-comments-sidebar.hxx" + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> + +#include <boost/property_tree/json_parser.hpp> + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE(GtvCommentsSidebar, gtv_comments_sidebar, GTK_TYPE_BOX); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +void +gtv_comments_sidebar_view_annotations(GtvCommentsSidebar* sidebar) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(sidebar))); + + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + char* pValues = pDocument->pClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); + g_info("lok::Document::getCommandValues(%s) : %s", ".uno:ViewAnnotations", pValues); + std::stringstream aStream(pValues); + free(pValues); + + // empty the comments grid + GtvGtkWrapper<GList> children(gtk_container_get_children(GTK_CONTAINER(sidebar->commentsgrid)), + [](GList* l) + { + g_list_free(l); + }); + GList* iter; + for (iter = children.get(); iter != nullptr; iter = g_list_next(iter)) + gtk_widget_destroy(GTK_WIDGET(iter->data)); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + try + { + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("comments")) + { + GtkWidget* pCommentBox = GtvHelpers::createCommentBox(rValue.second); + gtk_container_add(GTK_CONTAINER(sidebar->commentsgrid), pCommentBox); + } + gtk_widget_show_all(sidebar->scrolledwindow); + } + catch(boost::property_tree::ptree_bad_path& rException) + { + std::cerr << "CommentsSidebar::unoViewAnnotations: failed to get comments" << rException.what() << std::endl; + } +} + +static void +gtv_comments_sidebar_view_annotations_cb(GtkWidget* pWidget, gpointer) +{ + GtvCommentsSidebar* sidebar = GTV_COMMENTS_SIDEBAR(pWidget); + gtv_comments_sidebar_view_annotations(sidebar); +} + +static void +gtv_comments_sidebar_init(GtvCommentsSidebar* sidebar) +{ + sidebar->scrolledwindow = gtk_scrolled_window_new(nullptr, nullptr); + gtk_widget_set_vexpand(sidebar->scrolledwindow, true); + sidebar->commentsgrid = gtk_grid_new(); + g_object_set(sidebar->commentsgrid, "orientation", GTK_ORIENTATION_VERTICAL, nullptr); + + sidebar->viewannotationsButton = gtk_button_new_with_label(".uno:ViewAnnotations"); + // Hack to make sidebar grid wide enough to not need any horizontal scrollbar + gtk_widget_set_margin_start(sidebar->viewannotationsButton, 20); + gtk_widget_set_margin_end(sidebar->viewannotationsButton, 20); + gtk_container_add(GTK_CONTAINER(sidebar), sidebar->viewannotationsButton); + g_signal_connect_swapped(sidebar->viewannotationsButton, "clicked", G_CALLBACK(gtv_comments_sidebar_view_annotations_cb), sidebar); + + gtk_container_add(GTK_CONTAINER(sidebar), sidebar->scrolledwindow); + gtk_container_add(GTK_CONTAINER(sidebar->scrolledwindow), sidebar->commentsgrid); + + gtk_widget_show_all(GTK_WIDGET(sidebar)); +} + +static void +gtv_comments_sidebar_class_init(GtvCommentsSidebarClass* /*klass*/) +{ +} + +GtkWidget* +gtv_comments_sidebar_new() +{ + return GTK_WIDGET(g_object_new(GTV_TYPE_COMMENTS_SIDEBAR, + "orientation", GTK_ORIENTATION_VERTICAL, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.hxx b/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.hxx new file mode 100644 index 000000000..8ed096423 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-comments-sidebar.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_COMMENTS_SIDEBAR_H +#define GTV_COMMENTS_SIDEBAR_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GTV_TYPE_COMMENTS_SIDEBAR (gtv_comments_sidebar_get_type()) +#define GTV_COMMENTS_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_COMMENTS_SIDEBAR, GtvCommentsSidebar)) +#define GTV_IS_COMMENTS_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_COMMENTS_SIDEBAR)) +#define GTV_COMMENTS_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_COMMENTS_SIDEBAR, GtvCommentsSidebarClass)) +#define GTV_IS_COMMENTS_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_COMMENTS_SIDEBAR)) +#define GTV_COMMENTS_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_COMMENTS_SIDEBAR, GtvCommentsSidebarClass)) + +struct GtvCommentsSidebar +{ + GtkBox parent; + + GtkWidget* viewannotationsButton; + GtkWidget* scrolledwindow; + GtkWidget* commentsgrid; +}; + +struct GtvCommentsSidebarClass +{ + GtkBoxClass parentClass; +}; + +GType gtv_comments_sidebar_get_type (void) G_GNUC_CONST; + +GtkWidget* gtv_comments_sidebar_new(); + +void gtv_comments_sidebar_view_annotations(GtvCommentsSidebar* sidebar); + +G_END_DECLS + +#endif /* GTV_COMMENTS_SIDEBAR_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-helpers.cxx b/libreofficekit/qa/gtktiledviewer/gtv-helpers.cxx new file mode 100644 index 000000000..b557fe4ba --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-helpers.cxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include <pwd.h> + +#include <cstring> + +#include "gtv-helpers.hxx" +#include "gtv-signal-handlers.hxx" + +#include <boost/property_tree/ptree.hpp> + +void GtvHelpers::userPromptDialog(GtkWindow* pWindow, const std::string& aTitle, std::map<std::string, std::string>& aEntries) +{ + GtkWidget* pDialog = gtk_dialog_new_with_buttons (aTitle.c_str(), + pWindow, + GTK_DIALOG_MODAL, + "Ok", + GTK_RESPONSE_OK, + nullptr); + + GtkWidget* pDialogMessageArea = gtk_dialog_get_content_area (GTK_DIALOG (pDialog)); + GtkWidget* pEntryArea = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(pDialogMessageArea), pEntryArea); + for (const auto& entry : aEntries) + { + GtkWidget* pEntry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(pEntry), entry.first.c_str()); + gtk_container_add(GTK_CONTAINER(pEntryArea), pEntry); + } + + gtk_widget_show_all(pDialog); + + gint res = gtk_dialog_run(GTK_DIALOG(pDialog)); + switch(res) + { + case GTK_RESPONSE_OK: + GtvGtkWrapper<GList> pList(gtk_container_get_children(GTK_CONTAINER(pEntryArea)), + [](GList* l) + { + g_list_free(l); + }); + + for (GList* l = pList.get(); l != nullptr; l = l->next) + { + const gchar* pKey = gtk_entry_get_placeholder_text(GTK_ENTRY(l->data)); + aEntries[std::string(pKey)] = std::string(gtk_entry_get_text(GTK_ENTRY(l->data))); + } + break; + } + + gtk_widget_destroy(pDialog); +} + +/// Our GtkClipboardGetFunc implementation for HTML. +static void htmlGetFunc(GtkClipboard* /*pClipboard*/, GtkSelectionData* pSelectionData, guint /*info*/, gpointer pUserData) +{ + GdkAtom aAtom(gdk_atom_intern("text/html", false)); + const gchar* pSelection = static_cast<const gchar*>(pUserData); + gtk_selection_data_set(pSelectionData, aAtom, 8, reinterpret_cast<const guchar *>(pSelection), strlen(pSelection)); +} + +/// Our GtkClipboardClearFunc implementation for HTML. +static void htmlClearFunc(GtkClipboard* /*pClipboard*/, gpointer pData) +{ + g_free(pData); +} + +void GtvHelpers::clipboardSetHtml(GtkClipboard* pClipboard, const char* pSelection) +{ + GtvGtkWrapper<GtkTargetList> pList(gtk_target_list_new(nullptr, 0), + [](GtkTargetList* pTargetList) + { + gtk_target_list_unref(pTargetList); + }); + GdkAtom aAtom(gdk_atom_intern("text/html", false)); + gtk_target_list_add(pList.get(), aAtom, 0, 0); + gint nTargets = 0; + GtkTargetEntry* pTargets = gtk_target_table_new_from_list(pList.get(), &nTargets); + + gtk_clipboard_set_with_data(pClipboard, pTargets, nTargets, htmlGetFunc, htmlClearFunc, g_strdup(pSelection)); + + gtk_target_table_free(pTargets, nTargets); +} + +std::string GtvHelpers::getNextAuthor() +{ + static int nCounter = 0; + struct passwd* pPasswd = getpwuid(getuid()); + return std::string(pPasswd->pw_gecos) + " #" + std::to_string(++nCounter); +} + +GtkWidget* GtvHelpers::createCommentBox(const boost::property_tree::ptree& aComment) +{ + GtkWidget* pCommentVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); + gchar *id = g_strndup(aComment.get<std::string>("id").c_str(), 20); + g_object_set_data_full(G_OBJECT(pCommentVBox), "id", id, g_free); + + // Set background if it's a reply comment + if (aComment.get("parent", -1) > 0) + { + GtkStyleContext* pStyleContext = gtk_widget_get_style_context(pCommentVBox); + GtkCssProvider* pCssProvider = gtk_css_provider_new(); + gtk_style_context_add_class(pStyleContext, "commentbox"); + gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(pCssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_css_provider_load_from_data(pCssProvider, ".commentbox {background-color: lightgreen;}", -1, nullptr); + } + + GtkWidget* pCommentText = gtk_label_new(aComment.get<std::string>("text").c_str()); + GtkWidget* pCommentAuthor = gtk_label_new(aComment.get<std::string>("author").c_str()); + GtkWidget* pCommentDate = gtk_label_new(aComment.get<std::string>("dateTime").c_str()); + GtkWidget* pControlsHBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget* pEditButton = gtk_button_new_with_label("Edit"); + GtkWidget* pReplyButton = gtk_button_new_with_label("Reply"); + GtkWidget* pDeleteButton = gtk_button_new_with_label("Delete"); + g_signal_connect(G_OBJECT(pEditButton), "clicked", G_CALLBACK(editButtonClicked), pCommentVBox); + g_signal_connect(G_OBJECT(pReplyButton), "clicked", G_CALLBACK(replyButtonClicked), pCommentVBox); + g_signal_connect(G_OBJECT(pDeleteButton), "clicked", G_CALLBACK(deleteCommentButtonClicked), pCommentVBox); + + gtk_container_add(GTK_CONTAINER(pControlsHBox), pEditButton); + gtk_container_add(GTK_CONTAINER(pControlsHBox), pReplyButton); + gtk_container_add(GTK_CONTAINER(pControlsHBox), pDeleteButton); + GtkWidget* pCommentSeparator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + + gtk_container_add(GTK_CONTAINER(pCommentVBox), pCommentText); + gtk_container_add(GTK_CONTAINER(pCommentVBox), pCommentAuthor); + gtk_container_add(GTK_CONTAINER(pCommentVBox), pCommentDate); + gtk_container_add(GTK_CONTAINER(pCommentVBox), pControlsHBox); + gtk_container_add(GTK_CONTAINER(pCommentVBox), pCommentSeparator); + + gtk_label_set_line_wrap(GTK_LABEL(pCommentText), true); + gtk_label_set_max_width_chars(GTK_LABEL(pCommentText), 35); + + return pCommentVBox; +} + +std::string GtvHelpers::getDirPath(const std::string& filePath) +{ + int position = filePath.find_last_of('/'); + const std::string dirPath = filePath.substr(0, position + 1); + return dirPath; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-helpers.hxx b/libreofficekit/qa/gtktiledviewer/gtv-helpers.hxx new file mode 100644 index 000000000..4b304ab89 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-helpers.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_HELPERS_H +#define GTV_HELPERS_H + +#include <gtk/gtk.h> + +#include <map> +#include <sstream> +#include <string> +#include <vector> +#include <memory> + +#include <boost/property_tree/ptree_fwd.hpp> + +#define UI_FILE_NAME "gtv.ui" + +// Wrapper with custom deleter to use for Gtk objects +template <class T> +using GtvGtkWrapper = std::unique_ptr<T, void(*)(T*)>; + +namespace GtvHelpers +{ + void userPromptDialog(GtkWindow* pWindow, const std::string& aTitle, std::map<std::string, std::string>& aEntries); + + void clipboardSetHtml(GtkClipboard* pClipboard, const char* pSelection); + + /// Generate an author string for multiple views. + std::string getNextAuthor(); + + GtkWidget* createCommentBox(const boost::property_tree::ptree& aComment); + + std::string getDirPath(const std::string& filePath); + + template<typename T> + std::vector<T> split(const std::string& aPayload, const std::string& aDelim, const int nItems) + { + std::vector<T> aRet; + + if (!aPayload.empty()) + { + gchar** ppCoordinates = g_strsplit(aPayload.c_str(), aDelim.c_str(), nItems); + gchar** ppCoordinate = ppCoordinates; + while (*ppCoordinate) + { + std::stringstream strstream(*ppCoordinate); + T item; + strstream >> item; + aRet.push_back(item); + ++ppCoordinate; + } + g_strfreev(ppCoordinates); + } + + return aRet; + } +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.cxx b/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.cxx new file mode 100644 index 000000000..f23148eaf --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.cxx @@ -0,0 +1,717 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include <iostream> + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include "gtv-application-window.hxx" +#include "gtv-lok-dialog.hxx" + +#include <com/sun/star/awt/Key.hpp> + +#include <o3tl/unit_conversion.hxx> +#include <vcl/event.hxx> + +namespace { + +struct GtvLokDialogPrivate +{ + LOKDocView* lokdocview; + GtkWidget* pDialogDrawingArea; + GtkWidget* pFloatingWin; + + // state for dialog + guint32 m_nLastButtonPressTime; + guint32 m_nLastButtonReleaseTime; + guint32 m_nKeyModifier; + guint32 m_nLastButtonPressed; + guint32 m_nWidth; + guint32 m_nHeight; + + // state for child floating windows + guint32 m_nChildId; + guint32 m_nChildWidth; + guint32 m_nChildHeight; + guint32 m_nChildLastButtonPressTime; + guint32 m_nChildLastButtonReleaseTime; + guint32 m_nChildKeyModifier; + guint32 m_nChildLastButtonPressed; + + guint dialogid; +}; + +} + +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE_WITH_PRIVATE(GtvLokDialog, gtv_lok_dialog, GTK_TYPE_DIALOG); +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic pop +#endif +#endif + +enum +{ + PROP_0, + PROP_LOKDOCVIEW_CONTEXT, + PROP_DIALOG_ID, + PROP_DIALOG_WIDTH, + PROP_DIALOG_HEIGHT, + PROP_LAST +}; + +static GParamSpec* properties[PROP_LAST]; + +static GtvLokDialogPrivate* +getPrivate(GtvLokDialog* dialog) +{ + return static_cast<GtvLokDialogPrivate*>(gtv_lok_dialog_get_instance_private(dialog)); +} + +static void +gtv_lok_dialog_draw(GtkWidget* pDialogDrawingArea, cairo_t* pCairo, gpointer) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(gtk_widget_get_toplevel(pDialogDrawingArea)); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + GdkRectangle aRect; + gdk_cairo_get_clip_rectangle(pCairo, &aRect); + g_info("Painting dialog region: %d, %d, %d, %d", aRect.x, aRect.y, aRect.width, aRect.height); + + int nWidth = priv->m_nWidth; + int nHeight = priv->m_nHeight; + if (aRect.width != 0 && aRect.height != 0) + { + nWidth = aRect.width; + nHeight = aRect.height; + } + + cairo_surface_t* pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight); + unsigned char* pBuffer = cairo_image_surface_get_data(pSurface); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(priv->lokdocview)); + pDocument->pClass->paintWindow(pDocument, priv->dialogid, pBuffer, aRect.x, aRect.y, nWidth, nHeight); + + gtk_widget_set_size_request(GTK_WIDGET(pDialogDrawingArea), priv->m_nWidth, priv->m_nHeight); + + cairo_surface_flush(pSurface); + cairo_surface_mark_dirty(pSurface); + + cairo_set_source_surface(pCairo, pSurface, aRect.x, aRect.y); + // paint the dialog image + cairo_paint(pCairo); + + // debug red-colored border around the painted region + cairo_set_source_rgb(pCairo, 1.0, 0, 0); + cairo_rectangle(pCairo, aRect.x, aRect.y, nWidth, nHeight); + cairo_stroke(pCairo); +} + +static gboolean +gtv_lok_dialog_signal_button(GtkWidget* pDialogDrawingArea, GdkEventButton* pEvent) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(gtk_widget_get_toplevel(pDialogDrawingArea)); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(pDialog))); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + + std::string aEventType = "unknown"; + if (pEvent->type == GDK_BUTTON_PRESS) + aEventType = "BUTTON_PRESS"; + else if (pEvent->type == GDK_BUTTON_RELEASE) + aEventType = "BUTTON_RELEASE"; + + g_info("lok_dialog_signal_button (type: %s): %d, %d", + aEventType.c_str(), + static_cast<int>(pEvent->x), static_cast<int>(pEvent->y)); + gtk_widget_grab_focus(pDialogDrawingArea); + + switch (pEvent->type) + { + case GDK_BUTTON_PRESS: + { + int nCount = 1; + if ((pEvent->time - priv->m_nLastButtonPressTime) < 250) + nCount++; + priv->m_nLastButtonPressTime = pEvent->time; + int nEventButton = 0; + switch (pEvent->button) + { + case 1: + nEventButton = MOUSE_LEFT; + break; + case 2: + nEventButton = MOUSE_MIDDLE; + break; + case 3: + nEventButton = MOUSE_RIGHT; + break; + } + priv->m_nLastButtonPressed = nEventButton; + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->dialogid, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + (pEvent->x), + (pEvent->y), + nCount, + nEventButton, + priv->m_nKeyModifier); + + break; + } + case GDK_BUTTON_RELEASE: + { + int nCount = 1; + if ((pEvent->time - priv->m_nLastButtonReleaseTime) < 250) + nCount++; + priv->m_nLastButtonReleaseTime = pEvent->time; + int nEventButton = 0; + switch (pEvent->button) + { + case 1: + nEventButton = MOUSE_LEFT; + break; + case 2: + nEventButton = MOUSE_MIDDLE; + break; + case 3: + nEventButton = MOUSE_RIGHT; + break; + } + priv->m_nLastButtonPressed = nEventButton; + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->dialogid, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + (pEvent->x), + (pEvent->y), + nCount, + nEventButton, + priv->m_nKeyModifier); + break; + } + default: + break; + } + return FALSE; +} + +static gboolean +gtv_lok_dialog_signal_motion(GtkWidget* pDialogDrawingArea, GdkEventButton* pEvent) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(gtk_widget_get_toplevel(pDialogDrawingArea)); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(pDialog))); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + + g_info("lok_dialog_signal_motion: %d, %d (in twips: %d, %d)", + static_cast<int>(pEvent->x), static_cast<int>(pEvent->y), + static_cast<int>(o3tl::toTwips(pEvent->x, o3tl::Length::px)), + static_cast<int>(o3tl::toTwips(pEvent->y, o3tl::Length::px))); + + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->dialogid, + LOK_MOUSEEVENT_MOUSEMOVE, + (pEvent->x), + (pEvent->y), + 1, + priv->m_nLastButtonPressed, + priv->m_nKeyModifier); + + return FALSE; +} + +static gboolean +gtv_lok_dialog_signal_key(GtkWidget* pDialogDrawingArea, GdkEventKey* pEvent) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(gtk_widget_get_toplevel(pDialogDrawingArea)); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(pDialog))); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + + g_info("lok_dialog_signal_key"); + int nCharCode = 0; + int nKeyCode = 0; + priv->m_nKeyModifier &= KEY_MOD2; + switch (pEvent->keyval) + { + case GDK_KEY_BackSpace: + nKeyCode = com::sun::star::awt::Key::BACKSPACE; + break; + case GDK_KEY_Delete: + nKeyCode = com::sun::star::awt::Key::DELETE; + break; + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + nKeyCode = com::sun::star::awt::Key::RETURN; + break; + case GDK_KEY_Escape: + nKeyCode = com::sun::star::awt::Key::ESCAPE; + break; + case GDK_KEY_Tab: + nKeyCode = com::sun::star::awt::Key::TAB; + break; + case GDK_KEY_Down: + nKeyCode = com::sun::star::awt::Key::DOWN; + break; + case GDK_KEY_Up: + nKeyCode = com::sun::star::awt::Key::UP; + break; + case GDK_KEY_Left: + nKeyCode = com::sun::star::awt::Key::LEFT; + break; + case GDK_KEY_Right: + nKeyCode = com::sun::star::awt::Key::RIGHT; + break; + case GDK_KEY_Page_Down: + nKeyCode = com::sun::star::awt::Key::PAGEDOWN; + break; + case GDK_KEY_Page_Up: + nKeyCode = com::sun::star::awt::Key::PAGEUP; + break; + case GDK_KEY_Insert: + nKeyCode = com::sun::star::awt::Key::INSERT; + break; + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + if (pEvent->type == GDK_KEY_PRESS) + priv->m_nKeyModifier |= KEY_SHIFT; + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + if (pEvent->type == GDK_KEY_PRESS) + priv->m_nKeyModifier |= KEY_MOD1; + break; + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + if (pEvent->type == GDK_KEY_PRESS) + priv->m_nKeyModifier |= KEY_MOD2; + else + priv->m_nKeyModifier &= ~KEY_MOD2; + break; + default: + if (pEvent->keyval >= GDK_KEY_F1 && pEvent->keyval <= GDK_KEY_F26) + nKeyCode = com::sun::star::awt::Key::F1 + (pEvent->keyval - GDK_KEY_F1); + else + nCharCode = gdk_keyval_to_unicode(pEvent->keyval); + } + + // rsc is not public API, but should be good enough for debugging purposes. + // If this is needed for real, then probably a new param of type + // css::awt::KeyModifier is needed in postKeyEvent(). + if (pEvent->state & GDK_SHIFT_MASK) + nKeyCode |= KEY_SHIFT; + + if (pEvent->state & GDK_CONTROL_MASK) + nKeyCode |= KEY_MOD1; + + if (priv->m_nKeyModifier & KEY_MOD2) + nKeyCode |= KEY_MOD2; + + if (nKeyCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) { + if (pEvent->keyval >= GDK_KEY_a && pEvent->keyval <= GDK_KEY_z) + { + nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_a); + } + else if (pEvent->keyval >= GDK_KEY_A && pEvent->keyval <= GDK_KEY_Z) { + nKeyCode |= 512 + (pEvent->keyval - GDK_KEY_A); + } + else if (pEvent->keyval >= GDK_KEY_0 && pEvent->keyval <= GDK_KEY_9) { + nKeyCode |= 256 + (pEvent->keyval - GDK_KEY_0); + } + } + + std::stringstream ss; + ss << "gtv_lok_dialog::postKey(" << pEvent->type << ", " << nCharCode << ", " << nKeyCode << ")"; + g_info("%s", ss.str().c_str()); + + pDocument->pClass->postWindowKeyEvent(pDocument, + priv->dialogid, + pEvent->type == GDK_KEY_RELEASE ? LOK_KEYEVENT_KEYUP : LOK_KEYEVENT_KEYINPUT, + nCharCode, + nKeyCode); + + return FALSE; +} + +static void +gtv_lok_dialog_init(GtvLokDialog* dialog) +{ + GtvLokDialogPrivate* priv = getPrivate(dialog); + + GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + priv->pDialogDrawingArea = gtk_drawing_area_new(); + priv->pFloatingWin = nullptr; + priv->m_nChildId = 0; + priv->m_nChildWidth = 0; + priv->m_nChildHeight = 0; + + priv->m_nLastButtonPressTime = 0; + priv->m_nLastButtonReleaseTime = 0; + priv->m_nKeyModifier = 0; + priv->m_nLastButtonPressed = 0; + + gtk_widget_add_events(priv->pDialogDrawingArea, + GDK_BUTTON_PRESS_MASK + |GDK_BUTTON_RELEASE_MASK + |GDK_BUTTON_MOTION_MASK + |GDK_KEY_PRESS_MASK + |GDK_KEY_RELEASE_MASK); + // This is required to be able to capture key events on the drawing area + gtk_widget_set_can_focus(priv->pDialogDrawingArea, true); + + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "draw", G_CALLBACK(gtv_lok_dialog_draw), nullptr); + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "button-press-event", G_CALLBACK(gtv_lok_dialog_signal_button), nullptr); + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "button-release-event", G_CALLBACK(gtv_lok_dialog_signal_button), nullptr); + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "motion-notify-event", G_CALLBACK(gtv_lok_dialog_signal_motion), nullptr); + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "key-press-event", G_CALLBACK(gtv_lok_dialog_signal_key), nullptr); + g_signal_connect(G_OBJECT(priv->pDialogDrawingArea), "key-release-event", G_CALLBACK(gtv_lok_dialog_signal_key), nullptr); + gtk_container_add(GTK_CONTAINER(pContentArea), priv->pDialogDrawingArea); +} + +static void +gtv_lok_dialog_set_property(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) +{ + GtvLokDialog* self = GTV_LOK_DIALOG(object); + GtvLokDialogPrivate* priv = getPrivate(self); + + switch(propId) + { + case PROP_LOKDOCVIEW_CONTEXT: + priv->lokdocview = LOK_DOC_VIEW(g_value_get_object(value)); + break; + case PROP_DIALOG_ID: + priv->dialogid = g_value_get_uint(value); + break; + case PROP_DIALOG_WIDTH: + priv->m_nWidth = g_value_get_uint(value); + break; + case PROP_DIALOG_HEIGHT: + priv->m_nHeight = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec); + } + + //if (propId == PROP_DIALOG_WIDTH || propId == PROP_DIALOG_HEIGHT) + // gtk_widget_set_size_request(GTK_WIDGET(priv->pDialogDrawingArea), priv->m_nWidth, priv->m_nHeight); +} + +static void +gtv_lok_dialog_get_property(GObject* object, guint propId, GValue* value, GParamSpec* pspec) +{ + GtvLokDialog* self = GTV_LOK_DIALOG(object); + GtvLokDialogPrivate* priv = getPrivate(self); + + switch(propId) + { + case PROP_LOKDOCVIEW_CONTEXT: + g_value_set_object(value, priv->lokdocview); + break; + case PROP_DIALOG_ID: + g_value_set_uint(value, priv->dialogid); + break; + case PROP_DIALOG_WIDTH: + g_value_set_uint(value, priv->m_nWidth); + break; + case PROP_DIALOG_HEIGHT: + g_value_set_uint(value, priv->m_nHeight); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propId, pspec); + } +} + +static void +gtv_lok_dialog_class_init(GtvLokDialogClass* klass) +{ + G_OBJECT_CLASS(klass)->get_property = gtv_lok_dialog_get_property; + G_OBJECT_CLASS(klass)->set_property = gtv_lok_dialog_set_property; + + properties[PROP_LOKDOCVIEW_CONTEXT] = g_param_spec_object("lokdocview", + "LOKDocView Context", + "The LOKDocView context object to be used for dialog rendering", + LOK_TYPE_DOC_VIEW, + static_cast<GParamFlags>(G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_DIALOG_ID] = g_param_spec_uint("dialogid", + "Dialog identifier", + "Unique dialog identifier", + 0, G_MAXUINT, 0, + static_cast<GParamFlags>(G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_DIALOG_WIDTH] = g_param_spec_uint("width", + "Dialog width", + "Dialog width", + 0, 4096, 0, + static_cast<GParamFlags>(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_DIALOG_HEIGHT] = g_param_spec_uint("height", + "Dialog height", + "Dialog height", + 0, 2048, 0, + static_cast<GParamFlags>(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (G_OBJECT_CLASS(klass), PROP_LAST, properties); +} + +static void +gtv_lok_dialog_floating_win_draw(GtkWidget* pDrawingArea, cairo_t* pCairo, gpointer userdata) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(userdata); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + g_info("gtv_lok_dialog_floating_win_draw triggered"); + cairo_surface_t* pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, priv->m_nChildWidth, priv->m_nChildHeight); + unsigned char* pBuffer = cairo_image_surface_get_data(pSurface); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(priv->lokdocview)); + pDocument->pClass->paintWindow(pDocument, priv->m_nChildId, pBuffer, 0, 0, priv->m_nChildWidth, priv->m_nChildHeight); + + gtk_widget_set_size_request(GTK_WIDGET(pDrawingArea), priv->m_nChildWidth, priv->m_nChildHeight); + //gtk_widget_set_size_request(GTK_WIDGET(pDialog), nWidth, nHeight); + //gtk_window_resize(GTK_WINDOW(pDialog), nWidth, nHeight); + + cairo_surface_flush(pSurface); + cairo_surface_mark_dirty(pSurface); + + cairo_set_source_surface(pCairo, pSurface, 0, 0); + cairo_paint(pCairo); +} + +static gboolean +gtv_lok_dialog_floating_win_signal_button(GtkWidget* /*pDialogChildDrawingArea*/, GdkEventButton* pEvent, gpointer userdata) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(userdata); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(pDialog))); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + + std::string aEventType = "unknown"; + if (pEvent->type == GDK_BUTTON_PRESS) + aEventType = "BUTTON_PRESS"; + else if (pEvent->type == GDK_BUTTON_RELEASE) + aEventType = "BUTTON_RELEASE"; + + g_info("lok_dialog_floating_win_signal_button (type: %s): %d, %d (in twips: %d, %d)", + aEventType.c_str(), + static_cast<int>(pEvent->x), static_cast<int>(pEvent->y), + static_cast<int>(o3tl::toTwips(pEvent->x, o3tl::Length::px)), + static_cast<int>(o3tl::toTwips(pEvent->y, o3tl::Length::px))); + + switch (pEvent->type) + { + case GDK_BUTTON_PRESS: + { + int nCount = 1; + if ((pEvent->time - priv->m_nChildLastButtonPressTime) < 250) + nCount++; + priv->m_nChildLastButtonPressTime = pEvent->time; + int nEventButton = 0; + switch (pEvent->button) + { + case 1: + nEventButton = MOUSE_LEFT; + break; + case 2: + nEventButton = MOUSE_MIDDLE; + break; + case 3: + nEventButton = MOUSE_RIGHT; + break; + } + priv->m_nChildLastButtonPressed = nEventButton; + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->m_nChildId, + LOK_MOUSEEVENT_MOUSEBUTTONDOWN, + (pEvent->x), + (pEvent->y), + nCount, + nEventButton, + priv->m_nChildKeyModifier); + + break; + } + case GDK_BUTTON_RELEASE: + { + int nCount = 1; + if ((pEvent->time - priv->m_nChildLastButtonReleaseTime) < 250) + nCount++; + priv->m_nChildLastButtonReleaseTime = pEvent->time; + int nEventButton = 0; + switch (pEvent->button) + { + case 1: + nEventButton = MOUSE_LEFT; + break; + case 2: + nEventButton = MOUSE_MIDDLE; + break; + case 3: + nEventButton = MOUSE_RIGHT; + break; + } + priv->m_nChildLastButtonPressed = nEventButton; + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->m_nChildId, + LOK_MOUSEEVENT_MOUSEBUTTONUP, + (pEvent->x), + (pEvent->y), + nCount, + nEventButton, + priv->m_nChildKeyModifier); + break; + } + default: + break; + } + return FALSE; +} + +static gboolean +gtv_lok_dialog_floating_win_signal_motion(GtkWidget* /*pDialogDrawingArea*/, GdkEventButton* pEvent, gpointer userdata) +{ + GtvLokDialog* pDialog = GTV_LOK_DIALOG(userdata); + GtvLokDialogPrivate* priv = getPrivate(pDialog); + + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(pDialog))); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + + g_info("lok_dialog_floating_win_signal_motion: %d, %d (in twips: %d, %d)", + static_cast<int>(pEvent->x), static_cast<int>(pEvent->y), + static_cast<int>(o3tl::toTwips(pEvent->x, o3tl::Length::px)), + static_cast<int>(o3tl::toTwips(pEvent->y, o3tl::Length::px))); + + pDocument->pClass->postWindowMouseEvent(pDocument, + priv->m_nChildId, + LOK_MOUSEEVENT_MOUSEMOVE, + (pEvent->x), + (pEvent->y), + 1, + priv->m_nChildLastButtonPressed, + priv->m_nChildKeyModifier); + + return FALSE; +} + +// Public methods below + +void gtv_lok_dialog_invalidate(GtvLokDialog* dialog, const GdkRectangle& aRectangle) +{ + GtvLokDialogPrivate* priv = getPrivate(dialog); + if (aRectangle.width != 0 && aRectangle.height != 0) + gtk_widget_queue_draw_area(priv->pDialogDrawingArea, aRectangle.x, aRectangle.y, aRectangle.width, aRectangle.height); + else + gtk_widget_queue_draw(priv->pDialogDrawingArea); +} + +// checks if we are the parent of given childId +gboolean gtv_lok_dialog_is_parent_of(GtvLokDialog* dialog, guint childId) +{ + GtvLokDialogPrivate* priv = getPrivate(dialog); + + return priv->m_nChildId == childId; +} + +void gtv_lok_dialog_child_create(GtvLokDialog* dialog, guint childId, guint nX, guint nY, guint width, guint height) +{ + GtvLokDialogPrivate* priv = getPrivate(dialog); + + g_debug("Dialog [ %d ] child window [ %d] being created, with dimensions [%dx%d]@(%d,%d)", priv->dialogid, childId, width, height, nX, nY); + priv->pFloatingWin = gtk_window_new(GTK_WINDOW_POPUP); + priv->m_nChildId = childId; + priv->m_nChildWidth = width; + priv->m_nChildHeight = height; + GtkWidget* pDrawingArea = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(priv->pFloatingWin), pDrawingArea); + + gtk_window_set_transient_for(GTK_WINDOW(priv->pFloatingWin), GTK_WINDOW(dialog)); + gtk_window_set_destroy_with_parent(GTK_WINDOW(priv->pFloatingWin), true); + + gtk_widget_add_events(pDrawingArea, + GDK_BUTTON_PRESS_MASK + |GDK_POINTER_MOTION_MASK + |GDK_BUTTON_RELEASE_MASK + |GDK_BUTTON_MOTION_MASK); + + g_signal_connect(G_OBJECT(pDrawingArea), "draw", G_CALLBACK(gtv_lok_dialog_floating_win_draw), dialog); + g_signal_connect(G_OBJECT(pDrawingArea), "button-press-event", G_CALLBACK(gtv_lok_dialog_floating_win_signal_button), dialog); + g_signal_connect(G_OBJECT(pDrawingArea), "button-release-event", G_CALLBACK(gtv_lok_dialog_floating_win_signal_button), dialog); + g_signal_connect(G_OBJECT(pDrawingArea), "motion-notify-event", G_CALLBACK(gtv_lok_dialog_floating_win_signal_motion), dialog); + + gtk_widget_set_size_request(priv->pFloatingWin, 1, 1); + gtk_window_set_type_hint(GTK_WINDOW(priv->pFloatingWin), GDK_WINDOW_TYPE_HINT_POPUP_MENU); + gtk_window_set_screen(GTK_WINDOW(priv->pFloatingWin), gtk_window_get_screen(GTK_WINDOW(dialog))); + + gtk_widget_show_all(priv->pFloatingWin); + gtk_window_present(GTK_WINDOW(priv->pFloatingWin)); + gtk_widget_grab_focus(pDrawingArea); + + // Get the root coords of our new floating window + GdkWindow* pGdkWin = gtk_widget_get_window(GTK_WIDGET(dialog)); + int nrX = 0; + int nrY = 0; + gdk_window_get_root_coords(pGdkWin, nX, nY, &nrX, &nrY); + gtk_window_move(GTK_WINDOW(priv->pFloatingWin), nrX, nrY); +} + +void gtv_lok_dialog_child_invalidate(GtvLokDialog* dialog) +{ + GtvLokDialogPrivate* priv = getPrivate(dialog); + g_debug("Dialog [ %d ] child invalidate request", priv->dialogid); + gtk_widget_queue_draw(priv->pFloatingWin); +} + +void gtv_lok_dialog_child_close(GtvLokDialog* dialog) +{ + g_info("Dialog's floating window close"); + + GtvLokDialogPrivate* priv = getPrivate(dialog); + if (priv->pFloatingWin) + { + gtk_widget_destroy(priv->pFloatingWin); + priv->pFloatingWin = nullptr; + priv->m_nChildId = 0; + priv->m_nChildWidth = 0; + priv->m_nChildHeight = 0; + } +} + +GtkWidget* gtv_lok_dialog_new(LOKDocView* pDocView, guint dialogId, guint width, guint height) +{ + g_debug("Dialog [ %d ] of size: %d x %d created", dialogId, width, height); + GtkWindow* pWindow = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + return GTK_WIDGET(g_object_new(GTV_TYPE_LOK_DIALOG, + "lokdocview", pDocView, + "dialogid", dialogId, + "width", width, + "height", height, + "title", "LOK Dialog", + "modal", false, + "transient-for", pWindow, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.hxx b/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.hxx new file mode 100644 index 000000000..2a5bfb595 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-lok-dialog.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_LOK_DIALOG_H +#define GTV_LOK_DIALOG_H + +#include <gtk/gtk.h> + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> + +G_BEGIN_DECLS + +#define GTV_TYPE_LOK_DIALOG (gtv_lok_dialog_get_type()) +#define GTV_LOK_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_LOK_DIALOG, GtvLokDialog)) +#define GTV_IS_LOK_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_LOK_DIALOG)) +#define GTV_LOK_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_LOK_DIALOG, GtvLokDialogClass)) +#define GTV_IS_LOK_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_LOK_DIALOG)) +#define GTV_LOK_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_LOK_DIALOG, GtvLokDialogClass)) + +struct GtvLokDialog +{ + GtkDialog parent; +}; + +struct GtvLokDialogClass +{ + GtkDialogClass parentClass; +}; + +GType gtv_lok_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget* gtv_lok_dialog_new(LOKDocView* pDocView, guint dialogId, guint width, guint height); + +void gtv_lok_dialog_invalidate(GtvLokDialog* dialog, const GdkRectangle& aRectangle); + +void gtv_lok_dialog_child_create(GtvLokDialog* dialog, guint childId, guint nX, guint nY, guint width, guint height); + +void gtv_lok_dialog_child_invalidate(GtvLokDialog* dialog); + +void gtv_lok_dialog_child_close(GtvLokDialog* dialog); + +gboolean gtv_lok_dialog_is_parent_of(GtvLokDialog* dialog, guint childId); + +G_END_DECLS + +#endif /* GTV_LOK_DIALOG_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx new file mode 100644 index 000000000..8d1b3eb67 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include "gtv-application-window.hxx" +#include "gtv-helpers.hxx" +#include "gtv-calc-header-bar.hxx" +#include "gtv-comments-sidebar.hxx" +#include "gtv-lokdocview-signal-handlers.hxx" +#include "gtv-lok-dialog.hxx" + +#include <boost/property_tree/json_parser.hpp> + +static gboolean deleteLokDialog(GtkWidget* pWidget, GdkEvent* /*event*/, gpointer userdata) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(userdata); + g_info("deleteLokDialog"); + gtv_application_window_unregister_child_window(window, GTK_WINDOW(pWidget)); + + return FALSE; +} + +static gboolean destroyLokDialog(GtkWidget* pWidget, gpointer userdata) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(userdata); + g_info("destroyLokDialog"); + gtv_application_window_unregister_child_window(window, GTK_WINDOW(pWidget)); + + return FALSE; +} + +void LOKDocViewSigHandlers::editChanged(LOKDocView* pDocView, gboolean bWasEdit, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + bool bEdit = lok_doc_view_get_edit(LOK_DOC_VIEW(window->lokdocview)); + g_info("signalEdit: %d -> %d", bWasEdit, bEdit); + + // Let the main toolbar know, so that it can enable disable the button + GtvMainToolbar* pMainToolbar = gtv_application_window_get_main_toolbar(GTV_APPLICATION_WINDOW(window)); + gtv_main_toolbar_set_edit(pMainToolbar, bEdit); +} + +void LOKDocViewSigHandlers::commandChanged(LOKDocView* pDocView, char* pPayload, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + std::string aPayload(pPayload); + size_t nPosition = aPayload.find('='); + if (nPosition == std::string::npos) + return; + + const std::string aKey = aPayload.substr(0, nPosition); + const std::string aValue = aPayload.substr(nPosition + 1); + GtkToolItem* pItem = gtv_application_window_find_tool_by_unocommand(window, aKey); + if (pItem != nullptr) + { + if (aValue == "true" || aValue == "false") { + bool bEdit = aValue == "true"; + if (bool(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pItem))) != bEdit) + { + // Avoid invoking lok_doc_view_post_command(). + // FIXME: maybe block/unblock the signal (see + // g_signal_handlers_block_by_func) ? + gtv_application_window_set_toolbar_broadcast(window, false); + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pItem), bEdit); + gtv_application_window_set_toolbar_broadcast(window, true); + + } + } else if (aValue == "enabled" || aValue == "disabled") { + bool bSensitive = aValue == "enabled"; + gtk_widget_set_sensitive(GTK_WIDGET(pItem), bSensitive); + + // Remember state, so in case edit is disable and enabled + // later, the correct sensitivity can be restored. + GtvMainToolbar* pMainToolbar = gtv_application_window_get_main_toolbar(window); + gtv_main_toolbar_set_sensitive_internal(pMainToolbar, pItem, bSensitive); + } + } + else if (aKey == ".uno:TrackedChangeIndex") + { + std::string aText("Current redline: "); + if (aValue.empty()) + aText += "none"; + else + aText += aValue; + gtk_label_set_text(GTK_LABEL(window->redlinelabel), aText.c_str()); + } +} + +void LOKDocViewSigHandlers::commandResult(LOKDocView*, char* pPayload, gpointer) +{ + fprintf(stderr, "Command finished: %s\n", pPayload); +} + +void LOKDocViewSigHandlers::searchNotFound(LOKDocView* pDocView, char* , gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + gtk_label_set_text(GTK_LABEL(window->findbarlabel), "Search key not found"); +} + +void LOKDocViewSigHandlers::searchResultCount(LOKDocView* pDocView, char* pPayload, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + std::stringstream ss; + ss << pPayload << " match(es)"; + gtk_label_set_text(GTK_LABEL(window->findbarlabel), ss.str().c_str()); +} + +void LOKDocViewSigHandlers::partChanged(LOKDocView* /*pDocView*/, int, gpointer) +{ +// GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + //rWindow.m_bPartSelectorBroadcast = false; +// gtk_combo_box_set_active(GTK_COMBO_BOX(rWindow.m_pPartSelector), nPart); + // rWindow.m_bPartSelectorBroadcast = true; +} + +void LOKDocViewSigHandlers::hyperlinkClicked(LOKDocView* pDocView, char* pPayload, gpointer) +{ + GError* pError = nullptr; +#if GTK_CHECK_VERSION(3,22,0) + gtk_show_uri_on_window( + GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(pDocView))), + pPayload, GDK_CURRENT_TIME, &pError); +#else + (void) pDocView; + gtk_show_uri(nullptr, pPayload, GDK_CURRENT_TIME, &pError); +#endif + if (pError != nullptr) + { + g_warning("Unable to show URI %s : %s", pPayload, pError->message); + g_error_free(pError); + } +} + +void LOKDocViewSigHandlers::cursorChanged(LOKDocView* pDocView, gint nX, gint nY, + gint /*nWidth*/, gint /*nHeight*/, gpointer /*pData*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GtkAdjustment* vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(window->scrolledwindow)); + GtkAdjustment* hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(window->scrolledwindow)); + GdkRectangle visArea; + gdouble upper; + gint x = -1, y = -1; + + gtv_application_window_get_visible_area(window, &visArea); + + // check vertically + if (nY < visArea.y) + { + y = nY - visArea.height/2; + if (y < 0) + y = gtk_adjustment_get_lower(vadj); + } + else if (nY > visArea.y + visArea.height) + { + y = nY - visArea.height/2; + upper = lok_doc_view_pixel_to_twip(pDocView, gtk_adjustment_get_upper(vadj)); + if (y > upper) + y = upper; + + } + + if (nX < visArea.x) + { + x = nX - visArea.width/2; + if (x < 0) + x = gtk_adjustment_get_lower(hadj); + } + else if (nX > visArea.x + visArea.width) + { + x = nX - visArea.width/2; + upper = lok_doc_view_pixel_to_twip(pDocView, gtk_adjustment_get_upper(hadj)); + if (x > upper) + x = upper; + } + + if (y!=-1) + gtk_adjustment_set_value(vadj, lok_doc_view_twip_to_pixel(pDocView, y)); + if (x!=-1) + gtk_adjustment_set_value(hadj, lok_doc_view_twip_to_pixel(pDocView, x)); +} + +void LOKDocViewSigHandlers::addressChanged(LOKDocView* pDocView, char* pPayload, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GtvMainToolbar* toolbar = gtv_application_window_get_main_toolbar(window); + GtkEntry* pAddressbar = GTK_ENTRY(toolbar->m_pAddressbar); + gtk_entry_set_text(pAddressbar, pPayload); +} + +void LOKDocViewSigHandlers::formulaChanged(LOKDocView* pDocView, char* pPayload, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GtvMainToolbar* toolbar = gtv_application_window_get_main_toolbar(window); + GtkEntry* pFormulabar = GTK_ENTRY(toolbar->m_pFormulabar); + gtk_entry_set_text(pFormulabar, pPayload); +} + +void LOKDocViewSigHandlers::contentControl(LOKDocView* pDocView, gchar* pJson, gpointer) +{ + GtvApplicationWindow* window + = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GtvMainToolbar* toolbar = gtv_application_window_get_main_toolbar(window); + gtv_application_window_set_part_broadcast(window, false); + gtk_list_store_clear( + GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(toolbar->m_pContentControlSelector)))); + if (!window->lokdocview) + { + return; + } + + std::stringstream aStream(pJson); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + boost::optional<boost::property_tree::ptree&> oItems = aTree.get_child_optional("items"); + if (oItems) + { + for (const auto& rItem : *oItems) + { + std::string aValue = rItem.second.get_value<std::string>(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(toolbar->m_pContentControlSelector), + aValue.c_str()); + } + } + + boost::optional<boost::property_tree::ptree&> oDate = aTree.get_child_optional("date"); + gtk_widget_set_sensitive(GTK_WIDGET(toolbar->m_pContentControlDateSelector), bool(oDate)); + + gtv_application_window_set_part_broadcast(window, true); +} + +void LOKDocViewSigHandlers::passwordRequired(LOKDocView* pDocView, char* pUrl, gboolean bModify, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + GtkWidget* pPasswordDialog = gtk_dialog_new_with_buttons ("Password required", + GTK_WINDOW (window), + GTK_DIALOG_MODAL, + "OK", + GTK_RESPONSE_OK, + nullptr); + g_object_set(G_OBJECT(pPasswordDialog), "resizable", FALSE, nullptr); + GtkWidget* pDialogMessageArea = gtk_dialog_get_content_area (GTK_DIALOG (pPasswordDialog)); + GtkWidget* pPasswordEntry = gtk_entry_new (); + gtk_entry_set_visibility (GTK_ENTRY(pPasswordEntry), FALSE); + gtk_entry_set_invisible_char (GTK_ENTRY(pPasswordEntry), '*'); + gtk_box_pack_end(GTK_BOX(pDialogMessageArea), pPasswordEntry, true, true, 2); + if (bModify) + { + GtkWidget* pSecondaryLabel = gtk_label_new ("Document requires password to edit"); + gtk_box_pack_end(GTK_BOX(pDialogMessageArea), pSecondaryLabel, true, true, 2); + gtk_dialog_add_button (GTK_DIALOG (pPasswordDialog), "Open as read-only", GTK_RESPONSE_ACCEPT); + } + gtk_widget_show_all(pPasswordDialog); + + gint res = gtk_dialog_run (GTK_DIALOG(pPasswordDialog)); + switch (res) + { + case GTK_RESPONSE_OK: + lok_doc_view_set_document_password (LOK_DOC_VIEW(window->lokdocview), pUrl, gtk_entry_get_text(GTK_ENTRY(pPasswordEntry))); + break; + case GTK_RESPONSE_ACCEPT: + // User accepts to open this document as read-only + case GTK_RESPONSE_DELETE_EVENT: + lok_doc_view_set_document_password (LOK_DOC_VIEW(window->lokdocview), pUrl, nullptr); + break; + } + + gtk_widget_destroy(pPasswordDialog); +} + +void LOKDocViewSigHandlers::comment(LOKDocView* pDocView, gchar* pComment, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView))); + + std::stringstream aStream(pComment); + boost::property_tree::ptree aRoot; + boost::property_tree::read_json(aStream, aRoot); + boost::property_tree::ptree aComment = aRoot.get_child("comment"); + GtvCommentsSidebar* sidebar = GTV_COMMENTS_SIDEBAR(window->commentssidebar); + GtkWidget* pCommentsGrid = sidebar->commentsgrid; + GtvGtkWrapper<GList> pChildren(gtk_container_get_children(GTK_CONTAINER(pCommentsGrid)), + [](GList* l) + { + g_list_free(l); + }); + GtkWidget* pSelf = nullptr; + GtkWidget* pParent = nullptr; + for (GList* l = pChildren.get(); l != nullptr; l = l->next) + { + gchar *id = static_cast<gchar*>(g_object_get_data(G_OBJECT(l->data), "id")); + + if (g_strcmp0(id, aComment.get<std::string>("id").c_str()) == 0) + pSelf = GTK_WIDGET(l->data); + + // There is no 'parent' in Remove callbacks + if (g_strcmp0(id, aComment.get("parent", std::string("0")).c_str()) == 0) + pParent = GTK_WIDGET(l->data); + } + + if (aComment.get<std::string>("action") == "Remove") + { + if (pSelf) + gtk_widget_destroy(pSelf); + else + g_warning("Can't find the comment to remove in the list !!"); + } + else if (aComment.get<std::string>("action") == "Add" || aComment.get<std::string>("action") == "Modify") + { + GtkWidget* pCommentBox = GtvHelpers::createCommentBox(aComment); + if (pSelf != nullptr || pParent != nullptr) + { + gtk_grid_insert_next_to(GTK_GRID(pCommentsGrid), pSelf != nullptr ? pSelf : pParent, GTK_POS_BOTTOM); + gtk_grid_attach_next_to(GTK_GRID(pCommentsGrid), pCommentBox, pSelf != nullptr ? pSelf : pParent, GTK_POS_BOTTOM, 1, 1); + } + else + gtk_container_add(GTK_CONTAINER(pCommentsGrid), pCommentBox); + + gtk_widget_show_all(pCommentBox); + + // We added the widget already below the existing one, so destroy the + // already existing one now + if (pSelf) + gtk_widget_destroy(pSelf); + } +} + +void LOKDocViewSigHandlers::window(LOKDocView* pDocView, gchar* pPayload, gpointer pData) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(pData); + + std::stringstream aStream(pPayload); + boost::property_tree::ptree aRoot; + boost::property_tree::read_json(aStream, aRoot); + const unsigned nWinId = aRoot.get<unsigned>("id"); + const std::string aAction = aRoot.get<std::string>("action"); + + if (aAction == "created") + { + const std::string aType = aRoot.get<std::string>("type"); + const std::string aSize = aRoot.get<std::string>("size"); + const std::string aTitle = aRoot.get<std::string>("title", ""); + std::vector<int> aSizePoints = GtvHelpers::split<int>(aSize, ", ", 2); + + if (aType == "dialog") + { + GtkWidget* pDialog = gtv_lok_dialog_new(pDocView, nWinId, aSizePoints[0], aSizePoints[1]); + g_info("created dialog, for dialogid: %d with size: %s", nWinId, aSize.c_str()); + + gtv_application_window_register_child_window(window, GTK_WINDOW(pDialog)); + g_signal_connect(pDialog, "destroy", G_CALLBACK(destroyLokDialog), window); + g_signal_connect(pDialog, "delete-event", G_CALLBACK(deleteLokDialog), window); + + if (!aTitle.empty()) + gtk_window_set_title(GTK_WINDOW(pDialog), aTitle.c_str()); + + gtk_window_set_resizable(GTK_WINDOW(pDialog), false); + gtk_widget_show_all(GTK_WIDGET(pDialog)); + gtk_window_present(GTK_WINDOW(pDialog)); + } + else if (aType == "child") + { + const unsigned nParentId = std::atoi(aRoot.get<std::string>("parentId").c_str()); + GtkWindow* pDialog = gtv_application_window_get_child_window_by_id(window, nParentId); + const std::string aPos = aRoot.get<std::string>("position"); + std::vector<int> aPosPoints = GtvHelpers::split<int>(aPos, ", ", 2); + gtv_lok_dialog_child_create(GTV_LOK_DIALOG(pDialog), nWinId, aPosPoints[0], aPosPoints[1], aSizePoints[0], aSizePoints[1]); + } + } + else + { + // check if it's a child window + GtkWidget* pParent = gtv_application_window_get_parent(window, nWinId); + if (pParent) // it's a floating window in the dialog + { + if (aAction == "invalidate") + gtv_lok_dialog_child_invalidate(GTV_LOK_DIALOG(pParent)); + else if (aAction == "close") + gtv_lok_dialog_child_close(GTV_LOK_DIALOG(pParent)); + } + else if (GtkWindow* pDialog = gtv_application_window_get_child_window_by_id(window, nWinId)) + { // it's the dialog window itself + if (aAction == "close") + gtk_widget_destroy(GTK_WIDGET(pDialog)); + else if (aAction == "size_changed") + { + const std::string aSize = aRoot.get<std::string>("size"); + std::vector<int> aSizePoints = GtvHelpers::split<int>(aSize, ", ", 2); + if (aSizePoints.size() != 2) + { + g_error("Malformed size_changed callback"); + return; + } + + g_object_set(G_OBJECT(pDialog), + "width", aSizePoints[0], + "height", aSizePoints[1], + nullptr); + + GdkRectangle aGdkRectangle = {0, 0, 0, 0}; + gtv_lok_dialog_invalidate(GTV_LOK_DIALOG(pDialog), aGdkRectangle); + } + else if (aAction == "invalidate") + { + GdkRectangle aGdkRectangle = {0, 0, 0, 0}; + try + { + const std::string aRectangle = aRoot.get<std::string>("rectangle"); + std::vector<int> aRectPoints = GtvHelpers::split<int>(aRectangle, ", ", 4); + if (aRectPoints.size() == 4) + aGdkRectangle = {aRectPoints[0], aRectPoints[1], aRectPoints[2], aRectPoints[3]}; + } + catch(const std::exception&) + {} + + gtv_lok_dialog_invalidate(GTV_LOK_DIALOG(pDialog), aGdkRectangle); + } + else if (aAction == "title_changed") + { + const std::string aTitle = aRoot.get<std::string>("title", ""); + gtk_window_set_title(pDialog, aTitle.c_str()); + } + } + } +} + +gboolean LOKDocViewSigHandlers::configureEvent(GtkWidget* pWidget, GdkEventConfigure* /*pEvent*/, gpointer /*pData*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pWidget))); + + gboolean isInit = false; + g_object_get(G_OBJECT(window->lokdocview), "is-initialized", &isInit, nullptr); + if (!isInit) + { + g_info("Ignoring configure event; document not yet ready"); + return false; + } + + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + if (!pDocument || pDocument->pClass->getDocumentType(pDocument) != LOK_DOCTYPE_SPREADSHEET) + return true; + + GtkAdjustment* pVAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(window->scrolledwindow)); + int rowSizePixel = GTV_CALC_HEADER_BAR(window->rowbar)->m_nSizePixel = gtk_adjustment_get_page_size(pVAdjustment); + int rowPosPixel = GTV_CALC_HEADER_BAR(window->rowbar)->m_nPositionPixel = gtk_adjustment_get_value(pVAdjustment); + GtkAdjustment* pHAdjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(window->scrolledwindow)); + int colSizePixel = GTV_CALC_HEADER_BAR(window->columnbar)->m_nSizePixel = gtk_adjustment_get_page_size(pHAdjustment); + int colPosPixel = GTV_CALC_HEADER_BAR(window->columnbar)->m_nPositionPixel = gtk_adjustment_get_value(pHAdjustment); + + std::stringstream aCommand; + aCommand << ".uno:ViewRowColumnHeaders"; + aCommand << "?x=" << int(lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(window->lokdocview), colPosPixel)); + aCommand << "&width=" << int(lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(window->lokdocview), colSizePixel)); + aCommand << "&y=" << int(lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(window->lokdocview), rowPosPixel)); + aCommand << "&height=" << int(lok_doc_view_pixel_to_twip(LOK_DOC_VIEW(window->lokdocview), rowSizePixel)); + std::stringstream ss; + ss << "lok::Document::getCommandValues(" << aCommand.str() << ")"; + g_info("%s", ss.str().c_str()); + char* pValues = pDocument->pClass->getCommandValues(pDocument, aCommand.str().c_str()); + g_info("lok::Document::getCommandValues() returned '%s'", pValues); + std::stringstream aStream(pValues); + free(pValues); + assert(!aStream.str().empty()); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + gtv_calc_header_bar_configure(GTV_CALC_HEADER_BAR(window->rowbar), &aTree.get_child("rows")); + gtv_calc_header_bar_configure(GTV_CALC_HEADER_BAR(window->columnbar), &aTree.get_child("columns")); + gtv_calc_header_bar_configure(GTV_CALC_HEADER_BAR(window->cornerarea), nullptr); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx new file mode 100644 index 000000000..0c5bb7113 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_LOKDOCVIEW_SIGNAL_HANDLERS_H +#define GTV_LOKDOCVIEW_SIGNAL_HANDLERS_H + +#include <gtk/gtk.h> +#include <LibreOfficeKit/LibreOfficeKitGtk.h> + +namespace LOKDocViewSigHandlers { + void editChanged(LOKDocView* pDocView, gboolean bWasEdit, gpointer); + void commandChanged(LOKDocView* pDocView, char* pPayload, gpointer); + void commandResult(LOKDocView*, char*, gpointer); + void searchNotFound(LOKDocView*, char*, gpointer); + void searchResultCount(LOKDocView*, char*, gpointer); + void partChanged(LOKDocView*, int, gpointer); + void hyperlinkClicked(LOKDocView*, char*, gpointer); + void cursorChanged(LOKDocView* pDocView, gint nX, gint nY, gint nWidth, gint nHeight, gpointer); + void addressChanged(LOKDocView* pDocView, char* pPayload, gpointer); + void formulaChanged(LOKDocView* pDocView, char* pPayload, gpointer); + void passwordRequired(LOKDocView* pDocView, char* pUrl, gboolean bModify, gpointer); + void comment(LOKDocView* pDocView, gchar* pComment, gpointer); + void window(LOKDocView* pDocView, gchar* pPayload, gpointer); + void contentControl(LOKDocView* pDocView, gchar* pComment, gpointer); + + gboolean configureEvent(GtkWidget* pWidget, GdkEventConfigure* pEvent, gpointer pData); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx new file mode 100644 index 000000000..84c5335b3 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include "gtv-application-window.hxx" +#include "gtv-main-toolbar.hxx" +#include "gtv-signal-handlers.hxx" +#include "gtv-helpers.hxx" + +#include <LibreOfficeKit/LibreOfficeKitGtk.h> + +#include <fstream> +#include <map> +#include <memory> + +namespace { + +struct GtvMainToolbarPrivateImpl +{ + GtkWidget* toolbar1; + GtkWidget* toolbar2; + + GtkWidget* m_pEnableEditing; + GtkWidget* m_pLeftpara; + GtkWidget* m_pCenterpara; + GtkWidget* m_pRightpara; + GtkWidget* m_pJustifypara; + GtkWidget* m_pDeleteComment; + GtkWidget* m_pPartSelector; + GtkWidget* m_pPartModeSelector; + GtkWidget* m_pRecentUnoSelector; + std::map<std::string, std::string> m_pRecentUnoCommands; + + /// Sensitivity (enabled or disabled) for each tool item, ignoring edit state + std::map<GtkToolItem*, bool> m_aToolItemSensitivities; + + GtvMainToolbarPrivateImpl() : + toolbar1(nullptr), + toolbar2(nullptr), + m_pEnableEditing(nullptr), + m_pLeftpara(nullptr), + m_pCenterpara(nullptr), + m_pRightpara(nullptr), + m_pJustifypara(nullptr), + m_pDeleteComment(nullptr), + m_pPartSelector(nullptr), + m_pPartModeSelector(nullptr), + m_pRecentUnoSelector(nullptr) + { } +}; + +struct GtvMainToolbarPrivate +{ + GtvMainToolbarPrivateImpl* m_pImpl; + + GtvMainToolbarPrivateImpl* operator->() + { + return m_pImpl; + } +}; + +} + +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-volatile" +#endif +#endif +G_DEFINE_TYPE_WITH_PRIVATE(GtvMainToolbar, gtv_main_toolbar, GTK_TYPE_BOX); +#if defined __clang__ +#if __has_warning("-Wdeprecated-volatile") +#pragma clang diagnostic pop +#endif +#endif + +static GtvMainToolbarPrivate& +getPrivate(GtvMainToolbar* toolbar) +{ + return *static_cast<GtvMainToolbarPrivate*>(gtv_main_toolbar_get_instance_private(toolbar)); +} + +static void +gtv_main_toolbar_init(GtvMainToolbar* toolbar) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + priv.m_pImpl = new GtvMainToolbarPrivateImpl(); + + const std::string uiFilePath = GtvHelpers::getDirPath(__FILE__) + std::string(UI_FILE_NAME); + GtvGtkWrapper<GtkBuilder> builder(gtk_builder_new_from_file(uiFilePath.c_str()), + [](GtkBuilder* pBuilder) { + g_object_unref(pBuilder); + }); + + priv->toolbar1 = GTK_WIDGET(gtk_builder_get_object(builder.get(), "toolbar1")); + gtk_box_pack_start(GTK_BOX(toolbar), priv->toolbar1, false, false, false); + priv->toolbar2 = GTK_WIDGET(gtk_builder_get_object(builder.get(), "toolbar2")); + gtk_box_pack_start(GTK_BOX(toolbar), priv->toolbar2, false, false, false); + + priv->m_pEnableEditing = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_editmode")); + priv->m_pLeftpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyleft")); + priv->m_pCenterpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifycenter")); + priv->m_pRightpara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyright")); + priv->m_pJustifypara = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_justifyfill")); + priv->m_pDeleteComment = GTK_WIDGET(gtk_builder_get_object(builder.get(), "btn_removeannotation")); + priv->m_pPartSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_partselector")); + priv->m_pPartModeSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_partsmodeselector")); + priv->m_pRecentUnoSelector = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_recentunoselector")); + + toolbar->m_pAddressbar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "addressbar_entry")); + toolbar->m_pFormulabar = GTK_WIDGET(gtk_builder_get_object(builder.get(), "formulabar_entry")); + toolbar->m_pContentControlSelector + = GTK_WIDGET(gtk_builder_get_object(builder.get(), "combo_contentcontrolselector")); + toolbar->m_pContentControlDateSelector + = GTK_WIDGET(gtk_builder_get_object(builder.get(), "menu_contentcontroldateselector")); + + // TODO: compile with -rdynamic and get rid of it + gtk_builder_add_callback_symbol(builder.get(), "btn_clicked", G_CALLBACK(btn_clicked)); + gtk_builder_add_callback_symbol(builder.get(), "doCopy", G_CALLBACK(doCopy)); + gtk_builder_add_callback_symbol(builder.get(), "doPaste", G_CALLBACK(doPaste)); + gtk_builder_add_callback_symbol(builder.get(), "createView", G_CALLBACK(createView)); + gtk_builder_add_callback_symbol(builder.get(), "getRulerState", G_CALLBACK(getRulerState)); + gtk_builder_add_callback_symbol(builder.get(), "recentUnoChanged", G_CALLBACK(recentUnoChanged)); + gtk_builder_add_callback_symbol(builder.get(), "unoCommandDebugger", G_CALLBACK(unoCommandDebugger)); + gtk_builder_add_callback_symbol(builder.get(), "toggleEditing", G_CALLBACK(toggleEditing)); + gtk_builder_add_callback_symbol(builder.get(), "changePartMode", G_CALLBACK(changePartMode)); + gtk_builder_add_callback_symbol(builder.get(), "changePart", G_CALLBACK(changePart)); + gtk_builder_add_callback_symbol(builder.get(), "changeContentControl", + G_CALLBACK(changeContentControl)); + gtk_builder_add_callback_symbol(builder.get(), "changeDateContentControl", + G_CALLBACK(changeDateContentControl)); + gtk_builder_add_callback_symbol(builder.get(), "changeZoom", G_CALLBACK(changeZoom)); + gtk_builder_add_callback_symbol(builder.get(), "toggleFindbar", G_CALLBACK(toggleFindbar)); + gtk_builder_add_callback_symbol(builder.get(), "documentRedline", G_CALLBACK(documentRedline)); + gtk_builder_add_callback_symbol(builder.get(), "documentRepair", G_CALLBACK(documentRepair)); + gtk_builder_add_callback_symbol(builder.get(), "signalAddressbar", G_CALLBACK(signalAddressbar)); + gtk_builder_add_callback_symbol(builder.get(), "signalFormulabar", G_CALLBACK(signalFormulabar)); + + // find toolbar + // Note: These buttons are not the part of GtvMainToolbar + gtk_builder_add_callback_symbol(builder.get(), "signalSearchNext", G_CALLBACK(signalSearchNext)); + gtk_builder_add_callback_symbol(builder.get(), "signalSearchPrev", G_CALLBACK(signalSearchPrev)); + gtk_builder_add_callback_symbol(builder.get(), "signalFindbar", G_CALLBACK(signalFindbar)); + gtk_builder_add_callback_symbol(builder.get(), "toggleFindAll", G_CALLBACK(toggleFindAll)); + + gtk_builder_connect_signals(builder.get(), nullptr); + + gtk_widget_show_all(GTK_WIDGET(toolbar)); +} + +static void +gtv_main_toolbar_finalize(GObject* object) +{ + GtvMainToolbarPrivate& priv = getPrivate(GTV_MAIN_TOOLBAR(object)); + + delete priv.m_pImpl; + priv.m_pImpl = nullptr; + + G_OBJECT_CLASS (gtv_main_toolbar_parent_class)->finalize (object); +} + +static void +gtv_main_toolbar_class_init(GtvMainToolbarClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = gtv_main_toolbar_finalize; +} + +static void populatePartSelector(GtvMainToolbar* toolbar) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))); + gtv_application_window_set_part_broadcast(window, false); + gtk_list_store_clear( GTK_LIST_STORE( + gtk_combo_box_get_model( + GTK_COMBO_BOX(priv->m_pPartSelector) )) ); + + if (!window->lokdocview) + { + return; + } + + const int nMaxLength = 50; + char sText[nMaxLength]; + + int nParts = lok_doc_view_get_parts(LOK_DOC_VIEW(window->lokdocview)); + for ( int i = 0; i < nParts; i++ ) + { + char* pName = lok_doc_view_get_part_name(LOK_DOC_VIEW(window->lokdocview), i); + assert( pName ); + snprintf( sText, nMaxLength, "%i (%s)", i+1, pName ); + free( pName ); + + gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(priv->m_pPartSelector), sText ); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(priv->m_pPartSelector), lok_doc_view_get_part(LOK_DOC_VIEW(window->lokdocview))); + + gtv_application_window_set_part_broadcast(window, true); +} + +static void populateRecentUnoSelector(GtvMainToolbar* toolbar) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + GtkComboBoxText* pSelector = GTK_COMBO_BOX_TEXT(priv->m_pRecentUnoSelector); + + unsigned counter = 0; + std::ifstream is("/tmp/gtv-recentunos.txt"); + while (is.good() && counter < 10) + { + std::string unoCommandStr; + std::getline(is, unoCommandStr); + std::vector<std::string> aUnoCmd = GtvHelpers::split<std::string>(unoCommandStr, " | ", 2); + if (aUnoCmd.size() != 2) + continue; + auto it = priv->m_pRecentUnoCommands.emplace(aUnoCmd[0], aUnoCmd[1]); + if (it.second) + { + gtk_combo_box_text_append_text(pSelector, aUnoCmd[0].c_str()); + ++counter; + } + } +} + +void +gtv_main_toolbar_doc_loaded(GtvMainToolbar* toolbar, LibreOfficeKitDocumentType eDocType, bool bEditMode) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + gtk_widget_set_visible(toolbar->m_pAddressbar, false); + gtk_widget_set_visible(toolbar->m_pFormulabar, false); + if (eDocType == LOK_DOCTYPE_SPREADSHEET) + { + gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pLeftpara), ".uno:AlignLeft"); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pCenterpara), ".uno:AlignHorizontalCenter"); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pRightpara), ".uno:AlignRight"); + gtk_widget_hide(priv->m_pJustifypara); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pDeleteComment), ".uno:DeleteNote"); + + gtk_widget_set_visible(toolbar->m_pAddressbar, true); + gtk_widget_set_visible(toolbar->m_pFormulabar, true); + } + else if (eDocType == LOK_DOCTYPE_PRESENTATION) + { + gtk_tool_button_set_label(GTK_TOOL_BUTTON(priv->m_pDeleteComment), ".uno:DeleteAnnotation"); + } + + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->m_pEnableEditing), bEditMode); + + // populate combo boxes + populatePartSelector(toolbar); + + // populate recent uno selector + populateRecentUnoSelector(toolbar); +} + +void +gtv_main_toolbar_add_recent_uno(GtvMainToolbar* toolbar, const std::string& rUnoCmdStr) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + GtkComboBoxText* pSelector = GTK_COMBO_BOX_TEXT(priv->m_pRecentUnoSelector); + + const std::vector<std::string> aUnoCmd = GtvHelpers::split<std::string>(rUnoCmdStr, " | ", 2); + priv->m_pRecentUnoCommands[aUnoCmd[0]] = aUnoCmd[1]; + // keep placeholder string at the top + gtk_combo_box_text_insert_text(pSelector, 1, aUnoCmd[0].c_str()); + // TODO: Remove other text entries with same key +} + +std::string +gtv_main_toolbar_get_recent_uno_args(GtvMainToolbar* toolbar, const std::string& rUnoCmd) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + auto it = std::find_if(priv->m_pRecentUnoCommands.begin(), priv->m_pRecentUnoCommands.end(), + [&rUnoCmd](const std::pair<std::string, std::string>& pair) { + return rUnoCmd == pair.first; + }); + std::string ret; + if (it != priv->m_pRecentUnoCommands.end()) + ret = it->second; + return ret; +} + +GtkContainer* +gtv_main_toolbar_get_first_toolbar(GtvMainToolbar* toolbar) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + return GTK_CONTAINER(priv->toolbar1); +} + +GtkContainer* +gtv_main_toolbar_get_second_toolbar(GtvMainToolbar* toolbar) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + return GTK_CONTAINER(priv->toolbar2); +} + +void +gtv_main_toolbar_set_sensitive_internal(GtvMainToolbar* toolbar, GtkToolItem* pItem, bool isSensitive) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + priv->m_aToolItemSensitivities[pItem] = isSensitive; +} + +static void setSensitiveIfEdit(GtvMainToolbar* toolbar, GtkToolItem* pItem, bool bEdit) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + // some buttons remain enabled always + const gchar* pIconName = gtk_tool_button_get_icon_name(GTK_TOOL_BUTTON(pItem)); + if (g_strcmp0(pIconName, "zoom-in-symbolic") != 0 && + g_strcmp0(pIconName, "zoom-original-symbolic") != 0 && + g_strcmp0(pIconName, "zoom-out-symbolic") != 0 && + g_strcmp0(pIconName, "insert-text-symbolic") != 0 && + g_strcmp0(pIconName, "view-continuous-symbolic") != 0 && + g_strcmp0(pIconName, "document-properties") != 0 && + g_strcmp0(pIconName, "system-run") != 0) + { + bool state = true; + if (priv->m_aToolItemSensitivities.find(pItem) != priv->m_aToolItemSensitivities.end()) + state = priv->m_aToolItemSensitivities[pItem]; + + gtk_widget_set_sensitive(GTK_WIDGET(pItem), bEdit && state); + } +} + +void +gtv_main_toolbar_set_edit(GtvMainToolbar* toolbar, gboolean bEdit) +{ + GtvMainToolbarPrivate& priv = getPrivate(toolbar); + GtvGtkWrapper<GList> pList(gtk_container_get_children(GTK_CONTAINER(priv->toolbar1)), + [](GList* l) + { + g_list_free(l); + }); + for (GList* l = pList.get(); l != nullptr; l = l->next) + { + if (GTK_IS_TOOL_BUTTON(l->data)) + { + setSensitiveIfEdit(toolbar, GTK_TOOL_ITEM(l->data), bEdit); + } + } + + pList.reset(gtk_container_get_children(GTK_CONTAINER(priv->toolbar2))); + for (GList* l = pList.get(); l != nullptr; l = l->next) + { + if (GTK_IS_TOOL_BUTTON(l->data)) + { + setSensitiveIfEdit(toolbar, GTK_TOOL_ITEM(l->data), bEdit); + } + } +} + +GtkWidget* +gtv_main_toolbar_new() +{ + return GTK_WIDGET(g_object_new(GTV_TYPE_MAIN_TOOLBAR, + "orientation", GTK_ORIENTATION_VERTICAL, + nullptr)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx new file mode 100644 index 000000000..91827ef92 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_MAIN_TOOLBAR_H +#define GTV_MAIN_TOOLBAR_H + +#include <gtk/gtk.h> + +#include <string> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#define GTV_TYPE_MAIN_TOOLBAR (gtv_main_toolbar_get_type()) +#define GTV_MAIN_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTV_TYPE_MAIN_TOOLBAR, GtvMainToolbar)) +#define GTV_IS_MAIN_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTV_TYPE_MAIN_TOOLBAR)) +#define GTV_MAIN_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTV_TYPE_MAIN_TOOLBAR, GtvMainToolbarClass)) +#define GTV_IS_MAIN_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTV_TYPE_MAIN_TOOLBAR)) +#define GTV_MAIN_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTV_TYPE_MAIN_TOOLBAR, GtvMainToolbarClass)) + +struct GtvMainToolbar +{ + GtkBox parent; + + GtkWidget* m_pAddressbar; + GtkWidget* m_pFormulabar; + GtkWidget* m_pContentControlSelector; + GtkWidget* m_pContentControlDateSelector; +}; + +struct GtvMainToolbarClass +{ + GtkBoxClass parentClass; +}; + +GType gtv_main_toolbar_get_type (void) G_GNUC_CONST; + +GtkWidget* gtv_main_toolbar_new(); + +GtkContainer* gtv_main_toolbar_get_first_toolbar(GtvMainToolbar* toolbar); + +GtkContainer* gtv_main_toolbar_get_second_toolbar(GtvMainToolbar* toolbar); + +void gtv_main_toolbar_set_sensitive_internal(GtvMainToolbar* toolbar, GtkToolItem* pItem, bool isSensitive); + +/// Use internal sensitivity map to set actual widget's sensitiveness +void gtv_main_toolbar_set_edit(GtvMainToolbar* toolbar, gboolean bEdit); + +void gtv_main_toolbar_doc_loaded(GtvMainToolbar* toolbar, LibreOfficeKitDocumentType eDocType, bool bEditMode); + +void gtv_main_toolbar_add_recent_uno(GtvMainToolbar* toolbar, const std::string& rUnoCmdStr); + +std::string gtv_main_toolbar_get_recent_uno_args(GtvMainToolbar* toolbar, const std::string& rUnoCmd); + +#endif /* GTV_MAIN_TOOLBAR_H */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main.cxx b/libreofficekit/qa/gtktiledviewer/gtv-main.cxx new file mode 100644 index 000000000..a1bf7ed92 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-main.cxx @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gio/gio.h> + +#include "gtv-application.hxx" + +int main(int argc, char* argv[]) +{ + return g_application_run(G_APPLICATION(gtv_application_new()), argc, argv); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx new file mode 100644 index 000000000..bb0e7edd3 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx @@ -0,0 +1,809 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <gtk/gtk.h> + +#include "gtv-application-window.hxx" +#include "gtv-helpers.hxx" +#include "gtv-lokdocview-signal-handlers.hxx" +#include "gtv-signal-handlers.hxx" + +#include <sal/macros.h> + +#include <cassert> +#include <map> +#include <vector> + +#include <boost/property_tree/json_parser.hpp> +#include <optional> + +void btn_clicked(GtkWidget* pButton, gpointer) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkToolButton* pItem = GTK_TOOL_BUTTON(pButton); + const gchar* label = gtk_tool_button_get_label(pItem); + if (!(gtv_application_window_get_toolbar_broadcast(window) && g_str_has_prefix(label, ".uno:"))) + return; + + std::string aArguments; + if (g_strcmp0(label, ".uno:InsertAnnotation") == 0) + { + std::map<std::string, std::string> aEntries; + aEntries["Text"] = ""; + GtvHelpers::userPromptDialog(GTK_WINDOW(window), "Insert Comment", aEntries); + + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type("Text/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Text/value", '/'), aEntries["Text"]); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + aArguments = aStream.str(); + } + + bool bNotify = g_strcmp0(label, ".uno:Save") == 0; + if (window->lokdocview) + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), label, aArguments.c_str(), bNotify); +} + +void doCopy(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + char* pUsedFormat = nullptr; + // TODO: Should check `text-selection` signal before trying to copy + char* pSelection = lok_doc_view_copy_selection(LOK_DOC_VIEW(window->lokdocview), "text/html", &pUsedFormat); + if (!pSelection) + return; + + GtkClipboard* pClipboard = gtk_clipboard_get_for_display(gtk_widget_get_display(pButton), GDK_SELECTION_CLIPBOARD); + std::string aUsedFormat(pUsedFormat); + if (aUsedFormat == "text/plain;charset=utf-8") + gtk_clipboard_set_text(pClipboard, pSelection, -1); + else + GtvHelpers::clipboardSetHtml(pClipboard, pSelection); + + free(pSelection); + free(pUsedFormat); +} + +void doPaste(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkClipboard* pClipboard = gtk_clipboard_get_for_display(gtk_widget_get_display(pButton), GDK_SELECTION_CLIPBOARD); + GdkAtom* pTargets; + gint nTargets; + std::map<std::string, GdkAtom> aTargets; + if (gtk_clipboard_wait_for_targets(pClipboard, &pTargets, &nTargets)) + { + for (gint i = 0; i < nTargets; ++i) + { + gchar* pName = gdk_atom_name(pTargets[i]); + aTargets[pName] = pTargets[i]; + g_free(pName); + } + g_free(pTargets); + } + + std::optional<GdkAtom> oTarget; + std::string aTargetName; + + std::vector<std::string> aPreferredNames = + { + std::string("image/png"), + std::string("text/html") + }; + for (const std::string& rName : aPreferredNames) + { + std::map<std::string, GdkAtom>::iterator it = aTargets.find(rName); + if (it != aTargets.end()) + { + aTargetName = it->first; + oTarget = it->second; + break; + } + } + + if (oTarget) + { + GtkSelectionData* pSelectionData = gtk_clipboard_wait_for_contents(pClipboard, *oTarget); + if (!pSelectionData) + { + return; + } + gint nLength; + const guchar* pData = gtk_selection_data_get_data_with_length(pSelectionData, &nLength); + bool bSuccess = lok_doc_view_paste(LOK_DOC_VIEW(window->lokdocview), aTargetName.c_str(), reinterpret_cast<const char*>(pData), nLength); + gtk_selection_data_free(pSelectionData); + if (bSuccess) + return; + } + + gchar* pText = gtk_clipboard_wait_for_text(pClipboard); + if (pText) + lok_doc_view_paste(LOK_DOC_VIEW(window->lokdocview), "text/plain;charset=utf-8", pText, strlen(pText)); +} + +void createView(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + gtv_application_window_create_view_from_window(GTV_APPLICATION_WINDOW(window)); +} + +void getRulerState(GtkWidget* pButton, gpointer /*pItem*/) +{ + const std::string type = ".uno:RulerState"; + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + pDocument->pClass->getCommandValues(pDocument, type.c_str()); +} + +static void removeUnoParam(GtkWidget* pWidget, gpointer userdata) +{ + GtkWidget* pParamAreaBox = GTK_WIDGET(userdata); + GtkWidget* pParamContainer = gtk_widget_get_parent(pWidget); + + gtk_container_remove(GTK_CONTAINER(pParamAreaBox), pParamContainer); +} + +static void addMoreUnoParam(GtkWidget* /*pWidget*/, gpointer userdata) +{ + GtkWidget* pUnoParamAreaBox = GTK_WIDGET(userdata); + + GtkWidget* pParamContainer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(pUnoParamAreaBox), pParamContainer, true, true, 2); + + GtkWidget* pTypeEntry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(pParamContainer), pTypeEntry, true, true, 2); + gtk_entry_set_placeholder_text(GTK_ENTRY(pTypeEntry), "Param type (Eg. boolean, string etc.)"); + + GtkWidget* pNameEntry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(pParamContainer), pNameEntry, true, true, 2); + gtk_entry_set_placeholder_text(GTK_ENTRY(pNameEntry), "Param name"); + + GtkWidget* pValueEntry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(pParamContainer), pValueEntry, true, true, 2); + gtk_entry_set_placeholder_text(GTK_ENTRY(pValueEntry), "Param value"); + + GtkWidget* pRemoveButton = gtk_button_new_from_icon_name("list-remove-symbolic", GTK_ICON_SIZE_BUTTON); + g_signal_connect(pRemoveButton, "clicked", G_CALLBACK(removeUnoParam), pUnoParamAreaBox); + gtk_box_pack_start(GTK_BOX(pParamContainer), pRemoveButton, true, true, 2); + + gtk_widget_show_all(pUnoParamAreaBox); +} + +static void iterateUnoParams(GtkWidget* pWidget, gpointer userdata) +{ + boost::property_tree::ptree *pTree = static_cast<boost::property_tree::ptree*>(userdata); + GtvGtkWrapper<GList> pChildren(gtk_container_get_children(GTK_CONTAINER(pWidget)), + [](GList* pList) { + g_list_free(pList); + }); + GList* pIt = nullptr; + guint i = 0; + const gchar* unoParam[3]; + for (pIt = pChildren.get(), i = 0; i < 3; pIt = pIt->next, i++) + { + assert(pIt != nullptr); + unoParam[i] = gtk_entry_get_text(GTK_ENTRY(pIt->data)); + } + + gchar* pPath = g_strconcat(unoParam[1], "/", "type", nullptr); + pTree->put(boost::property_tree::ptree::path_type(pPath, '/'), unoParam[0]); + g_free(pPath); + pPath = g_strconcat(unoParam[1], "/", "value", nullptr); + pTree->put(boost::property_tree::ptree::path_type(pPath, '/'), unoParam[2]); + g_free(pPath); +} + +void recentUnoChanged( GtkWidget* pSelector, gpointer /* pItem */ ) +{ + GtvApplicationWindow* pWindow = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + gchar* pUnoCmd = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(pSelector)); + + GtvMainToolbar* pToolbar = gtv_application_window_get_main_toolbar(pWindow); + const std::string aUnoArgs = gtv_main_toolbar_get_recent_uno_args(pToolbar, pUnoCmd); + // this will also discard our default placeholder string, "Recent UNO" + if (aUnoArgs.empty()) + return; + + lok_doc_view_post_command(LOK_DOC_VIEW(pWindow->lokdocview), pUnoCmd, (aUnoArgs.empty() ? nullptr : aUnoArgs.c_str()), false); + g_free(pUnoCmd); +} + +static void addToRecentUnoCommands(GtvApplicationWindow* pWindow, const std::string& rUnoCmd, std::string rArgs) +{ + GtvMainToolbar* pToolbar = gtv_application_window_get_main_toolbar(pWindow); + rArgs.erase(std::find(rArgs.begin(), rArgs.end(), '\n')); + const std::string rUnoCmdStr = rUnoCmd + " | " + rArgs; + + + // add to file + std::ofstream outfile("/tmp/gtv-recentunos.txt", std::ios_base::app | std::ios_base::out); + if (outfile.good()) + outfile << rUnoCmdStr << '\n'; + + // add to combo box + gtv_main_toolbar_add_recent_uno(pToolbar, rUnoCmdStr); +} + +void unoCommandDebugger(GtkWidget* pButton, gpointer /* pItem */) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkWidget* pUnoCmdDialog = gtk_dialog_new_with_buttons ("Execute UNO command", + GTK_WINDOW (window), + GTK_DIALOG_MODAL, + "Execute", + GTK_RESPONSE_OK, + nullptr); + g_object_set(G_OBJECT(pUnoCmdDialog), "resizable", FALSE, nullptr); + GtkWidget* pDialogMessageArea = gtk_dialog_get_content_area (GTK_DIALOG (pUnoCmdDialog)); + GtkWidget* pUnoCmdAreaBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(pDialogMessageArea), pUnoCmdAreaBox, true, true, 2); + + GtkWidget* pUnoCmdLabel = gtk_label_new("Enter UNO command"); + gtk_box_pack_start(GTK_BOX(pUnoCmdAreaBox), pUnoCmdLabel, true, true, 2); + + GtkWidget* pUnoCmdEntry = gtk_entry_new (); + gtk_box_pack_start(GTK_BOX(pUnoCmdAreaBox), pUnoCmdEntry, true, true, 2); + gtk_entry_set_placeholder_text(GTK_ENTRY(pUnoCmdEntry), "UNO command (Eg. Bold, Italic etc.)"); + GtkWidget* pUnoParamAreaBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start(GTK_BOX(pDialogMessageArea), pUnoParamAreaBox, true, true, 2); + + GtkWidget* pAddMoreButton = gtk_button_new_with_label("Add UNO parameter"); + gtk_box_pack_start(GTK_BOX(pDialogMessageArea), pAddMoreButton, true, true, 2); + g_signal_connect(G_OBJECT(pAddMoreButton), "clicked", G_CALLBACK(addMoreUnoParam), pUnoParamAreaBox); + + gtk_widget_show_all(pUnoCmdDialog); + + gint res = gtk_dialog_run (GTK_DIALOG(pUnoCmdDialog)); + if (res == GTK_RESPONSE_OK) + { + gchar* sUnoCmd = g_strconcat(".uno:", gtk_entry_get_text(GTK_ENTRY(pUnoCmdEntry)), nullptr); + + boost::property_tree::ptree aTree; + gtk_container_foreach(GTK_CONTAINER(pUnoParamAreaBox), iterateUnoParams, &aTree); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree, false); + std::string aArguments = aStream.str(); + + g_info("Generated UNO command: %s %s", sUnoCmd, aArguments.c_str()); + + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), sUnoCmd, (aArguments.empty() ? nullptr : aArguments.c_str()), false); + addToRecentUnoCommands(window, sUnoCmd, aArguments); + + g_free(sUnoCmd); + } + + gtk_widget_destroy(pUnoCmdDialog); +} + +void toggleEditing(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + bool bActive = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pButton)); + if (bool(lok_doc_view_get_edit(LOK_DOC_VIEW(window->lokdocview))) != bActive) + lok_doc_view_set_edit(LOK_DOC_VIEW(window->lokdocview), bActive); +} + +void changePart( GtkWidget* pSelector, gpointer /* pItem */ ) +{ + int nPart = gtk_combo_box_get_active( GTK_COMBO_BOX(pSelector) ); + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + + if (gtv_application_window_get_part_broadcast(window) && window->lokdocview) + { + lok_doc_view_set_part( LOK_DOC_VIEW(window->lokdocview), nPart ); + lok_doc_view_reset_view(LOK_DOC_VIEW(window->lokdocview)); + } +} + +void changePartMode( GtkWidget* pSelector, gpointer /* pItem */ ) +{ + // Just convert directly back to the LibreOfficeKitPartMode enum. + // I.e. the ordering above should match the enum member ordering. + LibreOfficeKitPartMode ePartMode = + LibreOfficeKitPartMode( gtk_combo_box_get_active( GTK_COMBO_BOX(pSelector) ) ); + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + + if ( window->lokdocview ) + { + lok_doc_view_set_partmode( LOK_DOC_VIEW(window->lokdocview), ePartMode ); + } +} + +void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + if (gtv_application_window_get_part_broadcast(window) && window->lokdocview) + { + int nItem = gtk_combo_box_get_active(GTK_COMBO_BOX(pSelector)); + boost::property_tree::ptree aValues; + aValues.put("type", "drop-down"); + aValues.put("selected", std::to_string(nItem)); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aValues); + std::string aJson = aStream.str(); + lok_doc_view_send_content_control_event(LOK_DOC_VIEW(window->lokdocview), aJson.c_str()); + } +} + +void changeDateContentControl(GtkWidget* pSelector, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + if (gtv_application_window_get_part_broadcast(window) && window->lokdocview) + { + GtkPopover* pPopover = GTK_POPOVER(gtk_widget_get_parent(gtk_widget_get_parent(pSelector))); + guint nYear, nMonth, nDay; + gtk_calendar_get_date(GTK_CALENDAR(pSelector), &nYear, &nMonth, &nDay); + gtk_popover_popdown(pPopover); + + std::stringstream aDate; + aDate << std::setfill('0') << std::setw(4) << nYear; + aDate << "-"; + aDate << std::setfill('0') << std::setw(2) << (nMonth + 1); + aDate << "-"; + aDate << std::setfill('0') << std::setw(2) << nDay; + aDate << "T00:00:00Z"; + boost::property_tree::ptree aValues; + aValues.put("type", "date"); + aValues.put("selected", aDate.str()); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aValues); + std::string aJson = aStream.str(); + lok_doc_view_send_content_control_event(LOK_DOC_VIEW(window->lokdocview), aJson.c_str()); + } +} + +void changeZoom( GtkWidget* pButton, gpointer /* pItem */ ) +{ + static const float fZooms[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 5.0 }; + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + const char *sName = gtk_tool_button_get_icon_name( GTK_TOOL_BUTTON(pButton) ); + + float fZoom = 0; + float fCurrentZoom = 0; + + if ( window->lokdocview ) + { + fCurrentZoom = lok_doc_view_get_zoom( LOK_DOC_VIEW(window->lokdocview) ); + } + + if ( strcmp(sName, "zoom-in-symbolic") == 0) + { + for ( size_t i = 0; i < SAL_N_ELEMENTS( fZooms ); i++ ) + { + if ( fCurrentZoom < fZooms[i] ) + { + fZoom = fZooms[i]; + break; + } + } + } + else if ( strcmp(sName, "zoom-original-symbolic") == 0) + { + fZoom = 1; + } + else if ( strcmp(sName, "zoom-out-symbolic") == 0) + { + for ( size_t i = 0; i < SAL_N_ELEMENTS( fZooms ); i++ ) + { + if ( fCurrentZoom > fZooms[i] ) + { + fZoom = fZooms[i]; + } + } + } + + if ( fZoom != 0 && window->lokdocview ) + { + lok_doc_view_set_zoom( LOK_DOC_VIEW(window->lokdocview), fZoom ); + GdkRectangle aVisibleArea; + gtv_application_window_get_visible_area(window, &aVisibleArea); + lok_doc_view_set_visible_area(LOK_DOC_VIEW(window->lokdocview), &aVisibleArea); + } + const std::string aZoom = std::string("Zoom: ") + std::to_string(int(fZoom * 100)) + std::string("%"); + gtk_label_set_text(GTK_LABEL(window->zoomlabel), aZoom.c_str()); +} + +void documentRedline(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + // Get the data. + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + char* pValues = pDocument->pClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); + if (!pValues) + return; + + std::stringstream aInfo; + aInfo << "lok::Document::getCommandValues('.uno:AcceptTrackedChanges') returned '" << pValues << "'" << std::endl; + g_info("%s", aInfo.str().c_str()); + std::stringstream aStream(pValues); + free(pValues); + assert(!aStream.str().empty()); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + // Create the dialog. + GtkWidget* pDialog = gtk_dialog_new_with_buttons("Manage Changes", + GTK_WINDOW (window), + GTK_DIALOG_MODAL, + "Accept", + GTK_RESPONSE_YES, + "Reject", + GTK_RESPONSE_NO, + "Jump", + GTK_RESPONSE_APPLY, + nullptr); + gtk_window_set_default_size(GTK_WINDOW(pDialog), 800, 600); + GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG (pDialog)); + GtkWidget* pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr); + + // Build the table. + GtkTreeStore* pTreeStore = gtk_tree_store_new(6, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + for (const auto& rValue : aTree.get_child("redlines")) + { + GtkTreeIter aTreeIter; + gtk_tree_store_append(pTreeStore, &aTreeIter, nullptr); + gtk_tree_store_set(pTreeStore, &aTreeIter, + 0, rValue.second.get<int>("index"), + 1, rValue.second.get<std::string>("author").c_str(), + 2, rValue.second.get<std::string>("type").c_str(), + 3, rValue.second.get<std::string>("comment").c_str(), + 4, rValue.second.get<std::string>("description").c_str(), + 5, rValue.second.get<std::string>("dateTime").c_str(), + -1); + } + GtkWidget* pTreeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pTreeStore)); + std::vector<std::string> aColumns = {"Index", "Author", "Type", "Comment", "Description", "Timestamp"}; + for (size_t nColumn = 0; nColumn < aColumns.size(); ++nColumn) + { + GtkCellRenderer* pRenderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* pColumn = gtk_tree_view_column_new_with_attributes(aColumns[nColumn].c_str(), + pRenderer, + "text", nColumn, + nullptr); + gtk_tree_view_append_column(GTK_TREE_VIEW(pTreeView), pColumn); + } + gtk_container_add(GTK_CONTAINER(pScrolledWindow), pTreeView); + gtk_box_pack_start(GTK_BOX(pContentArea), pScrolledWindow, true, true, 2); + + // Show the dialog. + gtk_widget_show_all(pDialog); + gint res = gtk_dialog_run(GTK_DIALOG(pDialog)); + + // Dispatch the matching command, if necessary. + if (res == GTK_RESPONSE_YES || res == GTK_RESPONSE_NO || res == GTK_RESPONSE_APPLY) + { + GtkTreeSelection* pSelection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pTreeView)); + GtkTreeIter aTreeIter; + GtkTreeModel* pTreeModel; + if (gtk_tree_selection_get_selected(pSelection, &pTreeModel, &aTreeIter)) + { + gint nIndex = 0; + // 0: index + gtk_tree_model_get(pTreeModel, &aTreeIter, 0, &nIndex, -1); + std::string aCommand; + if (res == GTK_RESPONSE_YES) + aCommand = ".uno:AcceptTrackedChange"; + else if (res == GTK_RESPONSE_NO) + aCommand = ".uno:RejectTrackedChange"; + else + // Just select the given redline, don't accept or reject it. + aCommand = ".uno:NextTrackedChange"; + // Without the '.uno:' prefix. + std::string aKey = aCommand.substr(strlen(".uno:")); + + // Post the command. + boost::property_tree::ptree aCommandTree; + aCommandTree.put(boost::property_tree::ptree::path_type(aKey + "/type", '/'), "unsigned short"); + aCommandTree.put(boost::property_tree::ptree::path_type(aKey + "/value", '/'), nIndex); + + aStream.str(std::string()); + boost::property_tree::write_json(aStream, aCommandTree); + std::string aArguments = aStream.str(); + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), aCommand.c_str(), aArguments.c_str(), false); + } + } + + gtk_widget_destroy(pDialog); +} + +void documentRepair(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + // Get the data. + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + // Show it in linear time, so first redo in reverse order, then undo. + std::vector<std::string> aTypes = {".uno:Redo", ".uno:Undo"}; + std::vector<boost::property_tree::ptree> aTrees; + for (size_t nType = 0; nType < aTypes.size(); ++nType) + { + const std::string& rType = aTypes[nType]; + char* pValues = pDocument->pClass->getCommandValues(pDocument, rType.c_str()); + std::stringstream aInfo; + aInfo << "lok::Document::getCommandValues('" << rType << "') returned '" << pValues << "'" << std::endl; + g_info("%s", aInfo.str().c_str()); + std::stringstream aStream(pValues); + free(pValues); + assert(!aStream.str().empty()); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + aTrees.push_back(aTree); + } + + // Create the dialog. + GtkWidget* pDialog = gtk_dialog_new_with_buttons("Repair document", + GTK_WINDOW (window), + GTK_DIALOG_MODAL, + "Jump to state", + GTK_RESPONSE_OK, + nullptr); + gtk_window_set_default_size(GTK_WINDOW(pDialog), 800, 600); + GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG (pDialog)); + GtkWidget* pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr); + + // Build the table. + GtkTreeStore* pTreeStore = gtk_tree_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + for (size_t nTree = 0; nTree < aTrees.size(); ++nTree) + { + const auto& rTree = aTrees[nTree]; + for (const auto& rValue : rTree.get_child("actions")) + { + GtkTreeIter aTreeIter; + gtk_tree_store_append(pTreeStore, &aTreeIter, nullptr); + gtk_tree_store_set(pTreeStore, &aTreeIter, + 0, aTypes[nTree].c_str(), + 1, rValue.second.get<int>("index"), + 2, rValue.second.get<std::string>("comment").c_str(), + 3, rValue.second.get<std::string>("viewId").c_str(), + 4, rValue.second.get<std::string>("dateTime").c_str(), + -1); + } + } + GtkWidget* pTreeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pTreeStore)); + std::vector<std::string> aColumns = {"Type", "Index", "Comment", "View ID", "Timestamp"}; + for (size_t nColumn = 0; nColumn < aColumns.size(); ++nColumn) + { + GtkCellRenderer* pRenderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* pColumn = gtk_tree_view_column_new_with_attributes(aColumns[nColumn].c_str(), + pRenderer, + "text", nColumn, + nullptr); + gtk_tree_view_append_column(GTK_TREE_VIEW(pTreeView), pColumn); + } + gtk_container_add(GTK_CONTAINER(pScrolledWindow), pTreeView); + gtk_box_pack_start(GTK_BOX(pContentArea), pScrolledWindow, true, true, 2); + + // Show the dialog. + gtk_widget_show_all(pDialog); + gint res = gtk_dialog_run(GTK_DIALOG(pDialog)); + + // Dispatch the matching command, if necessary. + if (res == GTK_RESPONSE_OK) + { + GtkTreeSelection* pSelection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pTreeView)); + GtkTreeIter aTreeIter; + GtkTreeModel* pTreeModel; + if (gtk_tree_selection_get_selected(pSelection, &pTreeModel, &aTreeIter)) + { + gchar* pType = nullptr; + gint nIndex = 0; + // 0: type, 1: index + gtk_tree_model_get(pTreeModel, &aTreeIter, 0, &pType, 1, &nIndex, -1); + // '.uno:Undo' or '.uno:Redo' + const std::string aType(pType); + // Without the '.uno:' prefix. + std::string aKey = aType.substr(strlen(".uno:")); + g_free(pType); + + // Post the command. + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type(aKey + "/type", '/'), "unsigned short"); + aTree.put(boost::property_tree::ptree::path_type(aKey + "/value", '/'), nIndex + 1); + + // Without this, we could only undo our own commands. + aTree.put(boost::property_tree::ptree::path_type("Repair/type", '/'), "boolean"); + aTree.put(boost::property_tree::ptree::path_type("Repair/value", '/'), true); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aArguments = aStream.str(); + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), aType.c_str(), aArguments.c_str(), false); + } + } + + gtk_widget_destroy(pDialog); +} + +void toggleFindbar(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + gtv_application_window_toggle_findbar(window); +} + +void docAdjustmentChanged(GtkAdjustment*, gpointer pData) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(pData); + if (window->lokdocview) + LOKDocViewSigHandlers::configureEvent(window->lokdocview, nullptr, nullptr); +} + +void signalSearchNext(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkEntry* pEntry = GTK_ENTRY(window->findbarEntry); + const char* pText = gtk_entry_get_text(pEntry); + bool findAll = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(window->findAll)); + lok_doc_view_find_next(LOK_DOC_VIEW(window->lokdocview), pText, findAll); +} + +void signalSearchPrev(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkEntry* pEntry = GTK_ENTRY(window->findbarEntry); + const char* pText = gtk_entry_get_text(pEntry); + bool findAll = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(window->findAll)); + lok_doc_view_find_prev(LOK_DOC_VIEW(window->lokdocview), pText, findAll); +} + +gboolean signalFindbar(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer /*pData*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pWidget)); + gtk_label_set_text(GTK_LABEL(window->findbarlabel), ""); + switch(pEvent->keyval) + { + case GDK_KEY_Return: + { + // Search forward. + signalSearchNext(pWidget, nullptr); + return true; + } + case GDK_KEY_Escape: + { + // Hide the findbar. + gtk_widget_hide(GTK_WIDGET(window->findtoolbar)); + return true; + } + } + return FALSE; +} + +void toggleFindAll(GtkWidget* pButton, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pButton)); + GtkEntry* pEntry = GTK_ENTRY(window->findbarEntry); + const char* pText = gtk_entry_get_text(pEntry); + bool findAll = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(window->findAll)); + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(window->findAll), !findAll); + lok_doc_view_highlight_all(LOK_DOC_VIEW(window->lokdocview), pText); +} + +void editButtonClicked(GtkWidget* pWidget, gpointer userdata) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pWidget)); + std::map<std::string, std::string> aEntries; + aEntries["Text"] = ""; + + GtvHelpers::userPromptDialog(GTK_WINDOW(window), "Edit comment", aEntries); + + gchar *commentId = static_cast<gchar*>(g_object_get_data(G_OBJECT(userdata), "id")); + + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type("Id/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Id/value", '/'), std::string(commentId)); + + aTree.put(boost::property_tree::ptree::path_type("Text/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Text/value", '/'), aEntries["Text"]); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aArguments = aStream.str(); + + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), ".uno:EditAnnotation", aArguments.c_str(), false); +} + +void replyButtonClicked(GtkWidget* pWidget, gpointer userdata) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pWidget)); + std::map<std::string, std::string> aEntries; + aEntries["Text"] = ""; + + GtvHelpers::userPromptDialog(GTK_WINDOW(window), "Reply comment", aEntries); + + gchar *commentId = static_cast<gchar*>(g_object_get_data(G_OBJECT(userdata), "id")); + + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type("Id/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Id/value", '/'), std::string(commentId)); + + aTree.put(boost::property_tree::ptree::path_type("Text/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Text/value", '/'), aEntries["Text"]); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aArguments = aStream.str(); + + // Different reply UNO command for impress + std::string replyCommand = ".uno:ReplyComment"; + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + if (pDocument && pDocument->pClass->getDocumentType(pDocument) == LOK_DOCTYPE_PRESENTATION) + replyCommand = ".uno:ReplyToAnnotation"; + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), replyCommand.c_str(), aArguments.c_str(), false); +} + +void deleteCommentButtonClicked(GtkWidget* pWidget, gpointer userdata) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pWidget)); + gchar *commentid = static_cast<gchar*>(g_object_get_data(G_OBJECT(userdata), "id")); + + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type("Id/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("Id/value", '/'), std::string(commentid)); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aArguments = aStream.str(); + + // Different reply UNO command for impress + std::string deleteCommand = ".uno:DeleteComment"; + LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(window->lokdocview)); + if (pDocument) + { + if (pDocument->pClass->getDocumentType(pDocument) == LOK_DOCTYPE_PRESENTATION) + deleteCommand = ".uno:DeleteAnnotation"; + else if (pDocument->pClass->getDocumentType(pDocument) == LOK_DOCTYPE_SPREADSHEET) + deleteCommand = ".uno:DeleteNote"; + } + + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), deleteCommand.c_str(), aArguments.c_str(), false); +} + +/// Handles the key-press-event of the address entry widget. +gboolean signalAddressbar(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer /*pData*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pWidget)); + switch(pEvent->keyval) + { + case GDK_KEY_Return: + { + GtkEntry* pEntry = GTK_ENTRY(pWidget); + const char* pText = gtk_entry_get_text(pEntry); + + boost::property_tree::ptree aTree; + aTree.put(boost::property_tree::ptree::path_type("ToPoint/type", '/'), "string"); + aTree.put(boost::property_tree::ptree::path_type("ToPoint/value", '/'), pText); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aArguments = aStream.str(); + + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), ".uno:GoToCell", aArguments.c_str(), false); + gtk_widget_grab_focus(window->lokdocview); + return true; + } + case GDK_KEY_Escape: + { + std::string aArguments; + lok_doc_view_post_command(LOK_DOC_VIEW(window->lokdocview), ".uno:Cancel", aArguments.c_str(), false); + gtk_widget_grab_focus(window->lokdocview); + return true; + } + } + return FALSE; +} + +/// Handles the key-press-event of the formula entry widget. +gboolean signalFormulabar(GtkWidget* /*pWidget*/, GdkEventKey* /*pEvent*/, gpointer /*pData*/) +{ + // for now it just displays the callback + // TODO - submit the edited formula + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx new file mode 100644 index 000000000..c06017d87 --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef GTV_SIGNAL_HANDLERS_H +#define GTV_SIGNAL_HANDLERS_H + +#include <gtk/gtk.h> + +void btn_clicked(GtkWidget* pWidget, gpointer); + +void doCopy(GtkWidget* pButton, gpointer /*pItem*/); + +void doPaste(GtkWidget* pButton, gpointer /*pItem*/); + +void createView(GtkWidget* pButton, gpointer /*pItem*/); + +void getRulerState(GtkWidget* pButton, gpointer /*pItem*/); + +void recentUnoChanged(GtkWidget* pSelector, gpointer /* pItem */); + +void unoCommandDebugger(GtkWidget* pButton, gpointer /* pItem */); + +void toggleEditing(GtkWidget* pButton, gpointer /*pItem*/); + +void changePartMode(GtkWidget* pSelector, gpointer /* pItem */); + +void changePart(GtkWidget* pSelector, gpointer /*pItem*/); + +void openLokDialog(GtkWidget* pSelector, gpointer /*pItem*/); + +void changeZoom(GtkWidget* pButton, gpointer /* pItem */); + +void toggleFindbar(GtkWidget* pButton, gpointer /*pItem*/); + +void documentRedline(GtkWidget* pButton, gpointer /*pItem*/); + +void documentRepair(GtkWidget* pButton, gpointer /*pItem*/); + +void docAdjustmentChanged(GtkAdjustment*, gpointer); + +/// Click handler for the search next button. +void signalSearchNext(GtkWidget* pButton, gpointer /*pItem*/); + +/// Click handler for the search previous button. +void signalSearchPrev(GtkWidget* pButton, gpointer /*pItem*/); + +/// Handles the key-press-event of the search entry widget. +gboolean signalFindbar(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer /*pData*/); + +void toggleFindAll(GtkWidget* pButton, gpointer /*pItem*/); + +void editButtonClicked(GtkWidget*, gpointer); + +void replyButtonClicked(GtkWidget*, gpointer); + +void deleteCommentButtonClicked(GtkWidget*, gpointer); + +/// Handles the key-press-event of the address bar entry widget. +gboolean signalAddressbar(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer /*pData*/); + +/// Handles the key-press-event of the formula entry widget. +gboolean signalFormulabar(GtkWidget* /*pWidget*/, GdkEventKey* /*pEvent*/, gpointer /*pData*/); + +void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/); + +void changeDateContentControl(GtkWidget* pSelector, gpointer /*pItem*/); +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/gtktiledviewer/gtv.ui b/libreofficekit/qa/gtktiledviewer/gtv.ui new file mode 100644 index 000000000..79cb9a9ec --- /dev/null +++ b/libreofficekit/qa/gtktiledviewer/gtv.ui @@ -0,0 +1,827 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.20.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkBox" id="container"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="maingrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox" id="scrolledwindowcontainer"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkStatusbar" id="statusbar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">10</property> + <property name="margin_start">10</property> + <property name="margin_end">10</property> + <property name="margin_bottom">6</property> + <property name="spacing">2</property> + <child> + <object class="GtkLabel" id="zoomlabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">22</property> + <property name="label" translatable="yes">100%</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="redlinelabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">22</property> + <property name="label" translatable="yes">Current redline: </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkToolbar" id="findtoolbar"> + <property name="can_focus">True</property> + <property name="toolbar_style">both-horiz</property> + <child> + <object class="GtkToolButton" id="findbar_close"> + <property name="visible">True</property> + <property name="label" translatable="yes">__glade_unnamed_1</property> + <property name="use_underline">True</property> + <property name="icon_name">window-close-symbolic</property> + <signal name="clicked" handler="toggleFindbar" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="findbar_entrytoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkEntry" id="findbar_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="truncate-multiline">True</property> + <signal name="key-press-event" handler="signalFindbar" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="findbar_next"> + <property name="visible">True</property> + <property name="label" translatable="yes">__glade_unnamed_3</property> + <property name="use_underline">True</property> + <property name="icon_name">go-down-symbolic</property> + <signal name="clicked" handler="signalSearchNext" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="findbar_prev"> + <property name="visible">True</property> + <property name="label" translatable="yes">toolbutton</property> + <property name="use_underline">True</property> + <property name="icon_name">go-up-symbolic</property> + <signal name="clicked" handler="signalSearchPrev" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="findbar_findall"> + <property name="visible">True</property> + <property name="label" translatable="yes">Highlight all</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="toggleFindAll" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="findbar_labeltoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="findbar_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Search not found</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <object class="GtkToolbar" id="toolbar1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="toolbar_style">icons</property> + <child> + <object class="GtkToolButton" id="btn_save"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Save</property> + <property name="icon_name">document-save-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_copy"> + <property name="visible">True</property> + <property name="label" translatable="yes">Copy</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-copy-symbolic</property> + <signal name="clicked" handler="doCopy" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_paste"> + <property name="visible">True</property> + <property name="label" translatable="yes">Paste</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-paste-symbolic</property> + <signal name="clicked" handler="doPaste" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_undo"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Undo</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-undo-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_redo"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Redo</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-redo-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_docrepair"> + <property name="visible">True</property> + <property name="label" translatable="yes">Document Repair</property> + <property name="use_underline">True</property> + <property name="icon_name">document-properties</property> + <signal name="clicked" handler="documentRepair" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_docredlines"> + <property name="visible">True</property> + <property name="label" translatable="yes">Document redlines</property> + <property name="use_underline">True</property> + <property name="icon_name">system-run</property> + <signal name="clicked" handler="documentRedline" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_find"> + <property name="visible">True</property> + <property name="label" translatable="yes">Find</property> + <property name="use_underline">True</property> + <property name="icon_name">edit-find-symbolic</property> + <signal name="clicked" handler="toggleFindbar" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_zoomin"> + <property name="visible">True</property> + <property name="label" translatable="yes">Zoom In</property> + <property name="use_underline">True</property> + <property name="icon_name">zoom-in-symbolic</property> + <signal name="clicked" handler="changeZoom" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_zoomoriginal"> + <property name="visible">True</property> + <property name="label" translatable="yes">Zoom Original</property> + <property name="use_underline">True</property> + <property name="icon_name">zoom-original-symbolic</property> + <signal name="clicked" handler="changeZoom" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_zoomout"> + <property name="visible">True</property> + <property name="label" translatable="yes">Zoom out</property> + <property name="use_underline">True</property> + <property name="icon_name">zoom-out-symbolic</property> + <signal name="clicked" handler="changeZoom" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="partselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkComboBoxText" id="combo_partselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <signal name="changed" handler="changePart" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="partmodeselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkComboBoxText" id="combo_partsmodeselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="active">0</property> + <items> + <item translatable="yes">Standard</item> + <item translatable="yes">Notes</item> + </items> + <signal name="changed" handler="changePartMode" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="contentcontrolselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkComboBoxText" id="combo_contentcontrolselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text">Content control list items</property> + <signal name="changed" handler="changeContentControl" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="contentcontroldateselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuButton" id="menu_contentcontroldateselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text">Content control date</property> + <property name="popover">calendar</property> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_editmode"> + <property name="visible">True</property> + <property name="label" translatable="yes">Turn on/off edit mode</property> + <property name="use_underline">True</property> + <property name="icon_name">insert-text-symbolic</property> + <signal name="clicked" handler="toggleEditing" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="recentunoselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkComboBoxText" id="combo_recentunoselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="active">0</property> + <items> + <item translatable="no">Select UNO</item> + </items> + <signal name="changed" handler="recentUnoChanged" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_unodebugger"> + <property name="visible">True</property> + <property name="label" translatable="yes">Uno Command Debugger</property> + <property name="use_underline">True</property> + <property name="icon_name">dialog-question-symbolic</property> + <signal name="clicked" handler="unoCommandDebugger" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_createview"> + <property name="visible">True</property> + <property name="label" translatable="yes">Create new view</property> + <property name="use_underline">True</property> + <property name="icon_name">view-continuous-symbolic</property> + <signal name="clicked" handler="createView" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_rulerstate"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:UpdateRuler</property> + <property name="use_underline">True</property> + <property name="icon_name">text-x-generic</property> + <signal name="clicked" handler="getRulerState" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <object class="GtkToolbar" id="toolbar2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="toolbar_style">icons</property> + <child> + <object class="GtkToggleToolButton" id="btn_bold"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Bold</property> + <property name="use_underline">True</property> + <property name="icon_name">format-text-bold-symbolic</property> + <signal name="toggled" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_italic"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Italic</property> + <property name="use_underline">True</property> + <property name="icon_name">format-text-italic-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_underline"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Underline</property> + <property name="use_underline">True</property> + <property name="icon_name">format-text-underline-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_strikethrough"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:Strikeout</property> + <property name="use_underline">True</property> + <property name="icon_name">format-text-strikethrough-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_superscript"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:SuperScript</property> + <property name="use_underline">True</property> + <property name="icon_name">go-up-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_subscript"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:SubScript</property> + <property name="use_underline">True</property> + <property name="icon_name">go-down-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_justifyleft"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:LeftPara</property> + <property name="use_underline">True</property> + <property name="icon_name">format-justify-left-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_justifycenter"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:CenterPara</property> + <property name="use_underline">True</property> + <property name="icon_name">format-justify-center-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_justifyright"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:RightPara</property> + <property name="use_underline">True</property> + <property name="icon_name">format-justify-right-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_justifyfill"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:JustifyPara</property> + <property name="use_underline">True</property> + <property name="icon_name">format-justify-fill-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separator9"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_insertannotation"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:InsertAnnotation</property> + <property name="use_underline">True</property> + <property name="icon_name">changes-allow-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="btn_removeannotation"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:DeleteComment</property> + <property name="use_underline">True</property> + <property name="icon_name">changes-prevent-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="btn_trackchanges"> + <property name="visible">True</property> + <property name="label" translatable="yes">.uno:TrackChanges</property> + <property name="use_underline">True</property> + <property name="icon_name">media-record-symbolic</property> + <signal name="clicked" handler="btn_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="addressbar_toolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkEntry" id="addressbar_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="truncate-multiline">True</property> + <signal name="key-press-event" handler="signalAddressbar" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="formulabar_toolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkEntry" id="formulabar_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="truncate-multiline">True</property> + <signal name="key-press-event" handler="signalFormulabar" swapped="no"/> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <object class="GtkPopover" id="calendar"> + <property name="can-focus">False</property> + <property name="position">bottom</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkCalendar" id="date"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <signal name="day-selected-double-click" handler="changeDateContentControl" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/libreofficekit/qa/tilebench/tilebench.cxx b/libreofficekit/qa/tilebench/tilebench.cxx new file mode 100644 index 000000000..6b6dcf405 --- /dev/null +++ b/libreofficekit/qa/tilebench/tilebench.cxx @@ -0,0 +1,685 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <stdio.h> +#include <string.h> +#include <cmath> + +#include <vector> +#include <atomic> +#include <iostream> +#include <osl/time.h> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <LibreOfficeKit/LibreOfficeKitInit.h> +#include <LibreOfficeKit/LibreOfficeKit.hxx> + +#ifdef IOS +#include <vcl/svapp.hxx> +#endif + +#include <boost/property_tree/json_parser.hpp> + +using namespace lok; + +static int help( const char *error = nullptr ) +{ + if (error) + fprintf (stderr, "Error: %s\n\n", error); + fprintf( stderr, "Usage: tilebench <absolute-path-to-libreoffice-install> [path to document] [--preinit] <options>\n"); + fprintf( stderr, "\trenders a selection of small tiles from the document, checksums them and times the process based on options:\n" ); + fprintf( stderr, "\t--tile\t[max parts|-1] [max tiles|-1]\n" ); + fprintf( stderr, "\t--dialog\t<.uno:Command>\n" ); + fprintf( stderr, "\t--join\trun tile joining tests\n" ); + return 1; +} + +static double getTimeNow() +{ + TimeValue aValue; + osl_getSystemTime(&aValue); + return static_cast<double>(aValue.Seconds) + + static_cast<double>(aValue.Nanosec) / (1000*1000*1000); +} + +static double origin; + +namespace { + +struct TimeRecord { + const char *mpName; + double mfTime; + + TimeRecord() : mpName(nullptr), mfTime(getTimeNow()) { } + explicit TimeRecord(const char *pName) : + mpName(pName), mfTime(getTimeNow()) + { + fprintf(stderr, "%3.3fs - %s\n", (mfTime - origin), mpName); + } +}; + +} + +static std::vector< TimeRecord > aTimes; + +/// Dump an array (or sub-array) of RGBA or BGRA to an RGB PPM file. +static void dumpTile(const char *pNameStem, + const int nWidth, const int nHeight, + const int mode, const unsigned char* pBufferU, + const int nOffX = 0, const int nOffY = 0, + int nTotalWidth = -1) +{ + if (nTotalWidth < 0) + nTotalWidth = nWidth; + + auto pBuffer = reinterpret_cast<const char *>(pBufferU); + static int counter = 0; + std::string aName = "/tmp/dump_tile"; + aName += pNameStem; + aName += "_" + std::to_string(counter); + aName += ".ppm"; +#ifndef IOS + std::ofstream ofs(aName); +#else + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *path = [NSString stringWithFormat:@"%@/dump_tile_%d.ppm", documentsDirectory, counter]; + std::ofstream ofs([path UTF8String]); + std::cerr << "---> Dumping tile\n"; +#endif + counter++; + ofs << "P6\n" + << nWidth << " " + << nHeight << "\n" + << 255 << "\n" ; + + const bool dumpText = false; + + if (dumpText) + fprintf(stderr, "Stream %s - %dx%d:\n", pNameStem, nWidth, nHeight); + + for (int y = 0; y < nHeight; ++y) + { + const char* row = pBuffer + (y + nOffY) * nTotalWidth * 4 + nOffX * 4; + for (int x = 0; x < nWidth; ++x) + { + const char* pixel = row + x * 4; + if (mode == LOK_TILEMODE_RGBA) + { + ofs.write(pixel, 3); // Skip alpha + } + else if (mode == LOK_TILEMODE_BGRA) + { + const int alpha = *(pixel + 3); + char buf[3]; + if (alpha == 0) + { + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + } + else + { + buf[0] = (*(pixel + 2) * 255 + alpha / 2) / alpha; + buf[1] = (*(pixel + 1) * 255 + alpha / 2) / alpha; + buf[2] = (*(pixel + 0) * 255 + alpha / 2) / alpha; + } + + ofs.write(buf, 3); + } + if (dumpText) + { + int lowResI = (pixel[0] + pixel[1] + pixel[2])/(3*16); + fprintf(stderr,"%1x", lowResI); + } + } + if (dumpText) + fprintf(stderr,"\n"); + } + ofs.close(); +} + +static void testTile( Document *pDocument, int max_parts, + int max_tiles, bool dump ) +{ + const int mode = pDocument->getTileMode(); + + aTimes.emplace_back("getparts"); + const int nOriginalPart = (pDocument->getDocumentType() == LOK_DOCTYPE_TEXT ? 1 : pDocument->getPart()); + // Writer really has 1 part (the full doc). + const int nTotalParts = (pDocument->getDocumentType() == LOK_DOCTYPE_TEXT ? 1 : pDocument->getParts()); + const int nParts = (max_parts < 0 ? nTotalParts : std::min(max_parts, nTotalParts)); + aTimes.emplace_back(); + + aTimes.emplace_back("get size of parts"); + long nWidth = 0; + long nHeight = 0; + for (int n = 0; n < nParts; ++n) + { + const int nPart = (nOriginalPart + n) % nTotalParts; + char* pName = pDocument->getPartName(nPart); + pDocument->setPart(nPart); + pDocument->getDocumentSize(&nWidth, &nHeight); + fprintf (stderr, " '%s' -> %ld, %ld\n", pName, nWidth, nHeight); + free (pName); + } + aTimes.emplace_back(); + + // Use realistic dimensions, similar to the Online client. + long const nTilePixelWidth = 512; + long const nTilePixelHeight = 512; + long const nTileTwipWidth = 3840; + long const nTileTwipHeight = 3840; + + // Estimate the maximum tiles based on the number of parts requested, if Writer. + if (pDocument->getDocumentType() == LOK_DOCTYPE_TEXT) + max_tiles = static_cast<int>(ceil(max_parts * 16128. / nTilePixelHeight) * ceil(static_cast<double>(nWidth) / nTilePixelWidth)); + fprintf(stderr, "Parts to render: %d, Total Parts: %d, Max parts: %d, Max tiles: %d\n", nParts, nTotalParts, max_parts, max_tiles); + + std::vector<unsigned char> vBuffer(nTilePixelWidth * nTilePixelHeight * 4); + unsigned char* pPixels = vBuffer.data(); + + for (int n = 0; n < nParts; ++n) + { + const int nPart = (nOriginalPart + n) % nTotalParts; + char* pName = pDocument->getPartName(nPart); + pDocument->setPart(nPart); + pDocument->getDocumentSize(&nWidth, &nHeight); + fprintf (stderr, "render '%s' -> %ld, %ld\n", pName, nWidth, nHeight); + free (pName); + + if (dump || pDocument->getDocumentType() != LOK_DOCTYPE_TEXT) + { + // whole part; meaningful only for non-writer documents. + aTimes.emplace_back("render whole part"); + pDocument->paintTile(pPixels, nTilePixelWidth, nTilePixelHeight, + nWidth/2, 2000, 1000, 1000); + aTimes.emplace_back(); + if (dump) + dumpTile("tile", nTilePixelWidth, nTilePixelHeight, mode, pPixels); + } + + { // 1:1 + aTimes.emplace_back("render sub-region at 1:1"); + // Estimate the maximum tiles based on the number of parts requested, if Writer. + int nMaxTiles = max_tiles; + int nTiles = 0; + for (long nY = 0; nY < nHeight - 1; nY += nTilePixelHeight) + { + for (long nX = 0; nX < nWidth - 1; nX += nTilePixelWidth) + { + if (nMaxTiles >= 0 && nTiles >= nMaxTiles) + { + nY = nHeight; + break; + } + pDocument->paintTile(pPixels, nTilePixelWidth, nTilePixelHeight, + nX, nY, nTilePixelWidth, nTilePixelHeight); + nTiles++; + fprintf (stderr, " rendered 1:1 tile %d at %ld, %ld\n", + nTiles, nX, nY); + } + } + aTimes.emplace_back(); + } + + { // scaled + aTimes.emplace_back("render sub-regions at scale"); + int nMaxTiles = max_tiles; + if (pDocument->getDocumentType() == LOK_DOCTYPE_TEXT) + nMaxTiles = static_cast<int>(ceil(max_parts * 16128. / nTileTwipHeight) * ceil(static_cast<double>(nWidth) / nTileTwipWidth)); + int nTiles = 0; + for (long nY = 0; nY < nHeight - 1; nY += nTileTwipHeight) + { + for (long nX = 0; nX < nWidth - 1; nX += nTileTwipWidth) + { + if (nMaxTiles >= 0 && nTiles >= nMaxTiles) + { + nY = nHeight; + break; + } + pDocument->paintTile(pPixels, nTilePixelWidth, nTilePixelHeight, + nX, nY, nTileTwipWidth, nTileTwipHeight); + nTiles++; + fprintf (stderr, " rendered scaled tile %d at %ld, %ld\n", + nTiles, nX, nY); + } + } + aTimes.emplace_back(); + } + } +} + +static uint32_t fade(uint32_t col) +{ + uint8_t a = (col >> 24) & 0xff; + uint8_t b = (col >> 16) & 0xff; + uint8_t g = (col >> 8) & 0xff; + uint8_t r = (col >> 0) & 0xff; + uint8_t grey = (r+g+b)/6; + return (a<<24) + (grey<<16) + (grey<<8) + grey; +} + +static bool sloppyEqual(uint32_t pixA, uint32_t pixB) +{ + uint8_t a[4], b[4]; + + a[0] = (pixA >> 24) & 0xff; + a[1] = (pixA >> 16) & 0xff; + a[2] = (pixA >> 8) & 0xff; + a[3] = (pixA >> 0) & 0xff; + + b[0] = (pixB >> 24) & 0xff; + b[1] = (pixB >> 16) & 0xff; + b[2] = (pixB >> 8) & 0xff; + b[3] = (pixB >> 0) & 0xff; + + for (int i = 0; i < 4; ++i) + { + int delta = a[i]; + delta -= b[i]; + // tolerate small differences + if (delta < -4 || delta > 4) + return false; + } + return true; +} + +// Count and build a picture of any differences into rDiff +static int diffTiles( const std::vector<unsigned char> &vBase, + long nBaseRowPixelWidth, + const std::vector<unsigned char> &vCompare, + long nCompareRowPixelWidth, + long nTilePixelHeight, + long nPosX, long nPosY, + std::vector<unsigned char> &rDiff ) +{ + int nDifferent = 0; + const uint32_t *pBase = reinterpret_cast<const uint32_t *>(vBase.data()); + const uint32_t *pCompare = reinterpret_cast<const uint32_t *>(vCompare.data()); + uint32_t *pDiff = reinterpret_cast<uint32_t *>(rDiff.data()); + long left = 0, mid = nCompareRowPixelWidth, right = nCompareRowPixelWidth*2; + for (long y = 0; y < nTilePixelHeight; ++y) + { + long nBaseOffset = nBaseRowPixelWidth * (y + nPosY) + nPosX * nCompareRowPixelWidth; + long nCompareOffset = nCompareRowPixelWidth * y; + long nDiffRowStart = nCompareOffset * 3; + for (long x = 0; x < nCompareRowPixelWidth; ++x) + { + pDiff[nDiffRowStart + left + x] = pBase[nBaseOffset + x]; + pDiff[nDiffRowStart + mid + x] = pCompare[nCompareOffset + x]; + pDiff[nDiffRowStart + right + x] = fade(pBase[nBaseOffset + x]); + if (!sloppyEqual(pBase[nBaseOffset + x], pCompare[nCompareOffset + x])) + { + pDiff[nDiffRowStart + right + x] = 0xffff00ff; + if (!nDifferent) + fprintf (stderr, "First mismatching pixel at %ld (pixels) into row %ld\n", x, y); + nDifferent++; + } + } + } + return nDifferent; +} + +static std::vector<unsigned char> paintTile( Document *pDocument, + long nX, long nY, + long const nTilePixelWidth, + long const nTilePixelHeight, + long const nTileTwipWidth, + long const nTileTwipHeight ) +{ +// long e = 0; // tweak if we suspect an overlap / visibility issue. +// pDocument->setClientVisibleArea( nX - e, nY - e, nTileTwipWidth + e, nTileTwipHeight + e ); + std::vector<unsigned char> vData( nTilePixelWidth * nTilePixelHeight * 4 ); + pDocument->paintTile( vData.data(), nTilePixelWidth, nTilePixelHeight, + nX, nY, nTileTwipWidth, nTileTwipHeight ); + return vData; +} + +static int testJoinsAt( Document *pDocument, long nX, long nY, + long const nTilePixelSize, + long const nTileTwipSize ) +{ + const int mode = pDocument->getTileMode(); + + long const nTilePixelWidth = nTilePixelSize; + long const nTilePixelHeight = nTilePixelSize; + long const nTileTwipWidth = nTileTwipSize; + long const nTileTwipHeight = nTileTwipSize; + + long initPosX = nX * nTileTwipWidth, initPosY = nY * nTileTwipHeight; + + // Calc has to do significant work on changing zoom ... + pDocument->setClientZoom( nTilePixelWidth, nTilePixelHeight, + nTileTwipWidth, nTileTwipHeight ); + + // Unfortunately without getting this nothing renders ... + std::stringstream aForceHeaders; + aForceHeaders << ".uno:ViewRowColumnHeaders?x=" << initPosX << "&y=" << initPosY << + "&width=" << (nTileTwipWidth * 2) << "&height=" << (nTileTwipHeight * 2); + std::string cmd = aForceHeaders.str(); + char* pJSON = pDocument->getCommandValues(cmd.c_str()); + fprintf(stderr, "command: '%s' values '%s'\n", cmd.c_str(), pJSON); + free(pJSON); + + // Get a base image 4x the size + std::vector<unsigned char> vBase( + paintTile(pDocument, initPosX, initPosY, + nTilePixelWidth * 2, nTilePixelHeight * 2, + nTileTwipWidth * 2, nTileTwipHeight * 2)); + + const struct { + long X; + long Y; + } aCompare[] = { + { 0, 0 }, + { 1, 0 }, + { 0, 1 }, + { 1, 1 } + }; + + int nDifferences = 0; + // Compare each of the 4x tiles with a sub-tile of the larger image + for( auto &rPos : aCompare ) + { + std::vector<unsigned char> vCompare( + paintTile(pDocument, + initPosX + rPos.X * nTileTwipWidth, + initPosY + rPos.Y * nTileTwipHeight, + nTilePixelWidth, nTilePixelHeight, + nTileTwipWidth, nTileTwipHeight)); + + std::vector<unsigned char> vDiff( nTilePixelWidth * 3 * nTilePixelHeight * 4 ); + int nDiffs = diffTiles( vBase, nTilePixelWidth * 2, + vCompare, nTilePixelWidth, + nTilePixelHeight, + rPos.X, rPos.Y * nTilePixelHeight, + vDiff ); + if ( nDiffs > 0 ) + { + fprintf( stderr, " %d differences in sub-tile pixel mismatch at %ld, %ld at offset %ld, %ld (twips) size %ld\n", + nDiffs, rPos.X, rPos.Y, initPosX, initPosY, + nTileTwipWidth); + dumpTile("_base", nTilePixelWidth * 2, nTilePixelHeight * 2, + mode, vBase.data()); +/* dumpTile("_sub", nTilePixelWidth, nTilePixelHeight, + mode, vBase.data(), + rPos.X*nTilePixelWidth, rPos.Y*nTilePixelHeight, + nTilePixelWidth * 2); + dumpTile("_compare", nTilePixelWidth, nTilePixelHeight, + mode, vCompare.data());*/ + dumpTile("_diff", nTilePixelWidth * 3, nTilePixelHeight, mode, vDiff.data()); + } + nDifferences += nDiffs; + } + + return nDifferences; +} + +// Check that our tiles join nicely ... +static int testJoin( Document *pDocument) +{ + // Ignore parts - just the first for now ... + long nWidth = 0, nHeight = 0; + pDocument->getDocumentSize(&nWidth, &nHeight); + fprintf (stderr, "Width is %ld, %ld (twips)\n", nWidth, nHeight); + + // Use realistic dimensions, similar to the Online client. + long const nTilePixelSize = 256; + long const nTileTwipSize = 3840; + double fZooms[] = { + 0.5, + 0.6, 0.7, 0.85, + 1.0, + 1.2, 1.5, 1.75, + 2.0 + }; + long nFails = 0; + std::stringstream results; + + for( auto z : fZooms ) + { + long nBad = 0; + long nDifferences = 0; + for( long y = 0; y < 8; ++y ) + { + for( long x = 0; x < 8; ++x ) + { + int nDiffs = testJoinsAt( pDocument, x, y, nTilePixelSize, nTileTwipSize * z ); + if (nDiffs) + nBad++; + nDifferences += nDiffs; + } + } + if (nBad > 0) + results << "\tZoom " << z << " bad tiles: " << nBad << " with " << nDifferences << " mismatching pixels\n"; + nFails += nBad; + } + + if (nFails > 0) + fprintf( stderr, "Failed %ld joins\n", nFails ); + else + fprintf( stderr, "All joins compared correctly\n" ); + + fprintf(stderr, "%s\n", results.str().c_str()); + + return nFails; +} + +static std::atomic<bool> bDialogRendered(false); +static std::atomic<int> nDialogId(-1); + +static void kitCallback(int nType, const char* pPayload, void* pData) +{ + Document *pDocument = static_cast<Document *>(pData); + + if (nType != LOK_CALLBACK_WINDOW) + return; + + std::stringstream aStream(pPayload); + boost::property_tree::ptree aRoot; + boost::property_tree::read_json(aStream, aRoot); + nDialogId = aRoot.get<unsigned>("id"); + const std::string aAction = aRoot.get<std::string>("action"); + + if (aAction != "created") + return; + + const std::string aType = aRoot.get<std::string>("type"); + const std::string aSize = aRoot.get<std::string>("size"); + int nWidth = atoi(aSize.c_str()); + int nHeight = 400; + const char *pComma = strstr(aSize.c_str(), ", "); + if (pComma) + nHeight = atoi(pComma + 2); + std::cerr << "Size " << aSize << " is " << nWidth << ", " << nHeight << "\n"; + + if (aType != "dialog") + return; + + aTimes.emplace_back(); // complete wait for dialog + + unsigned char *pBuffer = new unsigned char[nWidth * nHeight * 4]; + + aTimes.emplace_back("render dialog"); + pDocument->paintWindow(nDialogId, pBuffer, 0, 0, nWidth, nHeight); + dumpTile("dialog", nWidth, nHeight, pDocument->getTileMode(), pBuffer); + aTimes.emplace_back(); + + delete[] pBuffer; + + bDialogRendered = true; +} + +static void testDialog( Document *pDocument, const char *uno_cmd ) +{ + int view = pDocument->createView(); + pDocument->setView(view); + pDocument->registerCallback(kitCallback, pDocument); + + aTimes.emplace_back("open dialog"); + pDocument->postUnoCommand(uno_cmd, nullptr, true); + aTimes.emplace_back(); + + aTimes.emplace_back("wait for dialog"); + while (!bDialogRendered) + { + usleep (1000); + } + + aTimes.emplace_back("post close dialog"); + pDocument->postWindow(nDialogId, LOK_WINDOW_CLOSE); + aTimes.emplace_back(); + + pDocument->destroyView(view); +} + +static void documentCallback(const int type, const char* p, void*) +{ + std::cerr << "Document callback " << type << ": " << (p ? p : "(null)") << "\n"; +} + +// Avoid excessive dbgutil churn. +static void ignoreCallback(const int /*type*/, const char* /*p*/, void* /*data*/) +{ +} + +int main( int argc, char* argv[] ) +{ + int arg = 2; + origin = getTimeNow(); + +#ifndef IOS + // avoid X oddness etc. + unsetenv("DISPLAY"); + + if( argc < 4 || + ( argc > 1 && ( !strcmp( argv[1], "--help" ) || !strcmp( argv[1], "-h" ) ) ) ) + return help(); + + if ( argv[1][0] != '/' ) + { + fprintf(stderr, "Absolute path required to libreoffice install\n"); + return 1; + } + + const char *doc_url = argv[arg++]; + const char *mode = argv[arg++]; + + bool pre_init = false; + if (!strcmp(mode, "--preinit")) + { + pre_init = true; + mode = argv[arg++]; + } + + std::string user_url("file:///"); + user_url.append(argv[1]); + user_url.append("../user"); + + if (pre_init) + { + aTimes.emplace_back("pre-initialization"); + setenv("LOK_ALLOWLIST_LANGUAGES", "en_US", 0); + // coverity[tainted_string] - build time test tool + lok_preinit(argv[1], user_url.c_str()); + aTimes.emplace_back(); + } + const char *install_path = argv[1]; + const char *user_profile = user_url.c_str(); +#else + const char *install_path = nullptr; + const char *user_profile = nullptr; + const char *doc_url = strdup([[[[[NSBundle mainBundle] bundleURL] absoluteString] stringByAppendingString:@"/test.odt"] UTF8String]); + const char *mode = "--tile"; +#endif + + aTimes.emplace_back("initialization"); + // coverity[tainted_string] - build time test tool + std::unique_ptr<Office> pOffice( lok_cpp_init(install_path, user_profile) ); + if (pOffice == nullptr) + { + fprintf(stderr, "Failed to initialize Office from %s\n", argv[1]); + return 1; + } + aTimes.emplace_back(); + pOffice->registerCallback(ignoreCallback, nullptr); + + std::unique_ptr<Document> pDocument; + + pOffice->setOptionalFeatures(LOK_FEATURE_NO_TILED_ANNOTATIONS); + + aTimes.emplace_back("load document"); + if (doc_url != nullptr) + pDocument.reset(pOffice->documentLoad(doc_url)); + aTimes.emplace_back(); + + if (pDocument) + { + pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"Local Host #0\"}}"); + pDocument->registerCallback(documentCallback, nullptr); + if (!strcmp(mode, "--tile")) + { + const int max_parts = (argc > arg ? atoi(argv[arg++]) : -1); + int max_tiles = (argc > arg ? atoi(argv[arg++]) : -1); + const bool dump = true; + + // coverity[tainted_data] - we trust the contents of this variable + testTile (pDocument.get(), max_parts, max_tiles, dump); + } + else if (!strcmp(mode, "--join")) + { + return testJoin (pDocument.get()); + } + else if (!strcmp (mode, "--dialog")) + { + const char *uno_cmd = argc > arg ? argv[arg++] : nullptr; + if (!uno_cmd) + { + switch (pDocument->getDocumentType()) + { + case LOK_DOCTYPE_SPREADSHEET: + uno_cmd = ".uno:FormatCellDialog"; + break; + case LOK_DOCTYPE_TEXT: + case LOK_DOCTYPE_PRESENTATION: + case LOK_DOCTYPE_DRAWING: + case LOK_DOCTYPE_OTHER: + return help("missing argument to --dialog and no default"); + } + } + testDialog (pDocument.get(), uno_cmd); + } else + return help ("unknown parameter"); + } + +#ifdef IOS + Application::Quit(); +#endif + aTimes.emplace_back("destroy document"); + pDocument.reset(); + aTimes.emplace_back(); + + pOffice.reset(); + + double nTotal = 0.0; + fprintf (stderr, "profile run:\n"); + for (size_t i = 0; i < aTimes.size() - 1; i++) + { + const double nDelta = aTimes[i+1].mfTime - aTimes[i].mfTime; + fprintf (stderr, " %s - %2.4f(ms)\n", aTimes[i].mpName, nDelta * 1000.0); + if (aTimes[i+1].mpName == nullptr) + i++; // skip it. + nTotal += nDelta; + } + fprintf (stderr, "Total: %2.4f(s)\n", nTotal); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/unit/checkapi.cxx b/libreofficekit/qa/unit/checkapi.cxx new file mode 100644 index 000000000..ec4a71836 --- /dev/null +++ b/libreofficekit/qa/unit/checkapi.cxx @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#if defined LIBO_INTERNAL_ONLY +#error Build system problem; LIBO_INTERNAL_ONLY should not be defined here +#endif + +#include <sal/config.h> +#include <sal/types.h> + +#include <cppunit/plugin/TestPlugIn.h> + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/unit/compile_test.c b/libreofficekit/qa/unit/compile_test.c new file mode 100644 index 000000000..650718efa --- /dev/null +++ b/libreofficekit/qa/unit/compile_test.c @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#define LOK_USE_UNSTABLE_API +#include <LibreOfficeKit/LibreOfficeKit.h> +#include <LibreOfficeKit/LibreOfficeKitInit.h> + +// fake usage for loplugin:unreffun plugin +#include "test.h" + +// just make sure this stuff compiles from a plain C file +LibreOfficeKit* compile_test(void) { return lok_init("install/path"); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/unit/test.h b/libreofficekit/qa/unit/test.h new file mode 100644 index 000000000..f29db13bb --- /dev/null +++ b/libreofficekit/qa/unit/test.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_LIBREOFFICEKIT_QA_TEST_H +#define INCLUDED_LIBREOFFICEKIT_QA_TEST_H + +#include <LibreOfficeKit/LibreOfficeKit.h> + +LibreOfficeKit* compile_test(void); + +#endif // INCLUDED_LIBREOFFICEKIT_QA_TEST_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/libreofficekit/qa/unit/tiledrendering.cxx b/libreofficekit/qa/unit/tiledrendering.cxx new file mode 100644 index 000000000..56d789b61 --- /dev/null +++ b/libreofficekit/qa/unit/tiledrendering.cxx @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <memory> +#include <thread> +#include <boost/property_tree/json_parser.hpp> +#include <cppunit/TestFixture.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cstdlib> +#include <string> +#include <stdio.h> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> + +#include <com/sun/star/awt/Key.hpp> + +#if defined __clang__ && defined __linux__ +#include <cxxabi.h> +#include <config_options.h> +#if defined _LIBCPPABI_VERSION || !ENABLE_RUNTIME_OPTIMIZATIONS +#define LOK_LOADLIB_GLOBAL +#endif +#endif + +#include <LibreOfficeKit/LibreOfficeKitInit.h> +#include <LibreOfficeKit/LibreOfficeKit.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +using namespace ::boost; +using namespace ::lok; +using namespace ::std; + +namespace { + +void processEventsToIdle() +{ + typedef void (ProcessEventsToIdleFn)(void); + static ProcessEventsToIdleFn *processFn = nullptr; + if (!processFn) + { + void *me = dlopen(nullptr, RTLD_NOW); + processFn = reinterpret_cast<ProcessEventsToIdleFn *>(dlsym(me, "unit_lok_process_events_to_idle")); + } + + CPPUNIT_ASSERT(processFn); + + (*processFn)(); +} + +void insertString(Document& rDocument, const std::string& s) +{ + for (const char c : s) + { + rDocument.postKeyEvent(LOK_KEYEVENT_KEYINPUT, c, 0); + rDocument.postKeyEvent(LOK_KEYEVENT_KEYUP, c, 0); + processEventsToIdle(); + } +} + +} + +static OUString getFileURLFromSystemPath(OUString const & path) +{ + OUString url; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(path, url); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, e); + if (!url.endsWith("/")) + url += "/"; + return url; +} + +// We specifically don't use the usual BootStrapFixture, as LOK does +// all its own setup and bootstrapping, and should be usable in a +// raw C++ program. +class TiledRenderingTest : public ::CppUnit::TestFixture +{ +public: + const string m_sSrcRoot; + const string m_sInstDir; + const string m_sLOPath; + + std::unique_ptr<Document> loadDocument( Office *pOffice, const string &pName, + const char *pFilterOptions = nullptr ); + + TiledRenderingTest() + : m_sSrcRoot( getenv( "SRC_ROOT" ) ) + , m_sInstDir( getenv( "INSTDIR" ) ) + , m_sLOPath( m_sInstDir + "/program" ) + { + } + + // Currently it isn't possible to do multiple startup/shutdown + // cycle of LOK in a single process -- hence we run all our tests + // as one test, which simply carries out the individual test + // components on the one Office instance that we retrieve. + void runAllTests(); + + void testDocumentLoadFail( Office* pOffice ); + void testDocumentTypes( Office* pOffice ); + void testImpressSlideNames( Office* pOffice ); + void testCalcSheetNames( Office* pOffice ); + void testPaintPartTile( Office* pOffice ); + void testDocumentLoadLanguage(Office* pOffice); + void testMultiKeyInput(Office *pOffice); +#if 0 + void testOverlay( Office* pOffice ); +#endif + + CPPUNIT_TEST_SUITE(TiledRenderingTest); + CPPUNIT_TEST(runAllTests); + CPPUNIT_TEST_SUITE_END(); +}; + +void TiledRenderingTest::runAllTests() +{ + // set UserInstallation to user profile dir in test/user-template + const char* pWorkdirRoot = getenv("WORKDIR_FOR_BUILD"); + OUString aWorkdirRootPath = OUString::createFromAscii(pWorkdirRoot); + OUString aWorkdirRootURL = getFileURLFromSystemPath(aWorkdirRootPath); + OUString sUserInstallURL = aWorkdirRootURL + "/unittest"; + rtl::Bootstrap::set("UserInstallation", sUserInstallURL); + + std::unique_ptr< Office > pOffice( lok_cpp_init( + m_sLOPath.c_str() ) ); + CPPUNIT_ASSERT( pOffice ); + + testDocumentLoadFail( pOffice.get() ); + testDocumentTypes( pOffice.get() ); + testMultiKeyInput(pOffice.get()); + testImpressSlideNames( pOffice.get() ); + testCalcSheetNames( pOffice.get() ); + testPaintPartTile( pOffice.get() ); + testDocumentLoadLanguage(pOffice.get()); +#if 0 + testOverlay( pOffice.get() ); +#endif +} + +void TiledRenderingTest::testDocumentLoadFail( Office* pOffice ) +{ + const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/IDONOTEXIST.odt"; + std::unique_ptr< Document> pDocument( pOffice->documentLoad( sDocPath.c_str() ) ); + CPPUNIT_ASSERT( !pDocument ); + // TODO: we probably want to have some way of returning what + // the cause of failure was. getError() will return + // something along the lines of: + // "Unsupported URL <file:///SRC_ROOT/libreofficekit/qa/data/IDONOTEXIST.odt>: "type detection failed"" +} + +// Our dumped .png files end up in +// workdir/CppunitTest/libreofficekit_tiledrendering.test.core + +static int getDocumentType( Office* pOffice, const string& rPath ) +{ + std::unique_ptr< Document> pDocument( pOffice->documentLoad( rPath.c_str() ) ); + CPPUNIT_ASSERT( pDocument ); + return pDocument->getDocumentType(); +} + +std::unique_ptr<Document> TiledRenderingTest::loadDocument( Office *pOffice, const string &pName, + const char *pFilterOptions ) +{ + const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/" + pName; + const string sLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock." + pName + "#"; + + remove( sLockFile.c_str() ); + + return std::unique_ptr<Document>(pOffice->documentLoad( sDocPath.c_str(), pFilterOptions )); +} + +void TiledRenderingTest::testDocumentTypes( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + // This crashed. + pDocument->postUnoCommand(".uno:Bold"); + processEventsToIdle(); + + const string sPresentationDocPath = m_sSrcRoot + "/libreofficekit/qa/data/blank_presentation.odp"; + const string sPresentationLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock.blank_presentation.odp#"; + + // FIXME: same comment as below wrt lockfile removal. + remove( sPresentationLockFile.c_str() ); + + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_PRESENTATION, static_cast<LibreOfficeKitDocumentType>(getDocumentType(pOffice, sPresentationDocPath))); + + // TODO: do this for all supported document types +} + +void TiledRenderingTest::testImpressSlideNames( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "impress_slidenames.odp")); + + CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts()); + CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0))); + CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1))); + // The third slide hasn't had a name given to it (i.e. using the rename + // context menu in Impress), thus it should (as far as I can determine) + // have a localised version of "Slide 3". +} + +void TiledRenderingTest::testCalcSheetNames( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "calc_sheetnames.ods")); + + CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts()); + CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0))); + CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1))); + CPPUNIT_ASSERT_EQUAL(std::string("Sheet3"), std::string(pDocument->getPartName(2))); +} + +void TiledRenderingTest::testPaintPartTile(Office* pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + + // Create two views. + pDocument->getView(); + pDocument->createView(); + + int nView2 = pDocument->getView(); + + // Destroy the current view + pDocument->destroyView(nView2); + + int nCanvasWidth = 256; + int nCanvasHeight = 256; + std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4); + + // And try to paintPartTile() - this used to crash when the current viewId + // was destroyed + pDocument->paintPartTile(aBuffer.data(), /*nPart=*/0, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840); +} + +void TiledRenderingTest::testDocumentLoadLanguage(Office* pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt", "Language=en-US")); + + // assert that '.' is the decimal separator + insertString(*pDocument, "1.5"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT); + processEventsToIdle(); + + insertString(*pDocument, "=2*A1"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN); + processEventsToIdle(); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP); + processEventsToIdle(); + +#if 0 + // FIXME disabled, as occasionally fails + // we've got a meaningful result + OString aResult = pDocument->getTextSelection("text/plain;charset=utf-8"); + CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult); + + pDocument.reset(); + + // FIXME: LOK will fail when trying to open a locked file + remove(sLockFile.c_str()); + + // load the file again, now in another language + pDocument.reset(pOffice->documentLoad(sDocPath.c_str(), "Language=cs-CZ")); + + // with cs-CZ, the decimal separator is ',' instead, assert that + insertString(*pDocument, "1,5"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT); + processEventsToIdle(); + + insertString(*pDocument, "=2*A1"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN); + processEventsToIdle(); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP); + processEventsToIdle(); + + // we've got a meaningful result + aResult = pDocument->getTextSelection("text/plain;charset=utf-8"); + CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult); +#endif +} + +#if 0 +static void dumpRGBABitmap( const OUString& rPath, const unsigned char* pBuffer, + const int nWidth, const int nHeight ) +{ + Bitmap aBitmap( Size( nWidth, nHeight ), 32 ); + BitmapScopedWriteAccess pWriteAccess( aBitmap ); + memcpy( pWriteAccess->GetBuffer(), pBuffer, 4*nWidth*nHeight ); + + BitmapEx aBitmapEx( aBitmap ); + vcl::PNGWriter aWriter( aBitmapEx ); + SvFileStream sOutput( rPath, StreamMode::WRITE ); + aWriter.Write( sOutput ); + sOutput.Close(); +} + +void TiledRenderingTest::testOverlay( Office* /*pOffice*/ ) +{ + const string sDocPath = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/test1.odt"; + const string sLockFile = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/.~lock.test1.odt#"; + + // FIXME: this is a temporary hack: LOK will fail when trying to open a + // locked file, and since we're reusing the file for a different unit + // test it's entirely possible that an unwanted lock file will remain. + // Hence forcefully remove it here. + remove( sLockFile.c_str() ); + std::unique_ptr< Office > pOffice( lok_cpp_init( + m_sLOPath.c_str() ) ); + assert( pOffice.get() ); + + std::unique_ptr< Document> pDocument( pOffice->documentLoad( + sDocPath.c_str() ) ); + + if ( !pDocument.get() ) + { + fprintf( stderr, "documentLoad failed: %s\n", pOffice->getError() ); + CPPUNIT_FAIL( "Document could not be loaded -- tiled rendering not possible." ); + } + + // We render one large tile, then subdivide it into 4 and render those parts, and finally + // iterate over each smaller tile and check whether their contents match the large + // tile. + const int nTotalWidthPix = 512; + const int nTotalHeightPix = 512; + int nRowStride; + + long nTotalWidthDoc; + long nTotalHeightDoc; + // pDocument->getDocumentSize( &nTotalWidthDoc, &nTotalHeightDoc ); + // TODO: make sure we select an actually interesting part of the document + // for this comparison, i.e. ideally an image and lots of text, in order + // to test as many edge cases as possible. + // Alternatively we could rewrite this to actually grab the document size + // and iterate over it (subdividing into an arbitrary number of tiles rather + // than our less sophisticated test of just 4 sub-tiles). + nTotalWidthDoc = 8000; + nTotalHeightDoc = 9000; + + std::unique_ptr< unsigned char []> pLarge( new unsigned char[ 4*nTotalWidthPix*nTotalHeightPix ] ); + pDocument->paintTile( pLarge.get(), nTotalWidthPix, nTotalHeightPix, &nRowStride, + 0, 0, + nTotalWidthDoc, nTotalHeightDoc ); + dumpRGBABitmap( "large.png", pLarge.get(), nTotalWidthPix, nTotalHeightPix ); + + std::unique_ptr< unsigned char []> pSmall[4]; + for ( int i = 0; i < 4; i++ ) + { + pSmall[i].reset( new unsigned char[ 4*(nTotalWidthPix/2)*(nTotalHeightPix/2) ] ); + pDocument->paintTile( pSmall[i].get(), nTotalWidthPix / 2, nTotalHeightPix / 2, &nRowStride, + // Tile 0/2: left. Tile 1/3: right. Tile 0/1: top. Tile 2/3: bottom + ((i%2 == 0) ? 0 : nTotalWidthDoc / 2), ((i < 2 ) ? 0 : nTotalHeightDoc / 2), + nTotalWidthDoc / 2, nTotalHeightDoc / 2); + dumpRGBABitmap( "small_" + OUString::number(i) + ".png", + pSmall[i].get(), nTotalWidthPix/2, nTotalHeightPix/2 ); + } + + // Iterate over each pixel of the sub-tile, and compare that pixel for every + // tile with the equivalent super-tile pixel. + for ( int i = 0; i < 4*nTotalWidthPix / 2 * nTotalHeightPix / 2; i++ ) + { + int xSmall = i % (4*nTotalWidthPix/2); + int ySmall = i / (4*nTotalWidthPix/2); + // Iterate over our array of tiles + // However for now we only bother with the top-left + // tile as the other ones don't match yet... + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + int xLarge = (x * (4 * nTotalWidthPix / 2)) + xSmall; + int yLarge = (y * (nTotalHeightPix / 2)) + ySmall; + CPPUNIT_ASSERT( pSmall[2*y+x][i] == pLarge[yLarge*4*nTotalWidthPix + xLarge] ); + } + } + } +} +#endif + +void TiledRenderingTest::testMultiKeyInput(Office *pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + + // Create two views. + int nViewA = pDocument->getView(); + pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jill\"}}"); + + pDocument->createView(); + int nViewB = pDocument->getView(); + pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jack\"}}"); + + // Enable change tracking + pDocument->postUnoCommand(".uno:TrackChanges"); + + // First a key-stroke from a + pDocument->setView(nViewA); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a + + // A space on 'a' - force commit + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' ' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' ' + + // Another 'a' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a + + // FIXME: Wait for writer input handler to commit that. + // without this we fall foul of edtwin's KeyInputFlushTimer + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // Quickly a new key-stroke from b + pDocument->setView(nViewB); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 98, 0); // b + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 514); // 'b + + // A space on 'b' - force commit + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' ' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' ' + + // Wait for writer input handler to commit that. + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // get track changes ? + char *values = pDocument->getCommandValues(".uno:AcceptTrackedChanges"); + std::cerr << "Values: '" << values << "'\n"; +} + +CPPUNIT_TEST_SUITE_REGISTRATION(TiledRenderingTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |