/* -*- 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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.