/* -*- 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include "controller/SlsSelectionFunction.hxx" #include "SlideSorter.hxx" #include "SlideSorterViewShell.hxx" #include "SlsDragAndDropContext.hxx" #include "controller/SlsTransferableData.hxx" #include "controller/SlideSorterController.hxx" #include "controller/SlsPageSelector.hxx" #include "controller/SlsFocusManager.hxx" #include "controller/SlsScrollBarManager.hxx" #include "controller/SlsClipboard.hxx" #include "controller/SlsCurrentSlideManager.hxx" #include "controller/SlsInsertionIndicatorHandler.hxx" #include "controller/SlsSelectionManager.hxx" #include "controller/SlsProperties.hxx" #include "controller/SlsSlotManager.hxx" #include "controller/SlsVisibleAreaManager.hxx" #include "model/SlideSorterModel.hxx" #include "model/SlsPageDescriptor.hxx" #include "model/SlsPageEnumerationProvider.hxx" #include "view/SlideSorterView.hxx" #include "view/SlsLayouter.hxx" #include "view/SlsPageObjectLayouter.hxx" #include "framework/FrameworkHelper.hxx" #include "ViewShellBase.hxx" #include "DrawController.hxx" #include "Window.hxx" #include "sdpage.hxx" #include "drawdoc.hxx" #include "DrawDocShell.hxx" #include "sdxfer.hxx" #include "ViewShell.hxx" #include "FrameView.hxx" #include "app.hrc" #include "sdresid.hxx" #include "strings.hrc" #include #include #include #include #include #include #include namespace { static const sal_uInt32 SINGLE_CLICK (0x00000001); static const sal_uInt32 DOUBLE_CLICK (0x00000002); static const sal_uInt32 LEFT_BUTTON (0x00000010); static const sal_uInt32 RIGHT_BUTTON (0x00000020); static const sal_uInt32 MIDDLE_BUTTON (0x00000040); static const sal_uInt32 BUTTON_DOWN (0x00000100); static const sal_uInt32 BUTTON_UP (0x00000200); static const sal_uInt32 MOUSE_MOTION (0x00000400); static const sal_uInt32 MOUSE_DRAG (0x00000800); // The rest leaves the lower 16 bit untouched so that it can be used with // key codes. static const sal_uInt32 OVER_SELECTED_PAGE (0x00010000); static const sal_uInt32 OVER_UNSELECTED_PAGE (0x00020000); static const sal_uInt32 SHIFT_MODIFIER (0x00200000); static const sal_uInt32 CONTROL_MODIFIER (0x00400000); // Some absent events are defined so they can be expressed explicitly. static const sal_uInt32 NO_MODIFIER (0x00000000); static const sal_uInt32 NOT_OVER_PAGE (0x00000000); // Masks static const sal_uInt32 MODIFIER_MASK (SHIFT_MODIFIER | CONTROL_MODIFIER); } // end of anonymous namespace // Define some macros to make the following switch statement more readable. #define ANY_MODIFIER(code) \ code|NO_MODIFIER: \ case code|SHIFT_MODIFIER: \ case code|CONTROL_MODIFIER namespace sd { namespace slidesorter { namespace controller { //===== SelectionFunction::EventDescriptor ==================================== class SelectionFunction::EventDescriptor { public: Point maMousePosition; Point maMouseModelPosition; model::SharedPageDescriptor mpHitDescriptor; SdrPage* mpHitPage; sal_uInt32 mnEventCode; InsertionIndicatorHandler::Mode meDragMode; bool mbMakeSelectionVisible; bool mbIsLeaving; EventDescriptor ( sal_uInt32 nEventType, const MouseEvent& rEvent, SlideSorter& rSlideSorter); EventDescriptor ( sal_uInt32 nEventType, const AcceptDropEvent& rEvent, const sal_Int8 nDragAction, SlideSorter& rSlideSorter); private: /** Compute a numerical code that describes a mouse event and that can be used for fast look up of the appropriate reaction. */ sal_uInt32 EncodeMouseEvent (const MouseEvent& rEvent) const; /** Compute a numerical code that describes the current state like whether the selection rectangle is visible or whether the page under the mouse or the one that has the focus is selected. */ sal_uInt32 EncodeState() const; }; //===== SelectionFunction::ModeHandler ======================================== class SelectionFunction::ModeHandler { public: ModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction, const bool bIsMouseOverIndicatorAllowed); virtual ~ModeHandler(); virtual Mode GetMode() const = 0; virtual void Abort() = 0; virtual void ProcessEvent (EventDescriptor& rDescriptor); /** Set the selection to exactly the specified page and also set it as the current page. */ void SetCurrentPage (const model::SharedPageDescriptor& rpDescriptor); /// Deselect all pages. void DeselectAllPages(); void SelectOnePage (const model::SharedPageDescriptor& rpDescriptor); /** When the view on which this selection function is working is the main view then the view is switched to the regular editing view. */ void SwitchView (const model::SharedPageDescriptor& rpDescriptor); void StartDrag ( const Point& rMousePosition, const InsertionIndicatorHandler::Mode eMode); bool IsMouseOverIndicatorAllowed() const { return mbIsMouseOverIndicatorAllowed;} protected: SlideSorter& mrSlideSorter; SelectionFunction& mrSelectionFunction; virtual bool ProcessButtonDownEvent (EventDescriptor& rDescriptor); virtual bool ProcessButtonUpEvent (EventDescriptor& rDescriptor); virtual bool ProcessMotionEvent (EventDescriptor& rDescriptor); virtual bool ProcessDragEvent (EventDescriptor& rDescriptor); virtual bool HandleUnprocessedEvent (EventDescriptor& rDescriptor); void ReprocessEvent (EventDescriptor& rDescriptor); private: const bool mbIsMouseOverIndicatorAllowed; }; /** This is the default handler for processing events. It activates the multi selection or drag-and-drop when the right conditions are met. */ class NormalModeHandler : public SelectionFunction::ModeHandler { public: NormalModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction); virtual ~NormalModeHandler(); virtual SelectionFunction::Mode GetMode() const SAL_OVERRIDE; virtual void Abort() SAL_OVERRIDE; void ResetButtonDownLocation(); protected: virtual bool ProcessButtonDownEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool ProcessMotionEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; private: ::boost::optional maButtonDownLocation; /** Select all pages between and including the selection anchor and the specified page. */ void RangeSelect (const model::SharedPageDescriptor& rpDescriptor); }; /** Handle events during a multi selection, which typically is started by pressing the left mouse button when not over a page. */ class MultiSelectionModeHandler : public SelectionFunction::ModeHandler { public: /** Start a rectangle selection at the given position. */ MultiSelectionModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction, #ifndef MACOSX const Point& rMouseModelPosition); #else const Point& rMouseModelPosition, const sal_uInt32 nEventCode); #endif virtual ~MultiSelectionModeHandler(); #ifndef MACOSX void Initialize(const sal_uInt32 nEventCode); #endif virtual SelectionFunction::Mode GetMode() const SAL_OVERRIDE; virtual void Abort() SAL_OVERRIDE; virtual void ProcessEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; enum SelectionMode { SM_Normal, SM_Add, SM_Toggle }; void SetSelectionMode (const SelectionMode eSelectionMode); void SetSelectionModeFromModifier (const sal_uInt32 nEventCode); protected: virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool ProcessMotionEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool HandleUnprocessedEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; private: SelectionMode meSelectionMode; Point maSecondCorner; Pointer maSavedPointer; bool mbAutoScrollInstalled; sal_Int32 mnAnchorIndex; sal_Int32 mnSecondIndex; void UpdateModelPosition (const Point& rMouseModelPosition); void UpdateSelection(); /** Update the rectangle selection so that the given position becomes the new second point of the selection rectangle. */ void UpdatePosition ( const Point& rMousePosition, const bool bAllowAutoScroll); void UpdateSelectionState ( const model::SharedPageDescriptor& rpDescriptor, const bool bIsInSelection) const; }; /** Handle events during drag-and-drop. */ class DragAndDropModeHandler : public SelectionFunction::ModeHandler { public: DragAndDropModeHandler ( SlideSorter& rSlideSorter, #ifndef MACOSX SelectionFunction& rSelectionFunction); #else SelectionFunction& rSelectionFunction, const Point& rMousePosition, vcl::Window* pWindow); #endif virtual ~DragAndDropModeHandler(); #ifndef MACOSX void Initialize(const Point& rMousePosition, vcl::Window* pWindow); #endif virtual SelectionFunction::Mode GetMode() const SAL_OVERRIDE; virtual void Abort() SAL_OVERRIDE; protected: virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; virtual bool ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) SAL_OVERRIDE; private: ::boost::scoped_ptr mpDragAndDropContext; }; //===== SelectionFunction ===================================================== TYPEINIT1(SelectionFunction, FuPoor); SelectionFunction::SelectionFunction ( SlideSorter& rSlideSorter, SfxRequest& rRequest) : FuPoor ( rSlideSorter.GetViewShell(), rSlideSorter.GetContentWindow(), &rSlideSorter.GetView(), rSlideSorter.GetModel().GetDocument(), rRequest), mrSlideSorter(rSlideSorter), mrController(mrSlideSorter.GetController()), mbDragSelection(false), maInsertionMarkerBox(), mbProcessingMouseButtonDown(false), mnShiftKeySelectionAnchor(-1), mpModeHandler(new NormalModeHandler(rSlideSorter, *this)) { } SelectionFunction::~SelectionFunction() { mpModeHandler.reset(); } rtl::Reference SelectionFunction::Create( SlideSorter& rSlideSorter, SfxRequest& rRequest) { rtl::Reference xFunc( new SelectionFunction( rSlideSorter, rRequest ) ); return xFunc; } bool SelectionFunction::MouseButtonDown (const MouseEvent& rEvent) { // remember button state for creation of own MouseEvents SetMouseButtonCode (rEvent.GetButtons()); aMDPos = rEvent.GetPosPixel(); mbProcessingMouseButtonDown = true; // mpWindow->CaptureMouse(); ProcessMouseEvent(BUTTON_DOWN, rEvent); return true; } bool SelectionFunction::MouseMove (const MouseEvent& rEvent) { ProcessMouseEvent(MOUSE_MOTION, rEvent); return true; } bool SelectionFunction::MouseButtonUp (const MouseEvent& rEvent) { mrController.GetScrollBarManager().StopAutoScroll (); ProcessMouseEvent(BUTTON_UP, rEvent); mbProcessingMouseButtonDown = false; return true; } void SelectionFunction::NotifyDragFinished() { SwitchToNormalMode(); } bool SelectionFunction::KeyInput (const KeyEvent& rEvent) { view::SlideSorterView::DrawLock aDrawLock (mrSlideSorter); PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter); PageSelector::UpdateLock aLock (mrSlideSorter); FocusManager& rFocusManager (mrController.GetFocusManager()); bool bResult = false; const vcl::KeyCode& rCode (rEvent.GetKeyCode()); switch (rCode.GetCode()) { case KEY_RETURN: { model::SharedPageDescriptor pDescriptor (rFocusManager.GetFocusedPageDescriptor()); ViewShell* pViewShell = mrSlideSorter.GetViewShell(); if (rFocusManager.HasFocus() && pDescriptor && pViewShell!=NULL) { // The Return key triggers different functions depending on // whether the slide sorter is the main view or displayed in // the right pane. if (pViewShell->IsMainViewShell()) { mpModeHandler->SetCurrentPage(pDescriptor); mpModeHandler->SwitchView(pDescriptor); } else if (pViewShell->GetDispatcher() != NULL) { pViewShell->GetDispatcher()->Execute( SID_INSERTPAGE, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD); } bResult = true; } break; } case KEY_TAB: if ( ! rFocusManager.IsFocusShowing()) { rFocusManager.ShowFocus(); bResult = true; } break; case KEY_ESCAPE: // When there is an active multiselection or drag-and-drop // operation then stop that. mpModeHandler->Abort(); SwitchToNormalMode(); bResult = true; break; case KEY_SPACE: { // Toggle the selection state. model::SharedPageDescriptor pDescriptor (rFocusManager.GetFocusedPageDescriptor()); if (pDescriptor && rCode.IsMod1()) { if (pDescriptor->HasState(model::PageDescriptor::ST_Selected)) mrController.GetPageSelector().DeselectPage(pDescriptor, false); else mrController.GetPageSelector().SelectPage(pDescriptor); } bResult = true; } break; // Move the focus indicator left. case KEY_LEFT: MoveFocus(FocusManager::FMD_LEFT, rCode.IsShift(), rCode.IsMod1()); bResult = true; break; // Move the focus indicator right. case KEY_RIGHT: MoveFocus(FocusManager::FMD_RIGHT, rCode.IsShift(), rCode.IsMod1()); bResult = true; break; // Move the focus indicator up. case KEY_UP: MoveFocus(FocusManager::FMD_UP, rCode.IsShift(), rCode.IsMod1()); bResult = true; break; // Move the focus indicator down. case KEY_DOWN: MoveFocus(FocusManager::FMD_DOWN, rCode.IsShift(), rCode.IsMod1()); bResult = true; break; // Go to previous page. No wrap around. case KEY_PAGEUP: GotoNextPage(-1); bResult = true; break; // Go to next page. No wrap around.. case KEY_PAGEDOWN: GotoNextPage(+1); bResult = true; break; case KEY_HOME: GotoPage(0); bResult = true; break; case KEY_END: GotoPage(mrSlideSorter.GetModel().GetPageCount()-1); bResult = true; break; case KEY_DELETE: case KEY_BACKSPACE: { if (mrSlideSorter.GetProperties()->IsUIReadOnly()) break; mrController.GetSelectionManager()->DeleteSelectedPages(rCode.GetCode()==KEY_DELETE); mnShiftKeySelectionAnchor = -1; bResult = true; } break; case KEY_F10: if (rCode.IsShift()) { mpModeHandler->SelectOnePage( mrSlideSorter.GetController().GetFocusManager().GetFocusedPageDescriptor()); } break; default: break; } if ( ! bResult) bResult = FuPoor::KeyInput(rEvent); return bResult; } void SelectionFunction::MoveFocus ( const FocusManager::FocusMoveDirection eDirection, const bool bIsShiftDown, const bool bIsControlDown) { // Remember the anchor of shift key multi selection. if (bIsShiftDown) { if (mnShiftKeySelectionAnchor<0) { model::SharedPageDescriptor pFocusedDescriptor ( mrController.GetFocusManager().GetFocusedPageDescriptor()); mnShiftKeySelectionAnchor = pFocusedDescriptor->GetPageIndex(); } } else if ( ! bIsControlDown) ResetShiftKeySelectionAnchor(); mrController.GetFocusManager().MoveFocus(eDirection); PageSelector& rSelector (mrController.GetPageSelector()); model::SharedPageDescriptor pFocusedDescriptor ( mrController.GetFocusManager().GetFocusedPageDescriptor()); if (bIsShiftDown) { // When shift is pressed then select all pages in the range between // the currently and the previously focused pages, including them. if (pFocusedDescriptor) { sal_Int32 nPageRangeEnd (pFocusedDescriptor->GetPageIndex()); model::PageEnumeration aPages ( model::PageEnumerationProvider::CreateAllPagesEnumeration( mrSlideSorter.GetModel())); while (aPages.HasMoreElements()) { model::SharedPageDescriptor pDescriptor (aPages.GetNextElement()); if (pDescriptor) { const sal_Int32 nPageIndex(pDescriptor->GetPageIndex()); if ((nPageIndex>=mnShiftKeySelectionAnchor && nPageIndex<=nPageRangeEnd) || (nPageIndex<=mnShiftKeySelectionAnchor && nPageIndex>=nPageRangeEnd)) { rSelector.SelectPage(pDescriptor); } else { rSelector.DeselectPage(pDescriptor); } } } } } else if (bIsControlDown) { // When control is pressed then do not alter the selection or the // current page, just move the focus. } else { // Without shift just select the focused page. mpModeHandler->SelectOnePage(pFocusedDescriptor); } } void SelectionFunction::Activate() { FuPoor::Activate(); } void SelectionFunction::Deactivate() { FuPoor::Deactivate(); } void SelectionFunction::DoCut() { if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly()) { mrController.GetClipboard().DoCut(); } } void SelectionFunction::DoCopy() { mrController.GetClipboard().DoCopy(); } void SelectionFunction::DoPaste() { if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly()) { mrController.GetClipboard().DoPaste(); } } bool SelectionFunction::cancel() { mrController.GetFocusManager().ToggleFocus(); return true; } void SelectionFunction::GotoNextPage (int nOffset) { model::SharedPageDescriptor pDescriptor = mrController.GetCurrentSlideManager()->GetCurrentSlide(); if (pDescriptor.get() != NULL) { SdPage* pPage = pDescriptor->GetPage(); OSL_ASSERT(pPage!=NULL); sal_Int32 nIndex = (pPage->GetPageNum()-1) / 2; GotoPage(nIndex + nOffset); } ResetShiftKeySelectionAnchor(); } void SelectionFunction::GotoPage (int nIndex) { sal_uInt16 nPageCount = (sal_uInt16)mrSlideSorter.GetModel().GetPageCount(); if (nIndex >= nPageCount) nIndex = nPageCount - 1; if (nIndex < 0) nIndex = 0; mrController.GetFocusManager().SetFocusedPage(nIndex); model::SharedPageDescriptor pNextPageDescriptor ( mrSlideSorter.GetModel().GetPageDescriptor (nIndex)); if (pNextPageDescriptor.get() != NULL) mpModeHandler->SetCurrentPage(pNextPageDescriptor); else { OSL_ASSERT(pNextPageDescriptor.get() != NULL); } ResetShiftKeySelectionAnchor(); } void SelectionFunction::ProcessMouseEvent (sal_uInt32 nEventType, const MouseEvent& rEvent) { // #95491# remember button state for creation of own MouseEvents SetMouseButtonCode (rEvent.GetButtons()); EventDescriptor aEventDescriptor (nEventType, rEvent, mrSlideSorter); ProcessEvent(aEventDescriptor); } void SelectionFunction::MouseDragged ( const AcceptDropEvent& rEvent, const sal_Int8 nDragAction) { EventDescriptor aEventDescriptor (MOUSE_DRAG, rEvent, nDragAction, mrSlideSorter); ProcessEvent(aEventDescriptor); } void SelectionFunction::ProcessEvent (EventDescriptor& rDescriptor) { // The call to ProcessEvent may switch to another mode handler. // Prevent the untimely destruction of the called handler by acquiring a // temporary reference here. ::boost::shared_ptr pModeHandler (mpModeHandler); pModeHandler->ProcessEvent(rDescriptor); } bool Match ( const sal_uInt32 nEventCode, const sal_uInt32 nPositivePattern) { return (nEventCode & nPositivePattern)==nPositivePattern; } void SelectionFunction::SwitchToNormalMode() { if (mpModeHandler->GetMode() != NormalMode) SwitchMode(::boost::shared_ptr( new NormalModeHandler(mrSlideSorter, *this))); } void SelectionFunction::SwitchToDragAndDropMode (const Point& rMousePosition) { if (mpModeHandler->GetMode() != DragAndDropMode) { #ifndef MACOSX ::boost::shared_ptr handler( new DragAndDropModeHandler(mrSlideSorter, *this)); SwitchMode(handler); // Delayed initialization, only after mpModeHanler is set, otherwise DND initialization // could already trigger DND events, which would recursively trigger this code again, // and without mpModeHandler set it would again try to set a new handler. handler->Initialize(rMousePosition, mpWindow); #else SwitchMode(::boost::shared_ptr( new DragAndDropModeHandler(mrSlideSorter, *this, rMousePosition, mpWindow))); #endif } } void SelectionFunction::SwitchToMultiSelectionMode ( const Point& rMousePosition, const sal_uInt32 nEventCode) { if (mpModeHandler->GetMode() != MultiSelectionMode) #ifndef MACOSX { ::boost::shared_ptr handler( new MultiSelectionModeHandler(mrSlideSorter, *this, rMousePosition)); SwitchMode(handler); // Delayed initialization, only after mpModeHanler is set, the handle ctor // is non-trivial, so it could possibly recurse just like the DND handler above. handler->Initialize(nEventCode); } #else SwitchMode(::boost::shared_ptr( new MultiSelectionModeHandler(mrSlideSorter, *this, rMousePosition, nEventCode))); #endif } void SelectionFunction::SwitchMode (const ::boost::shared_ptr& rpHandler) { // Not all modes allow mouse over indicator. if (mpModeHandler->IsMouseOverIndicatorAllowed() != rpHandler->IsMouseOverIndicatorAllowed()) { if ( ! rpHandler->IsMouseOverIndicatorAllowed()) { mrSlideSorter.GetView().SetPageUnderMouse(model::SharedPageDescriptor()); } else mrSlideSorter.GetView().UpdatePageUnderMouse(); } mpModeHandler = rpHandler; } void SelectionFunction::ResetShiftKeySelectionAnchor() { mnShiftKeySelectionAnchor = -1; } void SelectionFunction::ResetMouseAnchor() { if (mpModeHandler && mpModeHandler->GetMode() == NormalMode) { ::boost::shared_ptr pHandler ( ::boost::dynamic_pointer_cast(mpModeHandler)); if (pHandler) pHandler->ResetButtonDownLocation(); } } //===== EventDescriptor ======================================================= SelectionFunction::EventDescriptor::EventDescriptor ( const sal_uInt32 nEventType, const MouseEvent& rEvent, SlideSorter& rSlideSorter) : maMousePosition(rEvent.GetPosPixel()), maMouseModelPosition(), mpHitDescriptor(), mpHitPage(), mnEventCode(nEventType), meDragMode(InsertionIndicatorHandler::MoveMode), mbMakeSelectionVisible(true), mbIsLeaving(false) { maMouseModelPosition = rSlideSorter.GetContentWindow()->PixelToLogic(maMousePosition); mpHitDescriptor = rSlideSorter.GetController().GetPageAt(maMousePosition); if (mpHitDescriptor) { mpHitPage = mpHitDescriptor->GetPage(); } mnEventCode |= EncodeMouseEvent(rEvent); mnEventCode |= EncodeState(); // Detect the mouse leaving the window. When not button is pressed then // we can call IsLeaveWindow at the event. Otherwise we have to make an // explicit test. mbIsLeaving = rEvent.IsLeaveWindow() || ! Rectangle(Point(0,0), rSlideSorter.GetContentWindow()->GetOutputSizePixel()).IsInside(maMousePosition); } SelectionFunction::EventDescriptor::EventDescriptor ( const sal_uInt32 nEventType, const AcceptDropEvent& rEvent, const sal_Int8 nDragAction, SlideSorter& rSlideSorter) : maMousePosition(rEvent.maPosPixel), maMouseModelPosition(), mpHitDescriptor(), mpHitPage(), mnEventCode(nEventType), meDragMode(InsertionIndicatorHandler::GetModeFromDndAction(nDragAction)), mbMakeSelectionVisible(true), mbIsLeaving(false) { maMouseModelPosition = rSlideSorter.GetContentWindow()->PixelToLogic(maMousePosition); mpHitDescriptor = rSlideSorter.GetController().GetPageAt(maMousePosition); if (mpHitDescriptor) { mpHitPage = mpHitDescriptor->GetPage(); } mnEventCode |= EncodeState(); // Detect the mouse leaving the window. When not button is pressed then // we can call IsLeaveWindow at the event. Otherwise we have to make an // explicit test. mbIsLeaving = rEvent.mbLeaving || ! Rectangle(Point(0,0), rSlideSorter.GetContentWindow()->GetOutputSizePixel()).IsInside(maMousePosition); } sal_uInt32 SelectionFunction::EventDescriptor::EncodeMouseEvent ( const MouseEvent& rEvent) const { // Initialize with the type of mouse event. sal_uInt32 nEventCode (mnEventCode & (BUTTON_DOWN | BUTTON_UP | MOUSE_MOTION)); // Detect the affected button. switch (rEvent.GetButtons()) { case MOUSE_LEFT: nEventCode |= LEFT_BUTTON; break; case MOUSE_RIGHT: nEventCode |= RIGHT_BUTTON; break; case MOUSE_MIDDLE: nEventCode |= MIDDLE_BUTTON; break; } // Detect the number of clicks. switch (rEvent.GetClicks()) { case 1: nEventCode |= SINGLE_CLICK; break; case 2: nEventCode |= DOUBLE_CLICK; break; } // Detect pressed modifier keys. if (rEvent.IsShift()) nEventCode |= SHIFT_MODIFIER; if (rEvent.IsMod1()) nEventCode |= CONTROL_MODIFIER; return nEventCode; } sal_uInt32 SelectionFunction::EventDescriptor::EncodeState() const { sal_uInt32 nEventCode (0); // Detect whether the event has happened over a page object. if (mpHitPage!=NULL && mpHitDescriptor) { if (mpHitDescriptor->HasState(model::PageDescriptor::ST_Selected)) nEventCode |= OVER_SELECTED_PAGE; else nEventCode |= OVER_UNSELECTED_PAGE; } return nEventCode; } //===== SelectionFunction::ModeHandler ======================================== SelectionFunction::ModeHandler::ModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction, const bool bIsMouseOverIndicatorAllowed) : mrSlideSorter(rSlideSorter), mrSelectionFunction(rSelectionFunction), mbIsMouseOverIndicatorAllowed(bIsMouseOverIndicatorAllowed) { } SelectionFunction::ModeHandler::~ModeHandler() { } void SelectionFunction::ModeHandler::ReprocessEvent (EventDescriptor& rDescriptor) { mrSelectionFunction.ProcessEvent(rDescriptor); } void SelectionFunction::ModeHandler::ProcessEvent ( SelectionFunction::EventDescriptor& rDescriptor) { PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter); PageSelector::UpdateLock aUpdateLock (mrSlideSorter); bool bIsProcessed (false); switch (rDescriptor.mnEventCode & (BUTTON_DOWN | BUTTON_UP | MOUSE_MOTION | MOUSE_DRAG)) { case BUTTON_DOWN: bIsProcessed = ProcessButtonDownEvent(rDescriptor); break; case BUTTON_UP: bIsProcessed = ProcessButtonUpEvent(rDescriptor); break; case MOUSE_MOTION: bIsProcessed = ProcessMotionEvent(rDescriptor); break; case MOUSE_DRAG: bIsProcessed = ProcessDragEvent(rDescriptor); break; } if ( ! bIsProcessed) HandleUnprocessedEvent(rDescriptor); } bool SelectionFunction::ModeHandler::ProcessButtonDownEvent (EventDescriptor&) { return false; } bool SelectionFunction::ModeHandler::ProcessButtonUpEvent (EventDescriptor&) { mrSelectionFunction.SwitchToNormalMode(); return false; } bool SelectionFunction::ModeHandler::ProcessMotionEvent (EventDescriptor& rDescriptor) { if (mbIsMouseOverIndicatorAllowed) mrSlideSorter.GetView().UpdatePageUnderMouse(rDescriptor.maMousePosition); if (rDescriptor.mbIsLeaving) { mrSelectionFunction.SwitchToNormalMode(); mrSlideSorter.GetView().SetPageUnderMouse(model::SharedPageDescriptor()); return true; } else return false; } bool SelectionFunction::ModeHandler::ProcessDragEvent (EventDescriptor&) { return false; } bool SelectionFunction::ModeHandler::HandleUnprocessedEvent (EventDescriptor&) { return false; } void SelectionFunction::ModeHandler::SetCurrentPage ( const model::SharedPageDescriptor& rpDescriptor) { SelectOnePage(rpDescriptor); mrSlideSorter.GetController().GetCurrentSlideManager()->SwitchCurrentSlide(rpDescriptor); } void SelectionFunction::ModeHandler::DeselectAllPages() { mrSlideSorter.GetController().GetPageSelector().DeselectAllPages(); mrSelectionFunction.ResetShiftKeySelectionAnchor(); } void SelectionFunction::ModeHandler::SelectOnePage ( const model::SharedPageDescriptor& rpDescriptor) { DeselectAllPages(); mrSlideSorter.GetController().GetPageSelector().SelectPage(rpDescriptor); } void SelectionFunction::ModeHandler::SwitchView (const model::SharedPageDescriptor& rpDescriptor) { // Switch to the draw view. This is done only when the current // view is the main view. ViewShell* pViewShell = mrSlideSorter.GetViewShell(); if (pViewShell!=NULL && pViewShell->IsMainViewShell()) { if (rpDescriptor.get()!=NULL && rpDescriptor->GetPage()!=NULL) { mrSlideSorter.GetModel().GetDocument()->SetSelected(rpDescriptor->GetPage(), true); pViewShell->GetFrameView()->SetSelectedPage( (rpDescriptor->GetPage()->GetPageNum()-1)/2); } if (mrSlideSorter.GetViewShellBase() != NULL) framework::FrameworkHelper::Instance(*mrSlideSorter.GetViewShellBase())->RequestView( framework::FrameworkHelper::msImpressViewURL, framework::FrameworkHelper::msCenterPaneURL); } } void SelectionFunction::ModeHandler::StartDrag ( const Point& rMousePosition, const InsertionIndicatorHandler::Mode eMode) { (void)eMode; // Do not start a drag-and-drop operation when one is already active. // (when dragging pages from one document into another, pressing a // modifier key can trigger a MouseMotion event in the originating // window (focus still in there). Together with the mouse button pressed // (drag-and-drop is active) this triggers the start of drag-and-drop.) if (SD_MOD()->pTransferDrag != NULL) return; if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly()) { mrSelectionFunction.SwitchToDragAndDropMode(rMousePosition); } } //===== NormalModeHandler ===================================================== NormalModeHandler::NormalModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction) : ModeHandler(rSlideSorter, rSelectionFunction, true), maButtonDownLocation() { } NormalModeHandler::~NormalModeHandler() { } SelectionFunction::Mode NormalModeHandler::GetMode() const { return SelectionFunction::NormalMode; } void NormalModeHandler::Abort() { } bool NormalModeHandler::ProcessButtonDownEvent ( SelectionFunction::EventDescriptor& rDescriptor) { // Remember the location where the left button is pressed. With // that we can filter away motion events that are caused by key // presses. We also can tune the minimal motion distance that // triggers a drag-and-drop operation. if ((rDescriptor.mnEventCode & BUTTON_DOWN) != 0) maButtonDownLocation = rDescriptor.maMousePosition; switch (rDescriptor.mnEventCode) { case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE: SetCurrentPage(rDescriptor.mpHitDescriptor); break; case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE: break; case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | OVER_SELECTED_PAGE: case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | OVER_UNSELECTED_PAGE: // A double click always shows the selected slide in the center // pane in an edit view. SetCurrentPage(rDescriptor.mpHitDescriptor); SwitchView(rDescriptor.mpHitDescriptor); break; case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE | SHIFT_MODIFIER: case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE | SHIFT_MODIFIER: // Range selection with the shift modifier. RangeSelect(rDescriptor.mpHitDescriptor); break; // Right button for context menu. case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE: // Single right click and shift+F10 select as preparation to // show the context menu. Change the selection only when the // page under the mouse is not selected. In this case the // selection is set to this single page. Otherwise the // selection is not modified. SetCurrentPage(rDescriptor.mpHitDescriptor); rDescriptor.mbMakeSelectionVisible = false; break; case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE: // Do not change the selection. Just adjust the insertion indicator. rDescriptor.mbMakeSelectionVisible = false; break; case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE: // Remember the current selection so that when a multi selection // is started, we can restore the previous selection. mrSlideSorter.GetModel().SaveCurrentSelection(); DeselectAllPages(); break; case ANY_MODIFIER(BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE): // Remember the current selection so that when a multi selection // is started, we can restore the previous selection. mrSlideSorter.GetModel().SaveCurrentSelection(); DeselectAllPages(); break; case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | NOT_OVER_PAGE: { // Insert a new slide: // First of all we need to set the insertion indicator which sets the // position where the new slide will be inserted. ::boost::shared_ptr pInsertionIndicatorHandler = mrSlideSorter.GetController().GetInsertionIndicatorHandler(); pInsertionIndicatorHandler->Start(false); pInsertionIndicatorHandler->UpdatePosition( rDescriptor.maMousePosition, InsertionIndicatorHandler::MoveMode); mrSlideSorter.GetController().GetSelectionManager()->SetInsertionPosition( pInsertionIndicatorHandler->GetInsertionPageIndex()); mrSlideSorter.GetViewShell()->GetDispatcher()->Execute( SID_INSERTPAGE, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD); break; } default: return false; } return true; } bool NormalModeHandler::ProcessButtonUpEvent ( SelectionFunction::EventDescriptor& rDescriptor) { bool bIsProcessed (true); switch (rDescriptor.mnEventCode) { case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE: SetCurrentPage(rDescriptor.mpHitDescriptor); break; // Multi selection with the control modifier. case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE | CONTROL_MODIFIER: mrSlideSorter.GetController().GetPageSelector().DeselectPage( rDescriptor.mpHitDescriptor); break; case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE | CONTROL_MODIFIER: mrSlideSorter.GetController().GetPageSelector().SelectPage( rDescriptor.mpHitDescriptor); mrSlideSorter.GetView().SetPageUnderMouse(rDescriptor.mpHitDescriptor); break; case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE: break; default: bIsProcessed = false; break; } mrSelectionFunction.SwitchToNormalMode(); return bIsProcessed; } bool NormalModeHandler::ProcessMotionEvent ( SelectionFunction::EventDescriptor& rDescriptor) { if (ModeHandler::ProcessMotionEvent(rDescriptor)) return true; bool bIsProcessed (true); switch (rDescriptor.mnEventCode) { case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE): // SetCurrentPage(rDescriptor.mpHitDescriptor); // Fallthrough // A mouse motion without visible substitution starts that. case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE): { if (maButtonDownLocation) { const sal_Int32 nDistance (maButtonDownLocation ? ::std::max ( std::abs(maButtonDownLocation->X() - rDescriptor.maMousePosition.X()), std::abs(maButtonDownLocation->Y() - rDescriptor.maMousePosition.Y())) : 0); if (nDistance > 3) StartDrag( rDescriptor.maMousePosition, (rDescriptor.mnEventCode & CONTROL_MODIFIER) != 0 ? InsertionIndicatorHandler::CopyMode : InsertionIndicatorHandler::MoveMode); } } break; // A mouse motion not over a page starts a rectangle selection. case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE): mrSelectionFunction.SwitchToMultiSelectionMode( rDescriptor.maMouseModelPosition, rDescriptor.mnEventCode); break; default: bIsProcessed = false; break; } return bIsProcessed; } bool NormalModeHandler::ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) { mrSelectionFunction.SwitchToDragAndDropMode(rDescriptor.maMousePosition); ReprocessEvent(rDescriptor); return true; } void NormalModeHandler::RangeSelect (const model::SharedPageDescriptor& rpDescriptor) { PageSelector::UpdateLock aLock (mrSlideSorter); PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector()); model::SharedPageDescriptor pAnchor (rSelector.GetSelectionAnchor()); DeselectAllPages(); if (pAnchor.get() != NULL) { // Select all pages between the anchor and the given one, including // the two. const sal_uInt16 nAnchorIndex ((pAnchor->GetPage()->GetPageNum()-1) / 2); const sal_uInt16 nOtherIndex ((rpDescriptor->GetPage()->GetPageNum()-1) / 2); // Iterate over all pages in the range. Start with the anchor // page. This way the PageSelector will recognize it again as // anchor (the first selected page after a DeselectAllPages() // becomes the anchor.) const sal_uInt16 nStep ((nAnchorIndex < nOtherIndex) ? +1 : -1); sal_uInt16 nIndex (nAnchorIndex); while (true) { rSelector.SelectPage(nIndex); if (nIndex == nOtherIndex) break; nIndex = nIndex + nStep; } } } void NormalModeHandler::ResetButtonDownLocation() { maButtonDownLocation = ::boost::optional(); } //===== MultiSelectionModeHandler ============================================= MultiSelectionModeHandler::MultiSelectionModeHandler ( SlideSorter& rSlideSorter, SelectionFunction& rSelectionFunction, #ifndef MACOSX const Point& rMouseModelPosition) #else const Point& rMouseModelPosition, const sal_uInt32 nEventCode) #endif : ModeHandler(rSlideSorter, rSelectionFunction, false), meSelectionMode(SM_Normal), maSecondCorner(rMouseModelPosition), maSavedPointer(mrSlideSorter.GetContentWindow()->GetPointer()), mbAutoScrollInstalled(false), mnAnchorIndex(-1), mnSecondIndex(-1) { #ifndef MACOSX } void MultiSelectionModeHandler::Initialize(const sal_uInt32 nEventCode) { #endif const Pointer aSelectionPointer (PointerStyle::Text); mrSlideSorter.GetContentWindow()->SetPointer(aSelectionPointer); SetSelectionModeFromModifier(nEventCode); } MultiSelectionModeHandler::~MultiSelectionModeHandler() { if (mbAutoScrollInstalled) { //a call to this handler's MultiSelectionModeHandler::UpdatePosition //may be still waiting to be called back mrSlideSorter.GetController().GetScrollBarManager().clearAutoScrollFunctor(); } mrSlideSorter.GetContentWindow()->SetPointer(maSavedPointer); } SelectionFunction::Mode MultiSelectionModeHandler::GetMode() const { return SelectionFunction::MultiSelectionMode; } void MultiSelectionModeHandler::Abort() { mrSlideSorter.GetView().RequestRepaint(mrSlideSorter.GetModel().RestoreSelection()); } void MultiSelectionModeHandler::ProcessEvent ( SelectionFunction::EventDescriptor& rDescriptor) { // During a multi selection we do not want sudden jumps of the // visible area caused by moving newly selected pages into view. // Therefore disable that temporarily. The disabler object is // released at the end of the event processing, after the focus and // current slide have been updated. VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter); ModeHandler::ProcessEvent(rDescriptor); } bool MultiSelectionModeHandler::ProcessButtonUpEvent ( SelectionFunction::EventDescriptor& rDescriptor) { if (mbAutoScrollInstalled) { //a call to this handler's MultiSelectionModeHandler::UpdatePosition //may be still waiting to be called back mrSlideSorter.GetController().GetScrollBarManager().clearAutoScrollFunctor(); mbAutoScrollInstalled = false; } if (Match(rDescriptor.mnEventCode, BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK)) { mrSelectionFunction.SwitchToNormalMode(); return true; } else return false; } bool MultiSelectionModeHandler::ProcessMotionEvent ( SelectionFunction::EventDescriptor& rDescriptor) { // The selection rectangle is visible. Handle events accordingly. if (Match(rDescriptor.mnEventCode, MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK)) { SetSelectionModeFromModifier(rDescriptor.mnEventCode); UpdatePosition(rDescriptor.maMousePosition, true); rDescriptor.mbMakeSelectionVisible = false; return true; } else return false; } bool MultiSelectionModeHandler::HandleUnprocessedEvent ( SelectionFunction::EventDescriptor& rDescriptor) { if ( ! ModeHandler::HandleUnprocessedEvent(rDescriptor)) { // If the event has not been processed then stop multi selection. mrSelectionFunction.SwitchToNormalMode(); ReprocessEvent(rDescriptor); } return true; } void MultiSelectionModeHandler::UpdatePosition ( const Point& rMousePosition, const bool bAllowAutoScroll) { VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter); // Convert window coordinates into model coordinates (we need the // window coordinates for auto-scrolling because that remains // constant while scrolling.) sd::Window *pWindow (mrSlideSorter.GetContentWindow()); const Point aMouseModelPosition (pWindow->PixelToLogic(rMousePosition)); bool bDoAutoScroll = bAllowAutoScroll && mrSlideSorter.GetController().GetScrollBarManager().AutoScroll( rMousePosition, ::boost::bind( &MultiSelectionModeHandler::UpdatePosition, this, rMousePosition, false)); if (!bDoAutoScroll) UpdateModelPosition(aMouseModelPosition); mbAutoScrollInstalled |= bDoAutoScroll; } void MultiSelectionModeHandler::SetSelectionModeFromModifier ( const sal_uInt32 nEventCode) { switch (nEventCode & MODIFIER_MASK) { case NO_MODIFIER: SetSelectionMode(SM_Normal); break; case SHIFT_MODIFIER: SetSelectionMode(SM_Add); break; case CONTROL_MODIFIER: SetSelectionMode(SM_Toggle); break; } } void MultiSelectionModeHandler::SetSelectionMode (const SelectionMode eSelectionMode) { if (meSelectionMode != eSelectionMode) { meSelectionMode = eSelectionMode; UpdateSelection(); } } void MultiSelectionModeHandler::UpdateSelectionState ( const model::SharedPageDescriptor& rpDescriptor, const bool bIsInSelection) const { // Determine whether the page was selected before the rectangle // selection was started. const bool bWasSelected (rpDescriptor->HasState(model::PageDescriptor::ST_WasSelected)); // Combine the two selection states depending on the selection mode. bool bSelect (false); switch(meSelectionMode) { case SM_Normal: bSelect = bIsInSelection; break; case SM_Add: bSelect = bIsInSelection || bWasSelected; break; case SM_Toggle: if (bIsInSelection) bSelect = !bWasSelected; else bSelect = bWasSelected; break; } // Set the new selection state. if (bSelect) mrSlideSorter.GetController().GetPageSelector().SelectPage(rpDescriptor); else mrSlideSorter.GetController().GetPageSelector().DeselectPage(rpDescriptor); } void MultiSelectionModeHandler::UpdateModelPosition (const Point& rMouseModelPosition) { maSecondCorner = rMouseModelPosition; UpdateSelection(); } void MultiSelectionModeHandler::UpdateSelection() { view::SlideSorterView::DrawLock aLock (mrSlideSorter); model::SlideSorterModel& rModel (mrSlideSorter.GetModel()); const sal_Int32 nPageCount (rModel.GetPageCount()); const sal_Int32 nIndexUnderMouse ( mrSlideSorter.GetView().GetLayouter().GetIndexAtPoint ( maSecondCorner, false, false)); if (nIndexUnderMouse>=0 && nIndexUnderMousepTransferDrag; if (pDragTransferable==NULL && mrSlideSorter.GetViewShell() != NULL) { SlideSorterViewShell* pSlideSorterViewShell = dynamic_cast(mrSlideSorter.GetViewShell()); if (pSlideSorterViewShell != NULL) pSlideSorterViewShell->StartDrag(rMousePosition, pWindow); pDragTransferable = SD_MOD()->pTransferDrag; } mpDragAndDropContext.reset(new DragAndDropContext(mrSlideSorter)); mrSlideSorter.GetController().GetInsertionIndicatorHandler()->Start( pDragTransferable != NULL && pDragTransferable->GetView()==&mrSlideSorter.GetView()); } DragAndDropModeHandler::~DragAndDropModeHandler() { if (mpDragAndDropContext) { // Disconnect the substitution handler from this selection function. mpDragAndDropContext->SetTargetSlideSorter(); mpDragAndDropContext.reset(); } mrSlideSorter.GetController().GetInsertionIndicatorHandler()->End(Animator::AM_Animated); } SelectionFunction::Mode DragAndDropModeHandler::GetMode() const { return SelectionFunction::DragAndDropMode; } void DragAndDropModeHandler::Abort() { mrSlideSorter.GetController().GetClipboard().Abort(); if (mpDragAndDropContext) mpDragAndDropContext->Dispose(); // mrSlideSorter.GetView().RequestRepaint(mrSlideSorter.GetModel().RestoreSelection()); } bool DragAndDropModeHandler::ProcessButtonUpEvent ( SelectionFunction::EventDescriptor& rDescriptor) { if (Match(rDescriptor.mnEventCode, BUTTON_UP | LEFT_BUTTON)) { // The following Process() call may lead to the destruction // of rDescriptor.mpHitDescriptor so release our reference to it. rDescriptor.mpHitDescriptor.reset(); mrSelectionFunction.SwitchToNormalMode(); return true; } else return false; } bool DragAndDropModeHandler::ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) { OSL_ASSERT(mpDragAndDropContext); if (rDescriptor.mbIsLeaving) { mrSelectionFunction.SwitchToNormalMode(); } else if (mpDragAndDropContext) { mpDragAndDropContext->UpdatePosition( rDescriptor.maMousePosition, rDescriptor.meDragMode); } return true; } } } } // end of namespace ::sd::slidesorter::controller /* vim:set shiftwidth=4 softtabstop=4 expandtab: */