/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2006 * * Umbrello UML Modeller Authors * ***************************************************************************/ #ifndef UMLWIDGETCONTROLLER_H #define UMLWIDGETCONTROLLER_H // qt includes #include // app includes #include "umlwidgetlist.h" class TQCursor; class TQMouseEvent; class TQMoveEvent; class TQPoint; class UMLWidget; /** * Controller for UMLWidget * This class takes care of user interaction with UMLWidgets: select, deselect, * move, resize... * Those actions are done using events. There are handlers for mousePressEvent, * mouseMoveEvent, mouseReleaseEvent and mouseDoubleClickEvent. There's more * information about each of them in their respective documentation. * * Behaviour of this class can be customized overriding the handlers themselves * (which isn't recommended) or, better, overriding the virtual protected * methods. * * The area that is considered as "resize area" can be customized with * isInResizeArea, so when there's a pressed button in this area, mouseMoveEvent * will resize the widget. * Also, if the resize area doesn't need to be modified, but the cursor to be * used when the mouse is in that area can be done with getResizeCursor. * When a widget is being resized, it's done using resizeWidget, so overriding * it makes possible to, for example, constrain the resizing only in one axis * no matter how the mouse was moved. * * Widget move can also be customized. The widgets are moved in mouseMoveEvent * using the moveWidgetBy method of the controller of each selected widget. * Overriding this method widget movement can be, for example, constrained to * an axis, no matter if the widget is being moved explicitly or as part of a * selection. * On the other hand, the move of all the selected widgets can be constrained * with constrainMovementForAllWidgets. This method, called in the controller * that is handling the mouseMoveEvent, modifies the difference between the * current position of the widgets and the new position to be moved to. For * example, if a widget shouldn't be moved in X axis, it's receiving the * mouseMoveEvents and there are other widgets selected, those other widgets * shouldn't be allowed either to be moved in X axis. * * The behaviour when double clicking on the widget after it's selected can be * customized with doMouseDoubleClick. * * @author Umbrello UML Modeller Authors */ class UMLWidgetController { public: /** * Constructor for UMLWidgetController. * * @param widget The widget which uses the controller. */ UMLWidgetController(UMLWidget *widget); /** * Destructor for UMLWidgetController. */ virtual ~UMLWidgetController(); /** * Handles a mouse press event. * It'll select the widget (or mark it to be deselected) and prepare it to * be moved or resized. Go on reading for more info about this. * * Widget values and message bar status are saved. * * If shift or control buttons are pressed, we're in move area no matter * where the button was pressed in the widget. Moreover, if the widget * wasn't already selected, it's added to the selection. If already selected, * it's marked to be deselected when releasing the button (provided it isn't * moved). * Also, if the widget is already selected with other widgets but shift nor * control buttons are pressed, we're in move area. If finally we don't move * the widget, it's selected and the other widgets deselected when releasing * the left button. * * If shift nor control buttons are pressed, we're facing a single selection. * Depending on the position of the cursor, we're in move or in resize area. * If the widget wasn't selected (both when there are no widgets selected, or * when there're other widgets selected but not the one receiving the press * event) it's selected and the others deselected, if any. If already selected, * it's marked to be deselected when releasing the button (provided it wasn't * moved or resized). * * @param me The TQMouseEvent event. */ virtual void mousePressEvent(TQMouseEvent *me); /** * Handles a mouse move event. * It resizes or moves the widget, depending on where the cursor is pressed * on the widget. Go on reading for more info about this. * * If resizing, the widget is resized using resizeWidget (where specific * widget resize constrain can be applied), and then the associations are * adjusted. * The resizing can be constrained also to an specific axis using control * and shift buttons. If on or another is pressed, it's constrained to X axis. * If both are pressed, it's constrained to Y axis. * * If not resizing, the widget is being moved. If the move is being started, * the selection bounds are set (which includes updating the list of selected * widgets). * The difference between the previous position of the selection and the new * one is got (taking in account the selection bounds so widgets don't go * beyond the canvas limits). Then, it's constrained to X or Y axis depending * on shift and control buttons. * A further constrain is made using constrainMovementForAllWidgets (for example, * if the widget that receives the event can only be moved in Y axis, with this * method the movement of all the widgets in the selection can be constrained to * be moved only in Y axis). * Then, all the selected widgets are moved using moveWidgetBy (where specific * widget movement constrain can be applied) and, if an specific amount of time * passed from the last move event, the associations are also updated (they're * not updated always to be easy on the CPU). Finally, the canvas is resized, * and selection bounds updated. * * @param me The TQMouseEvent event. */ virtual void mouseMoveEvent(TQMouseEvent* me); /** * Handles a mouse release event. * It selects or deselects the widget and cancels or confirms the move or * resize. Go on reading for more info about this. * No matter which tool is selected, Z position of widget is updated. * * Middle button release resets the selection. * Left button release, if it wasn't moved nor resized, selects the widget * and deselect the others if it wasn't selected and there were other widgets * selected. If the widget was marked to be deselected, deselects it. * If it was moved or resized, the document is set to modified if position * or size changed. Also, if moved, all the associations are adjusted because * the timer could have prevented the adjustment in the last move event before * the release. * If mouse was pressed in resize area, cursor is set again to normal cursor * Right button release if right button was pressed shows the pop up menu for * the widget. * If left button was pressed, it cancels the move or resize with a mouse move * event at the same position than the cursor was when pressed. Another left * button release is also sent. * * @param me The TQMouseEvent event. */ virtual void mouseReleaseEvent(TQMouseEvent * me); /** * Handles a mouse double click event. * If the button wasn't left button it does nothing. Otherwise, it selects * the widget (deselecting other selected widgets, if any) and executes * doMouseDoubleClick. * @see doMouseDoubleClick * * @param me The TQMouseEvent event. */ virtual void mouseDoubleClickEvent(TQMouseEvent *me); protected: /** * Saves the values of the widget needed for move/resize. * The values saved are: the offset from the cursor respect to the upper left * corner of the widget in m_pressOffsetX/Y, the position in m_oldX/Y and the * size in m_oldW/H. * * It can be overridden to save subclass specific values whenever a move or * resize begins. However, parent method (that is, this method) must be * called in the overridden method. * * @param me The TQMouseEvent to get the offset from. */ virtual void saveWidgetValues(TQMouseEvent *me); /** * Checks if the mouse is in resize area (right bottom corner), and sets * the cursor depending on that. * The cursor used when resizing is gotten from getResizeCursor(). * * @param me The QMouseEVent to check. * @return true if the mouse is in resize area, false otherwise. */ virtual bool isInResizeArea(TQMouseEvent *me); /** * Returns the cursor to be shown when resizing the widget. * * Default cursor is KCursor::sizeFDiagCursor(). * * @return The cursor to be shown when resizing the widget. */ virtual TQCursor getResizeCursor(); /** * Resizes the widget. * It's called from resize, after the values are constrained and before * the associations are adjusted. * * Default behaviour is resize the widget using the new size values. * @see resize * * @param newW The new width for the widget. * @param newH The new height for the widget. */ virtual void resizeWidget(int newW, int newH); /** * Moves the widget to a new position using the difference between the * current position and the new position. * This method doesn't adjust associations. It only moves the widget. * * It can be overridden to constrain movement of m_widget only in one axis even when * the user isn't constraining the movement with shift or control buttons, for example. * The movement policy set here is applied whenever the widget is moved, being it * moving it explicitly, or as a part of a selection but not receiving directly the * mouse events. * * Default behaviour is move the widget to the new position using the diffs. * @see constrainMovementForAllWidgets * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ virtual void moveWidgetBy(int diffX, int diffY); /** * Modifies the value of the diffX and diffY variables used to move the widgets. * * It can be overridden to constrain movement of all the selected widgets only in one * axis even when the user isn't constraining the movement with shift or control * buttons, for example. * The difference with moveWidgetBy is that the diff positions used here are * applied to all the selected widgets instead of only to m_widget, and that * moveWidgetBy, in fact, moves the widget, and here simply the diff positions * are modified. * * Default behaviour is do nothing. * @see moveWidgetBy * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ virtual void constrainMovementForAllWidgets(int &diffX, int &diffY); /** * Executes the action for double click in the widget. * It's called only if the button used was left button. * Before calling this method, the widget is selected. * * Default behaviour is show the properties dialog for the widget using * m_widget->slotMenuSelection(ListPopupMenu::mt_Properties); * If the widget doesn't have a property dialog (from the Widget_Type enum, those that * don't have an UMLObject representation) there's no need to override * the method, it simply does nothing. * * @param me The TQMouseEvent which triggered the double click event. */ virtual void doMouseDoubleClick(TQMouseEvent *me); /** * Clears the selection, resets the toolbar and deselects the widget. */ void resetSelection(); /** * Selects the widget and clears the other selected widgets, if any. * * @param me The TQMouseEvent which made the selection. */ void selectSingle(TQMouseEvent *me); /** * Selects the widget and adds it to the list of selected widgets. * * @param me The TQMouseEvent which made the selection. */ void selectMultiple(TQMouseEvent *me); /** * Deselects the widget and removes it from the list of selected widgets. * * @param me The TQMouseEvent which made the selection. */ void deselect(TQMouseEvent *me); /** * Fills m_selectedWidgetsList and sets the selection bounds ((m_min/m_max)X/Y attributes). */ void setSelectionBounds(); /** * Updates the selection bounds based on the movement made. * If it was only a vertical movement, there's no need to update horizontal bounds, * and vice versa. * * @param diffX The difference between current X position and new X position. * @param diffY The difference between current Y position and new Y position. */ void updateSelectionBounds(int diffX, int diffY); /** * Resizes the widget and adjusts the associations. * It's called when a mouse move event happens and the cursor was * in resize area when pressed. * Resizing can be constrained to an specific axis using control and shift buttons. * * @param me The TQMouseEvent to get the values from. */ void resize(TQMouseEvent *me); /** * Returns the smallest X position of all the widgets in the list. * * @param widgetList A list with UMLWidgets. * @return The smallest X position. */ int getSmallestX(const UMLWidgetList &widgetList); /** * Returns the smallest Y position of all the widgets in the list. * * @param widgetList A list with UMLWidgets. * @return The smallest Y position. */ int getSmallestY(const UMLWidgetList &widgetList); /** * Returns the biggest X position of all the widgets in the list. * * @param widgetList A list with UMLWidgets. * @return The biggest X position. */ int getBiggestX(const UMLWidgetList &widgetList); /** * Returns the biggest Y position of all the widgets in the list. * * @param widgetList A list with UMLWidgets. * @return The biggest Y position. */ int getBiggestY(const UMLWidgetList &widgetList); /** * Returns the adjusted position for the given mouse event. * The adjusted position is computed using the current widget position * m_widget->get{X,Y}(), the previous position m_old{X,Y}, and the * mouse press offset m_pressOffset{X,Y}. * * @param me The TQMouseEvent for which to get the adjusted position. * @return A TQPoint with the adjusted position. */ TQPoint getPosition(TQMouseEvent *me); /** * Returns a TQPoint with the new X and Y position difference of the mouse event * respect to the position of the widget. * * @param me The TQMouseEvent to get the position to compare. * @return A TQPoint with the position difference. */ TQPoint getPositionDifference(TQMouseEvent *me); /** * Shows the widget popup menu where the mouse event points to. * * @param me The TQMouseEvent which triggered the showing. */ void showPopupMenu(TQMouseEvent *me); /** * Checks if the size of the widget changed respect to the size that * it had when press event was fired. * * @return true if was resized, false otherwise. */ bool wasSizeChanged(); /** * Checks if the position of the widget changed respect to the position that * it had when press event was fired. * * @return true if was moved, false otherwise. */ bool wasPositionChanged(); /** * The widget which uses the controller. */ UMLWidget *m_widget; /** * Timer that prevents excessive updates (be easy on the CPU). */ TQTime lastUpdate; /** * A list containing the selected widgets. * It's filled by setSelectionBounds method. It must be filled again if * selected widgets changed. It is cleared only in setSelectionBounds, just * before filling it. * Select, deselect and so on methods DON'T modify this list. */ UMLWidgetList m_selectedWidgetsList; /** * The text in the status bar when the cursor was pressed. */ TQString m_oldStatusBarMsg; /** * The X/Y offset from the position of the cursor when it was pressed to the * upper left corner of the widget. */ int m_pressOffsetX, m_pressOffsetY; /** * The X/Y position the widget had when the movement started. */ int m_oldX, m_oldY; /** * The width/height the widget had when the resize started. */ int m_oldW, m_oldH; /** * The minimum/maximum X/Y position of all the selected widgets. */ int m_minSelectedX, m_minSelectedY, m_maxSelectedX, m_maxSelectedY; /** * If shift or control button were pressed in mouse press event. */ bool m_shiftPressed; /** * If the left/middle/right button is pressed. */ bool m_leftButtonDown, m_middleButtonDown, m_rightButtonDown; /** * If cursor was in move/resize area when left button was pressed (and no * other widgets were selected). */ bool m_inMoveArea, m_inResizeArea; /** * If the widget was selected/moved/resized in the press and release cycle. * Moved/resized is true if the widget was moved/resized even if the final * position/size is the same as the starting one. */ bool m_wasSelected, m_moved, m_resized; }; #endif