diff options
Diffstat (limited to 'libreofficekit/qa/gtktiledviewer')
20 files changed, 4974 insertions, 0 deletions
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> |