/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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/.
*/
// Cursor bitmaps from the installation set. #define CURSOR_HANDLE_DIR "/../share/libreofficekit/" // Number of handles around a graphic selection. #define GRAPHIC_HANDLE_COUNT 8 // Maximum Zoom allowed #define MAX_ZOOM 5.0f // Minimum Zoom allowed #define MIN_ZOOM 0.25f
/// This is expected to be locked during setView(), doSomethingElse() LOK calls. static std::mutex g_aLOKMutex;
namespace {
/// Same as a GdkRectangle, but also tracks in which part the rectangle is. struct ViewRectangle
{ int m_nPart;
GdkRectangle m_aRectangle;
/// Same as a list of GdkRectangles, but also tracks in which part the rectangle is. struct ViewRectangles
{ int m_nPart;
std::vector<GdkRectangle> m_aRectangles;
gfloat m_fZoom;
glong m_nDocumentWidthTwips;
glong m_nDocumentHeightTwips; /// View or edit mode. bool m_bEdit; /// LOK Features
guint64 m_nLOKFeatures; /// Number of parts in currently loaded document
gint m_nParts; /// Position and size of the visible cursor.
GdkRectangle m_aVisibleCursor; /// Position and size of the view cursors. The current view can only see /// them, can't modify them. Key is the view id.
std::map<int, ViewRectangle> m_aViewCursors; /// Cursor overlay is visible or hidden (for blinking). bool m_bCursorOverlayVisible; /// Cursor is visible or hidden (e.g. for graphic selection). bool m_bCursorVisible; /// Visibility of view selections. The current view can only see / them, /// can't modify them. Key is the view id.
std::map<int, bool> m_aViewCursorVisibilities; /// Time of the last button press.
guint32 m_nLastButtonPressTime; /// Time of the last button release.
guint32 m_nLastButtonReleaseTime; /// Last pressed button (left, right, middle)
guint32 m_nLastButtonPressed; /// Key modifier (ctrl, atl, shift)
guint32 m_nKeyModifier; /// Rectangles of the current text selection.
std::vector<GdkRectangle> m_aTextSelectionRectangles; /// Rectangles of the current content control.
std::vector<GdkRectangle> m_aContentControlRectangles; /// Alias/title of the current content control.
std::string m_aContentControlAlias; /// Rectangles of view selections. The current view can only see /// them, can't modify them. Key is the view id.
std::map<int, ViewRectangles> m_aTextViewSelectionRectangles; /// Position and size of the selection start (as if there would be a cursor caret there).
GdkRectangle m_aTextSelectionStart; /// Position and size of the selection end.
GdkRectangle m_aTextSelectionEnd;
GdkRectangle m_aGraphicSelection; /// Position and size of the graphic view selections. The current view can only /// see them, can't modify them. Key is the view id.
std::map<int, ViewRectangle> m_aGraphicViewSelections;
GdkRectangle m_aCellCursor; /// Position and size of the cell view cursors. The current view can only /// see them, can't modify them. Key is the view id.
std::map<int, ViewRectangle> m_aCellViewCursors; bool m_bInDragGraphicSelection; /// Position, size and color of the reference marks. The current view can only /// see them, can't modify them. Key is the view id.
std::vector<std::pair<ViewRectangle, sal_uInt32>> m_aReferenceMarks;
/// @name Start/middle/end handle. ///@{ /// Bitmap of the text selection start handle.
cairo_surface_t* m_pHandleStart; /// Rectangle of the text selection start handle, to know if the user clicked on it or not
GdkRectangle m_aHandleStartRect; /// If we are in the middle of a drag of the text selection end handle. bool m_bInDragStartHandle; /// Bitmap of the text selection middle handle.
cairo_surface_t* m_pHandleMiddle; /// Rectangle of the text selection middle handle, to know if the user clicked on it or not
GdkRectangle m_aHandleMiddleRect; /// If we are in the middle of a drag of the text selection middle handle. bool m_bInDragMiddleHandle; /// Bitmap of the text selection end handle.
cairo_surface_t* m_pHandleEnd; /// Rectangle of the text selection end handle, to know if the user clicked on it or not
GdkRectangle m_aHandleEndRect; /// If we are in the middle of a drag of the text selection end handle. bool m_bInDragEndHandle; ///@}
/// @name Graphic handles. ///@{ /// Rectangle of a graphic selection handle, to know if the user clicked on it or not.
GdkRectangle m_aGraphicHandleRects[8]; /// If we are in the middle of a drag of a graphic selection handle. bool m_bInDragGraphicHandles[8]; ///@}
/// View ID, returned by createView() or 0 by default. int m_nViewId;
/// Cached part ID, returned by getPart(). int m_nPartId;
/// Cached document type, returned by getDocumentType().
LibreOfficeKitDocumentType m_eDocumentType;
/// Contains a freshly set zoom level: logic size of a tile. /// It gets reset back to 0 when LOK was informed about this zoom change. int m_nTileSizeTwips;
/// Event source ID for handleTimeout() of this widget.
guint m_nTimeoutId;
/// Rectangles of view locks. The current view can only see /// them, can't modify them. Key is the view id.
std::map<int, ViewRectangle> m_aViewLockRectangles;
~LOKDocViewPrivateImpl()
{ if (m_nTimeoutId)
g_source_remove(m_nTimeoutId);
}
};
// Must be run with g_aLOKMutex locked void setDocumentView(LibreOfficeKitDocument* pDoc, int viewId)
{
assert(pDoc);
std::stringstream ss;
ss << "lok::Document::setView(" << viewId << ")";
g_info("%s", ss.str().c_str());
pDoc->pClass->setView(pDoc, viewId);
}
}
/// Wrapper around LOKDocViewPrivateImpl, managed by malloc/memset/free. struct _LOKDocViewPrivate
{
LOKDocViewPrivateImpl* m_pImpl;
/// Helper struct used to pass the data from soffice thread -> main thread. struct CallbackData
{ int m_nType;
std::string m_aPayload;
LOKDocView* m_pDocView;
for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
{ if (priv->m_bInDragGraphicHandles[i])
{
g_info("LOKDocView_Impl::signalButton: end of drag graphic handle #%d", i);
priv->m_bInDragGraphicHandles[i] = false;
if (!priv->m_bEdit)
{
g_info("signalKey: not in edit mode, ignore"); returnFALSE;
}
priv->m_nKeyModifier &= KEY_MOD2; switch (pEvent->keyval)
{ case GDK_KEY_BackSpace:
nKeyCode = css::awt::Key::BACKSPACE; break; case GDK_KEY_Delete:
nKeyCode = css::awt::Key::DELETE; break; case GDK_KEY_Return: case GDK_KEY_KP_Enter:
nKeyCode = css::awt::Key::RETURN; break; case GDK_KEY_Escape:
nKeyCode = css::awt::Key::ESCAPE; break; case GDK_KEY_Tab:
nKeyCode = css::awt::Key::TAB; break; case GDK_KEY_Down:
nKeyCode = css::awt::Key::DOWN; break; case GDK_KEY_Up:
nKeyCode = css::awt::Key::UP; break; case GDK_KEY_Left:
nKeyCode = css::awt::Key::LEFT; break; case GDK_KEY_Right:
nKeyCode = css::awt::Key::RIGHT; break; case GDK_KEY_Page_Down:
nKeyCode = css::awt::Key::PAGEDOWN; break; case GDK_KEY_Page_Up:
nKeyCode = css::awt::Key::PAGEUP; break; case GDK_KEY_Insert:
nKeyCode = css::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 = css::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;
/// Trigger a redraw, invoked on the main thread by other functions running in a thread. static gboolean queueDraw(gpointer pData)
{
GtkWidget* pWidget = static_cast<GtkWidget*>(pData);
gtk_widget_queue_draw(pWidget);
return G_SOURCE_REMOVE;
}
/// Looks up the author string from initializeForRendering()'s rendering arguments. static std::string getAuthorRenderingArgument(LOKDocViewPrivate& priv)
{
std::stringstream aStream;
aStream << priv->m_aRenderingArguments;
boost::property_tree::ptree aTree;
boost::property_tree::read_json(aStream, aTree);
std::string aRet; for (constauto& rPair : aTree)
{ if (rPair.first == ".uno:Author")
{
aRet = rPair.second.get<std::string>("value"); break;
}
} return aRet;
}
priv->m_pDocument->pClass->getDocumentSize(priv->m_pDocument, &priv->m_nDocumentWidthTwips, &priv->m_nDocumentHeightTwips); float zoom = priv->m_fZoom;
gint nScaleFactor = gtk_widget_get_scale_factor(GTK_WIDGET(pDocView));
gint nTileSizePixelsScaled = nTileSizePixels * nScaleFactor; long nDocumentWidthTwips = priv->m_nDocumentWidthTwips; long nDocumentHeightTwips = priv->m_nDocumentHeightTwips; long nDocumentWidthPixels = twipToPixel(nDocumentWidthTwips, zoom); long nDocumentHeightPixels = twipToPixel(nDocumentHeightTwips, zoom);
// Total number of columns in this document.
guint nColumns = ceil(static_cast<double>(nDocumentWidthPixels) / nTileSizePixelsScaled);
priv->m_pTileBuffer = std::make_unique<TileBuffer>(nColumns, nScaleFactor);
gtk_widget_set_size_request(GTK_WIDGET(pDocView),
nDocumentWidthPixels,
nDocumentHeightPixels);
}
/// Set up LOKDocView after the document is loaded, invoked on the main thread by openDocumentInThread() running in a thread. static gboolean postDocumentLoad(gpointer pData)
{
LOKDocView* pLOKDocView = static_cast<LOKDocView*>(pData);
LOKDocViewPrivate& priv = getPrivate(pLOKDocView);
std::unique_lock<std::mutex> aGuard(g_aLOKMutex);
priv->m_pDocument->pClass->initializeForRendering(priv->m_pDocument, priv->m_aRenderingArguments.c_str()); // This returns the view id of the "current" view, but sadly if you load multiple documents that // is apparently not a view showing the most recently loaded document. Not much we can do here, // though. If that is fixed, this comment becomes incorrect.
priv->m_nViewId = priv->m_pDocument->pClass->getView(priv->m_pDocument);
g_aAuthorViews[getAuthorRenderingArgument(priv)] = priv->m_nViewId;
priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, callbackWorker, pLOKDocView);
priv->m_nParts = priv->m_pDocument->pClass->getParts(priv->m_pDocument);
aGuard.unlock();
priv->m_nTimeoutId = g_timeout_add(600, handleTimeout, pLOKDocView);
//callback registered before the widget was destroyed. //Use existence of lokThreadPool as flag it was torn down if (!priv->lokThreadPool)
{ delete pCallback; return G_SOURCE_REMOVE;
}
switch (static_cast<LibreOfficeKitCallbackType>(pCallback->m_nType))
{ case LOK_CALLBACK_INVALIDATE_TILES:
{ if (pCallback->m_aPayload.compare(0, 5, "EMPTY") != 0) // payload doesn't start with "EMPTY"
{
GdkRectangle aRectangle = payloadToRectangle(pDocView, pCallback->m_aPayload.c_str());
setTilesInvalid(pDocView, aRectangle);
} else
priv->m_pTileBuffer->resetAllTiles();
gtk_widget_queue_draw(GTK_WIDGET(pDocView));
} break; case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
{
case LOK_CALLBACK_STATUS_INDICATOR_START: case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE: case LOK_CALLBACK_STATUS_INDICATOR_FINISH: case LOK_CALLBACK_DOCUMENT_PASSWORD: case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY: case LOK_CALLBACK_VALIDITY_LIST_BUTTON: case LOK_CALLBACK_VALIDITY_INPUT_HELP: case LOK_CALLBACK_SIGNATURE_STATUS: case LOK_CALLBACK_CONTEXT_MENU: case LOK_CALLBACK_PROFILE_FRAME: case LOK_CALLBACK_CLIPBOARD_CHANGED: case LOK_CALLBACK_CONTEXT_CHANGED: case LOK_CALLBACK_CELL_SELECTION_AREA: case LOK_CALLBACK_CELL_AUTO_FILL_AREA: case LOK_CALLBACK_TABLE_SELECTED: case LOK_CALLBACK_JSDIALOG: case LOK_CALLBACK_CALC_FUNCTION_LIST: case LOK_CALLBACK_TAB_STOP_LIST: case LOK_CALLBACK_FORM_FIELD_BUTTON: case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY: case LOK_CALLBACK_DOCUMENT_BACKGROUND_COLOR: case LOK_COMMAND_BLOCKED: case LOK_CALLBACK_SC_FOLLOW_JUMP: case LOK_CALLBACK_PRINT_RANGES: case LOK_CALLBACK_FONTS_MISSING: case LOK_CALLBACK_MEDIA_SHAPE: case LOK_CALLBACK_EXPORT_FILE: case LOK_CALLBACK_VIEW_RENDER_STATE: case LOK_CALLBACK_APPLICATION_BACKGROUND_COLOR: case LOK_CALLBACK_A11Y_FOCUS_CHANGED: case LOK_CALLBACK_A11Y_CARET_CHANGED: case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED: case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED: case LOK_CALLBACK_COLOR_PALETTES: case LOK_CALLBACK_DOCUMENT_PASSWORD_RESET: case LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE: case LOK_CALLBACK_A11Y_SELECTION_CHANGED: case LOK_CALLBACK_CORE_LOG: case LOK_CALLBACK_TOOLTIP: case LOK_CALLBACK_SHAPE_INNER_TEXT:
{ // TODO: Implement me break;
}
} delete pCallback;
nHandleWidth = cairo_image_surface_get_width(pHandle);
nHandleHeight = cairo_image_surface_get_height(pHandle); // We want to scale down the handle, so that its height is the same as the cursor caret.
fHandleScale = twipToPixel(rCursor.height, priv->m_fZoom) / nHandleHeight; // We want the top center of the handle bitmap to be at the bottom center of the cursor rectangle.
aCursorBottom.x = twipToPixel(rCursor.x, priv->m_fZoom) + twipToPixel(rCursor.width, priv->m_fZoom) / 2 - (nHandleWidth * fHandleScale) / 2;
aCursorBottom.y = twipToPixel(rCursor.y, priv->m_fZoom) + twipToPixel(rCursor.height, priv->m_fZoom);
for (int i = 0; i < GRAPHIC_HANDLE_COUNT; ++i)
{ int x = aSelection.x, y = aSelection.y;
switch (i)
{ case 0: // top-left break; case 1: // top-middle
x += aSelection.width / 2; break; case 2: // top-right
x += aSelection.width; break; case 3: // middle-left
y += aSelection.height / 2; break; case 4: // middle-right
x += aSelection.width;
y += aSelection.height / 2; break; case 5: // bottom-left
y += aSelection.height; break; case 6: // bottom-middle
x += aSelection.width / 2;
y += aSelection.height; break; case 7: // bottom-right
x += aSelection.width;
y += aSelection.height; break;
}
// Center the handle.
x -= nHandleWidth / 2;
y -= nHandleHeight / 2;
/// Finishes the paint tile operation and returns the result, if any static gpointer
paintTileFinish(LOKDocView* pDocView, GAsyncResult* res, GError **error)
{
GTask* task = G_TASK(res);
// Determine size of the tile: the rightmost/bottommost tiles may // be smaller, and we need the size to decide if we need to repaint. if (nColumn == nColumns - 1)
aTileRectanglePixels.width = nDocumentWidthPixels - nColumn * nTileSizePixelsScaled; else
aTileRectanglePixels.width = nTileSizePixelsScaled; if (nRow == nRows - 1)
aTileRectanglePixels.height = nDocumentHeightPixels - nRow * nTileSizePixelsScaled; else
aTileRectanglePixels.height = nTileSizePixelsScaled;
// Determine size and position of the tile in document coordinates, // so we can decide if we can skip painting for partial rendering.
aTileRectangleTwips.x = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nColumn;
aTileRectangleTwips.y = pixelToTwip(nTileSizePixelsScaled, priv->m_fZoom) * nRow;
aTileRectangleTwips.width = pixelToTwip(aTileRectanglePixels.width, priv->m_fZoom);
aTileRectangleTwips.height = pixelToTwip(aTileRectanglePixels.height, priv->m_fZoom);
if (!gdk_rectangle_intersect(&aVisibleArea, &aTileRectangleTwips, nullptr))
bPaint = false;
if (priv->m_bEdit && priv->m_bCursorVisible && priv->m_bCursorOverlayVisible && !isEmptyRectangle(priv->m_aVisibleCursor))
{ if (priv->m_aVisibleCursor.width < 30) // Set a minimal width if it would be 0.
priv->m_aVisibleCursor.width = 30;
// View cursors: they do not blink and are colored. if (priv->m_bEdit && !priv->m_aViewCursors.empty())
{ for (auto& rPair : priv->m_aViewCursors)
{ auto itVisibility = priv->m_aViewCursorVisibilities.find(rPair.first); if (itVisibility != priv->m_aViewCursorVisibilities.end() && !itVisibility->second) continue;
// Show view cursors when in Writer or when the part matches. if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT) continue;
GdkRectangle& rCursor = rPair.second.m_aRectangle; if (rCursor.width < 30) // Set a minimal width if it would be 0.
rCursor.width = 30;
// Selections of other views. for (constauto& rPair : priv->m_aTextViewSelectionRectangles)
{ if (rPair.second.m_nPart != priv->m_nPartId && priv->m_eDocumentType != LOK_DOCTYPE_TEXT) continue;
g_timer_elapsed(aTimer, &nElapsedMs);
ss << " rendered in " << (nElapsedMs / 1000.) << " milliseconds";
g_info("%s", ss.str().c_str());
g_timer_destroy(aTimer);
cairo_surface_mark_dirty(pSurface);
// Its likely that while the tilebuffer has changed, one of the paint tile // requests has passed the previous check at start of this function, and has // rendered the tile already. We want to stop such rendered tiles from being // stored in new tile buffer. if (pLOEvent->m_pTileBuffer != &*priv->m_pTileBuffer)
{
pLOEvent->m_pTileBuffer = nullptr;
g_task_return_new_error(task,
LOK_TILEBUFFER_ERROR,
LOK_TILEBUFFER_CHANGED, "TileBuffer has changed"); return;
}
switch (pLOEvent->m_nType)
{ case LOK_LOAD_DOC:
openDocumentInThread(task); break; case LOK_POST_COMMAND:
postCommandInThread(task); break; case LOK_SET_EDIT:
setEditInThread(task); break; case LOK_SET_PART:
setPartInThread(task); break; case LOK_SET_PARTMODE:
setPartmodeInThread(task); break; case LOK_POST_KEY: // view-only/editable mode already checked during signal key signal emission
postKeyEventInThread(task); break; case LOK_PAINT_TILE:
paintTileInThread(task); break; case LOK_POST_MOUSE_EVENT:
postMouseEventInThread(task); break; case LOK_SET_GRAPHIC_SELECTION: if (priv->m_bEdit)
setGraphicSelectionInThread(task); else
g_info ("LOK_SET_GRAPHIC_SELECTION: skipping graphical operation in view-only mode"); break; case LOK_SET_CLIENT_ZOOM:
setClientZoomInThread(task); break;
}
g_object_unref(task);
}
staticvoid
onStyleContextChanged (LOKDocView* pDocView)
{ // The scale factor might have changed
updateClientZoom (pDocView);
gtk_widget_queue_draw (GTK_WIDGET (pDocView));
}
//rhbz#1444437 finalize may not occur immediately when this widget is destroyed //it may happen during GC of javascript, e.g. in gnome-documents but "destroy" //will be called promptly, so close documents in destroy, not finalize staticvoid lok_doc_view_destroy (GtkWidget* widget)
{
LOKDocView* pDocView = LOK_DOC_VIEW (widget);
LOKDocViewPrivate& priv = getPrivate(pDocView);
// Ignore notifications sent to this view on shutdown.
std::unique_lock<std::mutex> aGuard(g_aLOKMutex); if (priv->m_pDocument)
{
setDocumentView(priv->m_pDocument, priv->m_nViewId);
priv->m_pDocument->pClass->registerCallback(priv->m_pDocument, nullptr, nullptr);
}
if (priv->lokThreadPool)
{
g_thread_pool_free(priv->lokThreadPool, true, true);
priv->lokThreadPool = nullptr;
}
aGuard.unlock();
if (priv->m_pDocument)
{ // This call may drop several views - e.g., embedded OLE in-place clients
priv->m_pDocument->pClass->destroyView(priv->m_pDocument, priv->m_nViewId); if (priv->m_pDocument->pClass->getViewsCount(priv->m_pDocument) == 0)
{ // Last view(s) gone
priv->m_pDocument->pClass->destroy (priv->m_pDocument);
priv->m_pDocument = nullptr; if (priv->m_pOffice)
{
priv->m_pOffice->pClass->destroy (priv->m_pOffice);
priv->m_pOffice = nullptr;
}
}
}
/** * LOKDocView:lopath: * * The absolute path of the LibreOffice install.
*/
properties[PROP_LO_PATH] =
g_param_spec_string("lopath", "LO Path", "LibreOffice Install Path",
nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:unipoll: * * Whether we use our own unified polling mainloop in place of glib's
*/
properties[PROP_LO_UNIPOLL] =
g_param_spec_boolean("unipoll", "Unified Polling", "Whether we use a custom unified polling loop", FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)); /** * LOKDocView:lopointer: * * A LibreOfficeKit* in case lok_init() is already called * previously.
*/
properties[PROP_LO_POINTER] =
g_param_spec_pointer("lopointer", "LO Pointer", "A LibreOfficeKit* from lok_init()", static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:userprofileurl: * * The absolute path of the LibreOffice user profile.
*/
properties[PROP_USER_PROFILE_URL] =
g_param_spec_string("userprofileurl", "User profile path", "LibreOffice user profile path",
nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:docpath: * * The path of the document that is currently being viewed.
*/
properties[PROP_DOC_PATH] =
g_param_spec_string("docpath", "Document Path", "The URI of the document to open",
nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:docpointer: * * A LibreOfficeKitDocument* in case documentLoad() is already called * previously.
*/
properties[PROP_DOC_POINTER] =
g_param_spec_pointer("docpointer", "Document Pointer", "A LibreOfficeKitDocument* from documentLoad()", static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:editable: * * Whether the document loaded inside of #LOKDocView is editable or not.
*/
properties[PROP_EDITABLE] =
g_param_spec_boolean("editable", "Editable", "Whether the content is in edit mode or not", FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:load-progress: * * The percent completion of the current loading operation of the * document. This can be used for progress bars. Note that this is not a * very accurate progress indicator, and its value might reset it couple of * times to 0 and start again. You should not rely on its numbers.
*/
properties[PROP_LOAD_PROGRESS] =
g_param_spec_double("load-progress", "Estimated Load Progress", "Shows the progress of the document load operation",
0.0, 1.0, 0.0, static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:zoom-level: * * The current zoom level of the document loaded inside #LOKDocView. The * default value is 1.0.
*/
properties[PROP_ZOOM] =
g_param_spec_float("zoom-level", "Zoom Level", "The current zoom level of the content",
0, 5.0, 1.0, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:is-loading: * * Whether the requested document is being loaded or not. %TRUE if it is * being loaded, otherwise %FALSE.
*/
properties[PROP_IS_LOADING] =
g_param_spec_boolean("is-loading", "Is Loading", "Whether the view is loading a document", FALSE, static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:is-initialized: * * Whether the requested document has completely loaded or not.
*/
properties[PROP_IS_INITIALIZED] =
g_param_spec_boolean("is-initialized", "Has initialized", "Whether the view has completely initialized", FALSE, static_cast<GParamFlags>(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:doc-width: * * The width of the currently loaded document in #LOKDocView in twips.
*/
properties[PROP_DOC_WIDTH] =
g_param_spec_long("doc-width", "Document Width", "Width of the document in twips",
0, G_MAXLONG, 0, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:doc-height: * * The height of the currently loaded document in #LOKDocView in twips.
*/
properties[PROP_DOC_HEIGHT] =
g_param_spec_long("doc-height", "Document Height", "Height of the document in twips",
0, G_MAXLONG, 0, static_cast<GParamFlags>(G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/** * LOKDocView:can-zoom-in: * * It tells whether the view can further be zoomed in or not.
*/
properties[PROP_CAN_ZOOM_IN] =
g_param_spec_boolean("can-zoom-in", "Can Zoom In", "Whether the view can be zoomed in further", true, static_cast<GParamFlags>(G_PARAM_READABLE
| G_PARAM_STATIC_STRINGS));
/** * LOKDocView:can-zoom-out: * * It tells whether the view can further be zoomed out or not.
*/
properties[PROP_CAN_ZOOM_OUT] =
g_param_spec_boolean("can-zoom-out", "Can Zoom Out", "Whether the view can be zoomed out further", true, static_cast<GParamFlags>(G_PARAM_READABLE
| G_PARAM_STATIC_STRINGS));
/** * LOKDocView:doc-password: * * Set it to true if client supports providing password for viewing * password protected documents
*/
properties[PROP_DOC_PASSWORD] =
g_param_spec_boolean("doc-password", "Document password capability", "Whether client supports providing document passwords", FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/** * LOKDocView:doc-password-to-modify: * * Set it to true if client supports providing password for edit-protected documents
*/
properties[PROP_DOC_PASSWORD_TO_MODIFY] =
g_param_spec_boolean("doc-password-to-modify", "Edit document password capability", "Whether the client supports providing passwords to edit documents", FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/** * LOKDocView:tiled-annotations-rendering: * * Set it to false if client does not want LO to render comments in tiles and * instead interested in using comments API to access comments
*/
properties[PROP_TILED_ANNOTATIONS] =
g_param_spec_boolean("tiled-annotations", "Render comments in tiles", "Whether the client wants in tile comment rendering", true, static_cast<GParamFlags>(G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS));
/** * LOKDocView::load-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @fLoadProgress: the new progress value
*/
doc_view_signals[LOAD_CHANGED] =
g_signal_new("load-changed",
G_TYPE_FROM_CLASS (pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE, 1,
G_TYPE_DOUBLE);
/** * LOKDocView::edit-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @bEdit: the new edit value of the view
*/
doc_view_signals[EDIT_CHANGED] =
g_signal_new("edit-changed",
G_TYPE_FROM_CLASS (pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
/** * LOKDocView::command-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: the command that was changed
*/
doc_view_signals[COMMAND_CHANGED] =
g_signal_new("command-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::search-not-found: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: the string for which the search was not found.
*/
doc_view_signals[SEARCH_NOT_FOUND] =
g_signal_new("search-not-found",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::part-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: the part number which the view changed to
*/
doc_view_signals[PART_CHANGED] =
g_signal_new("part-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1,
G_TYPE_INT);
/** * LOKDocView::size-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: NULL, we just notify that want to notify the UI elements that are interested.
*/
doc_view_signals[SIZE_CHANGED] =
g_signal_new("size-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 1,
G_TYPE_INT);
/** * LOKDocView::hyperlinked-clicked: * @pDocView: the #LOKDocView on which the signal is emitted * @aHyperlink: the URI which the application should handle
*/
doc_view_signals[HYPERLINK_CLICKED] =
g_signal_new("hyperlink-clicked",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::cursor-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @nX: The new cursor position (X coordinate) in pixels * @nY: The new cursor position (Y coordinate) in pixels * @nWidth: The width of new cursor * @nHeight: The height of new cursor
*/
doc_view_signals[CURSOR_CHANGED] =
g_signal_new("cursor-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 4,
G_TYPE_INT, G_TYPE_INT,
G_TYPE_INT, G_TYPE_INT);
/** * LOKDocView::search-result-count: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: number of matches.
*/
doc_view_signals[SEARCH_RESULT_COUNT] =
g_signal_new("search-result-count",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::command-result: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: JSON containing the info about the command that finished, * and its success status.
*/
doc_view_signals[COMMAND_RESULT] =
g_signal_new("command-result",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::address-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: formula text content
*/
doc_view_signals[ADDRESS_CHANGED] =
g_signal_new("address-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::formula-changed: * @pDocView: the #LOKDocView on which the signal is emitted * @aCommand: formula text content
*/
doc_view_signals[FORMULA_CHANGED] =
g_signal_new("formula-changed",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::text-selection: * @pDocView: the #LOKDocView on which the signal is emitted * @bIsTextSelected: whether text selected is non-null
*/
doc_view_signals[TEXT_SELECTION] =
g_signal_new("text-selection",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
/** * LOKDocView::content-control: * @pDocView: the #LOKDocView on which the signal is emitted * @pPayload: the JSON string containing the information about ruler properties
*/
doc_view_signals[CONTENT_CONTROL] =
g_signal_new("content-control",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::password-required: * @pDocView: the #LOKDocView on which the signal is emitted * @pUrl: URL of the document for which password is required * @bModify: whether password id required to modify the document * This is true when password is required to edit the document, * while it can still be viewed without password. In such cases, provide a NULL * password for read-only access to the document. * If false, password is required for opening the document, and document * cannot be opened without providing a valid password. * * Password must be provided by calling lok_doc_view_set_document_password * function with pUrl as provided by the callback. * * Upon entering an invalid password, another `password-required` signal is * emitted. * Upon entering a valid password, document starts to load. * Upon entering a NULL password: if bModify is %TRUE, document starts to * open in view-only mode, else loading of document is aborted.
*/
doc_view_signals[PASSWORD_REQUIRED] =
g_signal_new("password-required",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_BOOLEAN);
/** * LOKDocView::comment: * @pDocView: the #LOKDocView on which the signal is emitted * @pComment: the JSON string containing comment notification * The has following structure containing the information telling whether * the comment has been added, deleted or modified. * The example: * { * "comment": { * "action": "Add", * "id": "11", * "parent": "4", * "author": "Unknown Author", * "text": "This is a comment", * "dateTime": "2016-08-18T13:13:00", * "anchorPos": "4529, 3906", * "textRange": "1418, 3906, 3111, 919" * } * } * 'action' can be 'Add', 'Remove' or 'Modify' depending on whether * comment has been added, removed or modified. * 'parent' is a non-zero comment id if this comment is a reply comment, * otherwise it's a root comment.
*/
doc_view_signals[COMMENT] =
g_signal_new("comment",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::ruler: * @pDocView: the #LOKDocView on which the signal is emitted * @pPayload: the JSON string containing the information about ruler properties * * The payload format is: * * { * "margin1": "...", * "margin2": "...", * "leftOffset": "...", * "pageOffset": "...", * "pageWidth": "...", * "unit": "..." * }
*/
doc_view_signals[RULER] =
g_signal_new("ruler",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::window:: * @pDocView: the #LOKDocView on which the signal is emitted * @pPayload: the JSON string containing the information about the window * * This signal emits information about external windows like dialogs, autopopups for now. * * The payload format of pPayload is: * * { * "id": "unique integer id of the dialog", * "action": "<see below>", * "type": "<see below>" * "rectangle": "x, y, width, height" * } * * "type" tells the type of the window the action is associated with * - "dialog" - window is a dialog * - "child" - window is a floating window (combo boxes, etc.) * * "action" can take following values: * - "created" - window is created in the backend, client can render it now * - "title_changed" - window's title is changed * - "size_changed" - window's size is changed * - "invalidate" - the area as described by "rectangle" is invalidated * Clients must request the new area * - "cursor_invalidate" - cursor is invalidated. New position is in "rectangle" * - "cursor_visible" - cursor visible status is changed. Status is available * in "visible" field * - "close" - window is closed
*/
doc_view_signals[WINDOW] =
g_signal_new("window",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/** * LOKDocView::invalidate-header:: * @pDocView: the #LOKDocView on which the signal is emitted * @pPayload: can be either "row", "column", or "all". * * The column/row header is no more valid because of a column/row insertion * or a similar event. Clients must query a new column/row header set. * * The payload says if we are invalidating a row or column header
*/
doc_view_signals[INVALIDATE_HEADER] =
g_signal_new("invalidate-header",
G_TYPE_FROM_CLASS(pGObjectClass),
G_SIGNAL_RUN_FIRST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
// No documentLoad(), just a createView().
LibreOfficeKitDocument* pDocument = lok_doc_view_get_document(LOK_DOC_VIEW(pNewDocView));
LOKDocViewPrivate& pNewPriv = getPrivate(LOK_DOC_VIEW(pNewDocView)); // Store the view id only later in postDocumentLoad(), as // initializeForRendering() changes the id in Impress.
pDocument->pClass->createView(pDocument);
pNewPriv->m_aRenderingArguments = pRenderingArguments;
namespace { // This used to be rtl::math::approxEqual() but since that isn't inline anymore // in rtl/math.hxx and was moved into libuno_sal as rtl_math_approxEqual() to // cater for representable integer cases and we don't want to link against // libuno_sal, we'll have to have an own implementation. The special large // integer cases seems not be needed here. bool lok_approxEqual(double a, double b)
{ staticconstdouble e48 = 1.0 / (16777216.0 * 16777216.0); if (a == b) returntrue; if (a == 0.0 || b == 0.0) returnfalse; constdouble d = fabs(a - b); return (d < fabs(a) * e48 && d < fabs(b) * e48);
}
}