View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0017364 | Scribus | Canvas | public | 2025-01-05 21:28 | 2025-02-17 21:14 |
Reporter | nitramr | Assigned To | nitramr | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | assigned | Resolution | open | ||
Platform | Desktop PC | OS | Ubuntu | OS Version | 24.10 64-bit |
Product Version | 1.7.1.svn | ||||
Target Version | 1.7 milestone | ||||
Summary | 0017364: Improved Node Editing | ||||
Description | Important requirements - Editing should be possible using keyboard shortcuts and/or the tools in the node editing palette - Nodes and node handles can be selected and edited without switching the mode in node editing palette Behavior: Nodes 1. Nodes can be selected with a mouse click - Multiple selection with the Shift key pressed + mouse click or the selection frame - Selected nodes can be deselected again with holding the Shift key + mouse click 2. The node handles can be changed: - Symmetrical handles: Alt key + mouse click sets the handles to the node center (corner node) - Asymmetrical handles: Alt key + mouse click makes both handles symmetrical - No handles: Shift key + mouse drag pulls the two handles out symmetrically 3. Ctrl key + mouse click on a node removes this one Node Control handles 4. Ctrl key + mouse drag snaps a handle in 15° increments (with symmetrical handles this also applies to the opposite handle) 5. Shift key and mouse drag to a node handle unlink the handles (different length, different angle) 6. Dragging the mouse moves the node handle - Symmetrical handles: The opposite one moves in a mirror image (same length, same angle) - Asymmetrical handles: Only the active handle changes length and angle - Asymmetrical handles: If the active handle has the same angle as the opposite handle when dragging with the mouse, the active handle "snaps" into place and both handles are "equal in angle" when the mouse is released - Asymmetrical handles: If the active handle has the same length as the opposite handle when dragging with the mouse, the active handle "snaps" into place and both handles are "equal in length" when the mouse is released - Equal in length and equal in angle also work in combination Path Segment 7. Ctrl + click on path segment add a new node Modifiers Alt key: toggle node: corner or symmetric Shift key: Selection and modifier for handle length and angle Ctrl: 15° snapping | ||||
Tags | No tags attached. | ||||
Patch | Yes | ||||
related to | 0016547 | new | Update Layout for Node Editor 2 | |
related to | 0009099 | new | A better Path tool | |
related to | 0000889 | acknowledged | inverse bezier tool handling | |
related to | 0013990 | new | Node Editor lacks keyboard shortcuts | |
related to | 0013991 | new | Node Editor lacks good multiple-node-selection functionality | |
related to | 0013989 | new | Node Editor lacks some constraint functions | |
related to | 0011676 | new | symmetrical control points doesn't support length deviation | |
related to | 0017128 | new | moving shape nodes with the arrow keys does not always work | |
related to | 0007753 | new | Shape editing: delete several nodes | |
related to | 0011976 | new | Editing shapes (nodes) needs an interface with values | |
related to | 0009100 | new | Paths simplification | |
related to | 0017411 | new | nodes editor: don't draw the "original" segment when moving a node | |
related to | 0017369 | new | add to the content palette the actions from the "image" and "preview" section" in the context menu | |
child of | 0017365 | assigned | nitramr | Create a context aware toolbar |
|
if you're going to work on the node editing tool, i think it would be good to differentiate between three types of nodes instead of just two: 'sharp nodes', 'smooth nodes' and 'symmetrical nodes'. |
|
Yes, I'm working on it. What do you mean by smooth nodes? The new implementation will support - sharp nodes: no handles - symmetrical nodes: same angle, same length - asymmetrical nodes: same angle, different length |
|
ok. my 'smooth nodes' are your asymmetrical ones. i just missed something – in this case it's rather a group of the sharp nodes. in your description you have nodes with: - symmetrical handles - asymmetrical handles - no handles but where are the sharp nodes with 'independent handles'? |
|
Oh, good point. I updated this point here: "4. Alt key and mouse drag to a node handle unlink the handles (different length, different angle)" I think we would need new tools in the node editor or somewhere else, where you can do the same actions as you can do with keyboard shortcuts. |
|
inkscape shows four buttons in the toolbar to transform selected nodes. from left to right the selected nodes change to: - sharp nodes (if a node was already sharp, a click will remove its handles) - smooth nodes - symmetrical nodes - auto-smooth nodes (auto-smooth handles adapt their handles to keep a fair curve when one of the neighbouring nodes or the auto-smooth one itself gets moved.) scribus could show those buttons in the node editor. (i never use the last one – don't know if we need it…) |
|
Auto smooth could be more complicated. At least, we should support the first 3 options. - Sharp/Corner nodes - smooth/asymmetric nodes - symmetric nodes In addition, I would like to replace the node edit palette with a new tool properties toolbar/window. Other tools like parametric shapes could be included there too. See proposal from an old mockup: |
|
It looks like you're creating a toolbar... i'm fine with it. But I'd prefer not to have one more and more toolbar to add at the top of my window. Can we have it content aware? Which toolbar can be already made content aware? Which other toolbars might come in the future that fit in the schema? The view tools? (would be the default one...) Otherwise, I think that I'd prefer to have it as a palette... |
|
>> It looks like you're creating a toolbar... Maybe :D >> Can we have it content aware? Yes of course. In my mind, it is like in Inkscape when you select a tool. There is just one toolbar/window that changes the content based on the selected tool. >> But I'd prefer not to have one more and more toolbar to add at the top of my window. I totally agree. There is no intention on my side to clutter the UI more and more. I would go a step further and suggest removing things. I don't know if anyone really needs all the actions in the current standard toolbars. Even if you only need one action, you have to dock the entire toolbar. I suggest having only 2 "toolbars". 1. Customizable toolbar: User can customize this toolbar with favorite actions. It replaces the existing ones. 2. Tool properties: This is a toolbar/dock that shows context specific tool settings and properties. Debatable whether you can move and dock it manually or not. Actually, there is a third one, the Canvas tools toolbar, docked on the left side. |
|
A user can customize the toolbar as you see in the screenshot. There is only one set of predefined blocks that you can add or remove. For "future toolbars" we can simply add new blocks to this set. |
|
nice plan! i think that the sub menu of the shape "canvas" tool is also a candidate... ... and the calligraphic line... |
|
>> i think that the sub menu of the shape "canvas" tool is also a candidate... Good point, I think the list will get longer. We can also show a color preview from the color picker tool there, or the measure values from the measurement tool, etc. However, I think that changing the toolbar should be a different task due to its own complexity. For this task, I think it is better to simply change the buttons in the current palette. |
|
Quick status update. This is the new palette for editing nodes. I merged "Move Node" and "Move Node Control Handle" into one button. Since you don't need to toggle this anymore. There are 3 buttons for the control handle modes (symmetric, asymmetric and independent). All 3 buttons behave like radio buttons, only one mode can be enabled or none. If no mode is enabled (as you see in the screenshot) then there is an automatic detection of the mode. For example, if angle and length of both control handles are the same, it behaves like symmetric mode. Same angle, but different length behaves like asymmetric. Different angle and different length behaves like independent. There is currently no separate "Sharp Edge" button. For that, I implemented a snapping feature. If you move the control handle close enough to the node, it automatically merges with that point. At the moment, most of the functionality is implemented, and I'm working on small fixes and improvements. I think I need to make a screencast to show the full functionality. :D |
|
Some more feedback from my side: - adding the DEL key for deleting a node? - Why are the mirror button needed? Are they not the same as the ones in the PP? - (+) and (-) are hard to distinguish. what about just using + and - without the circle around it? or generally making the symbol below a bit bugger (but, maybe, it's just the "wrong" combination of a low res monitor with eyes that are getting older...) - are you planning to remove the three rows of widgets for rotating and resizing? would be fine for me, since, in my eyes, they don't really belong in the node editing (rotating can be done in the PP, scaling, if needed, should be elsewhere) - for the coordinates, i wonder if the PP could be used... (but then one could not use the PP to move around the whole shape / resize it) |
|
- adding the DEL key for deleting a node? > Yes, DEL and Backspace are supported - Why are the mirror button needed? Are they not the same as the ones in the PP? > Good point. I don't see any difference to those in PP - (+) and (-) are hard to distinguish. what about just using + and - without the circle around it? or generally making the symbol below a bit bugger (but, maybe, it's just the "wrong" combination of a low res monitor with eyes that are getting older...) > I will think about it - are you planning to remove the three rows of widgets for rotating and resizing? would be fine for me, since, in my eyes, they don't really belong in the node editing (rotating can be done in the PP, scaling, if needed, should be elsewhere) > Good question. I think the current implementation is not very useful. Maybe it could be reimplemented to apply changes only to selected nodes? - for the coordinates, i wonder if the PP could be used... (but then one could not use the PP to move around the whole shape / resize it) > Yes, I thought of that too. The coordinates also look broken, if you move nodes all the way to the left or up, they are always 0 because the origin of the element is recalculated. Maybe it is better to use the PP settings instead and show relative coordinates to the page origin or ruler. But to edit nodes you would always have to have the PP open |
|
-> rotating i guess that nobody will have the idea to rotate selected (multiple) nodes, if those buttons were not in the nodes palette... one could probably do cool things with that, but i'm not sure it's something scribus (as a layout tool) should have. (... let's first get the basic interaction to work correctly and be comfortable for the user, not try to have feature that inkscape does not have...) personally, i'm for simply removing all the widgets concerning the scaling and rotation. -> coordinates no idea why there is a choice between canvas and page coords. if we want absolute coordinates, we only need the ones relative to the axis origins, the same that we otherwise get for the items. (which is currently not a choice) for now, i would have a simple choice between relative and absolute coordinates. -> shearing the path no idea why we have those buttons here. (and how they got that name...) imo, one should use "item > transformation" for that. it's available while editing the nodes. (of course, "item > transformation" should be improved for single shot uses... but that's another topic...) the solution might be related to the ticket 0017374 i just created. -> using the PP if i understood correctly, you're wondering if the content palette should actively be used for editing the nodes. (personally, while first reviewing your ticket, i did think about using the PP or the CP for it...) in the long term, i think i prefer to have the context aware toolbar you were proposing. and in the short term it can stay in the existing dedicated floating dialog. i would not use the PP (except maybe for the x,y values... but even there, i'd prefer to keep them in the floating dialog for now) |
|
I recorded a demo video of the current implementation. Video show node editing only with mouse and Alt + Shift keys. https://youtu.be/mmhDRVEU7Ic |
|
>> personally, i'm for simply removing all the widgets concerning the scaling and rotation. Yes, or since we have them, we can hide them in a menu or make it less prominent. >> if we want absolute coordinates, we only need the ones relative to the axis origins, the same that we otherwise get for the items. (which is currently not a choice) At the moment I merged 3 options in a dropdown button (canvas, page, item). Perhaps only ruler and item origin? >> no idea why we have those buttons here. (and how they got that name...) We could but them in the shape section, next to the shape selection >> if i understood correctly, you're wondering if the content palette should actively be used for editing the nodes. My comment refers to if we would remove the coordinate inputs from the node editing palette >> in the long term, i think i prefer to have the context aware toolbar you were proposing. Yes, it is better to keep the related things together in a toolbar or other kind of window. |
|
using the nodes editor after a long time. (https://forums.scribus.net/index.php/topic,5842.msg25380.html#msg25380) i thought i had several ideas to suggest, but after having reviewed them a few times, it mostly boils down to ensuring a better compatibility with inkscape: - the "delete" button should delete the selected nodes, not activate the deleting mode (somehow, i see why having a delete mode seems interesting... but i would go for doing the same as inkscape here) - it's ctrl-move to constrain the angle of the control points - use the same names as in inkscape for the tooltips - use the same icons as in inkscape for the buttons - extend the align and distribute palette to also work on nodes (this might need a separate ticket) there are three things i wanted to suggest that do not exist in inkscape. after some thoughts, i see why they are not as useful as i thought at first. i still add them in here: - maybe, they are not so crazy as i think; - and it documents document why i should not suggest them again later : - ) the first one is about moving the control points with the arrow keys (and the ctrl-key to keep on the same "axis") somehow, the result is probably not really predictable, since the steps for the arrow keys are defined for horizontal or vertical movements. i guess that's the reason why inkscape does not have the feature, either. allowing the selection of a control point by selecting the area around it it. but that was more a fear of mine to move the target when clicking on it: my tests seem to show that the target does not move when clicked. also, i wanted to be able to select multiple control points and move them at the same time. probably not as useful as i first thought. |
|
Ok, here is the first patch file. I updated the description of this task, which now also reflects the new implementation. Some mechanics could be improved, for example, if more than 1 node is selected, and you press the delete key, only one node will be removed and not all. All in all, node editing is now much faster and more intuitive. Now the button modifiers match (more or less) the implementation of Inkscape and Abode Illustrator. Also, I created a UI file for the node editing dialog. Now it's easy to make changes. Just a note: Because of the new modes, I removed the implementation to automatically align the neighbor node when you press Ctrl and move a node. nodeedit_2025-02-07_01.patch (119,897 bytes)
Index: resources/iconsets/1_7_0/1_7_0.xml =================================================================== --- resources/iconsets/1_7_0/1_7_0.xml (Revision 26678) +++ resources/iconsets/1_7_0/1_7_0.xml (Arbeitskopie) @@ -205,29 +205,34 @@ <icon id="AppIcon2.png" file="scribus-icon.svg" /> <!-- Bezier & Vector Path--> - <icon id="22/transform-shear-down.png" file="16/bezier-path-shear-down.svg" /> - <icon id="22/transform-shear-left.png" file="16/bezier-path-shear-left.svg" /> - <icon id="22/transform-shear-right.png" file="16/bezier-path-shear-right.svg" /> - <icon id="22/transform-shear-up.png" file="16/bezier-path-shear-up.svg" /> - <icon id="hmirror.png" file="16/object-flip-horizontal.svg" /> - <icon id="vmirror.png" file="16/object-flip-vertical.svg" /> - <icon id="AddNode.png" file="16/bezier-node-add.svg" /> - <icon id="DeleteNode.png" file="16/bezier-node-delete.svg" /> - <icon id="Reset1Node.png" file="16/bezier-control-reset.svg" /> - <icon id="ResetNode.png" file="16/bezier-controls-reset.svg" /> - <icon id="MoveAsym.png" file="16/bezier-control-asymetric.svg" /> - <icon id="MoveKontrol.png" file="16/bezier-control-move.svg" /> - <icon id="MoveNode.png" file="16/bezier-node-move.svg" /> - <icon id="MoveSym.png" file="16/bezier-control-symetric.svg" /> - <icon id="PolyCut.png" file="16/bezier-split.svg" /> - <icon id="BezierClose.png" file="16/bezier-close.svg" /> - <icon id="expand.png" file="16/bezier-scale-up.svg" /> - <icon id="crop.png" file="16/bezier-scale-down.svg" /> - <icon id="pathexclusion.png" file="16/path-exclusion.svg" /> + <icon id="pathexclusion.png" file="16/path-exclusion.svg" /> <icon id="pathintersection.png" file="16/path-intersection.svg" /> <icon id="pathparts.png" file="16/path-combine.svg" /> <icon id="pathsubtraction.png" file="16/path-difference.svg" /> <icon id="pathunite.png" file="16/path-union.svg" /> + <icon id="MoveKontrol.png" file="16/bezier-control-move.svg" /> + <icon id="Reset1Node.png" file="16/bezier-control-reset.svg" /> + <icon id="ResetNode.png" file="16/bezier-controls-reset.svg" /> + <icon id="bezier-close" file="16/bezier-close.svg" /> + <icon id="bezier-control-asymetric" file="16/bezier-control-asymetric.svg" /> + <icon id="bezier-control-independent" file="16/bezier-control-independent.svg" /> + <icon id="bezier-control-symetric" file="16/bezier-control-symetric.svg" /> + <icon id="bezier-node-add" file="16/bezier-node-add.svg" /> + <icon id="bezier-node-delete" file="16/bezier-node-delete.svg" /> + <icon id="bezier-node-edit" file="16/bezier-node-edit.svg" /> + <icon id="bezier-node-move" file="16/bezier-node-move.svg" /> + <icon id="bezier-path-shear-down" file="16/bezier-path-shear-down.svg" /> + <icon id="bezier-path-shear-left" file="16/bezier-path-shear-left.svg" /> + <icon id="bezier-path-shear-right" file="16/bezier-path-shear-right.svg" /> + <icon id="bezier-path-shear-up" file="16/bezier-path-shear-up.svg" /> + <icon id="bezier-scale-up" file="16/bezier-scale-up.svg" /> + <icon id="bezier-scale-down" file="16/bezier-scale-down.svg" /> + <icon id="bezier-split" file="16/bezier-split.svg" /> + <icon id="object-flip-horizontal" file="16/object-flip-horizontal.svg" /> + <icon id="object-flip-vertical" file="16/object-flip-vertical.svg" /> + <icon id="position-origin-canvas" file="16/position-origin-canvas.svg" /> + <icon id="position-origin-item" file="16/position-origin-item.svg" /> + <icon id="position-origin-page" file="16/position-origin-page.svg" /> <!-- Color --> <icon id="register.png" file="16/color-registration.svg" /> @@ -271,6 +276,8 @@ <icon id="select_column.png" file="32/cursor-select-column.svg" /><!-- table --> <icon id="select_row.png" file="32/cursor-select-row.svg" /><!-- table --> <icon id="dragpix.png" file="32/cursor-drop-image.svg" /> + <icon id="cursor-move" file="32/cursor-move.svg" /> + <icon id="cursor-node-edit" file="32/cursor-node-edit.svg" /> <!-- Drawable Shapes --> <!-- Index: scribus/canvasmode.cpp =================================================================== --- scribus/canvasmode.cpp (Revision 26678) +++ scribus/canvasmode.cpp (Arbeitskopie) @@ -257,6 +257,7 @@ if (!m_doc->Items->contains(currItem)) continue; psx->save(); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; w = currItem->visualWidth() ; h = currItem->visualHeight() ; @@ -265,14 +266,14 @@ psx->setRenderHint(QPainter::Antialiasing); psx->translate(currItem->xPos(), currItem->yPos()); psx->rotate(currItem->rotation()); - x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos()); - y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos()); + x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos() - lineAdjust); + y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos() - lineAdjust); } else { psx->translate(currItem->visualXPos(), currItem->visualYPos()); - x = 0; - y = 0; + x = currItem->asLine() ? 0 : -lineAdjust; + y = currItem->asLine() ? 0 : -lineAdjust; } // psx->setBrush(Qt::NoBrush); // psx->setPen(m_pen["frame-outline"]); @@ -285,12 +286,13 @@ } psx->save(); psx->setPen(m_pen["selection-group"]); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; m_doc->m_Selection->getVisualGroupRect(&x, &y, &w, &h); psx->translate(x,y); - x = 0; - y = 0; + x = -lineAdjust; + y = -lineAdjust; // psx->setBrush(Qt::NoBrush); // psx->setPen(m_pen["frame-outline"]); @@ -314,6 +316,7 @@ psx->save(); psx->setPen(m_pen["selection"]); psx->setBrush(m_brush["selection"]); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; if (currItem->isGroupChild()) { @@ -358,14 +361,14 @@ psx->setRenderHint(QPainter::Antialiasing); psx->translate(currItem->xPos(), currItem->yPos()); psx->rotate(currItem->rotation()); - x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos()); - y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos()); + x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos() - lineAdjust); + y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos() - lineAdjust); } else { psx->translate(currItem->visualXPos(), currItem->visualYPos()); - x = 0; - y = 0; + x = currItem->asLine() ? 0 : -lineAdjust; + y = currItem->asLine() ? 0 : -lineAdjust; } // psx->setBrush(Qt::NoBrush); if (m_doc->drawAsPreview && !m_doc->editOnPreview) @@ -656,7 +659,7 @@ cursor = im.loadCursor("DrawFreeLine.png", 0, 31); break; case modeDrawCalligraphicLine: - cursor = im.loadCursor("drawcalligraphy.png", 4, 4); + cursor = im.loadCursor("drawcalligraphy.png"); break; case modeImportObject: cursor = im.loadCursor("dragpix.png"); @@ -1291,7 +1294,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if ((nodeSelection.count() != 0) /*&& (m_doc->nodeEdit.edPoints())*/) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1385,7 +1388,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if ((nodeSelection.count() != 0) /*&& (m_doc->nodeEdit.edPoints())*/) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1479,7 +1482,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if ((nodeSelection.count() != 0) /*&& (m_doc->nodeEdit.edPoints())*/) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1573,7 +1576,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if ((nodeSelection.count() != 0) /*&& (m_doc->nodeEdit.edPoints())*/) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1734,9 +1737,10 @@ m_color["frame-group"] = PrefsManager::instance().appPrefs.displayPrefs.frameGroupColor; m_color["frame"] = PrefsManager::instance().appPrefs.displayPrefs.frameNormColor; m_color["frame-move"] = Qt::gray; - m_color["weld"] = QColor(214, 146, 43); - m_color["node"] = QColor(0, 136, 255); - m_color["node-handle"] = QColor(255, 51, 187); + m_color["weld"] = QColor(238, 152, 0); + m_color["node"] = QColor(0, 123, 241); + m_color["node-handle"] = QColor(166, 74, 253); + m_color["highlight"] = QColor(220, 58, 140); m_color["test"] = Qt::magenta; QColor selectionAlpha = m_color["frame-selection"]; selectionAlpha.setAlphaF(.05f); @@ -1747,18 +1751,19 @@ #else double stroke = 1.0; #endif - + // frame m_pen["frame-move"] = QPen(m_color["frame-move"], 1.0 , Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["frame-move"].setCosmetic(true); m_pen["frame-preview"] = QPen(Qt::black, 1.0, Qt::DotLine, Qt::FlatCap, Qt::MiterJoin); m_pen["frame-preview"].setCosmetic(true); - m_pen["selection"] = QPen(m_color["frame-selection"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["selection"] = QPen(m_color["frame-selection"], 1.0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection"].setCosmetic(true); - m_pen["selection-handle"] = QPen(m_color["frame-selection"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["selection-handle"] = QPen(m_color["frame-selection"], 1.0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection-handle"].setCosmetic(true); m_pen["selection-group"] = m_pen["selection"]; m_pen["selection-group-inside"] = QPen(m_color["frame-group"], 1.0 , Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection-group-inside"].setCosmetic(true); + // vector editing m_pen["vector"] = QPen(m_color["node"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["vector"].setCosmetic(true); m_pen["node"] = QPen(m_color["node"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); @@ -1767,6 +1772,8 @@ m_pen["node-handle"].setCosmetic(true); m_pen["node-dash"] = QPen(m_color["node-handle"], stroke, Qt::DashLine, Qt::FlatCap, Qt::MiterJoin); m_pen["node-dash"].setCosmetic(true); + m_pen["highlight"] = QPen(m_color["highlight"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["highlight"].setCosmetic(true); m_brush["frame-move"] = Qt::NoBrush; m_brush["selection"] = Qt::NoBrush; Index: scribus/canvasmode_nodeedit.cpp =================================================================== --- scribus/canvasmode_nodeedit.cpp (Revision 26678) +++ scribus/canvasmode_nodeedit.cpp (Arbeitskopie) @@ -30,6 +30,7 @@ #include "scribusdoc.h" #include "scribusview.h" #include "selection.h" +#include "ui/nodeeditpalette.h" #include "ui/pageselector.h" #include "ui/scrspinbox.h" @@ -38,20 +39,20 @@ m_ScMW = m_view->m_ScMW; } -void CanvasMode_NodeEdit::drawControls(QPainter* p) +void CanvasMode_NodeEdit::drawControls(QPainter* p) { - if (m_doc->m_Selection->isEmpty()) + if (m_doc->m_Selection->isEmpty() || m_doc->nodeEdit.previewMode()) return; - if (m_doc->nodeEdit.previewMode()) - return; + PageItem* currItem = m_doc->m_Selection->itemAt(0); FPointArray cli; - + p->save(); p->scale(m_canvas->scale(), m_canvas->scale()); p->translate(-m_doc->minCanvasCoordinate.x(), -m_doc->minCanvasCoordinate.y()); p->setTransform(currItem->getTransform(), true); p->setBrush(Qt::NoBrush); + p->setRenderHint(QPainter::Antialiasing); if ((m_doc->nodeEdit.isContourLine()) && (!currItem->ContourLine.empty())) cli = currItem->ContourLine; @@ -71,15 +72,12 @@ p->scale(1, -1); } } - } + } const double scale = m_canvas->m_viewMode.scale; // Draw Vector Path if (cli.size() > 3) { - p->save(); - p->setRenderHint(QPainter::Antialiasing); - for (int i = 0; i < cli.size() - 3; i += 4) { if (cli.isMarker(i)) @@ -88,20 +86,31 @@ const FPoint& a1 = cli.point(i); const FPoint& a2 = cli.point(i + 1); const FPoint& a3 = cli.point(i + 3); - const FPoint& a4 = cli.point(i + 2); + const FPoint& a4 = cli.point(i + 2); QPainterPath Bez; Bez.moveTo(a1.x(), a1.y()); Bez.cubicTo(a2.x(), a2.y(), a3.x(), a3.y(), a4.x(), a4.y()); + + // Vector Path p->setPen(pens().value("vector")); p->drawPath(Bez); - p->setPen(pens().value("node-handle")); - p->drawLine(QPointF(a1.x(), a1.y()), QPointF(a2.x(), a2.y())); - p->drawLine(QPointF(a3.x(), a3.y()), QPointF(a4.x(), a4.y())); + } - p->restore(); } - double x = 0.0; - double y = 0.0; + + double nx = 0.0; + double ny = 0.0; + double cx = 0.0; + double cy = 0.0; + + QPen penControl = pens().value("node"); + bool isActiveNode = false; + bool isActiveControl = false; + bool highlight = false; + bool mergedNodes = true; + int nr, nl, inr; + FPoint pn, pc; + // Draw Nodes for (int i = 0; i < cli.size() - 1; i += 2) { @@ -108,51 +117,90 @@ if (cli.isMarker(i)) continue; - if (m_doc->nodeEdit.edPoints()) - { - cli.point(i + 1, &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale); + nr = NodeEditContext::indexOfNode(m_doc->nodeEdit.clre(), NodeEditContext::NodeType::NodeRight, cli.size()); + nl = NodeEditContext::indexOfNode(m_doc->nodeEdit.clre(), NodeEditContext::NodeType::NodeLeft, cli.size()); + inr = NodeEditContext::indexOfNode(i, NodeEditContext::NodeType::NodeRight, cli.size()); - cli.point(i, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale); - } - else + mergedNodes = NodeEditContext::hasTwoNodes(i, cli); + highlight = (i == nr || i == nl) && NodeEditContext::hasTwoNodes(m_doc->nodeEdit.clre(), cli); + + isActiveNode = m_doc->nodeEdit.clre() == i || + m_doc->nodeEdit.selNode().contains(i) || + m_doc->nodeEdit.selNode().contains(i - 2) || + (m_doc->nodeEdit.selNode().contains(inr) && mergedNodes) ? true : false; + isActiveControl = m_doc->nodeEdit.clre() == i + 1 ? true : false; + + cli.point(i, &nx, &ny); + cli.point(i + 1, &cx, &cy); + + // Draw Control Handle Lines + if (FPoint(cx, cy) != FPoint(nx, ny)) { - cli.point(i, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale); + // changes color to highlight if angle is same as opponent handle + if (highlight && state.isSameAngle && state.clickedOn) + p->setPen(pens().value("highlight")); + else + p->setPen(pens().value("node-handle")); - cli.point(i + 1, &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale); + p->setRenderHint(QPainter::Antialiasing, true); + p->drawLine(QPointF(nx, ny), QPointF(cx, cy)); } - } - - if (m_doc->nodeEdit.clre() != -1) - { - cli.point(m_doc->nodeEdit.clre(), &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale, true); - const auto& selectedNodes = m_doc->nodeEdit.selNode(); - for (int i = 0; i < selectedNodes.count(); ++i) + p->setRenderHint(QPainter::Antialiasing, false); + + // Draw Node Handle + drawNodeHandle(p, QPointF(nx, ny), pens().value("node"), scale, isActiveNode); + + // Draw Control Handle + if (FPoint(cx, cy) != FPoint(nx, ny)) { - int selectedNode = selectedNodes.at(i); - cli.point(selectedNode, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale, true); + // changes color to highlight if length is same as opponent handle + if (highlight && state.isSameLength && state.isSameAngle && state.clickedOn) + penControl = pens().value("highlight"); + else + penControl = pens().value("node"); + + drawNodeControl(p, QPointF(cx, cy), penControl, scale, isActiveControl); } } - bool havePoint = (m_doc->nodeEdit.clre() != -1); - emit m_view->HavePoint(havePoint, m_doc->nodeEdit.moveSym()); + emit m_view->HavePoint(m_doc->nodeEdit.clre() != -1); + if (m_doc->nodeEdit.clre() != -1) { - cli.point(m_doc->nodeEdit.clre(), &x, &y); - emit m_view->ClipPo(x, y); + cli.point(m_doc->nodeEdit.clre(), &nx, &ny); + emit m_view->ClipPo(nx, ny); } p->restore(); } +QMap<int, int> CanvasMode_NodeEdit::selectedNodesList(FPointArray points) +{ + QMap<int, int> selList; + const auto& selNodes = m_doc->nodeEdit.selNode(); + if (points.size() > 3) + { + for (int i = 0; i < points.size() - 1; i += 2) + { + if (points.isMarker(i)) + continue; + + // i = path start; + // i + 2 = path end OR left part of a node + // i - 2 = right part of a node + if (selNodes.contains(i) || selNodes.contains(i + 2) || selNodes.contains(i - 2) || selNodes.contains(i - 4)) + { + selList.insert(i, i + 1); + } + } + } + + return selList; +} + void CanvasMode_NodeEdit::activate(bool fromGesture) { CanvasMode::activate(fromGesture); @@ -236,19 +284,47 @@ return (*pi) != nullptr; } +FPoint CanvasMode_NodeEdit::constraintControlAngle(FPoint anchor, FPoint mouse, double angleStep) +{ + QLineF line(anchor.toQPointF(), mouse.toQPointF()); + line.setAngle(constrainAngle(line.angle(), angleStep)); + return line.p2(); +} -void CanvasMode_NodeEdit::enterEvent(QEvent *e) +FPoint CanvasMode_NodeEdit::snapControlAngle(FPoint anchor, FPoint mouse, double angle) { + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF lineAM(anchor.x(), anchor.y(), mouse.x(), mouse.y()); // anchor to mouse + QLineF lineAC = lineAM; // anchor to opposite control point + lineAC.setAngle(360.0 - angle); // invert angle, Qt angle is counter-clockwise + + if (qMax(lineAM.angle(), lineAC.angle()) - qMin(lineAM.angle(), lineAC.angle()) <= 0.00001) + return lineAC.p2(); + + return QLineF(lineAM.p2(), lineAC.p2()).length() <= snap ? lineAC.p2() : mouse; } +FPoint CanvasMode_NodeEdit::snapControlLength(FPoint anchor, FPoint mouse, double length) +{ + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF line(anchor.toQPointF(), mouse.toQPointF()); -void CanvasMode_NodeEdit::leaveEvent(QEvent *e) -{ + if (length - snap <= line.length() && length + snap >= line.length()) + line.setLength(length); + + return line.p2(); } +FPoint CanvasMode_NodeEdit::snapControlOrigin(FPoint anchor, FPoint mouse) +{ + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF line(anchor.toQPointF(), mouse.toQPointF()); + return (line.length() <= snap) ? anchor : mouse; +} + void CanvasMode_NodeEdit::mouseDoubleClickEvent(QMouseEvent *m) { m->accept(); @@ -257,11 +333,11 @@ m_view->requestMode(submodeEndNodeEdit); } - void CanvasMode_NodeEdit::mouseMoveEvent(QMouseEvent *m) { double sc = m_canvas->scale(); FPoint npf = m_canvas->globalToCanvas(m->globalPosition()); + m_doc->nodeEdit.setScale(sc); m->accept(); @@ -348,6 +424,7 @@ m_canvas->m_viewMode.operItemMoving = false; m_doc->DragP = false; m_doc->leaveDrag = false; + m_doc->nodeEdit.setScale(m_canvas->scale()); m_MoveGX = m_MoveGY = false; m->accept(); m_view->registerMousePress(m->globalPosition()); @@ -369,7 +446,8 @@ if (m_doc->m_Selection->count() != 0) { handleNodeEditPress(m, mpo); - } + } + if ((m_doc->m_Selection->count() != 0) && (m->button() == Qt::RightButton)) { m_canvas->m_viewMode.m_MouseButtonPressed = true; @@ -378,16 +456,27 @@ } } - - void CanvasMode_NodeEdit::mouseReleaseEvent(QMouseEvent *m) { PageItem *currItem; + m_canvas->m_viewMode.m_MouseButtonPressed = false; m_canvas->resetRenderMode(); m->accept(); + + // Deselect selected node if SHIFT is pressed + if (state.clickedOn == NodeHandle && + m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && + !state.wasNewlySelected && + m->modifiers() == Qt::ShiftModifier) + { + m_doc->nodeEdit.deselectNode(m_doc->nodeEdit.clre()); + m_doc->regionsChanged()->update(QRectF()); + } + if (m_view->moveTimerElapsed()) { + // Path Segment if (m_doc->nodeEdit.segP1() != -1 && m_doc->nodeEdit.segP2() != -1) { m_doc->nodeEdit.deselect(); @@ -394,53 +483,58 @@ m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); } + + m_canvas->m_viewMode.operItemMoving = false; currItem = m_doc->m_Selection->itemAt(0); - m_canvas->m_viewMode.operItemMoving = false; double xposOrig = currItem->xPos(); double yposOrig = currItem->yPos(); - ScOldNewState<FPointArray> *state = m_doc->nodeEdit.finishTransaction1(currItem); + ScOldNewState<FPointArray> *onState = m_doc->nodeEdit.finishTransaction1(currItem); + if (m_doc->nodeEdit.hasNodeSelected() && (m_doc->nodeEdit.selectionCount() == 1)) { + //FIXME:av - FPoint newP = m_canvas->globalToCanvas(m->globalPosition()); + // FPoint newP = m_canvas->globalToCanvas(m->globalPosition()); currItem->OldB2 = currItem->width(); currItem->OldH2 = currItem->height(); - FPointArray Clip; - FPoint npf; - double nx = newP.x(); - double ny = newP.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) - npf = m_doc->ApplyGridF(FPoint(nx, ny)); - else - npf = FPoint(nx, ny); - QTransform pp = currItem->getTransform(); - npf = npf.transformPoint(pp, true); - if (m_doc->nodeEdit.isContourLine()) - Clip = currItem->ContourLine; - else - { - Clip = currItem->PoLine; - if (currItem->isSymbol() || currItem->isGroup()) - { - if (currItem->imageFlippedH()) - { - QTransform p; - p.translate(currItem->width(), 0); - p.scale(-1, 1); - npf = npf.transformPoint(p, false); - } - if (currItem->imageFlippedV()) - { - QTransform p; - p.translate(0, currItem->height()); - p.scale(1, -1); - npf = npf.transformPoint(p, false); - } - } - } - m_doc->nodeEdit.moveClipPoint(currItem, npf); + // currItem->ClipEdited = true; + // FPointArray Clip; + // FPoint npf; + // double nx = newP.x(); + // double ny = newP.y(); + // if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) + // npf = m_doc->ApplyGridF(FPoint(nx, ny)); + // else + // npf = FPoint(nx, ny); + // QTransform pp = currItem->getTransform(); + // npf = npf.transformPoint(pp, true); + // if (m_doc->nodeEdit.isContourLine()) + // Clip = currItem->ContourLine; + // else + // { + // Clip = currItem->PoLine; + // if (currItem->isSymbol() || currItem->isGroup()) + // { + // if (currItem->imageFlippedH()) + // { + // QTransform p; + // p.translate(currItem->width(), 0); + // p.scale(-1, 1); + // npf = npf.transformPoint(p, false); + // } + // if (currItem->imageFlippedV()) + // { + // QTransform p; + // p.translate(0, currItem->height()); + // p.scale(1, -1); + // npf = npf.transformPoint(p, false); + // } + // } + // } + // m_doc->nodeEdit.moveClipPoint(currItem, npf); } + // nitramr: Scribus crashes if ESC key is pressed during mouse drag operation m_doc->adjustItemSize(currItem, true); if (!m_doc->nodeEdit.isContourLine()) { @@ -450,41 +544,58 @@ currItem->ContourLine.translate(delta.x(), delta.y()); } m_doc->regionsChanged()->update(QRectF()); - if (state) - m_doc->nodeEdit.finishTransaction2(currItem, state); - return; - } + if (onState) + m_doc->nodeEdit.finishTransaction2(currItem, onState); - m_canvas->setRenderModeUseBuffer(false); - m_doc->DragP = false; - m_doc->leaveDrag = false; - m_canvas->m_viewMode.operItemMoving = false; - m_canvas->m_viewMode.operItemResizing = false; - m_view->MidButt = false; - if (m_view->groupTransactionStarted()) - { - for (int i = 0; i < m_doc->m_Selection->count(); ++i) - m_doc->m_Selection->itemAt(i)->checkChanges(true); - m_view->endGroupTransaction(); } - //Commit drag created items to undo manager. - if (m_doc->m_Selection->itemAt(0) != nullptr) + else { - m_doc->itemAddCommit(m_doc->m_Selection->itemAt(0)); + m_canvas->setRenderModeUseBuffer(false); + m_doc->DragP = false; + m_doc->leaveDrag = false; + m_canvas->m_viewMode.operItemMoving = false; + m_canvas->m_viewMode.operItemResizing = false; + m_view->MidButt = false; + if (m_view->groupTransactionStarted()) + { + for (int i = 0; i < m_doc->m_Selection->count(); ++i) + m_doc->m_Selection->itemAt(i)->checkChanges(true); + m_view->endGroupTransaction(); + } + //Commit drag created items to undo manager. + if (m_doc->m_Selection->itemAt(0) != nullptr) + m_doc->itemAddCommit(m_doc->m_Selection->itemAt(0)); + //Make sure the Zoom spinbox and page selector don't have focus if we click on the canvas + m_view->m_ScMW->zoomSpinBox->clearFocus(); + m_view->m_ScMW->pageSelector->clearFocus(); + if (m_doc->m_Selection->itemAt(0) != nullptr) // is there the old clip stored for the undo action + { + currItem = m_doc->m_Selection->itemAt(0); + m_doc->nodeEdit.finishTransaction(currItem); + } } - //Make sure the Zoom spinbox and page selector don't have focus if we click on the canvas - m_view->m_ScMW->zoomSpinBox->clearFocus(); - m_view->m_ScMW->pageSelector->clearFocus(); - if (m_doc->m_Selection->itemAt(0) != nullptr) // is there the old clip stored for the undo action - { - currItem = m_doc->m_Selection->itemAt(0); - m_doc->nodeEdit.finishTransaction(currItem); - } + + m_doc->nodeEdit.setMoveMode(state.originalMoveMode); + state.reset(); } void CanvasMode_NodeEdit::keyPressEvent(QKeyEvent *e) { commonkeyPressEvent_NormalNodeEdit(e); + + int kk = e->key(); +// Qt::KeyboardModifiers buttonModifiers = e->modifiers(); +// ScribusMainWindow* mainWindow = m_view->m_ScMW; + PageItem* currItem = m_doc->m_Selection->itemAt(0); + + if (kk == Qt::Key_Delete || kk == Qt::Key_Backspace) + { + if (currItem && m_doc->nodeEdit.hasNodeSelected()) + { + // delete Nodes + refreshPath(deleteNodes()); + } + } } void CanvasMode_NodeEdit::keyReleaseEvent(QKeyEvent *e) @@ -494,58 +605,131 @@ void CanvasMode_NodeEdit::handleNodeEditPress(QMouseEvent* m, QRect r) { - FPoint npf2; - bool edited = false; - bool pfound = false; - PageItem* currItem = m_doc->m_Selection->itemAt(0); FPointArray Clip = m_doc->nodeEdit.beginTransaction(currItem); - m_doc->nodeEdit.deselect(); + QTransform pm2 = currItem->getTransform(); + FPoint npf2 = m_canvas->globalToCanvas(m->globalPosition()).transformPoint(pm2, true); + bool edited = false; - QTransform pm2 = currItem->getTransform(); - npf2 = m_canvas->globalToCanvas(m->globalPosition()).transformPoint(pm2, true); - if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) + state.originalMoveMode = m_doc->nodeEdit.moveMode(); + state.moveMode = state.originalMoveMode; + state.clickedOn = None; + m_doc->nodeEdit.oppositeControl.isValid = false; + + switch (state.hoveredOver) { - if (currItem->imageFlippedH()) + case ControlHandle: + case NodeHandle: + { + if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { - pm2.translate(currItem->width(), 0); - pm2.scale(-1, 1); + if (currItem->imageFlippedH()) + { + pm2.translate(currItem->width(), 0); + pm2.scale(-1, 1); + } + if (currItem->imageFlippedV()) + { + pm2.translate(0, currItem->height()); + pm2.scale(1, -1); + } } - if (currItem->imageFlippedV()) + + for (int i = 0; i < Clip.size(); ++i) { - pm2.translate(0, currItem->height()); - pm2.scale(1, -1); - } - } - for (int i = 0; i < Clip.size(); ++i) - { - if (((m_doc->nodeEdit.edPoints()) && (i % 2 != 0)) || ((!m_doc->nodeEdit.edPoints()) && (i % 2 == 0))) - continue; - QPointF npf = pm2.map(Clip.pointQF(i)); - if (m_canvas->hitsCanvasPoint(m->globalPosition(), npf)) - { - m_doc->nodeEdit.setClre(i); - if ((m_doc->nodeEdit.edPoints()) && (m_doc->nodeEdit.selNode().contains(i) == 0)) + if (m_canvas->hitsCanvasPoint(m->globalPosition(), pm2.map(Clip.pointQF(i)))) { - if (m->modifiers() != Qt::ShiftModifier) + if (state.originalMoveMode == NodeEditContext::Auto) + { + bool sameAngle = m_doc->nodeEdit.sameAngle(currItem, i); + bool sameLength = m_doc->nodeEdit.sameLength(currItem, i); + + if (sameLength && sameAngle) + state.moveMode = NodeEditContext::Symmetric; + else if (!sameLength && sameAngle) + state.moveMode = NodeEditContext::Asymmetric; + else if (!sameLength && !sameAngle) + state.moveMode = NodeEditContext::Independent; + } + + state.clickedOn = NodeEditContext::isNode(i) ? NodeHandle : ControlHandle; + + if (state.clickedOn == NodeHandle) + { + // Deselect all controls + m_doc->nodeEdit.deselectNodeControls(); + + if (!m_doc->nodeEdit.selNode().contains(i) && m->modifiers() != Qt::ShiftModifier) + m_doc->nodeEdit.selNode().clear(); + + state.wasNewlySelected = !m_doc->nodeEdit.selNode().contains(i); + + m_doc->nodeEdit.selectNode(i); + + if (m->modifiers() == Qt::AltModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) + { + // Reset Controls + if (state.moveMode != NodeEditContext::Independent || !NodeEditContext::hasTwoNodes(i, Clip)) + m_doc->nodeEdit.resetControl(currItem); + else // Make Control Handles symetric + m_doc->nodeEdit.equalizeControls(currItem); + } + } + + if (state.clickedOn == ControlHandle) + { m_doc->nodeEdit.selNode().clear(); - m_doc->nodeEdit.selectNode(i); + m_doc->nodeEdit.selectNode(i); + } + + m_doc->nodeEdit.setClre(i); + + // Save position of opposite control handle + int indexNode = NodeEditContext::indexOfNode(i, NodeEditContext::OppositeSibling, Clip.size()); + int indexControl = NodeEditContext::indexOfNode(i, NodeEditContext::Opposite, Clip.size()); + + double dx = Clip.point(indexControl).x() - Clip.point(indexNode).x(); + double dy = Clip.point(indexControl).y() - Clip.point(indexNode).y(); + + m_doc->nodeEdit.oppositeControl.isValid = NodeEditContext::hasTwoNodes(i, Clip); + m_doc->nodeEdit.oppositeControl.relativePos = Clip.point(indexControl) - Clip.point(indexNode); + m_doc->nodeEdit.oppositeControl.controlIndex = indexControl; + m_doc->nodeEdit.oppositeControl.nodeIndex = indexNode; + m_doc->nodeEdit.oppositeControl.distance = distance(dx, dy); + m_doc->nodeEdit.oppositeControl.angle = xy2Deg(dx, dy); + m_doc->nodeEdit.update(Clip.pointQF(i)); + edited = true; + + break; } - m_doc->nodeEdit.update(Clip.pointQF(i)); - pfound = true; - edited = true; - break; + + //deselect all if no node or control has hit + if (i == Clip.size() - 1) + m_doc->nodeEdit.deselect(); + } + + break; } - if ((!pfound) || (!m_doc->nodeEdit.edPoints())) - m_doc->nodeEdit.selNode().clear(); + case PathSegment: + m_doc->nodeEdit.deselect(); + state.clickedOn = PathSegment; + break; + case None: + default: + m_doc->nodeEdit.deselect(); + state.clickedOn = None; + break; + } - if ((m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) && (m_doc->nodeEdit.clre() != -1) && !m_doc->nodeEdit.hasNodeSelected()) + if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && !m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == PathSegment) { m_doc->nodeEdit.setSegP1(m_doc->nodeEdit.clre2()); m_doc->nodeEdit.setSegP2(m_doc->nodeEdit.clre2()+2); m_doc->nodeEdit.setClre(m_doc->nodeEdit.clre2()); } + + FPointArray cli; int EndInd = Clip.size(); int StartInd = 0; @@ -568,10 +752,10 @@ } } } - if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) + + if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH && (state.clickedOn == NodeHandle || state.clickedOn == PathSegment)) { - if (!m_doc->nodeEdit.edPoints()) - return; + // Path Segment if ((!m_doc->nodeEdit.hasNodeSelected()) && (m_doc->nodeEdit.clre2() != -1)) // We don't have a Point, try to add one onto the current curve segment { bool foundP = false; @@ -582,7 +766,7 @@ FPoint tangent(0, 0); FPoint nearPoint(0, 0); double nearT = 0.0; - QRect mpo2(0, 0, m_doc->guidesPrefs().grabRadius*3, m_doc->guidesPrefs().grabRadius*3); + QRect mpo2(0, 0, m_doc->guidesPrefs().grabRadius * 3, m_doc->guidesPrefs().grabRadius * 3); mpo2.moveCenter(QPoint(qRound(npf2.x()), qRound(npf2.y()))); for (int poi = 0; poi < Clip.size() - 3; poi += 4) { @@ -594,7 +778,7 @@ Bez.moveTo(a1.x(), a1.y()); Bez.cubicTo(a2.x(), a2.y(), a3.x(), a3.y(), a4.x(), a4.y()); QPolygon cli2 = Bez.toFillPolygon().toPolygon(); - for (int clp = 0; clp < cli2.size()-1; ++clp) + for (int clp = 0; clp < cli2.size() - 1; ++clp) { if (m_view->PointOnLine(cli2.point(clp), cli2.point(clp + 1), mpo2)) { @@ -604,7 +788,7 @@ while (sp < 1.0) { Clip.pointTangentNormalAt(seg, sp, &point, &tangent, &normal ); - double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y() ,2))); + double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y(), 2))); if (d1 < absDist) { foundP = true; @@ -673,6 +857,7 @@ else m_doc->nodeEdit.deselect(); } + // Node Handle if (m_doc->nodeEdit.hasNodeSelected()) { if (currItem->isPolygon()) @@ -762,68 +947,12 @@ } } } - if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) && m_doc->nodeEdit.hasNodeSelected()) + + if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) && m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == NodeHandle) { - if (!m_doc->nodeEdit.edPoints()) - return; + edited = deleteNodes(); + } - int minClipSize = 4; - if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) - minClipSize = 12; - - if (Clip.size() <= minClipSize) - { - m_doc->nodeEdit.deselect(); - return; - } - - int numPoints = (StartInd != 0) ? (StartInd-4) : 0; - numPoints += (Clip.size() - EndInd); - if ((currItem->Segments.count() > 0) && ((EndInd - StartInd) <= minClipSize) && (numPoints > 0)) - { - if (StartInd != 0) - cli.putPoints(0, StartInd - 4, Clip); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - else if (m_doc->nodeEdit.clre() == StartInd) - { - if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) - { - const FPoint& kp(Clip.point(EndInd - 3)); - cli.putPoints(0, StartInd, Clip); - cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd); - cli.setPoint(StartInd, cli.point(cli.size() - 2)); - cli.setPoint(StartInd + 1, kp); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - else - { - cli.putPoints(0, StartInd, Clip); - cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd+4); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - } - else if (m_doc->nodeEdit.clre() == (EndInd - 2)) - { - Clip.remove(m_doc->nodeEdit.clre() - 2, 4); - cli = Clip.copy(); - } - else - { - if (m_doc->nodeEdit.clre() != 0) - cli.putPoints(0, m_doc->nodeEdit.clre(), Clip); - cli.putPoints(cli.size(), Clip.size() - (m_doc->nodeEdit.clre() + 4), Clip, m_doc->nodeEdit.clre() + 4); - } - while (cli.isMarker(0)) - cli.remove(0, 4); - if (m_doc->nodeEdit.isContourLine()) - currItem->ContourLine = cli.copy(); - else - currItem->PoLine = cli.copy(); - m_doc->nodeEdit.deselect(); - currItem->ClipEdited = true; - edited = true; - } if ((m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT) && (m_doc->nodeEdit.clre2() != -1)) { bool foundP = false; @@ -856,7 +985,7 @@ while (sp < 1.0) { Clip.pointTangentNormalAt(seg, sp, &point, &tangent, &normal ); - double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y() ,2))); + double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y(), 2))); if (d1 < absDist) { foundP = true; @@ -911,30 +1040,11 @@ currItem->ClipEdited = true; edited = true; } - if (edited) + + refreshPath(edited); + + if ((m_doc->nodeEdit.selectionCount() != 0) || ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) || (m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == ControlHandle)) { - currItem->FrameType = 3; - double xp = currItem->xPos(); - double yp = currItem->yPos(); - double w = currItem->width(); - double h = currItem->height(); - m_doc->adjustItemSize(currItem, true); - double xp2 = currItem->xPos(); - double yp2 = currItem->yPos(); - double w2 = currItem->width(); - double h2 = currItem->height(); - currItem->update(); - if ((xp != xp2) || (yp != yp2) || (w != w2) || (h != h2)) - m_view->DrawNew(); - emit m_view->PolyStatus(currItem->itemType(), currItem->PoLine.size()); - } - else - { - currItem->update(); - m_view->DrawNew(); - } - if ((m_doc->nodeEdit.selectionCount() != 0) || ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) || (m_doc->nodeEdit.hasNodeSelected() && (!m_doc->nodeEdit.edPoints()))) - { m_Mxp = m->position().x(); m_Myp = m->position().y(); m_canvas->setRenderModeFillBuffer(); @@ -950,7 +1060,7 @@ m_rectangleSelect->prepare(m->globalPosition()); m_view->startGesture(m_rectangleSelect); } - + } @@ -965,6 +1075,7 @@ itemPos.translate(currItem->width(), 0); itemPos.scale(-1, 1); } + if (currItem->imageFlippedV()) { itemPos.translate(0, currItem->height()); @@ -971,38 +1082,49 @@ itemPos.scale(1, -1); } } - FPointArray Clip; + FPointArray Clip = m_doc->nodeEdit.clipFromItem(currItem); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); - if (m_doc->nodeEdit.isContourLine()) - Clip = currItem->ContourLine; - else - Clip = currItem->PoLine; + state.hoveredOver = None; + + // Hover mouse on node or control handle if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) || - (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH)) + (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) || + (m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT)) { for (int i = 0; i < Clip.size(); ++i) { - if (((m_doc->nodeEdit.edPoints()) && (i % 2 != 0)) || ((!m_doc->nodeEdit.edPoints()) && (i % 2 == 0))) - continue; QPointF point = Clip.pointQF(i); if (m_canvas->hitsCanvasPoint(m->globalPosition(), itemPos.map(point))) { if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) - m_view->setCursor(QCursor(Qt::SizeAllCursor)); - if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) - m_view->setCursor(IconManager::instance().loadCursor("DelPoint.png", 1, 1)); - if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) - m_view->setCursor(IconManager::instance().loadCursor("Split.png", 1, 1)); + m_view->setCursor(IconManager::instance().loadCursor("cursor-move", 1, 1)); + else if (m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) + m_view->setCursor(IconManager::instance().loadCursor("cursor-node-edit", 1, 1)); + + if (NodeEditContext::isNode(i)) + { + if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) + m_view->setCursor(IconManager::instance().loadCursor("DelPoint.png", 1, 1)); + else if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) + m_view->setCursor(IconManager::instance().loadCursor("Split.png", 1, 1)); + + state.hoveredOver = NodeHandle; + } + else + state.hoveredOver = ControlHandle; + return true; } } } + + // Hover mouse on path segment if ((m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT) || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) || - (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH && m_doc->nodeEdit.edPoints())) + (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH)) { for (int poi = 0; poi < Clip.size() - 3; poi += 4) { @@ -1015,6 +1137,7 @@ Bez.moveTo(a1); Bez.cubicTo(a2, a3, a4); double len = Bez.length(); + if (len > 0.0) { double delta = 2.0 / len; @@ -1031,7 +1154,9 @@ m_view->setCursor(IconManager::instance().loadCursor("Split.png", 1, 1)); else m_view->setCursor(QCursor(Qt::ArrowCursor)); + m_doc->nodeEdit.setClre2(poi); + state.hoveredOver = PathSegment; return true; } } @@ -1044,6 +1169,7 @@ void CanvasMode_NodeEdit::handleNodeEditDrag(QMouseEvent* m, PageItem* currItem) { + // Selection Frame if ((m_canvas->m_viewMode.m_MouseButtonPressed) && !m_doc->nodeEdit.hasNodeSelected() && (m_doc->nodeEdit.segP1() == -1) && (m_doc->nodeEdit.segP2() == -1)) { if (!m_rectangleSelect) @@ -1052,6 +1178,7 @@ m_view->startGesture(m_rectangleSelect); return; } + FPoint npf; double newX = m->position().x(); double newY = m->position().y(); @@ -1059,6 +1186,8 @@ np = np * (1.0 / m_canvas->scale()); m_canvas->m_viewMode.operItemMoving = true; currItem = m_doc->m_Selection->itemAt(0); + + // Move Path Segment if ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) { if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) @@ -1068,37 +1197,38 @@ if (currItem->imageFlippedV()) np.setY(np.y() * -1); } - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.segP2()) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.segP2()) + np; + + // First segment + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.segP2()) + np; m_doc->nodeEdit.setClre(m_doc->nodeEdit.segP2()); m_doc->nodeEdit.moveClipPoint(currItem, npf); - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.segP1() ) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.segP1() ) + np; + + // Second segment + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.segP1()) + np; m_doc->nodeEdit.setClre(m_doc->nodeEdit.segP1()); m_doc->nodeEdit.moveClipPoint(currItem, npf); m_Mxp = newX; m_Myp = newY; } + // Move Nodes or Controls else { - if ((m_doc->nodeEdit.selectionCount() != 0) && (m_doc->nodeEdit.edPoints())) + if ((m_doc->nodeEdit.selectionCount() > 0) && (state.clickedOn == NodeHandle || state.clickedOn == ControlHandle)) { - int storedClRe = m_doc->nodeEdit.clre(); + // Single Nodes if (m_doc->nodeEdit.selectionCount() == 1) { npf = m_canvas->globalToCanvas(m->globalPosition()); double nx = npf.x(); double ny = npf.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) + if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny, true)) npf = m_doc->ApplyGridF(FPoint(nx, ny)); else npf = FPoint(nx, ny); + QTransform pp = currItem->getTransform(); npf = npf.transformPoint(pp, true); + if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { if (currItem->imageFlippedH()) @@ -1116,55 +1246,81 @@ npf = npf.transformPoint(p, false); } } - FPoint npf1(npf); - //Control Modifier to have parallel shape - if (m->modifiers() == Qt::ControlModifier) + + int curr = m_doc->nodeEdit.clre(); + FPoint pOpposite = m_doc->nodeEdit.getPoint(currItem, m_doc->nodeEdit.oppositeControl.nodeIndex); + FPoint pSibling = m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Sibling); + + if (m->modifiers() & Qt::ShiftModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) { - FPointArray cli; - if (m_doc->nodeEdit.isContourLine()) - cli = currItem->ContourLine; - else - cli = currItem->PoLine; - int tmpNode; - int curr = (m_doc->nodeEdit.clre() == 0) ? cli.size() - 2 : m_doc->nodeEdit.clre(); - int prev = (curr + cli.size() - 4) % cli.size(); - int next = (curr + 4) % cli.size(); + // Shift + drag on control + if (!NodeEditContext::isNode(curr) && state.clickedOn == ControlHandle) + { + m_doc->nodeEdit.setMoveMode(NodeEditContext::Independent); - if (std::abs(cli.point(prev).x() - cli.point(curr).x()) < std::abs(cli.point(next).x() - cli.point(curr).x())) - tmpNode = next; - else if (std::abs(cli.point(prev).x() - cli.point(curr).x()) == std::abs(cli.point(next).x() - cli.point(curr).x())) + if (m_doc->nodeEdit.oppositeControl.isValid) + { + m_doc->nodeEdit.setClre(m_doc->nodeEdit.oppositeControl.controlIndex); + m_doc->nodeEdit.moveClipPoint(currItem, pOpposite + m_doc->nodeEdit.oppositeControl.relativePos); + m_doc->nodeEdit.setClre(curr); + } + } + + // Shift + drag on node + if (NodeEditContext::isNode(curr) && state.clickedOn == NodeHandle) { - if (cli.point(prev).y() != cli.point(curr).y()) - tmpNode = next; - else - tmpNode = prev; + m_doc->nodeEdit.setMoveMode(NodeEditContext::Symmetric); + m_doc->nodeEdit.setClre(curr + 1); } - else - tmpNode = prev; + } + else + m_doc->nodeEdit.setMoveMode(state.moveMode); - m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_doc->nodeEdit.setClre(tmpNode); + if (m->modifiers() & Qt::ControlModifier) + { + // Snap control handle at 15° steps + if (!NodeEditContext::isNode(curr)) + npf = constraintControlAngle(m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Sibling), npf, 15.0); - if (m_doc->nodeEdit.isContourLine()) - npf1.setX(currItem->ContourLine.point(m_doc->nodeEdit.clre()).x()); - else - npf1.setX(currItem->PoLine.point(m_doc->nodeEdit.clre()).x()); + } - m_doc->nodeEdit.moveClipPoint(currItem, npf1); + // Snap control handle + if (!NodeEditContext::isNode(curr) && state.clickedOn == ControlHandle) + { + state.isSameAngle = m_doc->nodeEdit.sameAngle(currItem, curr); + state.isSameLength = m_doc->nodeEdit.sameLength(currItem, curr); - if (m_doc->nodeEdit.clre() == prev) - m_doc->nodeEdit.setClre(next); - else - m_doc->nodeEdit.setClre(prev); + bool modifier = m->modifiers() & Qt::ShiftModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT; - if (m_doc->nodeEdit.isContourLine()) - npf.setY(currItem->ContourLine.point(m_doc->nodeEdit.clre()).y()); - else - npf.setY(currItem->PoLine.point(m_doc->nodeEdit.clre()).y()); + // let's snap the control angle to opposite reference + if (state.isSameAngle && (m_doc->nodeEdit.moveMode() == NodeEditContext::Auto || m_doc->nodeEdit.moveMode() == NodeEditContext::Independent || modifier)) + npf = snapControlAngle(pOpposite, npf, m_doc->nodeEdit.oppositeControl.angle - 180.0); + + // let's snap the control length to opposite reference + if (state.isSameLength && state.isSameAngle && (m_doc->nodeEdit.moveMode() != NodeEditContext::Symmetric || modifier)) + npf = snapControlLength(pOpposite, npf, m_doc->nodeEdit.oppositeControl.distance); + + // let's snap the control to same position as control anchor if control is too close + npf = snapControlOrigin(pSibling, npf); } + + // Snap node handle + // if (NodeEditContext::isNode(curr) && state.clickedOn == NodeHandle) + // { + // FPointArray Clip = m_doc->nodeEdit.isContourLine() ? currItem->ContourLine : currItem->PoLine; + // FPoint pOppositeNode = m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Opposite); + + // if (curr == 0 || curr == Clip.size() -2) + // { + // // TODO: close bezier curve and merge endpoints + // npf = snapControlOrigin(pOppositeNode, npf); + // } + // } + m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf1.y()); + m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf.y()); } + // Multiple Nodes else { if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) @@ -1174,53 +1330,147 @@ if (currItem->imageFlippedV()) np.setY(np.y() * -1); } + const auto& nodeSelection = m_doc->nodeEdit.selNode(); for (int itm = 0; itm < nodeSelection.count(); ++itm) { m_doc->nodeEdit.setClre(nodeSelection.at(itm)); - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.clre()) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.clre()) + np; + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.clre()) + np; m_doc->nodeEdit.moveClipPoint(currItem, npf); } } - m_doc->nodeEdit.setClre(storedClRe); } - else + + m_Mxp = newX; + m_Myp = newY; + + } + m_canvas->m_viewMode.operItemMoving = false; + m_doc->regionsChanged()->update(QRectF()); +} + +bool CanvasMode_NodeEdit::deleteNodes() +{ + PageItem* currItem = m_doc->m_Selection->itemAt(0); + FPointArray Clip = m_doc->nodeEdit.beginTransaction(currItem); + + if (!currItem || Clip.size() == 0) + return false; + + FPointArray cli; + int EndInd = Clip.size(); + int StartInd = 0; + if (m_doc->nodeEdit.clre() > 0) + { + for (int n = m_doc->nodeEdit.clre(); n < Clip.size(); ++n) { - npf = m_canvas->globalToCanvas(m->globalPosition()); - double nx = npf.x(); - double ny = npf.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) - npf = m_doc->ApplyGridF(FPoint(nx, ny)); + if (Clip.isMarker(n)) + { + EndInd = n; + break; + } + } + for (int n2 = m_doc->nodeEdit.clre(); n2 > 0; n2--) + { + if (Clip.isMarker(n2)) + { + StartInd = n2 + 1; + break; + } + } + } + + if (m_doc->nodeEdit.hasNodeSelected()) + { + int minClipSize = 4; + if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) + minClipSize = 12; + + if (Clip.size() <= minClipSize) + { + m_doc->nodeEdit.deselect(); + return false; + } + + int numPoints = (StartInd != 0) ? (StartInd-4) : 0; + numPoints += (Clip.size() - EndInd); + if ((currItem->Segments.count() > 0) && ((EndInd - StartInd) <= minClipSize) && (numPoints > 0)) + { + if (StartInd != 0) + cli.putPoints(0, StartInd - 4, Clip); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); + } + else if (m_doc->nodeEdit.clre() == StartInd) + { + if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) + { + const FPoint& kp(Clip.point(EndInd - 3)); + cli.putPoints(0, StartInd, Clip); + cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd); + cli.setPoint(StartInd, cli.point(cli.size() - 2)); + cli.setPoint(StartInd + 1, kp); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); + } else - npf = FPoint(nx, ny); - QTransform pp = currItem->getTransform(); - npf = npf.transformPoint(pp, true); - if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { - if (currItem->imageFlippedH()) - { - QTransform p; - p.translate(currItem->width(), 0); - p.scale(-1, 1); - npf = npf.transformPoint(p, false); - } - if (currItem->imageFlippedV()) - { - QTransform p; - p.translate(0, currItem->height()); - p.scale(1, -1); - npf = npf.transformPoint(p, false); - } + cli.putPoints(0, StartInd, Clip); + cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd+4); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); } - m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf.y()); } - m_Mxp = newX; - m_Myp = newY; + else if (m_doc->nodeEdit.clre() == (EndInd - 2)) + { + Clip.remove(m_doc->nodeEdit.clre() - 2, 4); + cli = Clip.copy(); + } + else + { + if (m_doc->nodeEdit.clre() != 0) + cli.putPoints(0, m_doc->nodeEdit.clre(), Clip); + cli.putPoints(cli.size(), Clip.size() - (m_doc->nodeEdit.clre() + 4), Clip, m_doc->nodeEdit.clre() + 4); + } + while (cli.isMarker(0)) + cli.remove(0, 4); + if (m_doc->nodeEdit.isContourLine()) + currItem->ContourLine = cli.copy(); + else + currItem->PoLine = cli.copy(); + m_doc->nodeEdit.deselect(); + currItem->ClipEdited = true; + return true; } - m_canvas->m_viewMode.operItemMoving = false; - m_doc->regionsChanged()->update(QRectF()); + + + return false; } + +void CanvasMode_NodeEdit::refreshPath(bool edited) +{ + PageItem* currItem = m_doc->m_Selection->itemAt(0); + + if (!currItem) + return; + + if (edited) + { + currItem->FrameType = 3; + double xp = currItem->xPos(); + double yp = currItem->yPos(); + double w = currItem->width(); + double h = currItem->height(); + m_doc->adjustItemSize(currItem, true); + double xp2 = currItem->xPos(); + double yp2 = currItem->yPos(); + double w2 = currItem->width(); + double h2 = currItem->height(); + currItem->update(); + if ((xp != xp2) || (yp != yp2) || (w != w2) || (h != h2)) + m_view->DrawNew(); + emit m_view->PolyStatus(currItem->itemType(), currItem->PoLine.size()); + } + else + { + currItem->update(); + m_view->DrawNew(); + } +} Index: scribus/canvasmode_nodeedit.h =================================================================== --- scribus/canvasmode_nodeedit.h (Revision 26678) +++ scribus/canvasmode_nodeedit.h (Arbeitskopie) @@ -19,6 +19,7 @@ #define CANVAS_MODE_NODEEDIT_H #include "canvasmode.h" +#include "nodeeditcontext.h" #include <QTransform> #include <QRect> @@ -38,8 +39,8 @@ void activate(bool fromgesture) override; void deactivate(bool forGesture) override; - void enterEvent(QEvent *) override; - void leaveEvent(QEvent *) override; + void enterEvent(QEvent *) override {}; + void leaveEvent(QEvent *) override {}; void mouseDoubleClickEvent(QMouseEvent *m) override; void mouseReleaseEvent(QMouseEvent *m) override; @@ -55,13 +56,45 @@ void drawControls(QPainter* p) override; private: + + enum MouseMode { None = 0, NodeHandle, ControlHandle, PathSegment }; + + struct StateManager { + MouseMode clickedOn { None }; + MouseMode hoveredOver { None }; + bool isSameLength {false}; + bool isSameAngle {false}; + bool wasNewlySelected { false }; + NodeEditContext::ControlMoveMode originalMoveMode { NodeEditContext::ControlMoveMode::Auto }; + NodeEditContext::ControlMoveMode moveMode { originalMoveMode }; // we need this for converted auto mode + + void reset() + { + clickedOn = None; + isSameLength = false; + isSameAngle = false; + wasNewlySelected = false; + moveMode = NodeEditContext::ControlMoveMode::Auto; + } + }; + inline bool GetItem(PageItem** pi); + QMap<int, int> selectedNodesList(FPointArray points); void handleNodeEditPress(QMouseEvent*, QRect r); void handleNodeEditDrag(QMouseEvent*, PageItem*); bool handleNodeEditMove(QMouseEvent*, QRect r, PageItem*, const QTransform&); + FPoint constraintControlAngle(FPoint anchor, FPoint mouse, double angleStep); + FPoint snapControlAngle(FPoint anchor, FPoint mouse, double angle); + FPoint snapControlLength(FPoint anchor, FPoint mouse, double length); + FPoint snapControlOrigin(FPoint anchor, FPoint mouse); + bool deleteNodes(); + void updateItem(PageItem* currItem); + void refreshPath(bool edited); + ScribusMainWindow* m_ScMW { nullptr }; RectSelect* m_rectangleSelect { nullptr }; + StateManager state; double m_Mxp { -1.0 }; // last mouse position double m_Myp { -1.0 }; @@ -71,6 +104,7 @@ double m_GyM { -1.0 }; bool m_MoveGX { false }; bool m_MoveGY { false }; + }; Index: scribus/nodeeditcontext.cpp =================================================================== --- scribus/nodeeditcontext.cpp (Revision 26678) +++ scribus/nodeeditcontext.cpp (Arbeitskopie) @@ -23,9 +23,34 @@ void NodeEditContext::selectNode(int i) { m_ClRe = i; - m_SelNode.append(i); + if (!m_SelNode.contains(i)) + m_SelNode.append(i); } +void NodeEditContext::deselectNode(int i) +{ + int index = m_SelNode.indexOf(i); + m_SelNode.removeAll(i); + if (m_SelNode.count() == 0) + m_ClRe = -1; + else // select previous node or last one if there is no previous one + m_ClRe = (index - 1 >= 0) ? m_SelNode.at(index - 1) : m_SelNode.last(); +} + +void NodeEditContext::deselectNodeControls() +{ + for (int i = 0; i < m_SelNode.count(); i++) + { + if (!isNode(m_SelNode.at(i))) + { + m_SelNode.removeAt(i); + i --; + } + } + if (m_SelNode.count() == 0) + m_ClRe = -1; +} + int NodeEditContext::selectionCount() const { return m_SelNode.count(); @@ -42,7 +67,7 @@ delete oldClip; oldClip = nullptr; nodeTransaction.reset(); - m_MoveSym = false; + m_moveMode = Auto; m_SelNode.clear(); m_preview = false; } @@ -83,8 +108,6 @@ return Clip; } - - void NodeEditContext::finishTransaction(PageItem* currItem) { ScribusDoc* Doc = currItem->doc(); @@ -116,7 +139,6 @@ oldClip = nullptr; } - /** first part: create a new UndoState or cancel the current transaction */ @@ -167,25 +189,28 @@ delete oldClip; oldClip = nullptr; } - //CB-->Doc void NodeEditContext::moveClipPoint(PageItem *currItem, const FPoint& ip) { ScribusDoc* Doc = currItem->doc(); - if (((m_EdPoints) && (m_ClRe % 2 != 0)) || ((!m_EdPoints) && (m_ClRe % 2 == 0))) - return; + double xposOrig = currItem->xPos(); double yposOrig = currItem->yPos(); currItem->ClipEdited = true; + FPointArray Clip; + FPoint np(ip); + if (m_isContourLine) Clip = currItem->ContourLine.copy(); else Clip = currItem->PoLine.copy(); currItem->FrameType = 3; + uint EndInd = Clip.size(); uint StartInd = 0; + for (int n = m_ClRe; n < Clip.size(); ++n) { if (Clip.isMarker(n)) @@ -194,6 +219,7 @@ break; } } + if (m_ClRe > 0) { for (int n2 = m_ClRe; n2 > 0; n2--) @@ -205,9 +231,10 @@ } } } - FPoint np(ip); + if (hasNodeSelected()) { + // expand item on left side if x is out of bound if ((np.x() < 0) && (!m_isContourLine) && (!(currItem->isGroup() || currItem->isSymbol()))) { Doc->sizeItem(currItem->width() - np.x(), currItem->height(), currItem, false, false, false); @@ -223,6 +250,8 @@ currItem->moveImageInFrame(-np.x()/currItem->imageXScale(), 0); np.setX(0); } + + // expand item on top side if y is out of bound if ((np.y() < 0) && (!m_isContourLine) && (!(currItem->isGroup() || currItem->isSymbol()))) { Doc->sizeItem(currItem->width(), currItem->height() - np.y(), currItem, false, false, false); @@ -239,7 +268,9 @@ np.setY(0); } update(QPointF(np.x(), np.y())); - if ((m_ClRe + 1 < static_cast<int>(EndInd)) && (m_ClRe % 2 == 0)) + + // update control of node + if (isNode(m_ClRe) && (m_ClRe + 1 < static_cast<int>(EndInd))) { FPoint ap(Clip.point(m_ClRe)); FPoint ap2(Clip.point(m_ClRe + 1)); @@ -247,8 +278,11 @@ ap2.setY(ap2.y() - (ap.y() - np.y())); Clip.setPoint(m_ClRe + 1, ap2); } + Clip.setPoint(m_ClRe, np); - if (((m_ClRe % 4 != 0) && (m_ClRe % 2 == 0)) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) + + // update right node with control + if (isLeftNode(m_ClRe) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) { FPoint ap(Clip.point(m_ClRe + 2)); FPoint ap2(Clip.point(m_ClRe + 3)); @@ -257,7 +291,9 @@ Clip.setPoint(m_ClRe + 3, ap2); Clip.setPoint(m_ClRe + 2, np); } - if ((m_ClRe % 4 == 0) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) + + // update left node with control + if (isRightNode(m_ClRe) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) { FPoint ap(Clip.point(m_ClRe - 2)); FPoint ap2(Clip.point(m_ClRe - 1)); @@ -266,6 +302,8 @@ Clip.setPoint(m_ClRe - 1, ap2); Clip.setPoint(m_ClRe - 2, np); } + + // update first or last node with control if (((m_ClRe == static_cast<int>(StartInd)) || (m_ClRe == static_cast<int>(EndInd - 2))) && ((currItem->itemType() == PageItem::Polygon) || (currItem->itemType() == PageItem::Group) @@ -275,7 +313,7 @@ { if (m_ClRe == static_cast<int>(StartInd)) { - FPoint ap(Clip.point(EndInd- 2)); + FPoint ap(Clip.point(EndInd - 2)); FPoint ap2(Clip.point(EndInd - 1)); ap2.setX(ap2.x() - (ap.x() - np.x())); ap2.setY(ap2.y() - (ap.y() - np.y())); @@ -292,48 +330,53 @@ Clip.setPoint(StartInd + 1, ap2); } } - if (((m_ClRe == static_cast<int>(StartInd + 1)) || (m_ClRe == static_cast<int>(EndInd - 1))) && - ((currItem->itemType() == PageItem::Polygon) + + bool firstLastControl = ((m_ClRe == static_cast<int>(StartInd + 1)) || (m_ClRe == static_cast<int>(EndInd - 1))) + && ((currItem->itemType() == PageItem::Polygon) || (currItem->itemType() == PageItem::Group) || (currItem->itemType() == PageItem::Symbol) || (currItem->itemType() == PageItem::TextFrame) - || (currItem->itemType() == PageItem::ImageFrame)) && - (m_MoveSym)) + || (currItem->itemType() == PageItem::ImageFrame)); + + bool anyControl = !isNode(m_ClRe) && (m_ClRe != static_cast<int>(StartInd + 1)) && (m_ClRe != static_cast<int>(EndInd - 1)); + + // update control if there is an opposite control + if (firstLastControl || anyControl) { - uint kon = 0; - if (m_ClRe == static_cast<int>(StartInd + 1)) - kon = EndInd - 1; - else - kon = StartInd + 1; - FPoint lxy(Clip.point(m_ClRe - 1)); - FPoint lk(Clip.point(m_ClRe)); - double dx = lxy.x() - lk.x(); - double dy = lxy.y() - lk.y(); - lk.setX(lk.x() + dx * 2); - lk.setY(lk.y() + dy * 2); - Clip.setPoint(kon, lk); + int kon = indexOfNode(m_ClRe, Opposite, Clip.size()); + + FPoint pn1(Clip.point(m_ClRe - 1)); + FPoint pc1(Clip.point(m_ClRe)); + FPoint pc2(pn1 + oppositeControl.relativePos); + + double dx = pn1.x() - pc1.x(); + double dy = pn1.y() - pc1.y(); + + if (moveMode() == Symmetric) + { + pc2.setX(pc1.x() + dx * 2); + pc2.setY(pc1.y() + dy * 2); + Clip.setPoint(kon, pc2); + } + else if (moveMode() == Asymmetric) + { + double dx2 = pn1.x() - pc2.x(); + double dy2 = pn1.y() - pc2.y(); + + double angle = xy2Deg(dx, dy); + double dist = distance(dx2, dy2); + Clip.setPoint(kon, getPointFromRotation(pn1, dist, angle)); + } } - if ((m_ClRe % 2 != 0) && (m_MoveSym) && (m_ClRe != static_cast<int>(StartInd + 1)) && (m_ClRe != static_cast<int>(EndInd - 1))) - { - uint kon = 0; - if ((m_ClRe + 1) % 4 == 0) - kon = m_ClRe + 2; - else - kon = m_ClRe - 2; - FPoint lxy(Clip.point(m_ClRe - 1)); - FPoint lk(Clip.point(m_ClRe)); - double dx = lxy.x() - lk.x(); - double dy = lxy.y() - lk.y(); - lk.setX(lk.x() + dx * 2); - lk.setY(lk.y() + dy * 2); - Clip.setPoint(kon, lk); - } + if (Doc->nodeEdit.m_isContourLine) currItem->ContourLine = Clip.copy(); else currItem->PoLine = Clip.copy(); + currItem->Clip = flattenPath(currItem->PoLine, currItem->Segments); } + if (!m_isContourLine) { // Move the contour accordingly in the item's coordinate space @@ -343,12 +386,265 @@ } } +void NodeEditContext::equalizeControls(PageItem* currItem) +{ + FPointArray Clip(clipFromItem(currItem)); + int old = m_ClRe; + ControlMoveMode cmm = moveMode(); + FPoint cl, nl, nr, cr; + getPoints(currItem, m_ClRe, cl, nl, nr, cr); + + // if there is no left control we use the right one as reference + int indexControl = indexOfNode(m_ClRe, (nl == cl) ? ControlRight : ControlLeft, Clip.size()); + + // apply changes to opposite control + setMoveMode(Symmetric); + setClre(indexControl); + moveClipPoint(currItem, Clip.point(indexControl)); + + // restore old values + setClre(old); + setMoveMode(cmm); +} + +/** + * @brief NodeEditContext::getPoints Map all handle indecies based on i. A bezier curve start with right node and ends with left node + * @param currItem Current PageItem + * @param i index of any point in page item path + * @param ctrlLeft return index of left control handle + * @param nodeLeft return index of left node + * @param nodeRight return index of right node + * @param ctrlRight return index of right control handle + */ +void NodeEditContext::getPoints(PageItem* currItem, int i, FPoint& ctrlLeft, FPoint& nodeLeft, FPoint& nodeRight, FPoint& ctrlRight) +{ + i = (i == -1) ? m_ClRe : i; + + if (i < 0) + return; + + nodeLeft = getPoint(currItem, i, NodeType::NodeLeft); + ctrlLeft = getPoint(currItem, i, NodeType::ControlLeft); + nodeRight = getPoint(currItem, i, NodeType::NodeRight); + ctrlRight = getPoint(currItem, i, NodeType::ControlRight); +} + +FPoint NodeEditContext::getPoint(PageItem* currItem, int i, NodeType nodeType) +{ + FPointArray Clip(clipFromItem(currItem)); + return Clip.point(indexOfNode(i, nodeType, Clip.size())); +} + +bool NodeEditContext::isSymetric(PageItem* currItem, int i) +{ + return sameAngle(currItem, i) && sameLength(currItem, i); +} + +bool NodeEditContext::sameAngle(PageItem* currItem, int i) +{ + double snap = currItem->doc()->prefsData().guidesPrefs.guideRad / m_scale; + + FPoint pcl, pnl, pnr, pcr; + getPoints(currItem, i, pcl, pnl, pnr, pcr); + + if (pnr != pnl) + return false; + + QLineF lineLeft(pnl.toQPointF(), pcl.toQPointF()); + QLineF lineRight(pnr.toQPointF(), pcr.toQPointF()); + lineRight.setAngle(lineRight.angle() - 180); + + if (qMax(lineLeft.angle(), lineRight.angle()) - qMin(lineLeft.angle(), lineRight.angle()) <= 0.00001) + return true; + + if (lineLeft.length() >= lineRight.length()) + lineRight.setLength(lineLeft.length()); + else + lineLeft.setLength(lineRight.length()); + + QLineF distance(lineLeft.p2(), lineRight.p2()); + + return distance.length() <= snap ? true : false; +} + +bool NodeEditContext::sameLength(PageItem* currItem, int i) +{ + double snap = currItem->doc()->prefsData().guidesPrefs.guideRad / m_scale; + FPoint pcl, pnl, pnr, pcr; + getPoints(currItem, i, pcl, pnl, pnr, pcr); + + if (pnl != pnr) + return false; + + // length = 0 + if (pcl == pcr) + return true; + + double l1 = QLineF(pnl.toQPointF(), pcl.toQPointF()).length(); + double l2 = QLineF(pnr.toQPointF(), pcr.toQPointF()).length(); + + // Check if both controls have same length with difference + return (qMax(l1, l2) - qMin(l1, l2) <= snap) ? true : false; +} + +bool NodeEditContext::isLeftControl(int i) +{ + return (i + 1) % 4 == 0; +} + +bool NodeEditContext::isLeftNode(int i) +{ + return (i % 4 != 0) && isNode(i); +} + +bool NodeEditContext::isRightNode(int i) +{ + return i % 4 == 0; +} + +bool NodeEditContext::isRightControl(int i) +{ + return (i - 1) % 4 == 0; +} + +bool NodeEditContext::hasTwoNodes(int i, FPointArray clip) +{ + int ln = indexOfNode(i, NodeLeft, clip.size()); + int rn = indexOfNode(i, NodeRight, clip.size()); + + return clip.point(ln) == clip.point(rn); +} + +bool NodeEditContext::isSharpNode(int i, FPointArray clip) +{ + int lc = indexOfNode(i, ControlLeft, clip.size()); + int rc = indexOfNode(i, ControlRight, clip.size()); + int ln = indexOfNode(i, NodeLeft, clip.size()); + int rn = indexOfNode(i, NodeRight, clip.size()); + + return clip.point(ln) == clip.point(rn) && clip.point(ln) == clip.point(lc) && clip.point(rn) == clip.point(rc); +} + +/** + * @brief NodeEditContext::indexOfNode Returns the node index based on the input index and node type. + * If size is specified, an index between 0 and size is returned. Values outside this range are corrected. + * @param i Any index of FPointArray + * @param type Node type for output + * @param size Max size of node array + * @return return index of requested node type based on input index. + */ +int NodeEditContext::indexOfNode(int i, NodeType type, int size) +{ + if (size != -1) + i = qBound(0, i, size); + + if (isRightControl(i)) + { + switch (type) + { + case NodeType::OppositeSibling: + case NodeType::NodeLeft: + i -= 3; + break; + case NodeType::Opposite: + case NodeType::ControlLeft: + i -= 2; + break; + case NodeType::Sibling: + case NodeType::NodeRight: + i -= 1; + break; + case NodeType::Self: + case NodeType::ControlRight: + break; + } + + if (size != -1 && i < 0) + i = size - abs(i); + } + else if (isLeftControl(i)) + { + switch (type) + { + case NodeType::Sibling: + case NodeType::NodeLeft: + i -= 1; + break; + case NodeType::Self: + case NodeType::ControlLeft: + break; + case NodeType::OppositeSibling: + case NodeType::NodeRight: + i += 1; + break; + case NodeType::Opposite: + case NodeType::ControlRight: + i += 2; + break; + } + + if (size != -1 && i >= size) + i -= size; + } + else if (isRightNode(i)) + { + switch (type) + { + case NodeType::Opposite: + case NodeType::NodeLeft: + i -= 2; + break; + case NodeType::OppositeSibling: + case NodeType::ControlLeft: + i -= 1; + break; + case NodeType::Self: + case NodeType::NodeRight: + break; + case NodeType::Sibling: + case NodeType::ControlRight: + i += 1; + break; + } + + if (size != -1 && i < 0) + i = size - abs(i); + } + else if (isLeftNode(i)) + { + switch (type) + { + case NodeType::Self: + case NodeType::NodeLeft: + break; + case NodeType::Sibling: + case NodeType::ControlLeft: + i += 1; + break; + case NodeType::Opposite: + case NodeType::NodeRight: + i += 2; + break; + case NodeType::OppositeSibling: + case NodeType::ControlRight: + i += 3; + break; + } + + if (size != -1 && i >= size) + i -= size; + } + + return i; +} + void NodeEditContext::reset1Control(PageItem* currItem) -{ +{ + if ((m_ClRe < 0) || isNode(m_ClRe)) + return; + ScribusDoc* Doc = currItem->doc(); - if ((m_ClRe < 0) || (m_ClRe % 2 == 0)) - return; UndoManager* undoManager = UndoManager::instance(); // do no record anything else but the core reset points action @@ -401,13 +697,13 @@ void NodeEditContext::resetControl(PageItem* currItem) { ScribusDoc* Doc = currItem->doc(); - if (m_ClRe < 0) + if (m_ClRe < 0 || !isNode(m_ClRe)) return; UndoManager* undoManager = UndoManager::instance(); // do no record anything else but the core reset points action undoManager->setUndoEnabled(false); - + oldClip = new FPointArray(Doc->nodeEdit.m_isContourLine ? currItem->ContourLine.copy() : currItem->PoLine.copy()); m_oldItemX = currItem->xPos(); m_oldItemY = currItem->yPos(); @@ -458,8 +754,8 @@ } else { - Clip.setPoint(Doc->nodeEdit.m_ClRe + 1, np); - Clip.setPoint((Doc->nodeEdit.m_ClRe % 4 != 0 ? Doc->nodeEdit.m_ClRe + 3 : Doc->nodeEdit.m_ClRe - 1), np); + Clip.setPoint(indexOfNode(Doc->nodeEdit.m_ClRe, NodeType::ControlRight, Clip.size()), np); + Clip.setPoint(indexOfNode(Doc->nodeEdit.m_ClRe, NodeType::ControlLeft, Clip.size()), np); } if (!Doc->nodeEdit.m_isContourLine) { @@ -493,3 +789,7 @@ oldClip = nullptr; } +FPointArray NodeEditContext::clipFromItem(PageItem* currItem) +{ + return isContourLine() ? currItem->ContourLine : currItem->PoLine; +} Index: scribus/nodeeditcontext.h =================================================================== --- scribus/nodeeditcontext.h (Revision 26678) +++ scribus/nodeeditcontext.h (Arbeitskopie) @@ -39,6 +39,46 @@ class SCRIBUS_API NodeEditContext : public MassObservable<QPointF> { public: + enum SubMode + { + MOVE_POINT = 0, + ADD_POINT, + DEL_POINT, + SPLIT_PATH, + EDIT_POINT + }; + + enum NodeType + { + ControlLeft = 0, + NodeLeft, + NodeRight, + ControlRight, + Opposite, // opposite control/node index of input + Sibling, // sibling of node is control, sibling of control is node + OppositeSibling, // combination of opposite and sibling + Self + }; + + enum ControlMoveMode + { + Auto = 0, + Symmetric, + Asymmetric, + Independent + }; + + struct OppositeControl { + FPoint relativePos; + bool isValid {false}; + int controlIndex {-1}; + int nodeIndex {-1}; + double distance {0}; + double angle {0}; + bool sameAngle {false}; + bool sameLength {false}; + }; + NodeEditContext(); bool isContourLine() { return m_isContourLine; } @@ -51,17 +91,34 @@ void setSegP1(int i) { m_SegP1 = i; } int segP2() { return m_SegP2; } void setSegP2(int i) { m_SegP2 = i; } - bool edPoints() { return m_EdPoints; } - void setEdPoints(bool b) { m_EdPoints = b; } - bool moveSym() { return m_MoveSym; } - void setMoveSym(bool b) { m_MoveSym = b; } + ControlMoveMode moveMode() { return m_moveMode; } + void setMoveMode(ControlMoveMode b) { m_moveMode = b; } + double scale() { return m_scale; }; + void setScale(double scale) {m_scale = scale; } QList<int>& selNode() { return m_SelNode; } bool hasNodeSelected() const; void deselect(); void selectNode(int i); + void deselectNode(int i); + void deselectNodeControls(); int selectionCount() const; + void getPoints(PageItem* currItem, int i, FPoint& ctrlLeft, FPoint& nodeLeft, FPoint& nodeRight, FPoint& ctrlRight); + FPoint getPoint(PageItem* currItem, int i, NodeType nodeType = NodeType::Self); + bool isSymetric(PageItem* currItem, int i = -1); + bool sameAngle(PageItem* currItem, int i = -1); + bool sameLength(PageItem* currItem, int i = -1); + + static bool isNode(int i) { return i % 2 == 0; } + static bool isLeftControl(int i); + static bool isLeftNode(int i); + static bool isRightNode(int i); + static bool isRightControl(int i); + static bool hasTwoNodes(int i, FPointArray clip); + static bool isSharpNode(int i, FPointArray clip); + static int indexOfNode(int i, NodeType type = NodeType::NodeRight, int size = -1); + void reset(); void setPreviewMode(bool mode); bool previewMode() { return m_preview; } @@ -69,17 +126,18 @@ void setSubMode(int i) { m_submode = i; } void reset1Control(PageItem* currItem); void resetControl(PageItem* currItem); + FPointArray clipFromItem(PageItem* currItem); FPointArray beginTransaction(PageItem* currItem); void finishTransaction(PageItem* currItem); ScOldNewState<FPointArray>* finishTransaction1(PageItem* currItem); void finishTransaction2(PageItem* currItem, ScOldNewState<FPointArray>* state); void moveClipPoint(PageItem *currItem, const FPoint& ip); + void equalizeControls(PageItem *currItem); FPointArray *oldClip { nullptr }; UndoTransaction nodeTransaction; + OppositeControl oppositeControl; - enum SubMode { MOVE_POINT = 0, ADD_POINT = 1, DEL_POINT = 2, SPLIT_PATH = 3 }; - protected: int m_submode { MOVE_POINT }; bool m_isContourLine { false }; @@ -88,11 +146,11 @@ int m_ClRe2 { -1 }; int m_SegP1 { -1 }; int m_SegP2 { -1 }; - bool m_EdPoints { true }; - bool m_MoveSym { false }; + ControlMoveMode m_moveMode { Auto }; QList<int> m_SelNode; double m_oldItemX { 0.0 }; double m_oldItemY { 0.0 }; + double m_scale { 1.0 }; bool m_preview { false }; }; #endif Index: scribus/scribus.cpp =================================================================== --- scribus/scribus.cpp (Revision 26678) +++ scribus/scribus.cpp (Arbeitskopie) @@ -5825,11 +5825,11 @@ nodePalette->setDoc(doc, view); nodePalette->MoveN(); - nodePalette->HaveNode(false, false); + nodePalette->HaveNode(false); nodePalette->MoveNode->setChecked(true); nodePalette->show(); // qDebug() << "nodepalette show:" << nodePalette->geometry(); - connect(view, SIGNAL(HavePoint(bool,bool)), nodePalette, SLOT(HaveNode(bool,bool))); + connect(view, SIGNAL(HavePoint(bool)), nodePalette, SLOT(HaveNode(bool))); connect(view, SIGNAL(PolyStatus(int,uint)), nodePalette, SLOT(PolyStatus(int,uint))); doc->nodeEdit.reset(); appModeHelper->setFrameEditMode(true); @@ -5869,7 +5869,7 @@ void ScribusMainWindow::NoFrameEdit() { - disconnect(view, SIGNAL(HavePoint(bool, bool)), nodePalette, SLOT(HaveNode(bool, bool))); + disconnect(view, SIGNAL(HavePoint(bool)), nodePalette, SLOT(HaveNode(bool))); disconnect(view, SIGNAL(PolyStatus(int, uint)), nodePalette, SLOT(PolyStatus(int, uint))); actionManager->disconnectModeActions(); nodePalette->setDoc(nullptr, nullptr); Index: scribus/scribusdoc.cpp =================================================================== --- scribus/scribusdoc.cpp (Revision 26678) +++ scribus/scribusdoc.cpp (Arbeitskopie) @@ -8329,6 +8329,9 @@ for (int i = 0; i < selectedItemCount; ++i) { PageItem* currItem = itemSelection->itemAt(i); + + int oldType = currItem->gradientType(); + currItem->setGradientType(typ); switch (currItem->gradientType()) { @@ -8391,6 +8394,12 @@ currItem->updateGradientVectors(); if (currItem->gradientType() == Gradient_Conical) currItem->createConicalMesh(); + + // 17332: Conical gradient and mesh gradient are sharing the same meshGradientArray. + // We have to reset the array for mesh gradient to avoid a resue of the "fake" colors of the conical gradient + if (currItem->gradientType() == Gradient_Mesh && oldType == Gradient_Conical) + currItem->createGradientMesh(2, 2); + currItem->update(); } Index: scribus/scribusview.cpp =================================================================== --- scribus/scribusview.cpp (Revision 26678) +++ scribus/scribusview.cpp (Arbeitskopie) @@ -1986,7 +1986,6 @@ MidButt = false; HaveSelRect = false; Magnify = false; - m_doc->nodeEdit.setEdPoints(true); DraggedGroup = false; //FIXME:av MoveGY = false; //FIXME:av MoveGX = false; Index: scribus/scribusview.h =================================================================== --- scribus/scribusview.h (Revision 26678) +++ scribus/scribusview.h (Arbeitskopie) @@ -369,7 +369,7 @@ void DelBM(PageItem *); void ChBMText(PageItem *); void LoadElem(QString, double, double, bool, bool, ScribusDoc *, ScribusView *); - void HavePoint(bool, bool); + void HavePoint(bool); void ClipPo(double, double); void PolyStatus(int, uint); void MousePos(double, double); Index: scribus/ui/colorpicker/colorpicker_gradient_properties.cpp =================================================================== --- scribus/ui/colorpicker/colorpicker_gradient_properties.cpp (Revision 26678) +++ scribus/ui/colorpicker/colorpicker_gradient_properties.cpp (Arbeitskopie) @@ -373,11 +373,11 @@ void ColorPickerGradientProperties::iconSetChange() { IconManager& im = IconManager::instance(); - editPoints->setIcon(im.loadIcon("MoveNode.png")); + editPoints->setIcon(im.loadIcon("bezier-node-move")); editControlPoints->setIcon(im.loadIcon("MoveKontrol.png")); resetControlPoint->setIcon(im.loadIcon("Reset1Node.png")); resetAllControlPoints->setIcon(im.loadIcon("ResetNode.png")); - editPPoint->setIcon(im.loadIcon("MoveNode.png")); + editPPoint->setIcon(im.loadIcon("bezier-node-move")); editPControlPoints->setIcon(im.loadIcon("MoveKontrol.png")); resetPControlPoint->setIcon(im.loadIcon("Reset1Node.png")); resetAllPControlPoints->setIcon(im.loadIcon("ResetNode.png")); Index: scribus/ui/nodeeditpalette.cpp =================================================================== --- scribus/ui/nodeeditpalette.cpp (Revision 26678) +++ scribus/ui/nodeeditpalette.cpp (Arbeitskopie) @@ -5,6 +5,7 @@ for which a new license (GPL+exception) is in place. */ +#include <QButtonGroup> #include <QEvent> #include "nodeeditpalette.h" @@ -21,302 +22,109 @@ NodePalette::NodePalette( QWidget* parent) : ScrPaletteBase(parent, "nodePalette", false, Qt::WindowFlags()) { - QSize iconSize(22, 22); - QSize buttonSize(24, 24); - QSize maxButtonSize(16777215, buttonSize.height()); - QSizePolicy::Policy buttonSizePolicy = static_cast<QSizePolicy::Policy>(QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag); + setupUi(this); - vboxLayout = new QVBoxLayout(this); - vboxLayout->setSpacing(6); - vboxLayout->setContentsMargins(6, 6, 6, 6); - - MoveNode = new QToolButton(this); - MoveNode->setIconSize(iconSize); - MoveNode->setMinimumSize(buttonSize); - MoveNode->setMaximumSize(maxButtonSize); - MoveNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); MoveNode->setCheckable(true); MoveNode->setChecked(true); - AddNode = new QToolButton(this); - AddNode->setIconSize(iconSize); - AddNode->setMinimumSize(buttonSize); - AddNode->setMaximumSize(maxButtonSize); - AddNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); AddNode->setEnabled(true); AddNode->setCheckable(true); - DeleteNode = new QToolButton(this); - DeleteNode->setIconSize(iconSize); - DeleteNode->setMinimumSize(buttonSize); - DeleteNode->setMaximumSize(maxButtonSize); - DeleteNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); DeleteNode->setEnabled(true); DeleteNode->setCheckable(true); - ResNode = new QToolButton(this); - ResNode->setIconSize(iconSize); - ResNode->setMinimumSize(buttonSize); - ResNode->setMaximumSize(maxButtonSize); - ResNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - ResNode->setEnabled(true); + EditControl->setCheckable(true); + EditControl->setChecked(true); - MoveControl = new QToolButton(this); - MoveControl->setIconSize(iconSize); - MoveControl->setMinimumSize(buttonSize); - MoveControl->setMaximumSize(maxButtonSize); - MoveControl->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - MoveControl->setCheckable(true); - - AsymMove = new QToolButton(this); - AsymMove->setIconSize(iconSize); - AsymMove->setMinimumSize(buttonSize); - AsymMove->setMaximumSize(maxButtonSize); - AsymMove->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - AsymMove->setEnabled(false); AsymMove->setCheckable(true); + AsymMove->setChecked(false); - SymMove = new QToolButton(this); - SymMove->setIconSize(iconSize); - SymMove->setMinimumSize(buttonSize); - SymMove->setMaximumSize(maxButtonSize); - SymMove->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - SymMove->setEnabled(false); SymMove->setCheckable(true); + SymMove->setChecked(false); - Res1Node = new QToolButton(this); - Res1Node->setIconSize(iconSize); - Res1Node->setMinimumSize(buttonSize); - Res1Node->setMaximumSize(maxButtonSize); - Res1Node->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - Res1Node->setEnabled(false); + IndependentMove->setCheckable(true); + IndependentMove->setChecked(false); - PolySplit = new QToolButton(this); PolySplit->setEnabled(false); PolySplit->setCheckable(true); - PolySplit->setIconSize(iconSize); - PolySplit->setMinimumSize(buttonSize); - PolySplit->setMaximumSize(maxButtonSize); - PolySplit->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - BezierClose = new QToolButton(this); BezierClose->setEnabled(false); - BezierClose->setIconSize(iconSize); - BezierClose->setMinimumSize(buttonSize); - BezierClose->setMaximumSize(maxButtonSize); - BezierClose->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - PolyMirrorH = new QToolButton(this); - PolyMirrorH->setIconSize(iconSize); - PolyMirrorH->setMinimumSize(buttonSize); - PolyMirrorH->setMaximumSize(maxButtonSize); - PolyMirrorH->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - - PolyMirrorV = new QToolButton(this); - PolyMirrorV->setIconSize(iconSize); - PolyMirrorV->setMinimumSize(buttonSize); - PolyMirrorV->setMaximumSize(maxButtonSize); - PolyMirrorV->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - - PolyShearL = new QToolButton(this); - PolyShearL->setIconSize(iconSize); - PolyShearL->setMinimumSize(buttonSize); - PolyShearL->setMaximumSize(maxButtonSize); - PolyShearL->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearL->setAutoRepeat(true); - - PolyShearR = new QToolButton(this); - PolyShearR->setIconSize(iconSize); - PolyShearR->setMinimumSize(buttonSize); - PolyShearR->setMaximumSize(maxButtonSize); - PolyShearR->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearR->setAutoRepeat(true); - - PolyShearU = new QToolButton(this); - PolyShearU->setIconSize(iconSize); - PolyShearU->setMinimumSize(buttonSize); - PolyShearU->setMaximumSize(maxButtonSize); - PolyShearU->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearU->setAutoRepeat(true); - - PolyShearD = new QToolButton(this); - PolyShearD->setIconSize(iconSize); - PolyShearD->setMinimumSize(buttonSize); - PolyShearD->setMaximumSize(maxButtonSize); - PolyShearD->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearD->setAutoRepeat(true); - - RotateCCW = new QToolButton(this); - RotateCCW->setIconSize(iconSize); - RotateCCW->setMinimumSize(buttonSize); - RotateCCW->setMaximumSize(buttonSize); RotateCCW->setAutoRepeat(true); - - RotateCW = new QToolButton(this); - RotateCW->setIconSize(iconSize); - RotateCW->setMinimumSize(buttonSize); - RotateCW->setMaximumSize(buttonSize); RotateCW->setAutoRepeat(true); - RotVal = new QSpinBox(this); RotVal->setMinimum(1); RotVal->setMaximum(180); RotVal->setValue(1); RotVal->setSuffix( QString::fromUtf8(" °")); - Expand = new QToolButton(this); - Expand->setIconSize(iconSize); - Expand->setMinimumSize(buttonSize); - Expand->setMaximumSize(buttonSize); Expand->setAutoRepeat(true); - - Shrink = new QToolButton(this); - Shrink->setIconSize(iconSize); - Shrink->setMinimumSize(buttonSize); - Shrink->setMaximumSize(buttonSize); Shrink->setAutoRepeat(true); - scalePercentage = new QSpinBox(this); scalePercentage->setMinimum(1); scalePercentage->setMaximum(100); scalePercentage->setValue(10); - Enlarge = new QToolButton(this); - Enlarge->setIconSize(iconSize); - Enlarge->setMinimumSize(buttonSize); - Enlarge->setMaximumSize(buttonSize); Enlarge->setAutoRepeat(true); - - Reduce = new QToolButton(this); - Reduce->setIconSize(iconSize); - Reduce->setMinimumSize(buttonSize); - Reduce->setMaximumSize(buttonSize); Reduce->setAutoRepeat(true); - scaleDistance = new ScrSpinBox( 1, 16777215, this, 0); - scaleDistance->setMaximumSize(114, 16777215); // ScrSpinBox is bigger for 4px than expected + scaleDistance->setMaximumSize(114, 16777215); scaleDistance->setValues(-16777215, 16777215, 2, 30); - gridLayout = new QGridLayout(); - gridLayout->setSpacing(6); - gridLayout->setContentsMargins(0, 0, 0, 0); - gridLayout->addWidget(MoveNode, 0, 1, 1, 1); - gridLayout->addWidget(AddNode, 0, 2, 1, 1); - gridLayout->addWidget(DeleteNode, 0, 3, 1, 1); - gridLayout->addWidget(ResNode, 0, 4, 1, 1); - gridLayout->addWidget(MoveControl, 1, 1, 1, 1); - gridLayout->addWidget(AsymMove, 1, 2, 1, 1); - gridLayout->addWidget(SymMove, 1, 3, 1, 1); - gridLayout->addWidget(Res1Node, 1, 4, 1, 1); - gridLayout->addWidget(PolySplit, 2, 1, 1, 1); - gridLayout->addWidget(BezierClose, 2, 2, 1, 1); - gridLayout->addWidget(PolyMirrorH, 2, 3, 1, 1); - gridLayout->addWidget(PolyMirrorV, 2, 4, 1, 1); - gridLayout->addWidget(PolyShearL, 3, 1, 1, 1); - gridLayout->addWidget(PolyShearR, 3, 2, 1, 1); - gridLayout->addWidget(PolyShearU, 3, 3, 1, 1); - gridLayout->addWidget(PolyShearD, 3, 4, 1, 1); - vboxLayout->addLayout(gridLayout); - - gridLayout2 = new QGridLayout(); - gridLayout2->setSpacing(6); - gridLayout2->setContentsMargins(0, 0, 0, 0); - gridLayout2->addWidget(RotVal, 0, 0, 1, 4); - gridLayout2->addWidget(RotateCCW, 0, 4, 1, 1); - gridLayout2->addWidget(RotateCW, 0, 5, 1, 1); - gridLayout2->addWidget(scalePercentage, 1, 0, 1, 4); - gridLayout2->addWidget(Expand, 1, 4, 1, 1); - gridLayout2->addWidget(Shrink, 1, 5, 1, 1); - gridLayout2->addWidget(scaleDistance, 2, 0, 1, 4); - gridLayout2->addWidget(Enlarge, 2, 4, 1, 1); - gridLayout2->addWidget(Reduce, 2, 5, 1, 1); - vboxLayout->addLayout(gridLayout2); - - QSpacerItem* spacer = new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ); - vboxLayout->addItem(spacer); - - AbsMode = new QGroupBox( "&Absolute Coordinates", this ); - AbsMode->setCheckable(true); - AbsMode->setChecked(false); - vboxLayout1 = new QVBoxLayout(AbsMode); - vboxLayout1->setSpacing(6); - vboxLayout1->setContentsMargins(9, 9, 9, 9); - absToCanvas = new QRadioButton( tr("to Canvas"), AbsMode); - vboxLayout1->addWidget(absToCanvas); - absToPage = new QRadioButton( tr("to Page"), AbsMode); - vboxLayout1->addWidget(absToPage); - absToCanvas->setChecked(true); - vboxLayout->addWidget(AbsMode); - - gridLayout3 = new QGridLayout(); - gridLayout3->setSpacing(6); - gridLayout3->setContentsMargins(0, 0, 0, 0); - gridLayout3->setColumnStretch(0, 0); - gridLayout3->setColumnStretch(1, 1); - - TextLabel1 = new QLabel(this); - gridLayout3->addWidget(TextLabel1, 0, 0, 1, 1); - - XSpin = new ScrSpinBox( 0, 16777215, this, 2 ); + XSpin->setMinimum(0); + XSpin->setMaximum(16777215); + XSpin->setNewUnit(2); XSpin->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed)); XSpin->setEnabled(false); - gridLayout3->addWidget(XSpin, 0, 1, 1, 1); - TextLabel2 = new QLabel(this); - gridLayout3->addWidget(TextLabel2, 1, 0, 1, 1); - - YSpin = new ScrSpinBox( 0, 16777215, this, 2 ); + YSpin->setMinimum(0); + YSpin->setMaximum(16777215); + YSpin->setNewUnit(2); YSpin->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed)); YSpin->setEnabled(false); - gridLayout3->addWidget(YSpin, 1, 1, 1, 1); - vboxLayout->addLayout(gridLayout3); TextLabel1->setBuddy(XSpin); TextLabel2->setBuddy(YSpin); - EditCont = new QCheckBox(this); EditCont->setChecked(false); - vboxLayout->addWidget(EditCont); - PreviewMode = new QCheckBox(this); PreviewMode->setChecked(false); - vboxLayout->addWidget(PreviewMode); - ResetCont = new QPushButton(this); ResetCont->setEnabled(false); - vboxLayout->addWidget(ResetCont); - ResetContClip = new QPushButton(this); ResetContClip->setEnabled(true); ResetContClip->hide(); - vboxLayout->addWidget(ResetContClip); - ResetShape2Clip = new QPushButton(this); ResetShape2Clip->setEnabled(true); ResetShape2Clip->hide(); - vboxLayout->addWidget(ResetShape2Clip); - resetDefaultButton = new QPushButton(this); resetDefaultButton->setEnabled(true); - vboxLayout->addWidget(resetDefaultButton); - hboxLayout = new QHBoxLayout(); - hboxLayout->setSpacing(6); - hboxLayout->setContentsMargins(0, 0, 0, 0); - editEditButton = new QPushButton(IconManager::instance().loadIcon("22/exit.png"), tr("OK"), this); editEditButton->setEnabled(true); editEditButton->setDefault(true); - hboxLayout->addWidget(editEditButton); - cancelEditButton = new QPushButton( tr("Cancel"), this); + cancelEditButton->setEnabled(true); - hboxLayout->addWidget(cancelEditButton); - vboxLayout->addLayout(hboxLayout); + QButtonGroup *btngpTools = new QButtonGroup(); + btngpTools->setExclusive(true); + btngpTools->addButton(MoveNode); + btngpTools->addButton(AddNode); + btngpTools->addButton(DeleteNode); + btngpTools->addButton(PolySplit); + btngpTools->addButton(BezierClose); + btngpTools->addButton(EditControl); + resize(QSize(170, 380).expandedTo(minimumSizeHint())); layout()->setSizeConstraint(QLayout::SetFixedSize); iconSetChange(); languageChange(); + PosOriginButton->setCurrentIndex(m_posOrigin); + connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange())); } @@ -331,11 +139,10 @@ connect(MoveNode, SIGNAL(clicked()), this, SLOT(MoveN())); connect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); connect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - connect(ResNode, SIGNAL(clicked()), this, SLOT(ResetControl())); - connect(Res1Node, SIGNAL(clicked()), this, SLOT(Reset1Control())); + connect(IndependentMove, SIGNAL(clicked()), this, SLOT(SetIndependentMove())); + connect(EditControl, SIGNAL(clicked()), this, SLOT(editControl())); connect(BezierClose, SIGNAL(clicked()), this, SLOT(CloseBezier())); connect(PolySplit, SIGNAL(clicked()), this, SLOT(SplitPoly())); - connect(MoveControl, SIGNAL(clicked()), this, SLOT(MoveK())); connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(PolyMirrorH, SIGNAL(clicked()), this, SLOT(MirrorH())); @@ -350,14 +157,12 @@ connect(Expand, SIGNAL(clicked()), this, SLOT(doExpand())); connect(Reduce, SIGNAL(clicked()), this, SLOT(doReduce())); connect(Enlarge, SIGNAL(clicked()), this, SLOT(doEnlarge())); - connect(AbsMode, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(absToCanvas, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(absToPage, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleConMode())); + connect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleContourMode())); connect(ResetCont, SIGNAL(clicked()), this, SLOT(ResetContour())); connect(ResetContClip, SIGNAL(clicked()), this, SLOT(ResetContourToImageClip())); connect(ResetShape2Clip, SIGNAL(clicked()), this, SLOT(ResetShapeToImageClip())); connect(PreviewMode, SIGNAL(clicked()), this, SLOT(TogglePreview())); + connect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); } void NodePalette::disconnectSignals() @@ -371,11 +176,10 @@ disconnect(MoveNode, SIGNAL(clicked()), this, SLOT(MoveN())); disconnect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); disconnect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - disconnect(ResNode, SIGNAL(clicked()), this, SLOT(ResetControl())); - disconnect(Res1Node, SIGNAL(clicked()), this, SLOT(Reset1Control())); + disconnect(IndependentMove, SIGNAL(clicked()), this, SLOT(SetIndependentMove())); + disconnect(EditControl, SIGNAL(clicked()), this, SLOT(editControl())); disconnect(BezierClose, SIGNAL(clicked()), this, SLOT(CloseBezier())); disconnect(PolySplit, SIGNAL(clicked()), this, SLOT(SplitPoly())); - disconnect(MoveControl, SIGNAL(clicked()), this, SLOT(MoveK())); disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(PolyMirrorH, SIGNAL(clicked()), this, SLOT(MirrorH())); @@ -394,8 +198,25 @@ disconnect(ResetContClip, SIGNAL(clicked()), this, SLOT(ResetContourToImageClip())); disconnect(ResetShape2Clip, SIGNAL(clicked()), this, SLOT(ResetShapeToImageClip())); disconnect(PreviewMode, SIGNAL(clicked()), this, SLOT(TogglePreview())); + disconnect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); } +void NodePalette::initPosOriginMenu() +{ + IconManager& im = IconManager::instance(); + QSignalBlocker sigPosOriginButton(PosOriginButton); + + int index = PosOriginButton->currentIndex(); + + PosOriginButton->clear(); + PosOriginButton->addAction(im.loadIcon("position-origin-canvas"), tr("Canvas"))->setData(PositionOrigin::Canvas); + PosOriginButton->addAction(im.loadIcon("position-origin-page"), tr("Page"))->setData(PositionOrigin::Page); + PosOriginButton->addAction(im.loadIcon("position-origin-item"), tr("Item"))->setData(PositionOrigin::Item); + + PosOriginButton->setCurrentIndex(index); + +} + void NodePalette::setDoc(ScribusDoc *dc, ScribusView *vi) { m_doc = dc; @@ -402,14 +223,15 @@ m_view = vi; unitChange(); - disconnect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleConMode())); - disconnect(AbsMode, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); + disconnect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleContourMode())); + disconnect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); if (m_doc != nullptr) { YSpin->setNewUnit(m_doc->unitIndex()); XSpin->setNewUnit(m_doc->unitIndex()); } - AbsMode->setChecked(false); + PosOriginButton->setCurrentIndex(Page); + EditCont->setChecked(false); if (m_doc == nullptr) disconnectSignals(); @@ -417,20 +239,11 @@ connectSignals(); } -void NodePalette::SplitPoly() -{ - if (m_doc == nullptr) - return; - MoveN(); - m_doc->nodeEdit.setSubMode(NodeEditContext::SPLIT_PATH); - PolySplit->setChecked(true); -} - void NodePalette::IsOpen() { if (m_doc == nullptr) return; - PolySplit->setChecked(false); + MoveNode->setChecked(true); BezierClose->setEnabled(true); PolySplit->setEnabled(true); EditCont->setEnabled(false); @@ -459,21 +272,6 @@ } } -void NodePalette::CloseBezier() -{ - if (m_doc == nullptr) - return; - MoveN(); - PageItem *currItem = m_doc->m_Selection->itemAt(0); - PageItem* newItem = m_doc->convertItemTo(currItem, PageItem::Polygon); - m_doc->m_Selection->clear(); - m_doc->m_Selection->addItem(newItem); - newItem->update(); - BezierClose->setEnabled(false); - PolySplit->setEnabled(true); - EditCont->setEnabled(true); -} - void NodePalette::doRotCCW() { if (m_doc == nullptr) @@ -558,20 +356,6 @@ m_doc->MirrorPolyV(m_doc->m_Selection->itemAt(0)); } -void NodePalette::ResetControl() -{ - if (m_doc == nullptr) - return; - m_doc->nodeEdit.resetControl(m_doc->m_Selection->itemAt(0)); -} - -void NodePalette::Reset1Control() -{ - if (m_doc == nullptr) - return; - m_doc->nodeEdit.reset1Control(m_doc->m_Selection->itemAt(0)); -} - void NodePalette::ResetContour() { if (m_doc == nullptr) @@ -629,9 +413,9 @@ PageItem *currItem = m_doc->m_Selection->itemAt(0); FPoint zp; FPoint np(XSpin->value() / m_doc->unitRatio(), YSpin->value() / m_doc->unitRatio()); - if (AbsMode->isChecked()) + if (m_posOrigin == Canvas || m_posOrigin == Page) { - if (absToCanvas->isChecked()) + if (m_posOrigin == Canvas) zp = FPoint(currItem->xPos(), currItem->yPos()); else zp = FPoint(currItem->xPos() - m_doc->currentPage()->xOffset(), currItem->yPos() - m_doc->currentPage()->yOffset()); @@ -648,43 +432,50 @@ void NodePalette::SetSym() { - if (m_doc != nullptr) - m_doc->nodeEdit.setMoveSym(true); - SymMove->setChecked(true); AsymMove->setChecked(false); + IndependentMove->setChecked(false); PolySplit->setChecked(false); + + if (m_doc == nullptr) + return; + + if (SymMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Symmetric); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } void NodePalette::SetAsym() { - if (m_doc != nullptr) - m_doc->nodeEdit.setMoveSym(false); SymMove->setChecked(false); - AsymMove->setChecked(true); + IndependentMove->setChecked(false); PolySplit->setChecked(false); + + if (m_doc == nullptr) + return; + + if (AsymMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Asymmetric); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } -void NodePalette::SetXY(double x, double y) +void NodePalette::SetIndependentMove() { + SymMove->setChecked(false); + AsymMove->setChecked(false); + PolySplit->setChecked(false); + if (m_doc == nullptr) return; - FPoint zp(0.0, 0.0); - disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - if (AbsMode->isChecked()) - { - if (absToCanvas->isChecked()) - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); - else - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); - } - XSpin->setValue((x + zp.x()) * m_doc->unitRatio()); - YSpin->setValue((y + zp.y()) * m_doc->unitRatio()); - connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + + if (IndependentMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Independent); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } -void NodePalette::ToggleAbsMode() +void NodePalette::SetXY(double x, double y) { if (m_doc == nullptr) return; @@ -691,35 +482,19 @@ FPoint zp(0.0, 0.0); disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - FPointArray Clip; - FPoint np(0.0, 0.0); - if (EditCont->isChecked()) - Clip = m_doc->m_Selection->itemAt(0)->ContourLine; - else - Clip = m_doc->m_Selection->itemAt(0)->PoLine; - if (m_doc->nodeEdit.selectionCount() != 0) - np = Clip.point(m_doc->nodeEdit.selNode().at(0)); - if (AbsMode->isChecked()) - { - XSpin->setMinimum(-16777215); - YSpin->setMinimum(-16777215); - if (absToCanvas->isChecked()) - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); - else - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); - } - else - { - XSpin->setMinimum(0); - YSpin->setMinimum(0); - } - XSpin->setValue((np.x() + zp.x()) * m_doc->unitRatio()); - YSpin->setValue((np.y() + zp.y()) * m_doc->unitRatio()); + + if (PosOriginButton->currentIndex() == Page) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); + else if (PosOriginButton->currentIndex() == Canvas) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); + + XSpin->setValue((x + zp.x()) * m_doc->unitRatio()); + YSpin->setValue((y + zp.y()) * m_doc->unitRatio()); connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); } -void NodePalette::ToggleConMode() +void NodePalette::ToggleContourMode() { if (m_doc == nullptr) return; @@ -748,7 +523,7 @@ PolySplit->setEnabled(true); ResetCont->setEnabled(false); ResetContClip->setEnabled(false); - if (AbsMode->isChecked()) + if (m_posOrigin == Canvas | m_posOrigin == Page) { XSpin->setMinimum(-16777215); YSpin->setMinimum(-16777215); @@ -773,10 +548,52 @@ m_doc->regionsChanged()->update(QRectF()); } -void NodePalette::HaveNode(bool have, bool mov) +void NodePalette::changePosOrigin(int index) { + m_posOrigin = index; + if (m_doc == nullptr) return; + + FPoint zp(0.0, 0.0); + disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + FPointArray Clip; + FPoint np(0.0, 0.0); + + if (EditCont->isChecked()) + Clip = m_doc->m_Selection->itemAt(0)->ContourLine; + else + Clip = m_doc->m_Selection->itemAt(0)->PoLine; + + if (m_doc->nodeEdit.selectionCount() != 0) + np = Clip.point(m_doc->nodeEdit.selNode().at(0)); + + if (m_posOrigin == Canvas || m_posOrigin == Page) + { + XSpin->setMinimum(-16777215); + YSpin->setMinimum(-16777215); + if (m_posOrigin == Canvas) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); + else + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); + } + else + { + XSpin->setMinimum(0); + YSpin->setMinimum(0); + } + + XSpin->setValue((np.x() + zp.x()) * m_doc->unitRatio()); + YSpin->setValue((np.y() + zp.y()) * m_doc->unitRatio()); + connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); +} + +void NodePalette::HaveNode(bool have) +{ + if (m_doc == nullptr) + return; bool setter = have; if (m_doc->nodeEdit.selectionCount() > 1) { @@ -788,35 +605,7 @@ XSpin->setEnabled(setter); YSpin->setEnabled(setter); } - if (setter) - { - if (m_doc->nodeEdit.edPoints()) - ResNode->setEnabled(setter); - else - Res1Node->setEnabled(setter); - } - else - { - ResNode->setEnabled(setter); - Res1Node->setEnabled(setter); - } - if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) - { - disconnect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); - disconnect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - if (mov) - { - SymMove->setChecked(true); - AsymMove->setChecked(false); - } - else - { - AsymMove->setChecked(true); - SymMove->setChecked(false); - } - connect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); - connect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - } + if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) { uint cc; @@ -837,91 +626,106 @@ leaveEd = true; } if (leaveEd) - { MoveN(); - MoveControl->setEnabled(true); - } } } -void NodePalette::MoveK() +void NodePalette::MoveN() { if (m_doc == nullptr) return; + m_doc->nodeEdit.setSubMode(NodeEditContext::MOVE_POINT); - m_doc->nodeEdit.setEdPoints(false); m_doc->nodeEdit.setClre(-1); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); + if (m_doc->m_Selection->count() > 0) m_doc->m_Selection->itemAt(0)->update(); + SymMove->setEnabled(true); AsymMove->setEnabled(true); - Res1Node->setEnabled(false); - ResNode->setEnabled(false); - AddNode->setEnabled(false); - DeleteNode->setEnabled(false); - MoveNode->setChecked(false); - MoveControl->setChecked(true); - DeleteNode->setChecked(false); - AddNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(true); + MoveNode->setChecked(true); } -void NodePalette::MoveN() +void NodePalette::AddN() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::MOVE_POINT); - m_doc->nodeEdit.setEdPoints(true); + + m_doc->nodeEdit.setSubMode(NodeEditContext::ADD_POINT); + SymMove->setEnabled(false); + AsymMove->setEnabled(false); + IndependentMove->setEnabled(false); + AddNode->setChecked(true); +} + +void NodePalette::DelN() +{ + if (m_doc == nullptr) + return; + + m_doc->nodeEdit.setSubMode(NodeEditContext::DEL_POINT); + SymMove->setEnabled(false); + AsymMove->setEnabled(false); + IndependentMove->setEnabled(false); + DeleteNode->setChecked(true); +} + +void NodePalette::SplitPoly() +{ + if (m_doc == nullptr) + return; + + m_doc->nodeEdit.setSubMode(NodeEditContext::SPLIT_PATH); m_doc->nodeEdit.setClre(-1); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); + if (m_doc->m_Selection->count() > 0) m_doc->m_Selection->itemAt(0)->update(); - ResNode->setEnabled(false); - AddNode->setEnabled(true); - DeleteNode->setEnabled(true); + SymMove->setEnabled(false); AsymMove->setEnabled(false); - Res1Node->setEnabled(false); - MoveNode->setChecked(true); - MoveControl->setChecked(false); - DeleteNode->setChecked(false); - AddNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(false); + PolySplit->setChecked(true); } -void NodePalette::AddN() +void NodePalette::editControl() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::ADD_POINT); - m_doc->nodeEdit.setEdPoints(true); + + m_doc->nodeEdit.setSubMode(NodeEditContext::EDIT_POINT); + m_doc->nodeEdit.setClre(-1); + m_doc->nodeEdit.setClre2(-1); + m_doc->nodeEdit.setSegP1(-1); + m_doc->nodeEdit.setSegP2(-1); + SymMove->setEnabled(false); AsymMove->setEnabled(false); - ResNode->setEnabled(false); - Res1Node->setEnabled(false); - AddNode->setChecked(true); - DeleteNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(false); + EditControl->setChecked(true); + } -void NodePalette::DelN() +void NodePalette::CloseBezier() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::DEL_POINT); - m_doc->nodeEdit.setEdPoints(true); - SymMove->setEnabled(false); - AsymMove->setEnabled(false); - ResNode->setEnabled(false); - Res1Node->setEnabled(false); - AddNode->setChecked(false); - DeleteNode->setChecked(true); - PolySplit->setChecked(false); + + MoveN(); + PageItem *currItem = m_doc->m_Selection->itemAt(0); + PageItem* newItem = m_doc->convertItemTo(currItem, PageItem::Polygon); + m_doc->m_Selection->clear(); + m_doc->m_Selection->addItem(newItem); + newItem->update(); + BezierClose->setEnabled(false); + PolySplit->setEnabled(true); + EditCont->setEnabled(true); } void NodePalette::closeEvent(QCloseEvent *ce) @@ -956,7 +760,7 @@ m_doc->nodeEdit.selNode().clear(); m_doc->nodeEdit.setPreviewMode(false); EditCont->setChecked(false); - ToggleConMode(); + ToggleContourMode(); PageItem *currItem = m_doc->m_Selection->itemAt(0); if (currItem && currItem->itemType() == PageItem::PathText) currItem->updatePolyClip(); @@ -979,7 +783,7 @@ m_doc->nodeEdit.selNode().clear(); m_doc->nodeEdit.setPreviewMode(false); EditCont->setChecked(false); - ToggleConMode(); + ToggleContourMode(); PageItem *currItem = m_doc->m_Selection->itemAt(0); // Calculate the difference of the current position and the original @@ -1069,28 +873,29 @@ { IconManager& im = IconManager::instance(); - MoveNode->setIcon(im.loadIcon("MoveNode.png")); - AddNode->setIcon(im.loadIcon("AddNode.png")); - DeleteNode->setIcon(im.loadIcon("DeleteNode.png")); - ResNode->setIcon(im.loadIcon("ResetNode.png")); - MoveControl->setIcon(im.loadIcon("MoveKontrol.png")); - AsymMove->setIcon(im.loadIcon("MoveAsym.png")); - SymMove->setIcon(im.loadIcon("MoveSym.png")); - Res1Node->setIcon(im.loadIcon("Reset1Node.png")); - PolySplit->setIcon(im.loadIcon("PolyCut.png")); - BezierClose->setIcon(im.loadIcon("BezierClose.png")); - PolyMirrorH->setIcon(im.loadIcon("hmirror.png")); - PolyMirrorV->setIcon(im.loadIcon("vmirror.png")); - PolyShearL->setIcon(im.loadIcon("22/transform-shear-left.png")); - PolyShearR->setIcon(im.loadIcon("22/transform-shear-right.png")); - PolyShearU->setIcon(im.loadIcon("22/transform-shear-up.png")); - PolyShearD->setIcon(im.loadIcon("22/transform-shear-down.png")); + initPosOriginMenu(); + + MoveNode->setIcon(im.loadIcon("bezier-node-move")); + AddNode->setIcon(im.loadIcon("bezier-node-add")); + DeleteNode->setIcon(im.loadIcon("bezier-node-delete")); + AsymMove->setIcon(im.loadIcon("bezier-control-asymetric")); + SymMove->setIcon(im.loadIcon("bezier-control-symetric")); + IndependentMove->setIcon(im.loadIcon("bezier-control-independent")); + EditControl->setIcon(im.loadIcon("bezier-node-edit")); + PolySplit->setIcon(im.loadIcon("bezier-split")); + BezierClose->setIcon(im.loadIcon("bezier-close")); + PolyMirrorH->setIcon(im.loadIcon("object-flip-horizontal")); + PolyMirrorV->setIcon(im.loadIcon("object-flip-vertical")); + PolyShearL->setIcon(im.loadIcon("bezier-path-shear-left")); + PolyShearR->setIcon(im.loadIcon("bezier-path-shear-right")); + PolyShearU->setIcon(im.loadIcon("bezier-path-shear-up")); + PolyShearD->setIcon(im.loadIcon("bezier-path-shear-down")); RotateCCW->setIcon(im.loadIcon("rotate_ccw.png")); RotateCW->setIcon(im.loadIcon("rotate_cw.png")); - Expand->setIcon(im.loadIcon("expand.png")); - Shrink->setIcon(im.loadIcon("crop.png")); - Enlarge->setIcon(im.loadIcon("expand.png")); - Reduce->setIcon(im.loadIcon("crop.png")); + Expand->setIcon(im.loadIcon("bezier-scale-up")); + Shrink->setIcon(im.loadIcon("bezier-scale-down")); + Enlarge->setIcon(im.loadIcon("bezier-scale-up")); + Reduce->setIcon(im.loadIcon("bezier-scale-down")); } void NodePalette::languageChange() @@ -1098,11 +903,11 @@ setWindowTitle( tr( "Nodes" ) ); scalePercentage->setSuffix( tr(" %")); - AbsMode->setTitle( tr("&Absolute Coordinates")); - absToCanvas->setText( tr("to Canvas")); - absToPage->setText( tr("to Page")); - TextLabel1->setText( tr("&X-Pos:")); - TextLabel2->setText( tr("&Y-Pos:")); + initPosOriginMenu(); + + PosOriginLabel->setText( tr("Origin:")); + TextLabel1->setText( tr("&X:")); + TextLabel2->setText( tr("&Y:")); PreviewMode->setText( tr("Hide Nodes")); EditCont->setText( tr("Edit &Contour Line")); ResetCont->setText( tr("&Reset Contour Line")); @@ -1113,13 +918,12 @@ resetDefaultButton->setText( tr("Reset all edits")); // editEditButton->setText( tr("&End Editing")); MoveNode->setToolTip( tr("Move Nodes")); - MoveControl->setToolTip( tr("Move Control Points")); AddNode->setToolTip( tr("Add Nodes")); DeleteNode->setToolTip( tr("Delete Nodes")); - AsymMove->setToolTip( tr("Move Control Points Independently")); + AsymMove->setToolTip( tr("Move Control Points Asymetrical")); SymMove->setToolTip( tr("Move Control Points Symmetrical")); - ResNode->setToolTip( tr("Reset Control Points")); - Res1Node->setToolTip( tr("Reset this Control Point")); + IndependentMove->setToolTip( tr("Move Control Points Independently")); + EditControl->setToolTip( tr("Edit Control Points")); PolySplit->setToolTip( tr("Open a Polygon or Cuts a Bezier Curve")); BezierClose->setToolTip( tr("Close this Bezier Curve")); PolyMirrorH->setToolTip( tr("Mirror the Path Horizontally")); @@ -1141,7 +945,7 @@ ResetCont->setToolTip( tr("Reset the Contour Line to the Original Shape of the Frame")); ResetContClip->setToolTip( tr("Reset the Contour Line to the Clipping Path of the Image")); ResetShape2Clip->setToolTip( tr("Set the Shape to the Clipping Path of the Image")); - AbsMode->setToolTip( "<qt>" + tr("When checked use coordinates relative to the page, otherwise coordinates are relative to the Object") + "</qt>"); + PosOriginButton->setToolTip( "<qt>" + tr("Use coordinates relative to the canvas, page, or to the Object") + "</qt>"); } void NodePalette::unitChange() Index: scribus/ui/nodeeditpalette.h =================================================================== --- scribus/ui/nodeeditpalette.h (Revision 26678) +++ scribus/ui/nodeeditpalette.h (Arbeitskopie) @@ -25,13 +25,13 @@ #include "scribusapi.h" #include "fpointarray.h" #include "scrpalettebase.h" -#include "scrspinbox.h" +#include "ui_nodeeditpalette.h" class ScribusDoc; class ScribusView; class PageItem; -class SCRIBUS_API NodePalette : public ScrPaletteBase +class SCRIBUS_API NodePalette : public ScrPaletteBase, public Ui::nodePalette { Q_OBJECT @@ -40,60 +40,19 @@ ~NodePalette() {}; void setDefaults(PageItem* currItem); - - QToolButton* MoveNode { nullptr }; - QToolButton* MoveControl { nullptr }; - QToolButton* AddNode { nullptr }; - QToolButton* DeleteNode { nullptr }; - QToolButton* AsymMove { nullptr }; - QToolButton* SymMove { nullptr }; - QToolButton* ResNode { nullptr }; - QToolButton* Res1Node { nullptr }; - QToolButton* PolySplit { nullptr }; - QToolButton* BezierClose { nullptr }; - QToolButton* PolyMirrorH { nullptr }; - QToolButton* PolyMirrorV { nullptr }; - QToolButton* PolyShearL { nullptr }; - QToolButton* PolyShearR { nullptr }; - QToolButton* PolyShearU { nullptr }; - QToolButton* PolyShearD { nullptr }; - QToolButton* RotateCCW { nullptr }; - QToolButton* RotateCW { nullptr }; - QToolButton* Expand { nullptr }; - QToolButton* Shrink { nullptr }; - QToolButton* Enlarge { nullptr }; - QToolButton* Reduce { nullptr }; - QCheckBox* PreviewMode { nullptr }; - QGroupBox* AbsMode { nullptr }; - QRadioButton* absToCanvas { nullptr }; - QRadioButton* absToPage { nullptr }; - QCheckBox* EditCont { nullptr }; - QLabel* TextLabel1 { nullptr }; - ScrSpinBox* YSpin { nullptr }; - QLabel* TextLabel2 { nullptr }; - ScrSpinBox* XSpin { nullptr }; - QSpinBox *RotVal { nullptr }; - QSpinBox *scalePercentage { nullptr }; - ScrSpinBox *scaleDistance { nullptr }; - QPushButton* ResetCont { nullptr }; - QPushButton* ResetContClip { nullptr }; - QPushButton* ResetShape2Clip { nullptr }; - QPushButton* editEditButton { nullptr }; - QPushButton* cancelEditButton { nullptr }; - QPushButton* resetDefaultButton { nullptr }; - void setDoc(ScribusDoc *dc, ScribusView *vi); ScribusDoc* currentDocument() const; + bool isSymetricMove() { return SymMove->isChecked(); }; + private slots: - void MoveK(); void AddN(); void DelN(); void MovePoint(); void SetSym(); void SetAsym(); - void ResetControl(); - void Reset1Control(); + void SetIndependentMove(); + void editControl(); void ResetContour(); void ResetContourToImageClip(); void ResetShapeToImageClip(); @@ -111,14 +70,14 @@ void ShearL(); void ShearU(); void ShearD(); - void ToggleAbsMode(); - void ToggleConMode(); + void ToggleContourMode(); void TogglePreview(); + void changePosOrigin(int index); public slots: void MoveN(); void SetXY(double x, double y); - void HaveNode(bool have, bool mov); + void HaveNode(bool have); void IsOpen(); void PolyStatus(int typ, uint size); void iconSetChange(); @@ -132,19 +91,21 @@ void reject() override; protected: + + enum PositionOrigin { + Canvas = 0, + Page, + Item + }; + void connectSignals(); void disconnectSignals(); + void initPosOriginMenu(); + void changeEvent(QEvent *e) override; void closeEvent(QCloseEvent *) override; - QVBoxLayout *vboxLayout { nullptr }; - QVBoxLayout *vboxLayout1 { nullptr }; - QHBoxLayout *hboxLayout { nullptr }; - QGridLayout *gridLayout { nullptr }; - QGridLayout *gridLayout2 { nullptr }; - QGridLayout *gridLayout3 { nullptr }; - ScribusDoc *m_doc { nullptr }; ScribusView *m_view { nullptr }; @@ -153,6 +114,7 @@ double m_yPos { 0.0 }; FPointArray m_itemPath; FPointArray m_itemContourPath; + int m_posOrigin { Page }; signals: void DocChanged(); Index: scribus/util_math.h =================================================================== --- scribus/util_math.h (Revision 26678) +++ scribus/util_math.h (Arbeitskopie) @@ -19,6 +19,7 @@ #include <QPainterPath> #include <QLineF> +#include "fpoint.h" #include "scribusapi.h" class FPoint; @@ -42,7 +43,7 @@ * @param t * @return */ -QList<QPointF> catmullToBezier(const QList<QPointF>& inputPoints, double t = 1); +QList<QPointF> SCRIBUS_API catmullToBezier(const QList<QPointF>& inputPoints, double t = 1); QPainterPath SCRIBUS_API regularPolygonPath(double w, double h, uint c, bool star, double factor, double rota, double factor2 = 0.0, double innerRot = 0.0, double factor3 = 0.0); QPainterPath SCRIBUS_API spiralPath(double spiralWidth, double spiralHeight, double spiralStartAngle, double spiralEndAngle, double spiralFactor); inline double SCRIBUS_API xy2Deg(double x, double y); @@ -51,6 +52,7 @@ inline double SCRIBUS_API square(double); inline double SCRIBUS_API distance(double, double); inline double SCRIBUS_API manhattanDistance(const QPointF& p1, const QPointF& p2); + /*! \brief Constrains an angle of rotation to 45 degree intervals Will make code simpler and reduce interval or provide as a parameter \param angle angle Angle in degrees @@ -58,6 +60,7 @@ \retval double Constrained angle */ double SCRIBUS_API constrainAngle(double angle, double constrain); +inline FPoint SCRIBUS_API getPointFromRotation(const FPoint &p1, double distance, double angle); /*! \brief Get the rotation angle (in radian) from a transformation matrix Will make code simpler and reduce interval or provide as a parameter \param matrix the transformation matrix @@ -92,6 +95,14 @@ return point.manhattanLength(); } +inline FPoint getPointFromRotation(const FPoint &p1, double distance, double angle) +{ + FPoint p2; + p2.setX(p1.x() + cosd(angle) * distance); + p2.setY(p1.y() + sind(angle) * distance); + return p2; +} + inline double xy2Deg(double x, double y) { return atan2(y,x) * (180.0/M_PI); |
|
i downloaded your codeberg and gave it a try. - the editing of the nodes and of the control points is now comfortable. yeah! ... and i took some notes: - i would remove the button for editing the control points (shift in node mode might be enough...) or, at least add the shift as mirrored modifier of the "opposite" action on the "move node" (if we keep two modes, in both modes it should be possible to do the other action). - there should be a way to add nodes while in "move node" mode (ctrl click on a line?). i'm fine with the current behavior with two "clicks" for deleting (click on the nodes + del key) - in inkscape, the different types of nodes (symmetrical, ...) are a property of the node, not a way to move the control points: if it's not too hard, i would prefer to have the same behavior in scribus. - i would make the x, y coordinates optional by adding a forth option to the "Origin" drop down: "Do not show". as far as i can tell this something that is only needed in very specific workflows and the big widgets distract from the main parts of this dialog. - "Edit contour line" and "Hide nodes" (renamed to "Preview mode"?) could be toolbuttons, too... - we only need one reset button (... toolbutton) which will reset the contour line or the nodes, depending on what is currently active - it should be possible to open the "dialog" (or start the node editing) in both the contour line and in node mode (as an example as a "Edit contour line" toolbutton in the "Text flow" form widget of "Properties > Shape" and a menu entry that could be triggered with the Action Search). - "open a polygon" and "close this bezier this curve" should be together in a buttons group. - we could start having a "status bar" inside of the dialog that shows what the modifier are in the current state ("What it does. (more: Shift, Alt)"). In the long term we could the feature to the main status bar and provide the information for the other scribus tools and contexts. (that would help, if we remove the button dedicated to the editing of the control points). - in inkscape only the control points of the adjacent nodes to the current one are shown... not sure if the same behavior would better also for scribus. |
|
@ale great ideas. I prefer to focus this task mainly on canvas changes. I know the node editing palette needs a lot of more improvements, and I propose to move the palette related UI changes to the new toolbar task. >> i would remove the button for editing the control points (shift in node mode might be enough...) Yes, it is not necessary. I thought it is a good idea to have a button that permanently active the button modifiers like ALT or SHIFT. But a common status bar would be more sufficient. Blender has a great status bar for these button modifiers. >> at least add the shift as mirrored modifier of the "opposite" action on the "move node" (if we keep two modes, in both modes it should be possible to do the other action). I had some trouble to implement different modes like in Inkscape. A similar logic applies when you "pull out" a control handle of a node if there is already one control handle visible. >> "open a polygon" and "close this bezier this curve" should be together in a buttons group. IMO "open a polygon" is more like a tool, because you have to click where you want to cut the path. "Close Bézier" is an action. >> there should be a way to add nodes while in "move node" mode (ctrl click on a line?). Good idea. It will be done with the next patch because it was an easy task of 2 lines of code. Now: CTRL + click on path add a new node; CTRL + click on a node removes this node. >> in inkscape, the different types of nodes (symmetrical, ...) are a property of the node, not a way to move the control points: if it's not too hard, i would prefer to have the same behavior in scribus. That would make things easier. Unfortunately, the FPointArray doesn't store the information of the control mode. My solution to work around this was to have an auto-detection of this mode. >> in inkscape only the control points of the adjacent nodes to the current one are shown... not sure if the same behavior would better also for scribus. Let's give it a try how it feels like. It should be not hard to implement. >> - "Edit contour line" and "Hide nodes" (renamed to "Preview mode"?) could be toolbuttons, too... BTW, there are two more buttons if you edit the clipping path of an image. |
|
i guess that what is not doable without more broad changes, should go to one or more new tickets for future reference... (the same for the things you think are good ideas, but for any reason you don't want to add to this patch) concerning the "cut tool": i admit that i have not tried it out and misunderstood its working. i'd prefer to see it replaced by its inkscape counterpart "break path at selected node" (first select a node, than break). conforming to what i've written above, i'm adding further ideas to new tickets with the tag "nodes"... so you don't feel pressured into implementing them NOW : - ) but feel free to attach them to this ticket, if you feel like doing them... |
|
Here's another update. I've added the option to add and remove nodes by holding CTRL and clicking on the path line (add) or on the node (remove). The node editing palette is now smaller and I've replaced the large buttons with icon buttons. There's always a risk of losing information when replacing readable text for the sake of saving space. In a future version, we may also split the "Origin" button into 3 individual buttons (similar to the references in the Alignment Panel), but if this palette is "converted" to a toolbar, the space-saving is certainly worthwhile. nodeedit_2025-02-16_01.patch (120,774 bytes)
Index: resources/iconsets/1_7_0/1_7_0.xml =================================================================== --- resources/iconsets/1_7_0/1_7_0.xml (Revision 26704) +++ resources/iconsets/1_7_0/1_7_0.xml (Arbeitskopie) @@ -141,7 +141,7 @@ <icon onLight="#68B259" onDark="#88CC7A" id="alert-ok" file="16/alert-ok.svg" /> <!-- Align & Distribute --> - <icon id="align-horizontal-center" file="16/align-item-horizontal-center.svg" /> + <icon id="align-horizontal-center" file="16/align-item-horizontal-center.svg" /> <icon id="align-horizontal-left" file="16/align-item-horizontal-left.svg" /> <icon id="align-horizontal-left-out" file="16/align-item-horizontal-left-out.svg" /> <icon id="align-horizontal-right" file="16/align-item-horizontal-right.svg" /> @@ -184,11 +184,13 @@ <!-- Bezier & Vector Path--> <icon id="bezier-close" file="16/bezier-close.svg" /> <icon id="bezier-control-asymetric" file="16/bezier-control-asymetric.svg" /> + <icon id="bezier-control-independent" file="16/bezier-control-independent.svg" /> <icon id="bezier-control-move" file="16/bezier-control-move.svg" /> <icon id="bezier-control-reset" file="16/bezier-control-reset.svg" /> <icon id="bezier-control-reset-all" file="16/bezier-controls-reset.svg" /> <icon id="bezier-control-symetric" file="16/bezier-control-symetric.svg" /> <icon id="bezier-node-add" file="16/bezier-node-add.svg" /> + <icon id="bezier-node-edit" file="16/bezier-node-edit.svg" /> <icon id="bezier-node-delete" file="16/bezier-node-delete.svg" /> <icon id="bezier-node-move" file="16/bezier-node-move.svg" /> <icon id="bezier-scale-down" file="16/bezier-scale-down.svg" /> @@ -196,16 +198,25 @@ <icon id="bezier-split" file="16/bezier-split.svg" /> <icon id="object-flip-horizontal" file="16/object-flip-horizontal.svg" /> <icon id="object-flip-vertical" file="16/object-flip-vertical.svg" /> + <icon id="path-contour-edit" file="16/path-contour-edit.svg" /> + <icon id="path-contour-reset" file="16/path-contour-reset.svg" /> + <icon id="path-contour-to-clip" file="16/path-contour-to-clip.svg" /> <icon id="path-exclusion" file="16/path-exclusion.svg" /> <icon id="path-intersection" file="16/path-intersection.svg" /> + <icon id="path-node-visibility" file="16/path-node-visibility.svg" /> <icon id="path-parts" file="16/path-combine.svg" /> + <icon id="path-reset-all" file="16/action-reload.svg" /> + <icon id="path-shape-to-clip" file="16/path-shape-to-clip.svg" /> <icon id="path-subtraction" file="16/path-difference.svg" /> <icon id="path-unite" file="16/path-union.svg" /> + <icon id="position-origin-canvas" file="16/position-origin-canvas.svg" /> + <icon id="position-origin-item" file="16/position-origin-item.svg" /> + <icon id="position-origin-page" file="16/position-origin-page.svg" /> <icon id="transform-shear-down" file="16/bezier-path-shear-down.svg" /> <icon id="transform-shear-left" file="16/bezier-path-shear-left.svg" /> <icon id="transform-shear-right" file="16/bezier-path-shear-right.svg" /> <icon id="transform-shear-up" file="16/bezier-path-shear-up.svg" /> - + <!-- Color --> <icon id="color-cmyk" file="16/color-cmyk.svg" /> <icon id="color-lab" file="16/color-lab.svg" /> @@ -248,6 +259,8 @@ <icon id="cursor-text-frame" file="32/cursor-text.svg" /> <icon id="cursor-zoom-in" file="32/cursor-zoom-in.svg" /> <icon id="cursor-zoom-out" file="32/cursor-zoom-out.svg" /> + <icon id="cursor-move" file="32/cursor-move.svg" /> + <icon id="cursor-node-edit" file="32/cursor-node-edit.svg" /> <!-- Drawable Shapes --> <!-- @@ -463,7 +476,7 @@ <icon id="split-table-cells" file="16/table-split-cell.svg" /> <!-- Text & Font--> - <icon id="character-capitals-all" file="16/character-capitals-all.svg" /> + <icon id="character-capitals-all" file="16/character-capitals-all.svg" /> <icon id="character-capitals-small" file="16/character-capitals-small.svg" /> <icon id="character-letter-tracking" file="16/character-letter-spacing.svg" /> <icon id="character-offset-baseline" file="16/character-offset-baseline.svg" /> Index: scribus/canvasmode.cpp =================================================================== --- scribus/canvasmode.cpp (Revision 26704) +++ scribus/canvasmode.cpp (Arbeitskopie) @@ -221,13 +221,13 @@ // } drawSelectionHandle(psx, QPointF(x, y), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x+w/2.0, y), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x+w, y), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x+w, y+h/2.0), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x+w, y+h), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x+w/2.0, y+h), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x, y+h), p, m_canvas->scale()); - drawSelectionHandle(psx, QPointF(x, y+h/2.0), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x + w / 2.0, y), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x + w, y), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x + w, y + h / 2.0), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x + w, y + h), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x + w / 2.0, y + h), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x, y + h), p, m_canvas->scale()); + drawSelectionHandle(psx, QPointF(x, y + h / 2.0), p, m_canvas->scale()); } void CanvasMode::drawSelection(QPainter* psx, bool drawHandles) @@ -257,6 +257,7 @@ if (!m_doc->Items->contains(currItem)) continue; psx->save(); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; w = currItem->visualWidth() ; h = currItem->visualHeight() ; @@ -265,14 +266,14 @@ psx->setRenderHint(QPainter::Antialiasing); psx->translate(currItem->xPos(), currItem->yPos()); psx->rotate(currItem->rotation()); - x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos()); - y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos()); + x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos() - lineAdjust); + y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos() - lineAdjust); } else { psx->translate(currItem->visualXPos(), currItem->visualYPos()); - x = 0; - y = 0; + x = currItem->asLine() ? 0 : -lineAdjust; + y = currItem->asLine() ? 0 : -lineAdjust; } // psx->setBrush(Qt::NoBrush); // psx->setPen(m_pen["frame-outline"]); @@ -285,12 +286,13 @@ } psx->save(); psx->setPen(m_pen["selection-group"]); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; m_doc->m_Selection->getVisualGroupRect(&x, &y, &w, &h); psx->translate(x,y); - x = 0; - y = 0; + x = -lineAdjust; + y = -lineAdjust; // psx->setBrush(Qt::NoBrush); // psx->setPen(m_pen["frame-outline"]); @@ -314,6 +316,7 @@ psx->save(); psx->setPen(m_pen["selection"]); psx->setBrush(m_brush["selection"]); + double lineAdjust(psx->pen().width() / m_canvas->scale()); double x, y, w, h; if (currItem->isGroupChild()) { @@ -358,14 +361,14 @@ psx->setRenderHint(QPainter::Antialiasing); psx->translate(currItem->xPos(), currItem->yPos()); psx->rotate(currItem->rotation()); - x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos()); - y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos()); + x = currItem->asLine() ? 0 : (currItem->visualXPos() - currItem->xPos() - lineAdjust); + y = currItem->asLine() ? (h / -2.0) : (currItem->visualYPos() - currItem->yPos() - lineAdjust); } else { psx->translate(currItem->visualXPos(), currItem->visualYPos()); - x = 0; - y = 0; + x = currItem->asLine() ? 0 : -lineAdjust; + y = currItem->asLine() ? 0 : -lineAdjust; } // psx->setBrush(Qt::NoBrush); if (m_doc->drawAsPreview && !m_doc->editOnPreview) @@ -740,7 +743,7 @@ QPoint pageOrigin = m_canvas->canvasToLocal(QPointF(xOffset, yOffset)); if (ySnap) { - p->setPen(Qt::green); + p->setPen(m_pen["snap-line"]); QPoint pt = m_canvas->canvasToLocal(QPointF(xOffset, ySnap)); double w = (dragToPage->width() + bleedValues.left() + bleedValues.right()) * m_canvas->scale(); p->drawLine(pageOrigin.x(), pt.y(), pageOrigin.x() + w, pt.y()); @@ -748,7 +751,7 @@ } if (xSnap) { - p->setPen(Qt::green); + p->setPen(m_pen["snap-line"]); QPoint pt = m_canvas->canvasToLocal(QPointF(xSnap, yOffset)); double h = (dragToPage->height() + bleedValues.bottom() + bleedValues.top()) * m_canvas->scale(); p->drawLine(pt.x(), pageOrigin.y(), pt.x(), pageOrigin.y() + h); @@ -1291,7 +1294,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if (nodeSelection.count() != 0) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1385,7 +1388,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if (nodeSelection.count() != 0) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1479,7 +1482,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if (nodeSelection.count() != 0) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1573,7 +1576,7 @@ { int storedClRe = m_doc->nodeEdit.clre(); const auto& nodeSelection = m_doc->nodeEdit.selNode(); - if ((nodeSelection.count() != 0) && (m_doc->nodeEdit.edPoints())) + if (nodeSelection.count() != 0) { QPolygonF poly; m_doc->nodeEdit.beginTransaction(currItem); @@ -1734,9 +1737,11 @@ m_color["frame-group"] = PrefsManager::instance().appPrefs.displayPrefs.frameGroupColor; m_color["frame"] = PrefsManager::instance().appPrefs.displayPrefs.frameNormColor; m_color["frame-move"] = Qt::gray; - m_color["weld"] = QColor(214, 146, 43); - m_color["node"] = QColor(0, 136, 255); - m_color["node-handle"] = QColor(255, 51, 187); + m_color["weld"] = QColor(238, 152, 0); + m_color["node"] = QColor(0, 123, 241); + m_color["node-handle"] = QColor(166, 74, 253); + m_color["highlight"] = QColor(220, 58, 140); + m_color["snap-line"] = Qt::green; m_color["test"] = Qt::magenta; QColor selectionAlpha = m_color["frame-selection"]; selectionAlpha.setAlphaF(.05f); @@ -1747,18 +1752,19 @@ #else double stroke = 1.0; #endif - + // frame m_pen["frame-move"] = QPen(m_color["frame-move"], 1.0 , Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["frame-move"].setCosmetic(true); m_pen["frame-preview"] = QPen(Qt::black, 1.0, Qt::DotLine, Qt::FlatCap, Qt::MiterJoin); m_pen["frame-preview"].setCosmetic(true); - m_pen["selection"] = QPen(m_color["frame-selection"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["selection"] = QPen(m_color["frame-selection"], 1.0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection"].setCosmetic(true); - m_pen["selection-handle"] = QPen(m_color["frame-selection"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["selection-handle"] = QPen(m_color["frame-selection"], 1.0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection-handle"].setCosmetic(true); m_pen["selection-group"] = m_pen["selection"]; m_pen["selection-group-inside"] = QPen(m_color["frame-group"], 1.0 , Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["selection-group-inside"].setCosmetic(true); + // vector editing m_pen["vector"] = QPen(m_color["node"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_pen["vector"].setCosmetic(true); m_pen["node"] = QPen(m_color["node"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); @@ -1767,6 +1773,10 @@ m_pen["node-handle"].setCosmetic(true); m_pen["node-dash"] = QPen(m_color["node-handle"], stroke, Qt::DashLine, Qt::FlatCap, Qt::MiterJoin); m_pen["node-dash"].setCosmetic(true); + m_pen["highlight"] = QPen(m_color["highlight"], stroke, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + m_pen["highlight"].setCosmetic(true); + // guides + m_pen["snap-line"] = QPen(m_color["snap-line"], 1.0 , Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); m_brush["frame-move"] = Qt::NoBrush; m_brush["selection"] = Qt::NoBrush; Index: scribus/canvasmode_nodeedit.cpp =================================================================== --- scribus/canvasmode_nodeedit.cpp (Revision 26704) +++ scribus/canvasmode_nodeedit.cpp (Arbeitskopie) @@ -30,6 +30,7 @@ #include "scribusdoc.h" #include "scribusview.h" #include "selection.h" +#include "ui/nodeeditpalette.h" #include "ui/pageselector.h" #include "ui/scrspinbox.h" @@ -38,20 +39,20 @@ m_ScMW = m_view->m_ScMW; } -void CanvasMode_NodeEdit::drawControls(QPainter* p) +void CanvasMode_NodeEdit::drawControls(QPainter* p) { - if (m_doc->m_Selection->isEmpty()) + if (m_doc->m_Selection->isEmpty() || m_doc->nodeEdit.previewMode()) return; - if (m_doc->nodeEdit.previewMode()) - return; + PageItem* currItem = m_doc->m_Selection->itemAt(0); FPointArray cli; - + p->save(); p->scale(m_canvas->scale(), m_canvas->scale()); p->translate(-m_doc->minCanvasCoordinate.x(), -m_doc->minCanvasCoordinate.y()); p->setTransform(currItem->getTransform(), true); p->setBrush(Qt::NoBrush); + p->setRenderHint(QPainter::Antialiasing); if ((m_doc->nodeEdit.isContourLine()) && (!currItem->ContourLine.empty())) cli = currItem->ContourLine; @@ -71,15 +72,25 @@ p->scale(1, -1); } } - } + } const double scale = m_canvas->m_viewMode.scale; + double nx = 0.0; + double ny = 0.0; + double cx = 0.0; + double cy = 0.0; + + QPen penControl = pens().value("node"); + bool isActiveNode = false; + bool isActiveControl = false; + bool highlight = false; + bool mergedNodes = true; + int nr, nl, inr; + FPoint pn, pc; + // Draw Vector Path if (cli.size() > 3) { - p->save(); - p->setRenderHint(QPainter::Antialiasing); - for (int i = 0; i < cli.size() - 3; i += 4) { if (cli.isMarker(i)) @@ -88,20 +99,18 @@ const FPoint& a1 = cli.point(i); const FPoint& a2 = cli.point(i + 1); const FPoint& a3 = cli.point(i + 3); - const FPoint& a4 = cli.point(i + 2); + const FPoint& a4 = cli.point(i + 2); QPainterPath Bez; Bez.moveTo(a1.x(), a1.y()); Bez.cubicTo(a2.x(), a2.y(), a3.x(), a3.y(), a4.x(), a4.y()); + + // Vector Path p->setPen(pens().value("vector")); p->drawPath(Bez); - p->setPen(pens().value("node-handle")); - p->drawLine(QPointF(a1.x(), a1.y()), QPointF(a2.x(), a2.y())); - p->drawLine(QPointF(a3.x(), a3.y()), QPointF(a4.x(), a4.y())); + } - p->restore(); } - double x = 0.0; - double y = 0.0; + // Draw Nodes for (int i = 0; i < cli.size() - 1; i += 2) { @@ -108,51 +117,90 @@ if (cli.isMarker(i)) continue; - if (m_doc->nodeEdit.edPoints()) - { - cli.point(i + 1, &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale); + nr = NodeEditContext::indexOfNode(m_doc->nodeEdit.clre(), NodeEditContext::NodeType::NodeRight, cli.size()); + nl = NodeEditContext::indexOfNode(m_doc->nodeEdit.clre(), NodeEditContext::NodeType::NodeLeft, cli.size()); + inr = NodeEditContext::indexOfNode(i, NodeEditContext::NodeType::NodeRight, cli.size()); - cli.point(i, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale); - } - else + mergedNodes = NodeEditContext::hasTwoNodes(i, cli); + highlight = (i == nr || i == nl) && NodeEditContext::hasTwoNodes(m_doc->nodeEdit.clre(), cli); + + isActiveControl = m_doc->nodeEdit.clre() == i + 1 ? true : false; + isActiveNode = m_doc->nodeEdit.clre() == i || + m_doc->nodeEdit.selNode().contains(i) || + m_doc->nodeEdit.selNode().contains(i - 2) || + (m_doc->nodeEdit.selNode().contains(inr) && mergedNodes) ? true : false; + + cli.point(i, &nx, &ny); + cli.point(i + 1, &cx, &cy); + + // Draw Control Handle Lines + if (FPoint(cx, cy) != FPoint(nx, ny)) { - cli.point(i, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale); + // changes color to highlight if angle is same as opponent handle + if (highlight && state.isSameAngle && state.clickedOn) + p->setPen(pens().value("highlight")); + else + p->setPen(pens().value("node-handle")); - cli.point(i + 1, &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale); + p->setRenderHint(QPainter::Antialiasing, true); + p->drawLine(QPointF(nx, ny), QPointF(cx, cy)); } - } - - if (m_doc->nodeEdit.clre() != -1) - { - cli.point(m_doc->nodeEdit.clre(), &x, &y); - drawNodeControl(p, QPointF(x, y), pens().value("node"), scale, true); - const auto& selectedNodes = m_doc->nodeEdit.selNode(); - for (int i = 0; i < selectedNodes.count(); ++i) + p->setRenderHint(QPainter::Antialiasing, false); + + // Draw Node Handle + drawNodeHandle(p, QPointF(nx, ny), pens().value("node"), scale, isActiveNode); + + // Draw Control Handle + if (FPoint(cx, cy) != FPoint(nx, ny)) { - int selectedNode = selectedNodes.at(i); - cli.point(selectedNode, &x, &y); - drawNodeHandle(p, QPointF(x, y), pens().value("node"), scale, true); + // changes color to highlight if length is same as opponent handle + if (highlight && state.isSameLength && state.isSameAngle && state.clickedOn) + penControl = pens().value("highlight"); + else + penControl = pens().value("node"); + + drawNodeControl(p, QPointF(cx, cy), penControl, scale, isActiveControl); } } - bool havePoint = (m_doc->nodeEdit.clre() != -1); - emit m_view->HavePoint(havePoint, m_doc->nodeEdit.moveSym()); + emit m_view->HavePoint(m_doc->nodeEdit.clre() != -1); + if (m_doc->nodeEdit.clre() != -1) { - cli.point(m_doc->nodeEdit.clre(), &x, &y); - emit m_view->ClipPo(x, y); + cli.point(m_doc->nodeEdit.clre(), &nx, &ny); + emit m_view->ClipPo(nx, ny); } p->restore(); } +QMap<int, int> CanvasMode_NodeEdit::selectedNodesList(FPointArray points) +{ + QMap<int, int> selList; + const auto& selNodes = m_doc->nodeEdit.selNode(); + if (points.size() > 3) + { + for (int i = 0; i < points.size() - 1; i += 2) + { + if (points.isMarker(i)) + continue; + + // i = path start; + // i + 2 = path end OR left part of a node + // i - 2 = right part of a node + if (selNodes.contains(i) || selNodes.contains(i + 2) || selNodes.contains(i - 2) || selNodes.contains(i - 4)) + { + selList.insert(i, i + 1); + } + } + } + + return selList; +} + void CanvasMode_NodeEdit::activate(bool fromGesture) { CanvasMode::activate(fromGesture); @@ -236,19 +284,47 @@ return (*pi) != nullptr; } +FPoint CanvasMode_NodeEdit::constraintControlAngle(FPoint anchor, FPoint mouse, double angleStep) +{ + QLineF line(anchor.toQPointF(), mouse.toQPointF()); + line.setAngle(constrainAngle(line.angle(), angleStep)); + return line.p2(); +} -void CanvasMode_NodeEdit::enterEvent(QEvent *e) +FPoint CanvasMode_NodeEdit::snapControlAngle(FPoint anchor, FPoint mouse, double angle) { + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF lineAM(anchor.x(), anchor.y(), mouse.x(), mouse.y()); // anchor to mouse + QLineF lineAC = lineAM; // anchor to opposite control point + lineAC.setAngle(360.0 - angle); // invert angle, Qt angle is counter-clockwise + + if (qMax(lineAM.angle(), lineAC.angle()) - qMin(lineAM.angle(), lineAC.angle()) <= 0.00001) + return lineAC.p2(); + + return QLineF(lineAM.p2(), lineAC.p2()).length() <= snap ? lineAC.p2() : mouse; } +FPoint CanvasMode_NodeEdit::snapControlLength(FPoint anchor, FPoint mouse, double length) +{ + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF line(anchor.toQPointF(), mouse.toQPointF()); -void CanvasMode_NodeEdit::leaveEvent(QEvent *e) -{ + if (length - snap <= line.length() && length + snap >= line.length()) + line.setLength(length); + + return line.p2(); } +FPoint CanvasMode_NodeEdit::snapControlOrigin(FPoint anchor, FPoint mouse) +{ + double snap = m_doc->prefsData().guidesPrefs.guideRad / m_canvas->scale(); + QLineF line(anchor.toQPointF(), mouse.toQPointF()); + return (line.length() <= snap) ? anchor : mouse; +} + void CanvasMode_NodeEdit::mouseDoubleClickEvent(QMouseEvent *m) { m->accept(); @@ -257,11 +333,11 @@ m_view->requestMode(submodeEndNodeEdit); } - void CanvasMode_NodeEdit::mouseMoveEvent(QMouseEvent *m) { double sc = m_canvas->scale(); FPoint npf = m_canvas->globalToCanvas(m->globalPosition()); + m_doc->nodeEdit.setScale(sc); m->accept(); @@ -348,6 +424,7 @@ m_canvas->m_viewMode.operItemMoving = false; m_doc->DragP = false; m_doc->leaveDrag = false; + m_doc->nodeEdit.setScale(m_canvas->scale()); m_MoveGX = m_MoveGY = false; m->accept(); m_view->registerMousePress(m->globalPosition()); @@ -369,7 +446,8 @@ if (m_doc->m_Selection->count() != 0) { handleNodeEditPress(m, mpo); - } + } + if ((m_doc->m_Selection->count() != 0) && (m->button() == Qt::RightButton)) { m_canvas->m_viewMode.m_MouseButtonPressed = true; @@ -378,16 +456,27 @@ } } - - void CanvasMode_NodeEdit::mouseReleaseEvent(QMouseEvent *m) { PageItem *currItem; + m_canvas->m_viewMode.m_MouseButtonPressed = false; m_canvas->resetRenderMode(); m->accept(); + + // Deselect selected node if SHIFT is pressed + if (state.clickedOn == NodeHandle && + m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && + !state.wasNewlySelected && + m->modifiers() == Qt::ShiftModifier) + { + m_doc->nodeEdit.deselectNode(m_doc->nodeEdit.clre()); + m_doc->regionsChanged()->update(QRectF()); + } + if (m_view->moveTimerElapsed()) { + // Path Segment if (m_doc->nodeEdit.segP1() != -1 && m_doc->nodeEdit.segP2() != -1) { m_doc->nodeEdit.deselect(); @@ -394,53 +483,58 @@ m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); } + + m_canvas->m_viewMode.operItemMoving = false; currItem = m_doc->m_Selection->itemAt(0); - m_canvas->m_viewMode.operItemMoving = false; double xposOrig = currItem->xPos(); double yposOrig = currItem->yPos(); - ScOldNewState<FPointArray> *state = m_doc->nodeEdit.finishTransaction1(currItem); + ScOldNewState<FPointArray> *onState = m_doc->nodeEdit.finishTransaction1(currItem); + if (m_doc->nodeEdit.hasNodeSelected() && (m_doc->nodeEdit.selectionCount() == 1)) { + //FIXME:av - FPoint newP = m_canvas->globalToCanvas(m->globalPosition()); + // FPoint newP = m_canvas->globalToCanvas(m->globalPosition()); currItem->OldB2 = currItem->width(); currItem->OldH2 = currItem->height(); - FPointArray Clip; - FPoint npf; - double nx = newP.x(); - double ny = newP.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) - npf = m_doc->ApplyGridF(FPoint(nx, ny)); - else - npf = FPoint(nx, ny); - QTransform pp = currItem->getTransform(); - npf = npf.transformPoint(pp, true); - if (m_doc->nodeEdit.isContourLine()) - Clip = currItem->ContourLine; - else - { - Clip = currItem->PoLine; - if (currItem->isSymbol() || currItem->isGroup()) - { - if (currItem->imageFlippedH()) - { - QTransform p; - p.translate(currItem->width(), 0); - p.scale(-1, 1); - npf = npf.transformPoint(p, false); - } - if (currItem->imageFlippedV()) - { - QTransform p; - p.translate(0, currItem->height()); - p.scale(1, -1); - npf = npf.transformPoint(p, false); - } - } - } - m_doc->nodeEdit.moveClipPoint(currItem, npf); + // currItem->ClipEdited = true; + // FPointArray Clip; + // FPoint npf; + // double nx = newP.x(); + // double ny = newP.y(); + // if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) + // npf = m_doc->ApplyGridF(FPoint(nx, ny)); + // else + // npf = FPoint(nx, ny); + // QTransform pp = currItem->getTransform(); + // npf = npf.transformPoint(pp, true); + // if (m_doc->nodeEdit.isContourLine()) + // Clip = currItem->ContourLine; + // else + // { + // Clip = currItem->PoLine; + // if (currItem->isSymbol() || currItem->isGroup()) + // { + // if (currItem->imageFlippedH()) + // { + // QTransform p; + // p.translate(currItem->width(), 0); + // p.scale(-1, 1); + // npf = npf.transformPoint(p, false); + // } + // if (currItem->imageFlippedV()) + // { + // QTransform p; + // p.translate(0, currItem->height()); + // p.scale(1, -1); + // npf = npf.transformPoint(p, false); + // } + // } + // } + // m_doc->nodeEdit.moveClipPoint(currItem, npf); } + // nitramr: Scribus crashes if ESC key is pressed during mouse drag operation m_doc->adjustItemSize(currItem, true); if (!m_doc->nodeEdit.isContourLine()) { @@ -450,41 +544,58 @@ currItem->ContourLine.translate(delta.x(), delta.y()); } m_doc->regionsChanged()->update(QRectF()); - if (state) - m_doc->nodeEdit.finishTransaction2(currItem, state); - return; - } + if (onState) + m_doc->nodeEdit.finishTransaction2(currItem, onState); - m_canvas->setRenderModeUseBuffer(false); - m_doc->DragP = false; - m_doc->leaveDrag = false; - m_canvas->m_viewMode.operItemMoving = false; - m_canvas->m_viewMode.operItemResizing = false; - m_view->MidButt = false; - if (m_view->groupTransactionStarted()) - { - for (int i = 0; i < m_doc->m_Selection->count(); ++i) - m_doc->m_Selection->itemAt(i)->checkChanges(true); - m_view->endGroupTransaction(); } - //Commit drag created items to undo manager. - if (m_doc->m_Selection->itemAt(0) != nullptr) + else { - m_doc->itemAddCommit(m_doc->m_Selection->itemAt(0)); + m_canvas->setRenderModeUseBuffer(false); + m_doc->DragP = false; + m_doc->leaveDrag = false; + m_canvas->m_viewMode.operItemMoving = false; + m_canvas->m_viewMode.operItemResizing = false; + m_view->MidButt = false; + if (m_view->groupTransactionStarted()) + { + for (int i = 0; i < m_doc->m_Selection->count(); ++i) + m_doc->m_Selection->itemAt(i)->checkChanges(true); + m_view->endGroupTransaction(); + } + //Commit drag created items to undo manager. + if (m_doc->m_Selection->itemAt(0) != nullptr) + m_doc->itemAddCommit(m_doc->m_Selection->itemAt(0)); + //Make sure the Zoom spinbox and page selector don't have focus if we click on the canvas + m_view->m_ScMW->zoomSpinBox->clearFocus(); + m_view->m_ScMW->pageSelector->clearFocus(); + if (m_doc->m_Selection->itemAt(0) != nullptr) // is there the old clip stored for the undo action + { + currItem = m_doc->m_Selection->itemAt(0); + m_doc->nodeEdit.finishTransaction(currItem); + } } - //Make sure the Zoom spinbox and page selector don't have focus if we click on the canvas - m_view->m_ScMW->zoomSpinBox->clearFocus(); - m_view->m_ScMW->pageSelector->clearFocus(); - if (m_doc->m_Selection->itemAt(0) != nullptr) // is there the old clip stored for the undo action - { - currItem = m_doc->m_Selection->itemAt(0); - m_doc->nodeEdit.finishTransaction(currItem); - } + + m_doc->nodeEdit.setMoveMode(state.originalMoveMode); + state.reset(); } void CanvasMode_NodeEdit::keyPressEvent(QKeyEvent *e) { commonkeyPressEvent_NormalNodeEdit(e); + + int kk = e->key(); +// Qt::KeyboardModifiers buttonModifiers = e->modifiers(); +// ScribusMainWindow* mainWindow = m_view->m_ScMW; + PageItem* currItem = m_doc->m_Selection->itemAt(0); + + if (kk == Qt::Key_Delete || kk == Qt::Key_Backspace) + { + if (currItem && m_doc->nodeEdit.hasNodeSelected()) + { + // delete Nodes + refreshPath(deleteNodes()); + } + } } void CanvasMode_NodeEdit::keyReleaseEvent(QKeyEvent *e) @@ -494,58 +605,131 @@ void CanvasMode_NodeEdit::handleNodeEditPress(QMouseEvent* m, QRect r) { - FPoint npf2; - bool edited = false; - bool pfound = false; - PageItem* currItem = m_doc->m_Selection->itemAt(0); FPointArray Clip = m_doc->nodeEdit.beginTransaction(currItem); - m_doc->nodeEdit.deselect(); + QTransform pm2 = currItem->getTransform(); + FPoint npf2 = m_canvas->globalToCanvas(m->globalPosition()).transformPoint(pm2, true); + bool edited = false; - QTransform pm2 = currItem->getTransform(); - npf2 = m_canvas->globalToCanvas(m->globalPosition()).transformPoint(pm2, true); - if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) + state.originalMoveMode = m_doc->nodeEdit.moveMode(); + state.moveMode = state.originalMoveMode; + state.clickedOn = None; + m_doc->nodeEdit.oppositeControl.isValid = false; + + switch (state.hoveredOver) { - if (currItem->imageFlippedH()) + case ControlHandle: + case NodeHandle: + { + if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { - pm2.translate(currItem->width(), 0); - pm2.scale(-1, 1); + if (currItem->imageFlippedH()) + { + pm2.translate(currItem->width(), 0); + pm2.scale(-1, 1); + } + if (currItem->imageFlippedV()) + { + pm2.translate(0, currItem->height()); + pm2.scale(1, -1); + } } - if (currItem->imageFlippedV()) + + for (int i = 0; i < Clip.size(); ++i) { - pm2.translate(0, currItem->height()); - pm2.scale(1, -1); - } - } - for (int i = 0; i < Clip.size(); ++i) - { - if (((m_doc->nodeEdit.edPoints()) && (i % 2 != 0)) || ((!m_doc->nodeEdit.edPoints()) && (i % 2 == 0))) - continue; - QPointF npf = pm2.map(Clip.pointQF(i)); - if (m_canvas->hitsCanvasPoint(m->globalPosition(), npf)) - { - m_doc->nodeEdit.setClre(i); - if ((m_doc->nodeEdit.edPoints()) && (m_doc->nodeEdit.selNode().contains(i) == 0)) + if (m_canvas->hitsCanvasPoint(m->globalPosition(), pm2.map(Clip.pointQF(i)))) { - if (m->modifiers() != Qt::ShiftModifier) + if (state.originalMoveMode == NodeEditContext::Auto) + { + bool sameAngle = m_doc->nodeEdit.sameAngle(currItem, i); + bool sameLength = m_doc->nodeEdit.sameLength(currItem, i); + + if (sameLength && sameAngle) + state.moveMode = NodeEditContext::Symmetric; + else if (!sameLength && sameAngle) + state.moveMode = NodeEditContext::Asymmetric; + else if (!sameLength && !sameAngle) + state.moveMode = NodeEditContext::Independent; + } + + state.clickedOn = NodeEditContext::isNode(i) ? NodeHandle : ControlHandle; + + if (state.clickedOn == NodeHandle) + { + // Deselect all controls + m_doc->nodeEdit.deselectNodeControls(); + + if (!m_doc->nodeEdit.selNode().contains(i) && m->modifiers() != Qt::ShiftModifier) + m_doc->nodeEdit.selNode().clear(); + + state.wasNewlySelected = !m_doc->nodeEdit.selNode().contains(i); + + m_doc->nodeEdit.selectNode(i); + + if (m->modifiers() == Qt::AltModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) + { + // Reset Controls + if (state.moveMode != NodeEditContext::Independent || !NodeEditContext::hasTwoNodes(i, Clip)) + m_doc->nodeEdit.resetControl(currItem); + else // Make Control Handles symetric + m_doc->nodeEdit.equalizeControls(currItem); + } + } + + if (state.clickedOn == ControlHandle) + { m_doc->nodeEdit.selNode().clear(); - m_doc->nodeEdit.selectNode(i); + m_doc->nodeEdit.selectNode(i); + } + + m_doc->nodeEdit.setClre(i); + + // Save position of opposite control handle + int indexNode = NodeEditContext::indexOfNode(i, NodeEditContext::OppositeSibling, Clip.size()); + int indexControl = NodeEditContext::indexOfNode(i, NodeEditContext::Opposite, Clip.size()); + + double dx = Clip.point(indexControl).x() - Clip.point(indexNode).x(); + double dy = Clip.point(indexControl).y() - Clip.point(indexNode).y(); + + m_doc->nodeEdit.oppositeControl.isValid = NodeEditContext::hasTwoNodes(i, Clip); + m_doc->nodeEdit.oppositeControl.relativePos = Clip.point(indexControl) - Clip.point(indexNode); + m_doc->nodeEdit.oppositeControl.controlIndex = indexControl; + m_doc->nodeEdit.oppositeControl.nodeIndex = indexNode; + m_doc->nodeEdit.oppositeControl.distance = distance(dx, dy); + m_doc->nodeEdit.oppositeControl.angle = xy2Deg(dx, dy); + m_doc->nodeEdit.update(Clip.pointQF(i)); + edited = true; + + break; } - m_doc->nodeEdit.update(Clip.pointQF(i)); - pfound = true; - edited = true; - break; + + //deselect all if no node or control has hit + if (i == Clip.size() - 1) + m_doc->nodeEdit.deselect(); + } + + break; } - if ((!pfound) || (!m_doc->nodeEdit.edPoints())) - m_doc->nodeEdit.selNode().clear(); + case PathSegment: + m_doc->nodeEdit.deselect(); + state.clickedOn = PathSegment; + break; + case None: + default: + m_doc->nodeEdit.deselect(); + state.clickedOn = None; + break; + } - if ((m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) && (m_doc->nodeEdit.clre() != -1) && !m_doc->nodeEdit.hasNodeSelected()) + if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && !m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == PathSegment) { m_doc->nodeEdit.setSegP1(m_doc->nodeEdit.clre2()); m_doc->nodeEdit.setSegP2(m_doc->nodeEdit.clre2()+2); m_doc->nodeEdit.setClre(m_doc->nodeEdit.clre2()); } + + FPointArray cli; int EndInd = Clip.size(); int StartInd = 0; @@ -568,10 +752,10 @@ } } } - if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) + + if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH && (state.clickedOn == NodeHandle || state.clickedOn == PathSegment)) { - if (!m_doc->nodeEdit.edPoints()) - return; + // Path Segment if ((!m_doc->nodeEdit.hasNodeSelected()) && (m_doc->nodeEdit.clre2() != -1)) // We don't have a Point, try to add one onto the current curve segment { bool foundP = false; @@ -582,7 +766,7 @@ FPoint tangent(0, 0); FPoint nearPoint(0, 0); double nearT = 0.0; - QRect mpo2(0, 0, m_doc->guidesPrefs().grabRadius*3, m_doc->guidesPrefs().grabRadius*3); + QRect mpo2(0, 0, m_doc->guidesPrefs().grabRadius * 3, m_doc->guidesPrefs().grabRadius * 3); mpo2.moveCenter(QPoint(qRound(npf2.x()), qRound(npf2.y()))); for (int poi = 0; poi < Clip.size() - 3; poi += 4) { @@ -594,7 +778,7 @@ Bez.moveTo(a1.x(), a1.y()); Bez.cubicTo(a2.x(), a2.y(), a3.x(), a3.y(), a4.x(), a4.y()); QPolygon cli2 = Bez.toFillPolygon().toPolygon(); - for (int clp = 0; clp < cli2.size()-1; ++clp) + for (int clp = 0; clp < cli2.size() - 1; ++clp) { if (m_view->PointOnLine(cli2.point(clp), cli2.point(clp + 1), mpo2)) { @@ -604,7 +788,7 @@ while (sp < 1.0) { Clip.pointTangentNormalAt(seg, sp, &point, &tangent, &normal ); - double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y() ,2))); + double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y(), 2))); if (d1 < absDist) { foundP = true; @@ -673,6 +857,7 @@ else m_doc->nodeEdit.deselect(); } + // Node Handle if (m_doc->nodeEdit.hasNodeSelected()) { if (currItem->isPolygon()) @@ -762,69 +947,14 @@ } } } - if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) && m_doc->nodeEdit.hasNodeSelected()) + + if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT || m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && m->modifiers() == Qt::ControlModifier) && m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == NodeHandle) { - if (!m_doc->nodeEdit.edPoints()) - return; + edited = deleteNodes(); + } - int minClipSize = 4; - if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) - minClipSize = 12; - - if (Clip.size() <= minClipSize) - { - m_doc->nodeEdit.deselect(); - return; - } - - int numPoints = (StartInd != 0) ? (StartInd-4) : 0; - numPoints += (Clip.size() - EndInd); - if ((currItem->Segments.count() > 0) && ((EndInd - StartInd) <= minClipSize) && (numPoints > 0)) - { - if (StartInd != 0) - cli.putPoints(0, StartInd - 4, Clip); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - else if (m_doc->nodeEdit.clre() == StartInd) - { - if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) - { - const FPoint& kp(Clip.point(EndInd - 3)); - cli.putPoints(0, StartInd, Clip); - cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd); - cli.setPoint(StartInd, cli.point(cli.size() - 2)); - cli.setPoint(StartInd + 1, kp); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - else - { - cli.putPoints(0, StartInd, Clip); - cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd+4); - cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); - } - } - else if (m_doc->nodeEdit.clre() == (EndInd - 2)) - { - Clip.remove(m_doc->nodeEdit.clre() - 2, 4); - cli = Clip.copy(); - } - else - { - if (m_doc->nodeEdit.clre() != 0) - cli.putPoints(0, m_doc->nodeEdit.clre(), Clip); - cli.putPoints(cli.size(), Clip.size() - (m_doc->nodeEdit.clre() + 4), Clip, m_doc->nodeEdit.clre() + 4); - } - while (cli.isMarker(0)) - cli.remove(0, 4); - if (m_doc->nodeEdit.isContourLine()) - currItem->ContourLine = cli.copy(); - else - currItem->PoLine = cli.copy(); - m_doc->nodeEdit.deselect(); - currItem->ClipEdited = true; - edited = true; - } - if ((m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT) && (m_doc->nodeEdit.clre2() != -1)) + // Add node id "add point" mode OR "move point" mode + CTRL + if ((m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT || m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && m->modifiers() == Qt::ControlModifier) && (m_doc->nodeEdit.clre2() != -1)) { bool foundP = false; uint seg = 0; @@ -856,7 +986,7 @@ while (sp < 1.0) { Clip.pointTangentNormalAt(seg, sp, &point, &tangent, &normal ); - double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y() ,2))); + double d1 = fabs(sqrt(pow(point.x() - npf2.x(), 2) + pow(point.y() - npf2.y(), 2))); if (d1 < absDist) { foundP = true; @@ -911,30 +1041,11 @@ currItem->ClipEdited = true; edited = true; } - if (edited) + + refreshPath(edited); + + if ((m_doc->nodeEdit.selectionCount() != 0) || ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) || (m_doc->nodeEdit.hasNodeSelected() && state.clickedOn == ControlHandle)) { - currItem->FrameType = 3; - double xp = currItem->xPos(); - double yp = currItem->yPos(); - double w = currItem->width(); - double h = currItem->height(); - m_doc->adjustItemSize(currItem, true); - double xp2 = currItem->xPos(); - double yp2 = currItem->yPos(); - double w2 = currItem->width(); - double h2 = currItem->height(); - currItem->update(); - if ((xp != xp2) || (yp != yp2) || (w != w2) || (h != h2)) - m_view->DrawNew(); - emit m_view->PolyStatus(currItem->itemType(), currItem->PoLine.size()); - } - else - { - currItem->update(); - m_view->DrawNew(); - } - if ((m_doc->nodeEdit.selectionCount() != 0) || ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) || (m_doc->nodeEdit.hasNodeSelected() && (!m_doc->nodeEdit.edPoints()))) - { m_Mxp = m->position().x(); m_Myp = m->position().y(); m_canvas->setRenderModeFillBuffer(); @@ -950,11 +1061,9 @@ m_rectangleSelect->prepare(m->globalPosition()); m_view->startGesture(m_rectangleSelect); } - + } - - bool CanvasMode_NodeEdit::handleNodeEditMove(QMouseEvent* m, QRect /*r*/, PageItem* currItem, const QTransform& /*t*/) { QTransform itemPos = currItem->getTransform(); @@ -965,6 +1074,7 @@ itemPos.translate(currItem->width(), 0); itemPos.scale(-1, 1); } + if (currItem->imageFlippedV()) { itemPos.translate(0, currItem->height()); @@ -971,38 +1081,49 @@ itemPos.scale(1, -1); } } - FPointArray Clip; + FPointArray Clip = m_doc->nodeEdit.clipFromItem(currItem); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); - if (m_doc->nodeEdit.isContourLine()) - Clip = currItem->ContourLine; - else - Clip = currItem->PoLine; + state.hoveredOver = None; + + // Hover mouse on node or control handle if ((m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) || - (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH)) + (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) || + (m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT)) { for (int i = 0; i < Clip.size(); ++i) { - if (((m_doc->nodeEdit.edPoints()) && (i % 2 != 0)) || ((!m_doc->nodeEdit.edPoints()) && (i % 2 == 0))) - continue; QPointF point = Clip.pointQF(i); if (m_canvas->hitsCanvasPoint(m->globalPosition(), itemPos.map(point))) { if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) - m_view->setCursor(QCursor(Qt::SizeAllCursor)); - if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) - m_view->setCursor(IconManager::instance().loadCursor("cursor-remove-point", 1, 1)); - if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) - m_view->setCursor(IconManager::instance().loadCursor("cursor-split", 1, 1)); + m_view->setCursor(IconManager::instance().loadCursor("cursor-move", 1, 1)); + else if (m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) + m_view->setCursor(IconManager::instance().loadCursor("cursor-node-edit", 1, 1)); + + if (NodeEditContext::isNode(i)) + { + if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && m->modifiers() == Qt::ControlModifier)) + m_view->setCursor(IconManager::instance().loadCursor("cursor-remove-point", 1, 1)); + else if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) + m_view->setCursor(IconManager::instance().loadCursor("cursor-split", 1, 1)); + + state.hoveredOver = NodeHandle; + } + else + state.hoveredOver = ControlHandle; + return true; } } } + + // Hover mouse on path segment if ((m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT) || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) || - (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH && m_doc->nodeEdit.edPoints())) + (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH)) { for (int poi = 0; poi < Clip.size() - 3; poi += 4) { @@ -1015,6 +1136,7 @@ Bez.moveTo(a1); Bez.cubicTo(a2, a3, a4); double len = Bez.length(); + if (len > 0.0) { double delta = 2.0 / len; @@ -1023,15 +1145,17 @@ QPointF pl = Bez.pointAtPercent(d); if (m_canvas->hitsCanvasPoint(m->globalPosition(), FPoint(pl.x(), pl.y()))) { - if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) + if (m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT || (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT && m->modifiers() == Qt::ControlModifier)) + m_view->setCursor(IconManager::instance().loadCursor("cursor-add-point", 1, 1)); + else if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) m_view->setCursor(IconManager::instance().loadCursor("cursor-hand")); - else if (m_doc->nodeEdit.submode() == NodeEditContext::ADD_POINT) - m_view->setCursor(IconManager::instance().loadCursor("cursor-add-point", 1, 1)); else if (m_doc->nodeEdit.submode() == NodeEditContext::SPLIT_PATH) m_view->setCursor(IconManager::instance().loadCursor("cursor-split", 1, 1)); else m_view->setCursor(QCursor(Qt::ArrowCursor)); + m_doc->nodeEdit.setClre2(poi); + state.hoveredOver = PathSegment; return true; } } @@ -1044,6 +1168,7 @@ void CanvasMode_NodeEdit::handleNodeEditDrag(QMouseEvent* m, PageItem* currItem) { + // Selection Frame if ((m_canvas->m_viewMode.m_MouseButtonPressed) && !m_doc->nodeEdit.hasNodeSelected() && (m_doc->nodeEdit.segP1() == -1) && (m_doc->nodeEdit.segP2() == -1)) { if (!m_rectangleSelect) @@ -1052,6 +1177,7 @@ m_view->startGesture(m_rectangleSelect); return; } + FPoint npf; double newX = m->position().x(); double newY = m->position().y(); @@ -1059,6 +1185,8 @@ np = np * (1.0 / m_canvas->scale()); m_canvas->m_viewMode.operItemMoving = true; currItem = m_doc->m_Selection->itemAt(0); + + // Move Path Segment if ((m_doc->nodeEdit.segP1() != -1) && (m_doc->nodeEdit.segP2() != -1)) { if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) @@ -1068,37 +1196,38 @@ if (currItem->imageFlippedV()) np.setY(np.y() * -1); } - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.segP2()) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.segP2()) + np; + + // First segment + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.segP2()) + np; m_doc->nodeEdit.setClre(m_doc->nodeEdit.segP2()); m_doc->nodeEdit.moveClipPoint(currItem, npf); - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.segP1() ) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.segP1() ) + np; + + // Second segment + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.segP1()) + np; m_doc->nodeEdit.setClre(m_doc->nodeEdit.segP1()); m_doc->nodeEdit.moveClipPoint(currItem, npf); m_Mxp = newX; m_Myp = newY; } + // Move Nodes or Controls else { - if ((m_doc->nodeEdit.selectionCount() != 0) && (m_doc->nodeEdit.edPoints())) + if ((m_doc->nodeEdit.selectionCount() > 0) && (state.clickedOn == NodeHandle || state.clickedOn == ControlHandle)) { - int storedClRe = m_doc->nodeEdit.clre(); + // Single Nodes if (m_doc->nodeEdit.selectionCount() == 1) { npf = m_canvas->globalToCanvas(m->globalPosition()); double nx = npf.x(); double ny = npf.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) + if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny, true)) npf = m_doc->ApplyGridF(FPoint(nx, ny)); else npf = FPoint(nx, ny); + QTransform pp = currItem->getTransform(); npf = npf.transformPoint(pp, true); + if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { if (currItem->imageFlippedH()) @@ -1116,55 +1245,81 @@ npf = npf.transformPoint(p, false); } } - FPoint npf1(npf); - //Control Modifier to have parallel shape - if (m->modifiers() == Qt::ControlModifier) + + int curr = m_doc->nodeEdit.clre(); + FPoint pOpposite = m_doc->nodeEdit.getPoint(currItem, m_doc->nodeEdit.oppositeControl.nodeIndex); + FPoint pSibling = m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Sibling); + + if (m->modifiers() & Qt::ShiftModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT) { - FPointArray cli; - if (m_doc->nodeEdit.isContourLine()) - cli = currItem->ContourLine; - else - cli = currItem->PoLine; - int tmpNode; - int curr = (m_doc->nodeEdit.clre() == 0) ? cli.size() - 2 : m_doc->nodeEdit.clre(); - int prev = (curr + cli.size() - 4) % cli.size(); - int next = (curr + 4) % cli.size(); + // Shift + drag on control + if (!NodeEditContext::isNode(curr) && state.clickedOn == ControlHandle) + { + m_doc->nodeEdit.setMoveMode(NodeEditContext::Independent); - if (std::abs(cli.point(prev).x() - cli.point(curr).x()) < std::abs(cli.point(next).x() - cli.point(curr).x())) - tmpNode = next; - else if (std::abs(cli.point(prev).x() - cli.point(curr).x()) == std::abs(cli.point(next).x() - cli.point(curr).x())) + if (m_doc->nodeEdit.oppositeControl.isValid) + { + m_doc->nodeEdit.setClre(m_doc->nodeEdit.oppositeControl.controlIndex); + m_doc->nodeEdit.moveClipPoint(currItem, pOpposite + m_doc->nodeEdit.oppositeControl.relativePos); + m_doc->nodeEdit.setClre(curr); + } + } + + // Shift + drag on node + if (NodeEditContext::isNode(curr) && state.clickedOn == NodeHandle) { - if (cli.point(prev).y() != cli.point(curr).y()) - tmpNode = next; - else - tmpNode = prev; + m_doc->nodeEdit.setMoveMode(NodeEditContext::Symmetric); + m_doc->nodeEdit.setClre(curr + 1); } - else - tmpNode = prev; + } + else + m_doc->nodeEdit.setMoveMode(state.moveMode); - m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_doc->nodeEdit.setClre(tmpNode); + if (m->modifiers() & Qt::ControlModifier) + { + // Snap control handle at 15° steps + if (!NodeEditContext::isNode(curr)) + npf = constraintControlAngle(m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Sibling), npf, 15.0); - if (m_doc->nodeEdit.isContourLine()) - npf1.setX(currItem->ContourLine.point(m_doc->nodeEdit.clre()).x()); - else - npf1.setX(currItem->PoLine.point(m_doc->nodeEdit.clre()).x()); + } - m_doc->nodeEdit.moveClipPoint(currItem, npf1); + // Snap control handle + if (!NodeEditContext::isNode(curr) && state.clickedOn == ControlHandle) + { + state.isSameAngle = m_doc->nodeEdit.sameAngle(currItem, curr); + state.isSameLength = m_doc->nodeEdit.sameLength(currItem, curr); - if (m_doc->nodeEdit.clre() == prev) - m_doc->nodeEdit.setClre(next); - else - m_doc->nodeEdit.setClre(prev); + bool modifier = m->modifiers() & Qt::ShiftModifier || m_doc->nodeEdit.submode() == NodeEditContext::EDIT_POINT; - if (m_doc->nodeEdit.isContourLine()) - npf.setY(currItem->ContourLine.point(m_doc->nodeEdit.clre()).y()); - else - npf.setY(currItem->PoLine.point(m_doc->nodeEdit.clre()).y()); + // let's snap the control angle to opposite reference + if (state.isSameAngle && (m_doc->nodeEdit.moveMode() == NodeEditContext::Auto || m_doc->nodeEdit.moveMode() == NodeEditContext::Independent || modifier)) + npf = snapControlAngle(pOpposite, npf, m_doc->nodeEdit.oppositeControl.angle - 180.0); + + // let's snap the control length to opposite reference + if (state.isSameLength && state.isSameAngle && (m_doc->nodeEdit.moveMode() != NodeEditContext::Symmetric || modifier)) + npf = snapControlLength(pOpposite, npf, m_doc->nodeEdit.oppositeControl.distance); + + // let's snap the control to same position as control anchor if control is too close + npf = snapControlOrigin(pSibling, npf); } + + // Snap node handle + // if (NodeEditContext::isNode(curr) && state.clickedOn == NodeHandle) + // { + // FPointArray Clip = m_doc->nodeEdit.isContourLine() ? currItem->ContourLine : currItem->PoLine; + // FPoint pOppositeNode = m_doc->nodeEdit.getPoint(currItem, curr, NodeEditContext::Opposite); + + // if (curr == 0 || curr == Clip.size() -2) + // { + // // TODO: close bezier curve and merge endpoints + // npf = snapControlOrigin(pOppositeNode, npf); + // } + // } + m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf1.y()); + m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf.y()); } + // Multiple Nodes else { if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) @@ -1174,53 +1329,147 @@ if (currItem->imageFlippedV()) np.setY(np.y() * -1); } + const auto& nodeSelection = m_doc->nodeEdit.selNode(); for (int itm = 0; itm < nodeSelection.count(); ++itm) { m_doc->nodeEdit.setClre(nodeSelection.at(itm)); - if (m_doc->nodeEdit.isContourLine()) - npf = currItem->ContourLine.point(m_doc->nodeEdit.clre()) + np; - else - npf = currItem->PoLine.point(m_doc->nodeEdit.clre()) + np; + npf = m_doc->nodeEdit.clipFromItem(currItem).point(m_doc->nodeEdit.clre()) + np; m_doc->nodeEdit.moveClipPoint(currItem, npf); } } - m_doc->nodeEdit.setClre(storedClRe); } - else + + m_Mxp = newX; + m_Myp = newY; + + } + m_canvas->m_viewMode.operItemMoving = false; + m_doc->regionsChanged()->update(QRectF()); +} + +bool CanvasMode_NodeEdit::deleteNodes() +{ + PageItem* currItem = m_doc->m_Selection->itemAt(0); + FPointArray Clip = m_doc->nodeEdit.beginTransaction(currItem); + + if (!currItem || Clip.size() == 0) + return false; + + FPointArray cli; + int EndInd = Clip.size(); + int StartInd = 0; + if (m_doc->nodeEdit.clre() > 0) + { + for (int n = m_doc->nodeEdit.clre(); n < Clip.size(); ++n) { - npf = m_canvas->globalToCanvas(m->globalPosition()); - double nx = npf.x(); - double ny = npf.y(); - if (!m_doc->ApplyGuides(&nx, &ny) && !m_doc->ApplyGuides(&nx, &ny,true)) - npf = m_doc->ApplyGridF(FPoint(nx, ny)); + if (Clip.isMarker(n)) + { + EndInd = n; + break; + } + } + for (int n2 = m_doc->nodeEdit.clre(); n2 > 0; n2--) + { + if (Clip.isMarker(n2)) + { + StartInd = n2 + 1; + break; + } + } + } + + if (m_doc->nodeEdit.hasNodeSelected()) + { + int minClipSize = 4; + if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) + minClipSize = 12; + + if (Clip.size() <= minClipSize) + { + m_doc->nodeEdit.deselect(); + return false; + } + + int numPoints = (StartInd != 0) ? (StartInd-4) : 0; + numPoints += (Clip.size() - EndInd); + if ((currItem->Segments.count() > 0) && ((EndInd - StartInd) <= minClipSize) && (numPoints > 0)) + { + if (StartInd != 0) + cli.putPoints(0, StartInd - 4, Clip); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); + } + else if (m_doc->nodeEdit.clre() == StartInd) + { + if (!currItem->isLine() && !currItem->isPathText() && !currItem->isPolyLine()) + { + const FPoint& kp(Clip.point(EndInd - 3)); + cli.putPoints(0, StartInd, Clip); + cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd); + cli.setPoint(StartInd, cli.point(cli.size() - 2)); + cli.setPoint(StartInd + 1, kp); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); + } else - npf = FPoint(nx, ny); - QTransform pp = currItem->getTransform(); - npf = npf.transformPoint(pp, true); - if ((currItem->isSymbol() || currItem->isGroup()) && (!m_doc->nodeEdit.isContourLine())) { - if (currItem->imageFlippedH()) - { - QTransform p; - p.translate(currItem->width(), 0); - p.scale(-1, 1); - npf = npf.transformPoint(p, false); - } - if (currItem->imageFlippedV()) - { - QTransform p; - p.translate(0, currItem->height()); - p.scale(1, -1); - npf = npf.transformPoint(p, false); - } + cli.putPoints(0, StartInd, Clip); + cli.putPoints(cli.size(), EndInd - StartInd - 4, Clip, StartInd+4); + cli.putPoints(cli.size(), Clip.size() - EndInd, Clip, EndInd); } - m_doc->nodeEdit.moveClipPoint(currItem, npf); - m_canvas->displayXYHUD(m->globalPosition(), npf.x(), npf.y()); } - m_Mxp = newX; - m_Myp = newY; + else if (m_doc->nodeEdit.clre() == (EndInd - 2)) + { + Clip.remove(m_doc->nodeEdit.clre() - 2, 4); + cli = Clip.copy(); + } + else + { + if (m_doc->nodeEdit.clre() != 0) + cli.putPoints(0, m_doc->nodeEdit.clre(), Clip); + cli.putPoints(cli.size(), Clip.size() - (m_doc->nodeEdit.clre() + 4), Clip, m_doc->nodeEdit.clre() + 4); + } + while (cli.isMarker(0)) + cli.remove(0, 4); + if (m_doc->nodeEdit.isContourLine()) + currItem->ContourLine = cli.copy(); + else + currItem->PoLine = cli.copy(); + m_doc->nodeEdit.deselect(); + currItem->ClipEdited = true; + return true; } - m_canvas->m_viewMode.operItemMoving = false; - m_doc->regionsChanged()->update(QRectF()); + + + return false; } + +void CanvasMode_NodeEdit::refreshPath(bool edited) +{ + PageItem* currItem = m_doc->m_Selection->itemAt(0); + + if (!currItem) + return; + + if (edited) + { + currItem->FrameType = 3; + double xp = currItem->xPos(); + double yp = currItem->yPos(); + double w = currItem->width(); + double h = currItem->height(); + m_doc->adjustItemSize(currItem, true); + double xp2 = currItem->xPos(); + double yp2 = currItem->yPos(); + double w2 = currItem->width(); + double h2 = currItem->height(); + currItem->update(); + if ((xp != xp2) || (yp != yp2) || (w != w2) || (h != h2)) + m_view->DrawNew(); + emit m_view->PolyStatus(currItem->itemType(), currItem->PoLine.size()); + } + else + { + currItem->update(); + m_view->DrawNew(); + } +} Index: scribus/canvasmode_nodeedit.h =================================================================== --- scribus/canvasmode_nodeedit.h (Revision 26704) +++ scribus/canvasmode_nodeedit.h (Arbeitskopie) @@ -19,6 +19,7 @@ #define CANVAS_MODE_NODEEDIT_H #include "canvasmode.h" +#include "nodeeditcontext.h" #include <QTransform> #include <QRect> @@ -38,8 +39,8 @@ void activate(bool fromgesture) override; void deactivate(bool forGesture) override; - void enterEvent(QEvent *) override; - void leaveEvent(QEvent *) override; + void enterEvent(QEvent *) override {}; + void leaveEvent(QEvent *) override {}; void mouseDoubleClickEvent(QMouseEvent *m) override; void mouseReleaseEvent(QMouseEvent *m) override; @@ -55,13 +56,45 @@ void drawControls(QPainter* p) override; private: + + enum MouseMode { None = 0, NodeHandle, ControlHandle, PathSegment }; + + struct StateManager { + MouseMode clickedOn { None }; + MouseMode hoveredOver { None }; + bool isSameLength {false}; + bool isSameAngle {false}; + bool wasNewlySelected { false }; + NodeEditContext::ControlMoveMode originalMoveMode { NodeEditContext::ControlMoveMode::Auto }; + NodeEditContext::ControlMoveMode moveMode { originalMoveMode }; // we need this for converted auto mode + + void reset() + { + clickedOn = None; + isSameLength = false; + isSameAngle = false; + wasNewlySelected = false; + moveMode = NodeEditContext::ControlMoveMode::Auto; + } + }; + inline bool GetItem(PageItem** pi); + QMap<int, int> selectedNodesList(FPointArray points); void handleNodeEditPress(QMouseEvent*, QRect r); void handleNodeEditDrag(QMouseEvent*, PageItem*); bool handleNodeEditMove(QMouseEvent*, QRect r, PageItem*, const QTransform&); + FPoint constraintControlAngle(FPoint anchor, FPoint mouse, double angleStep); + FPoint snapControlAngle(FPoint anchor, FPoint mouse, double angle); + FPoint snapControlLength(FPoint anchor, FPoint mouse, double length); + FPoint snapControlOrigin(FPoint anchor, FPoint mouse); + bool deleteNodes(); + void updateItem(PageItem* currItem); + void refreshPath(bool edited); + ScribusMainWindow* m_ScMW { nullptr }; RectSelect* m_rectangleSelect { nullptr }; + StateManager state; double m_Mxp { -1.0 }; // last mouse position double m_Myp { -1.0 }; @@ -71,6 +104,7 @@ double m_GyM { -1.0 }; bool m_MoveGX { false }; bool m_MoveGY { false }; + }; Index: scribus/nodeeditcontext.cpp =================================================================== --- scribus/nodeeditcontext.cpp (Revision 26704) +++ scribus/nodeeditcontext.cpp (Arbeitskopie) @@ -23,9 +23,34 @@ void NodeEditContext::selectNode(int i) { m_ClRe = i; - m_SelNode.append(i); + if (!m_SelNode.contains(i)) + m_SelNode.append(i); } +void NodeEditContext::deselectNode(int i) +{ + int index = m_SelNode.indexOf(i); + m_SelNode.removeAll(i); + if (m_SelNode.count() == 0) + m_ClRe = -1; + else // select previous node or last one if there is no previous one + m_ClRe = (index - 1 >= 0) ? m_SelNode.at(index - 1) : m_SelNode.last(); +} + +void NodeEditContext::deselectNodeControls() +{ + for (int i = 0; i < m_SelNode.count(); i++) + { + if (!isNode(m_SelNode.at(i))) + { + m_SelNode.removeAt(i); + i --; + } + } + if (m_SelNode.count() == 0) + m_ClRe = -1; +} + int NodeEditContext::selectionCount() const { return m_SelNode.count(); @@ -42,7 +67,7 @@ delete oldClip; oldClip = nullptr; nodeTransaction.reset(); - m_MoveSym = false; + m_moveMode = Auto; m_SelNode.clear(); m_preview = false; } @@ -83,8 +108,6 @@ return Clip; } - - void NodeEditContext::finishTransaction(PageItem* currItem) { ScribusDoc* Doc = currItem->doc(); @@ -116,7 +139,6 @@ oldClip = nullptr; } - /** first part: create a new UndoState or cancel the current transaction */ @@ -167,25 +189,28 @@ delete oldClip; oldClip = nullptr; } - //CB-->Doc void NodeEditContext::moveClipPoint(PageItem *currItem, const FPoint& ip) { ScribusDoc* Doc = currItem->doc(); - if (((m_EdPoints) && (m_ClRe % 2 != 0)) || ((!m_EdPoints) && (m_ClRe % 2 == 0))) - return; + double xposOrig = currItem->xPos(); double yposOrig = currItem->yPos(); currItem->ClipEdited = true; + FPointArray Clip; + FPoint np(ip); + if (m_isContourLine) Clip = currItem->ContourLine.copy(); else Clip = currItem->PoLine.copy(); currItem->FrameType = 3; + uint EndInd = Clip.size(); uint StartInd = 0; + for (int n = m_ClRe; n < Clip.size(); ++n) { if (Clip.isMarker(n)) @@ -194,6 +219,7 @@ break; } } + if (m_ClRe > 0) { for (int n2 = m_ClRe; n2 > 0; n2--) @@ -205,9 +231,10 @@ } } } - FPoint np(ip); + if (hasNodeSelected()) { + // expand item on left side if x is out of bound if ((np.x() < 0) && (!m_isContourLine) && (!(currItem->isGroup() || currItem->isSymbol()))) { Doc->sizeItem(currItem->width() - np.x(), currItem->height(), currItem, false, false, false); @@ -223,6 +250,8 @@ currItem->moveImageInFrame(-np.x()/currItem->imageXScale(), 0); np.setX(0); } + + // expand item on top side if y is out of bound if ((np.y() < 0) && (!m_isContourLine) && (!(currItem->isGroup() || currItem->isSymbol()))) { Doc->sizeItem(currItem->width(), currItem->height() - np.y(), currItem, false, false, false); @@ -239,7 +268,9 @@ np.setY(0); } update(QPointF(np.x(), np.y())); - if ((m_ClRe + 1 < static_cast<int>(EndInd)) && (m_ClRe % 2 == 0)) + + // update control of node + if (isNode(m_ClRe) && (m_ClRe + 1 < static_cast<int>(EndInd))) { FPoint ap(Clip.point(m_ClRe)); FPoint ap2(Clip.point(m_ClRe + 1)); @@ -247,8 +278,11 @@ ap2.setY(ap2.y() - (ap.y() - np.y())); Clip.setPoint(m_ClRe + 1, ap2); } + Clip.setPoint(m_ClRe, np); - if (((m_ClRe % 4 != 0) && (m_ClRe % 2 == 0)) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) + + // update right node with control + if (isLeftNode(m_ClRe) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) { FPoint ap(Clip.point(m_ClRe + 2)); FPoint ap2(Clip.point(m_ClRe + 3)); @@ -257,7 +291,9 @@ Clip.setPoint(m_ClRe + 3, ap2); Clip.setPoint(m_ClRe + 2, np); } - if ((m_ClRe % 4 == 0) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) + + // update left node with control + if (isRightNode(m_ClRe) && (m_ClRe + 3 < static_cast<int>(EndInd)) && (m_ClRe != static_cast<int>(StartInd))) { FPoint ap(Clip.point(m_ClRe - 2)); FPoint ap2(Clip.point(m_ClRe - 1)); @@ -266,6 +302,8 @@ Clip.setPoint(m_ClRe - 1, ap2); Clip.setPoint(m_ClRe - 2, np); } + + // update first or last node with control if (((m_ClRe == static_cast<int>(StartInd)) || (m_ClRe == static_cast<int>(EndInd - 2))) && ((currItem->itemType() == PageItem::Polygon) || (currItem->itemType() == PageItem::Group) @@ -275,7 +313,7 @@ { if (m_ClRe == static_cast<int>(StartInd)) { - FPoint ap(Clip.point(EndInd- 2)); + FPoint ap(Clip.point(EndInd - 2)); FPoint ap2(Clip.point(EndInd - 1)); ap2.setX(ap2.x() - (ap.x() - np.x())); ap2.setY(ap2.y() - (ap.y() - np.y())); @@ -292,48 +330,53 @@ Clip.setPoint(StartInd + 1, ap2); } } - if (((m_ClRe == static_cast<int>(StartInd + 1)) || (m_ClRe == static_cast<int>(EndInd - 1))) && - ((currItem->itemType() == PageItem::Polygon) + + bool firstLastControl = ((m_ClRe == static_cast<int>(StartInd + 1)) || (m_ClRe == static_cast<int>(EndInd - 1))) + && ((currItem->itemType() == PageItem::Polygon) || (currItem->itemType() == PageItem::Group) || (currItem->itemType() == PageItem::Symbol) || (currItem->itemType() == PageItem::TextFrame) - || (currItem->itemType() == PageItem::ImageFrame)) && - (m_MoveSym)) + || (currItem->itemType() == PageItem::ImageFrame)); + + bool anyControl = !isNode(m_ClRe) && (m_ClRe != static_cast<int>(StartInd + 1)) && (m_ClRe != static_cast<int>(EndInd - 1)); + + // update control if there is an opposite control + if (firstLastControl || anyControl) { - uint kon = 0; - if (m_ClRe == static_cast<int>(StartInd + 1)) - kon = EndInd - 1; - else - kon = StartInd + 1; - FPoint lxy(Clip.point(m_ClRe - 1)); - FPoint lk(Clip.point(m_ClRe)); - double dx = lxy.x() - lk.x(); - double dy = lxy.y() - lk.y(); - lk.setX(lk.x() + dx * 2); - lk.setY(lk.y() + dy * 2); - Clip.setPoint(kon, lk); + int kon = indexOfNode(m_ClRe, Opposite, Clip.size()); + + FPoint pn1(Clip.point(m_ClRe - 1)); + FPoint pc1(Clip.point(m_ClRe)); + FPoint pc2(pn1 + oppositeControl.relativePos); + + double dx = pn1.x() - pc1.x(); + double dy = pn1.y() - pc1.y(); + + if (moveMode() == Symmetric) + { + pc2.setX(pc1.x() + dx * 2); + pc2.setY(pc1.y() + dy * 2); + Clip.setPoint(kon, pc2); + } + else if (moveMode() == Asymmetric) + { + double dx2 = pn1.x() - pc2.x(); + double dy2 = pn1.y() - pc2.y(); + + double angle = xy2Deg(dx, dy); + double dist = distance(dx2, dy2); + Clip.setPoint(kon, getPointFromRotation(pn1, dist, angle)); + } } - if ((m_ClRe % 2 != 0) && (m_MoveSym) && (m_ClRe != static_cast<int>(StartInd + 1)) && (m_ClRe != static_cast<int>(EndInd - 1))) - { - uint kon = 0; - if ((m_ClRe + 1) % 4 == 0) - kon = m_ClRe + 2; - else - kon = m_ClRe - 2; - FPoint lxy(Clip.point(m_ClRe - 1)); - FPoint lk(Clip.point(m_ClRe)); - double dx = lxy.x() - lk.x(); - double dy = lxy.y() - lk.y(); - lk.setX(lk.x() + dx * 2); - lk.setY(lk.y() + dy * 2); - Clip.setPoint(kon, lk); - } + if (Doc->nodeEdit.m_isContourLine) currItem->ContourLine = Clip.copy(); else currItem->PoLine = Clip.copy(); + currItem->Clip = flattenPath(currItem->PoLine, currItem->Segments); } + if (!m_isContourLine) { // Move the contour accordingly in the item's coordinate space @@ -343,12 +386,265 @@ } } +void NodeEditContext::equalizeControls(PageItem* currItem) +{ + FPointArray Clip(clipFromItem(currItem)); + int old = m_ClRe; + ControlMoveMode cmm = moveMode(); + FPoint cl, nl, nr, cr; + getPoints(currItem, m_ClRe, cl, nl, nr, cr); + + // if there is no left control we use the right one as reference + int indexControl = indexOfNode(m_ClRe, (nl == cl) ? ControlRight : ControlLeft, Clip.size()); + + // apply changes to opposite control + setMoveMode(Symmetric); + setClre(indexControl); + moveClipPoint(currItem, Clip.point(indexControl)); + + // restore old values + setClre(old); + setMoveMode(cmm); +} + +/** + * @brief NodeEditContext::getPoints Map all handle indecies based on i. A bezier curve start with right node and ends with left node + * @param currItem Current PageItem + * @param i index of any point in page item path + * @param ctrlLeft return index of left control handle + * @param nodeLeft return index of left node + * @param nodeRight return index of right node + * @param ctrlRight return index of right control handle + */ +void NodeEditContext::getPoints(PageItem* currItem, int i, FPoint& ctrlLeft, FPoint& nodeLeft, FPoint& nodeRight, FPoint& ctrlRight) +{ + i = (i == -1) ? m_ClRe : i; + + if (i < 0) + return; + + nodeLeft = getPoint(currItem, i, NodeType::NodeLeft); + ctrlLeft = getPoint(currItem, i, NodeType::ControlLeft); + nodeRight = getPoint(currItem, i, NodeType::NodeRight); + ctrlRight = getPoint(currItem, i, NodeType::ControlRight); +} + +FPoint NodeEditContext::getPoint(PageItem* currItem, int i, NodeType nodeType) +{ + FPointArray Clip(clipFromItem(currItem)); + return Clip.point(indexOfNode(i, nodeType, Clip.size())); +} + +bool NodeEditContext::isSymetric(PageItem* currItem, int i) +{ + return sameAngle(currItem, i) && sameLength(currItem, i); +} + +bool NodeEditContext::sameAngle(PageItem* currItem, int i) +{ + double snap = currItem->doc()->prefsData().guidesPrefs.guideRad / m_scale; + + FPoint pcl, pnl, pnr, pcr; + getPoints(currItem, i, pcl, pnl, pnr, pcr); + + if (pnr != pnl) + return false; + + QLineF lineLeft(pnl.toQPointF(), pcl.toQPointF()); + QLineF lineRight(pnr.toQPointF(), pcr.toQPointF()); + lineRight.setAngle(lineRight.angle() - 180); + + if (qMax(lineLeft.angle(), lineRight.angle()) - qMin(lineLeft.angle(), lineRight.angle()) <= 0.00001) + return true; + + if (lineLeft.length() >= lineRight.length()) + lineRight.setLength(lineLeft.length()); + else + lineLeft.setLength(lineRight.length()); + + QLineF distance(lineLeft.p2(), lineRight.p2()); + + return distance.length() <= snap ? true : false; +} + +bool NodeEditContext::sameLength(PageItem* currItem, int i) +{ + double snap = currItem->doc()->prefsData().guidesPrefs.guideRad / m_scale; + FPoint pcl, pnl, pnr, pcr; + getPoints(currItem, i, pcl, pnl, pnr, pcr); + + if (pnl != pnr) + return false; + + // length = 0 + if (pcl == pcr) + return true; + + double l1 = QLineF(pnl.toQPointF(), pcl.toQPointF()).length(); + double l2 = QLineF(pnr.toQPointF(), pcr.toQPointF()).length(); + + // Check if both controls have same length with difference + return (qMax(l1, l2) - qMin(l1, l2) <= snap) ? true : false; +} + +bool NodeEditContext::isLeftControl(int i) +{ + return (i + 1) % 4 == 0; +} + +bool NodeEditContext::isLeftNode(int i) +{ + return (i % 4 != 0) && isNode(i); +} + +bool NodeEditContext::isRightNode(int i) +{ + return i % 4 == 0; +} + +bool NodeEditContext::isRightControl(int i) +{ + return (i - 1) % 4 == 0; +} + +bool NodeEditContext::hasTwoNodes(int i, FPointArray clip) +{ + int ln = indexOfNode(i, NodeLeft, clip.size()); + int rn = indexOfNode(i, NodeRight, clip.size()); + + return clip.point(ln) == clip.point(rn); +} + +bool NodeEditContext::isSharpNode(int i, FPointArray clip) +{ + int lc = indexOfNode(i, ControlLeft, clip.size()); + int rc = indexOfNode(i, ControlRight, clip.size()); + int ln = indexOfNode(i, NodeLeft, clip.size()); + int rn = indexOfNode(i, NodeRight, clip.size()); + + return clip.point(ln) == clip.point(rn) && clip.point(ln) == clip.point(lc) && clip.point(rn) == clip.point(rc); +} + +/** + * @brief NodeEditContext::indexOfNode Returns the node index based on the input index and node type. + * If size is specified, an index between 0 and size is returned. Values outside this range are corrected. + * @param i Any index of FPointArray + * @param type Node type for output + * @param size Max size of node array + * @return return index of requested node type based on input index. + */ +int NodeEditContext::indexOfNode(int i, NodeType type, int size) +{ + if (size != -1) + i = qBound(0, i, size); + + if (isRightControl(i)) + { + switch (type) + { + case NodeType::OppositeSibling: + case NodeType::NodeLeft: + i -= 3; + break; + case NodeType::Opposite: + case NodeType::ControlLeft: + i -= 2; + break; + case NodeType::Sibling: + case NodeType::NodeRight: + i -= 1; + break; + case NodeType::Self: + case NodeType::ControlRight: + break; + } + + if (size != -1 && i < 0) + i = size - abs(i); + } + else if (isLeftControl(i)) + { + switch (type) + { + case NodeType::Sibling: + case NodeType::NodeLeft: + i -= 1; + break; + case NodeType::Self: + case NodeType::ControlLeft: + break; + case NodeType::OppositeSibling: + case NodeType::NodeRight: + i += 1; + break; + case NodeType::Opposite: + case NodeType::ControlRight: + i += 2; + break; + } + + if (size != -1 && i >= size) + i -= size; + } + else if (isRightNode(i)) + { + switch (type) + { + case NodeType::Opposite: + case NodeType::NodeLeft: + i -= 2; + break; + case NodeType::OppositeSibling: + case NodeType::ControlLeft: + i -= 1; + break; + case NodeType::Self: + case NodeType::NodeRight: + break; + case NodeType::Sibling: + case NodeType::ControlRight: + i += 1; + break; + } + + if (size != -1 && i < 0) + i = size - abs(i); + } + else if (isLeftNode(i)) + { + switch (type) + { + case NodeType::Self: + case NodeType::NodeLeft: + break; + case NodeType::Sibling: + case NodeType::ControlLeft: + i += 1; + break; + case NodeType::Opposite: + case NodeType::NodeRight: + i += 2; + break; + case NodeType::OppositeSibling: + case NodeType::ControlRight: + i += 3; + break; + } + + if (size != -1 && i >= size) + i -= size; + } + + return i; +} + void NodeEditContext::reset1Control(PageItem* currItem) -{ +{ + if ((m_ClRe < 0) || isNode(m_ClRe)) + return; + ScribusDoc* Doc = currItem->doc(); - if ((m_ClRe < 0) || (m_ClRe % 2 == 0)) - return; UndoManager* undoManager = UndoManager::instance(); // do no record anything else but the core reset points action @@ -401,13 +697,13 @@ void NodeEditContext::resetControl(PageItem* currItem) { ScribusDoc* Doc = currItem->doc(); - if (m_ClRe < 0) + if (m_ClRe < 0 || !isNode(m_ClRe)) return; UndoManager* undoManager = UndoManager::instance(); // do no record anything else but the core reset points action undoManager->setUndoEnabled(false); - + oldClip = new FPointArray(Doc->nodeEdit.m_isContourLine ? currItem->ContourLine.copy() : currItem->PoLine.copy()); m_oldItemX = currItem->xPos(); m_oldItemY = currItem->yPos(); @@ -458,8 +754,8 @@ } else { - Clip.setPoint(Doc->nodeEdit.m_ClRe + 1, np); - Clip.setPoint((Doc->nodeEdit.m_ClRe % 4 != 0 ? Doc->nodeEdit.m_ClRe + 3 : Doc->nodeEdit.m_ClRe - 1), np); + Clip.setPoint(indexOfNode(Doc->nodeEdit.m_ClRe, NodeType::ControlRight, Clip.size()), np); + Clip.setPoint(indexOfNode(Doc->nodeEdit.m_ClRe, NodeType::ControlLeft, Clip.size()), np); } if (!Doc->nodeEdit.m_isContourLine) { @@ -493,3 +789,7 @@ oldClip = nullptr; } +FPointArray NodeEditContext::clipFromItem(PageItem* currItem) +{ + return isContourLine() ? currItem->ContourLine : currItem->PoLine; +} Index: scribus/nodeeditcontext.h =================================================================== --- scribus/nodeeditcontext.h (Revision 26704) +++ scribus/nodeeditcontext.h (Arbeitskopie) @@ -39,6 +39,46 @@ class SCRIBUS_API NodeEditContext : public MassObservable<QPointF> { public: + enum SubMode + { + MOVE_POINT = 0, + ADD_POINT, + DEL_POINT, + SPLIT_PATH, + EDIT_POINT + }; + + enum NodeType + { + ControlLeft = 0, + NodeLeft, + NodeRight, + ControlRight, + Opposite, // opposite control/node index of input + Sibling, // sibling of node is control, sibling of control is node + OppositeSibling, // combination of opposite and sibling + Self + }; + + enum ControlMoveMode + { + Auto = 0, + Symmetric, + Asymmetric, + Independent + }; + + struct OppositeControl { + FPoint relativePos; + bool isValid {false}; + int controlIndex {-1}; + int nodeIndex {-1}; + double distance {0}; + double angle {0}; + bool sameAngle {false}; + bool sameLength {false}; + }; + NodeEditContext(); bool isContourLine() { return m_isContourLine; } @@ -51,17 +91,34 @@ void setSegP1(int i) { m_SegP1 = i; } int segP2() { return m_SegP2; } void setSegP2(int i) { m_SegP2 = i; } - bool edPoints() { return m_EdPoints; } - void setEdPoints(bool b) { m_EdPoints = b; } - bool moveSym() { return m_MoveSym; } - void setMoveSym(bool b) { m_MoveSym = b; } + ControlMoveMode moveMode() { return m_moveMode; } + void setMoveMode(ControlMoveMode b) { m_moveMode = b; } + double scale() { return m_scale; }; + void setScale(double scale) {m_scale = scale; } QList<int>& selNode() { return m_SelNode; } bool hasNodeSelected() const; void deselect(); void selectNode(int i); + void deselectNode(int i); + void deselectNodeControls(); int selectionCount() const; + void getPoints(PageItem* currItem, int i, FPoint& ctrlLeft, FPoint& nodeLeft, FPoint& nodeRight, FPoint& ctrlRight); + FPoint getPoint(PageItem* currItem, int i, NodeType nodeType = NodeType::Self); + bool isSymetric(PageItem* currItem, int i = -1); + bool sameAngle(PageItem* currItem, int i = -1); + bool sameLength(PageItem* currItem, int i = -1); + + static bool isNode(int i) { return i % 2 == 0; } + static bool isLeftControl(int i); + static bool isLeftNode(int i); + static bool isRightNode(int i); + static bool isRightControl(int i); + static bool hasTwoNodes(int i, FPointArray clip); + static bool isSharpNode(int i, FPointArray clip); + static int indexOfNode(int i, NodeType type = NodeType::NodeRight, int size = -1); + void reset(); void setPreviewMode(bool mode); bool previewMode() { return m_preview; } @@ -69,17 +126,18 @@ void setSubMode(int i) { m_submode = i; } void reset1Control(PageItem* currItem); void resetControl(PageItem* currItem); + FPointArray clipFromItem(PageItem* currItem); FPointArray beginTransaction(PageItem* currItem); void finishTransaction(PageItem* currItem); ScOldNewState<FPointArray>* finishTransaction1(PageItem* currItem); void finishTransaction2(PageItem* currItem, ScOldNewState<FPointArray>* state); void moveClipPoint(PageItem *currItem, const FPoint& ip); + void equalizeControls(PageItem *currItem); FPointArray *oldClip { nullptr }; UndoTransaction nodeTransaction; + OppositeControl oppositeControl; - enum SubMode { MOVE_POINT = 0, ADD_POINT = 1, DEL_POINT = 2, SPLIT_PATH = 3 }; - protected: int m_submode { MOVE_POINT }; bool m_isContourLine { false }; @@ -88,11 +146,11 @@ int m_ClRe2 { -1 }; int m_SegP1 { -1 }; int m_SegP2 { -1 }; - bool m_EdPoints { true }; - bool m_MoveSym { false }; + ControlMoveMode m_moveMode { Auto }; QList<int> m_SelNode; double m_oldItemX { 0.0 }; double m_oldItemY { 0.0 }; + double m_scale { 1.0 }; bool m_preview { false }; }; #endif Index: scribus/scribus.cpp =================================================================== --- scribus/scribus.cpp (Revision 26704) +++ scribus/scribus.cpp (Arbeitskopie) @@ -5825,11 +5825,11 @@ nodePalette->setDoc(doc, view); nodePalette->MoveN(); - nodePalette->HaveNode(false, false); + nodePalette->HaveNode(false); nodePalette->MoveNode->setChecked(true); nodePalette->show(); // qDebug() << "nodepalette show:" << nodePalette->geometry(); - connect(view, SIGNAL(HavePoint(bool,bool)), nodePalette, SLOT(HaveNode(bool,bool))); + connect(view, SIGNAL(HavePoint(bool)), nodePalette, SLOT(HaveNode(bool))); connect(view, SIGNAL(PolyStatus(int,uint)), nodePalette, SLOT(PolyStatus(int,uint))); doc->nodeEdit.reset(); appModeHelper->setFrameEditMode(true); @@ -5869,7 +5869,7 @@ void ScribusMainWindow::NoFrameEdit() { - disconnect(view, SIGNAL(HavePoint(bool, bool)), nodePalette, SLOT(HaveNode(bool, bool))); + disconnect(view, SIGNAL(HavePoint(bool)), nodePalette, SLOT(HaveNode(bool))); disconnect(view, SIGNAL(PolyStatus(int, uint)), nodePalette, SLOT(PolyStatus(int, uint))); actionManager->disconnectModeActions(); nodePalette->setDoc(nullptr, nullptr); Index: scribus/scribusview.cpp =================================================================== --- scribus/scribusview.cpp (Revision 26704) +++ scribus/scribusview.cpp (Arbeitskopie) @@ -1986,7 +1986,6 @@ MidButt = false; HaveSelRect = false; Magnify = false; - m_doc->nodeEdit.setEdPoints(true); DraggedGroup = false; //FIXME:av MoveGY = false; //FIXME:av MoveGX = false; Index: scribus/scribusview.h =================================================================== --- scribus/scribusview.h (Revision 26704) +++ scribus/scribusview.h (Arbeitskopie) @@ -369,7 +369,7 @@ void DelBM(PageItem *); void ChBMText(PageItem *); void LoadElem(QString, double, double, bool, bool, ScribusDoc *, ScribusView *); - void HavePoint(bool, bool); + void HavePoint(bool); void ClipPo(double, double); void PolyStatus(int, uint); void MousePos(double, double); Index: scribus/ui/nodeeditpalette.cpp =================================================================== --- scribus/ui/nodeeditpalette.cpp (Revision 26704) +++ scribus/ui/nodeeditpalette.cpp (Arbeitskopie) @@ -5,6 +5,7 @@ for which a new license (GPL+exception) is in place. */ +#include <QButtonGroup> #include <QEvent> #include "nodeeditpalette.h" @@ -21,302 +22,109 @@ NodePalette::NodePalette( QWidget* parent) : ScrPaletteBase(parent, "nodePalette", false, Qt::WindowFlags()) { - QSize iconSize(22, 22); - QSize buttonSize(24, 24); - QSize maxButtonSize(16777215, buttonSize.height()); - QSizePolicy::Policy buttonSizePolicy = static_cast<QSizePolicy::Policy>(QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag); + setupUi(this); - vboxLayout = new QVBoxLayout(this); - vboxLayout->setSpacing(6); - vboxLayout->setContentsMargins(6, 6, 6, 6); - - MoveNode = new QToolButton(this); - MoveNode->setIconSize(iconSize); - MoveNode->setMinimumSize(buttonSize); - MoveNode->setMaximumSize(maxButtonSize); - MoveNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); MoveNode->setCheckable(true); MoveNode->setChecked(true); - AddNode = new QToolButton(this); - AddNode->setIconSize(iconSize); - AddNode->setMinimumSize(buttonSize); - AddNode->setMaximumSize(maxButtonSize); - AddNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); AddNode->setEnabled(true); AddNode->setCheckable(true); - DeleteNode = new QToolButton(this); - DeleteNode->setIconSize(iconSize); - DeleteNode->setMinimumSize(buttonSize); - DeleteNode->setMaximumSize(maxButtonSize); - DeleteNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); DeleteNode->setEnabled(true); DeleteNode->setCheckable(true); - ResNode = new QToolButton(this); - ResNode->setIconSize(iconSize); - ResNode->setMinimumSize(buttonSize); - ResNode->setMaximumSize(maxButtonSize); - ResNode->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - ResNode->setEnabled(true); + EditControl->setCheckable(true); + EditControl->setChecked(true); - MoveControl = new QToolButton(this); - MoveControl->setIconSize(iconSize); - MoveControl->setMinimumSize(buttonSize); - MoveControl->setMaximumSize(maxButtonSize); - MoveControl->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - MoveControl->setCheckable(true); - - AsymMove = new QToolButton(this); - AsymMove->setIconSize(iconSize); - AsymMove->setMinimumSize(buttonSize); - AsymMove->setMaximumSize(maxButtonSize); - AsymMove->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - AsymMove->setEnabled(false); AsymMove->setCheckable(true); + AsymMove->setChecked(false); - SymMove = new QToolButton(this); - SymMove->setIconSize(iconSize); - SymMove->setMinimumSize(buttonSize); - SymMove->setMaximumSize(maxButtonSize); - SymMove->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - SymMove->setEnabled(false); SymMove->setCheckable(true); + SymMove->setChecked(false); - Res1Node = new QToolButton(this); - Res1Node->setIconSize(iconSize); - Res1Node->setMinimumSize(buttonSize); - Res1Node->setMaximumSize(maxButtonSize); - Res1Node->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - Res1Node->setEnabled(false); + IndependentMove->setCheckable(true); + IndependentMove->setChecked(false); - PolySplit = new QToolButton(this); PolySplit->setEnabled(false); PolySplit->setCheckable(true); - PolySplit->setIconSize(iconSize); - PolySplit->setMinimumSize(buttonSize); - PolySplit->setMaximumSize(maxButtonSize); - PolySplit->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - BezierClose = new QToolButton(this); BezierClose->setEnabled(false); - BezierClose->setIconSize(iconSize); - BezierClose->setMinimumSize(buttonSize); - BezierClose->setMaximumSize(maxButtonSize); - BezierClose->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - PolyMirrorH = new QToolButton(this); - PolyMirrorH->setIconSize(iconSize); - PolyMirrorH->setMinimumSize(buttonSize); - PolyMirrorH->setMaximumSize(maxButtonSize); - PolyMirrorH->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - - PolyMirrorV = new QToolButton(this); - PolyMirrorV->setIconSize(iconSize); - PolyMirrorV->setMinimumSize(buttonSize); - PolyMirrorV->setMaximumSize(maxButtonSize); - PolyMirrorV->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); - - PolyShearL = new QToolButton(this); - PolyShearL->setIconSize(iconSize); - PolyShearL->setMinimumSize(buttonSize); - PolyShearL->setMaximumSize(maxButtonSize); - PolyShearL->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearL->setAutoRepeat(true); - - PolyShearR = new QToolButton(this); - PolyShearR->setIconSize(iconSize); - PolyShearR->setMinimumSize(buttonSize); - PolyShearR->setMaximumSize(maxButtonSize); - PolyShearR->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearR->setAutoRepeat(true); - - PolyShearU = new QToolButton(this); - PolyShearU->setIconSize(iconSize); - PolyShearU->setMinimumSize(buttonSize); - PolyShearU->setMaximumSize(maxButtonSize); - PolyShearU->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearU->setAutoRepeat(true); - - PolyShearD = new QToolButton(this); - PolyShearD->setIconSize(iconSize); - PolyShearD->setMinimumSize(buttonSize); - PolyShearD->setMaximumSize(maxButtonSize); - PolyShearD->setSizePolicy(QSizePolicy(buttonSizePolicy, QSizePolicy::Fixed)); PolyShearD->setAutoRepeat(true); - - RotateCCW = new QToolButton(this); - RotateCCW->setIconSize(iconSize); - RotateCCW->setMinimumSize(buttonSize); - RotateCCW->setMaximumSize(buttonSize); RotateCCW->setAutoRepeat(true); - - RotateCW = new QToolButton(this); - RotateCW->setIconSize(iconSize); - RotateCW->setMinimumSize(buttonSize); - RotateCW->setMaximumSize(buttonSize); RotateCW->setAutoRepeat(true); - RotVal = new QSpinBox(this); RotVal->setMinimum(1); RotVal->setMaximum(180); RotVal->setValue(1); RotVal->setSuffix( QString::fromUtf8(" °")); - Expand = new QToolButton(this); - Expand->setIconSize(iconSize); - Expand->setMinimumSize(buttonSize); - Expand->setMaximumSize(buttonSize); Expand->setAutoRepeat(true); - - Shrink = new QToolButton(this); - Shrink->setIconSize(iconSize); - Shrink->setMinimumSize(buttonSize); - Shrink->setMaximumSize(buttonSize); Shrink->setAutoRepeat(true); - scalePercentage = new QSpinBox(this); scalePercentage->setMinimum(1); scalePercentage->setMaximum(100); scalePercentage->setValue(10); - Enlarge = new QToolButton(this); - Enlarge->setIconSize(iconSize); - Enlarge->setMinimumSize(buttonSize); - Enlarge->setMaximumSize(buttonSize); Enlarge->setAutoRepeat(true); - - Reduce = new QToolButton(this); - Reduce->setIconSize(iconSize); - Reduce->setMinimumSize(buttonSize); - Reduce->setMaximumSize(buttonSize); Reduce->setAutoRepeat(true); - scaleDistance = new ScrSpinBox( 1, 16777215, this, 0); - scaleDistance->setMaximumSize(114, 16777215); // ScrSpinBox is bigger for 4px than expected + scaleDistance->setMaximumSize(114, 16777215); scaleDistance->setValues(-16777215, 16777215, 2, 30); - gridLayout = new QGridLayout(); - gridLayout->setSpacing(6); - gridLayout->setContentsMargins(0, 0, 0, 0); - gridLayout->addWidget(MoveNode, 0, 1, 1, 1); - gridLayout->addWidget(AddNode, 0, 2, 1, 1); - gridLayout->addWidget(DeleteNode, 0, 3, 1, 1); - gridLayout->addWidget(ResNode, 0, 4, 1, 1); - gridLayout->addWidget(MoveControl, 1, 1, 1, 1); - gridLayout->addWidget(AsymMove, 1, 2, 1, 1); - gridLayout->addWidget(SymMove, 1, 3, 1, 1); - gridLayout->addWidget(Res1Node, 1, 4, 1, 1); - gridLayout->addWidget(PolySplit, 2, 1, 1, 1); - gridLayout->addWidget(BezierClose, 2, 2, 1, 1); - gridLayout->addWidget(PolyMirrorH, 2, 3, 1, 1); - gridLayout->addWidget(PolyMirrorV, 2, 4, 1, 1); - gridLayout->addWidget(PolyShearL, 3, 1, 1, 1); - gridLayout->addWidget(PolyShearR, 3, 2, 1, 1); - gridLayout->addWidget(PolyShearU, 3, 3, 1, 1); - gridLayout->addWidget(PolyShearD, 3, 4, 1, 1); - vboxLayout->addLayout(gridLayout); - - gridLayout2 = new QGridLayout(); - gridLayout2->setSpacing(6); - gridLayout2->setContentsMargins(0, 0, 0, 0); - gridLayout2->addWidget(RotVal, 0, 0, 1, 4); - gridLayout2->addWidget(RotateCCW, 0, 4, 1, 1); - gridLayout2->addWidget(RotateCW, 0, 5, 1, 1); - gridLayout2->addWidget(scalePercentage, 1, 0, 1, 4); - gridLayout2->addWidget(Expand, 1, 4, 1, 1); - gridLayout2->addWidget(Shrink, 1, 5, 1, 1); - gridLayout2->addWidget(scaleDistance, 2, 0, 1, 4); - gridLayout2->addWidget(Enlarge, 2, 4, 1, 1); - gridLayout2->addWidget(Reduce, 2, 5, 1, 1); - vboxLayout->addLayout(gridLayout2); - - QSpacerItem* spacer = new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ); - vboxLayout->addItem(spacer); - - AbsMode = new QGroupBox( "&Absolute Coordinates", this ); - AbsMode->setCheckable(true); - AbsMode->setChecked(false); - vboxLayout1 = new QVBoxLayout(AbsMode); - vboxLayout1->setSpacing(6); - vboxLayout1->setContentsMargins(9, 9, 9, 9); - absToCanvas = new QRadioButton( tr("to Canvas"), AbsMode); - vboxLayout1->addWidget(absToCanvas); - absToPage = new QRadioButton( tr("to Page"), AbsMode); - vboxLayout1->addWidget(absToPage); - absToCanvas->setChecked(true); - vboxLayout->addWidget(AbsMode); - - gridLayout3 = new QGridLayout(); - gridLayout3->setSpacing(6); - gridLayout3->setContentsMargins(0, 0, 0, 0); - gridLayout3->setColumnStretch(0, 0); - gridLayout3->setColumnStretch(1, 1); - - TextLabel1 = new QLabel(this); - gridLayout3->addWidget(TextLabel1, 0, 0, 1, 1); - - XSpin = new ScrSpinBox( 0, 16777215, this, 2 ); + XSpin->setMinimum(0); + XSpin->setMaximum(16777215); + XSpin->setNewUnit(2); XSpin->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed)); XSpin->setEnabled(false); - gridLayout3->addWidget(XSpin, 0, 1, 1, 1); - TextLabel2 = new QLabel(this); - gridLayout3->addWidget(TextLabel2, 1, 0, 1, 1); - - YSpin = new ScrSpinBox( 0, 16777215, this, 2 ); + YSpin->setMinimum(0); + YSpin->setMaximum(16777215); + YSpin->setNewUnit(2); YSpin->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed)); YSpin->setEnabled(false); - gridLayout3->addWidget(YSpin, 1, 1, 1, 1); - vboxLayout->addLayout(gridLayout3); TextLabel1->setBuddy(XSpin); TextLabel2->setBuddy(YSpin); - EditCont = new QCheckBox(this); EditCont->setChecked(false); - vboxLayout->addWidget(EditCont); - PreviewMode = new QCheckBox(this); PreviewMode->setChecked(false); - vboxLayout->addWidget(PreviewMode); - ResetCont = new QPushButton(this); ResetCont->setEnabled(false); - vboxLayout->addWidget(ResetCont); - ResetContClip = new QPushButton(this); ResetContClip->setEnabled(true); ResetContClip->hide(); - vboxLayout->addWidget(ResetContClip); - ResetShape2Clip = new QPushButton(this); ResetShape2Clip->setEnabled(true); ResetShape2Clip->hide(); - vboxLayout->addWidget(ResetShape2Clip); - resetDefaultButton = new QPushButton(this); resetDefaultButton->setEnabled(true); - vboxLayout->addWidget(resetDefaultButton); - hboxLayout = new QHBoxLayout(); - hboxLayout->setSpacing(6); - hboxLayout->setContentsMargins(0, 0, 0, 0); - editEditButton = new QPushButton(IconManager::instance().loadIcon("exit"), tr("OK"), this); editEditButton->setEnabled(true); editEditButton->setDefault(true); - hboxLayout->addWidget(editEditButton); - cancelEditButton = new QPushButton( tr("Cancel"), this); + cancelEditButton->setEnabled(true); - hboxLayout->addWidget(cancelEditButton); - vboxLayout->addLayout(hboxLayout); + QButtonGroup *btngpTools = new QButtonGroup(); + btngpTools->setExclusive(true); + btngpTools->addButton(MoveNode); + btngpTools->addButton(AddNode); + btngpTools->addButton(DeleteNode); + btngpTools->addButton(PolySplit); + btngpTools->addButton(BezierClose); + btngpTools->addButton(EditControl); + resize(QSize(170, 380).expandedTo(minimumSizeHint())); layout()->setSizeConstraint(QLayout::SetFixedSize); iconSetChange(); languageChange(); + PosOriginButton->setCurrentIndex(m_posOrigin); + connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange())); } @@ -331,11 +139,10 @@ connect(MoveNode, SIGNAL(clicked()), this, SLOT(MoveN())); connect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); connect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - connect(ResNode, SIGNAL(clicked()), this, SLOT(ResetControl())); - connect(Res1Node, SIGNAL(clicked()), this, SLOT(Reset1Control())); + connect(IndependentMove, SIGNAL(clicked()), this, SLOT(SetIndependentMove())); + connect(EditControl, SIGNAL(clicked()), this, SLOT(editControl())); connect(BezierClose, SIGNAL(clicked()), this, SLOT(CloseBezier())); connect(PolySplit, SIGNAL(clicked()), this, SLOT(SplitPoly())); - connect(MoveControl, SIGNAL(clicked()), this, SLOT(MoveK())); connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(PolyMirrorH, SIGNAL(clicked()), this, SLOT(MirrorH())); @@ -350,14 +157,12 @@ connect(Expand, SIGNAL(clicked()), this, SLOT(doExpand())); connect(Reduce, SIGNAL(clicked()), this, SLOT(doReduce())); connect(Enlarge, SIGNAL(clicked()), this, SLOT(doEnlarge())); - connect(AbsMode, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(absToCanvas, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(absToPage, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); - connect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleConMode())); + connect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleContourMode())); connect(ResetCont, SIGNAL(clicked()), this, SLOT(ResetContour())); connect(ResetContClip, SIGNAL(clicked()), this, SLOT(ResetContourToImageClip())); connect(ResetShape2Clip, SIGNAL(clicked()), this, SLOT(ResetShapeToImageClip())); connect(PreviewMode, SIGNAL(clicked()), this, SLOT(TogglePreview())); + connect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); } void NodePalette::disconnectSignals() @@ -371,11 +176,10 @@ disconnect(MoveNode, SIGNAL(clicked()), this, SLOT(MoveN())); disconnect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); disconnect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - disconnect(ResNode, SIGNAL(clicked()), this, SLOT(ResetControl())); - disconnect(Res1Node, SIGNAL(clicked()), this, SLOT(Reset1Control())); + disconnect(IndependentMove, SIGNAL(clicked()), this, SLOT(SetIndependentMove())); + disconnect(EditControl, SIGNAL(clicked()), this, SLOT(editControl())); disconnect(BezierClose, SIGNAL(clicked()), this, SLOT(CloseBezier())); disconnect(PolySplit, SIGNAL(clicked()), this, SLOT(SplitPoly())); - disconnect(MoveControl, SIGNAL(clicked()), this, SLOT(MoveK())); disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(PolyMirrorH, SIGNAL(clicked()), this, SLOT(MirrorH())); @@ -394,8 +198,25 @@ disconnect(ResetContClip, SIGNAL(clicked()), this, SLOT(ResetContourToImageClip())); disconnect(ResetShape2Clip, SIGNAL(clicked()), this, SLOT(ResetShapeToImageClip())); disconnect(PreviewMode, SIGNAL(clicked()), this, SLOT(TogglePreview())); + disconnect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); } +void NodePalette::initPosOriginMenu() +{ + IconManager& im = IconManager::instance(); + QSignalBlocker sigPosOriginButton(PosOriginButton); + + int index = PosOriginButton->currentIndex(); + + PosOriginButton->clear(); + PosOriginButton->addAction(im.loadIcon("position-origin-canvas"), tr("Canvas"))->setData(PositionOrigin::Canvas); + PosOriginButton->addAction(im.loadIcon("position-origin-page"), tr("Page"))->setData(PositionOrigin::Page); + PosOriginButton->addAction(im.loadIcon("position-origin-item"), tr("Item"))->setData(PositionOrigin::Item); + + PosOriginButton->setCurrentIndex(index); + +} + void NodePalette::setDoc(ScribusDoc *dc, ScribusView *vi) { m_doc = dc; @@ -402,14 +223,15 @@ m_view = vi; unitChange(); - disconnect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleConMode())); - disconnect(AbsMode, SIGNAL(clicked()), this, SLOT(ToggleAbsMode())); + disconnect(EditCont, SIGNAL(clicked()), this, SLOT(ToggleContourMode())); + disconnect(PosOriginButton, SIGNAL(currentIndexChanged(int)), this, SLOT(changePosOrigin(int))); if (m_doc != nullptr) { YSpin->setNewUnit(m_doc->unitIndex()); XSpin->setNewUnit(m_doc->unitIndex()); } - AbsMode->setChecked(false); + PosOriginButton->setCurrentIndex(Page); + EditCont->setChecked(false); if (m_doc == nullptr) disconnectSignals(); @@ -417,20 +239,11 @@ connectSignals(); } -void NodePalette::SplitPoly() -{ - if (m_doc == nullptr) - return; - MoveN(); - m_doc->nodeEdit.setSubMode(NodeEditContext::SPLIT_PATH); - PolySplit->setChecked(true); -} - void NodePalette::IsOpen() { if (m_doc == nullptr) return; - PolySplit->setChecked(false); + MoveNode->setChecked(true); BezierClose->setEnabled(true); PolySplit->setEnabled(true); EditCont->setEnabled(false); @@ -459,21 +272,6 @@ } } -void NodePalette::CloseBezier() -{ - if (m_doc == nullptr) - return; - MoveN(); - PageItem *currItem = m_doc->m_Selection->itemAt(0); - PageItem* newItem = m_doc->convertItemTo(currItem, PageItem::Polygon); - m_doc->m_Selection->clear(); - m_doc->m_Selection->addItem(newItem); - newItem->update(); - BezierClose->setEnabled(false); - PolySplit->setEnabled(true); - EditCont->setEnabled(true); -} - void NodePalette::doRotCCW() { if (m_doc == nullptr) @@ -558,20 +356,6 @@ m_doc->MirrorPolyV(m_doc->m_Selection->itemAt(0)); } -void NodePalette::ResetControl() -{ - if (m_doc == nullptr) - return; - m_doc->nodeEdit.resetControl(m_doc->m_Selection->itemAt(0)); -} - -void NodePalette::Reset1Control() -{ - if (m_doc == nullptr) - return; - m_doc->nodeEdit.reset1Control(m_doc->m_Selection->itemAt(0)); -} - void NodePalette::ResetContour() { if (m_doc == nullptr) @@ -629,9 +413,9 @@ PageItem *currItem = m_doc->m_Selection->itemAt(0); FPoint zp; FPoint np(XSpin->value() / m_doc->unitRatio(), YSpin->value() / m_doc->unitRatio()); - if (AbsMode->isChecked()) + if (m_posOrigin == Canvas || m_posOrigin == Page) { - if (absToCanvas->isChecked()) + if (m_posOrigin == Canvas) zp = FPoint(currItem->xPos(), currItem->yPos()); else zp = FPoint(currItem->xPos() - m_doc->currentPage()->xOffset(), currItem->yPos() - m_doc->currentPage()->yOffset()); @@ -648,43 +432,50 @@ void NodePalette::SetSym() { - if (m_doc != nullptr) - m_doc->nodeEdit.setMoveSym(true); - SymMove->setChecked(true); AsymMove->setChecked(false); + IndependentMove->setChecked(false); PolySplit->setChecked(false); + + if (m_doc == nullptr) + return; + + if (SymMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Symmetric); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } void NodePalette::SetAsym() { - if (m_doc != nullptr) - m_doc->nodeEdit.setMoveSym(false); SymMove->setChecked(false); - AsymMove->setChecked(true); + IndependentMove->setChecked(false); PolySplit->setChecked(false); + + if (m_doc == nullptr) + return; + + if (AsymMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Asymmetric); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } -void NodePalette::SetXY(double x, double y) +void NodePalette::SetIndependentMove() { + SymMove->setChecked(false); + AsymMove->setChecked(false); + PolySplit->setChecked(false); + if (m_doc == nullptr) return; - FPoint zp(0.0, 0.0); - disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - if (AbsMode->isChecked()) - { - if (absToCanvas->isChecked()) - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); - else - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); - } - XSpin->setValue((x + zp.x()) * m_doc->unitRatio()); - YSpin->setValue((y + zp.y()) * m_doc->unitRatio()); - connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + + if (IndependentMove->isChecked()) + m_doc->nodeEdit.setMoveMode(NodeEditContext::Independent); + else + m_doc->nodeEdit.setMoveMode(NodeEditContext::Auto); } -void NodePalette::ToggleAbsMode() +void NodePalette::SetXY(double x, double y) { if (m_doc == nullptr) return; @@ -691,35 +482,19 @@ FPoint zp(0.0, 0.0); disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); - FPointArray Clip; - FPoint np(0.0, 0.0); - if (EditCont->isChecked()) - Clip = m_doc->m_Selection->itemAt(0)->ContourLine; - else - Clip = m_doc->m_Selection->itemAt(0)->PoLine; - if (m_doc->nodeEdit.selectionCount() != 0) - np = Clip.point(m_doc->nodeEdit.selNode().at(0)); - if (AbsMode->isChecked()) - { - XSpin->setMinimum(-16777215); - YSpin->setMinimum(-16777215); - if (absToCanvas->isChecked()) - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); - else - zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); - } - else - { - XSpin->setMinimum(0); - YSpin->setMinimum(0); - } - XSpin->setValue((np.x() + zp.x()) * m_doc->unitRatio()); - YSpin->setValue((np.y() + zp.y()) * m_doc->unitRatio()); + + if (PosOriginButton->currentIndex() == Page) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); + else if (PosOriginButton->currentIndex() == Canvas) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); + + XSpin->setValue((x + zp.x()) * m_doc->unitRatio()); + YSpin->setValue((y + zp.y()) * m_doc->unitRatio()); connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); } -void NodePalette::ToggleConMode() +void NodePalette::ToggleContourMode() { if (m_doc == nullptr) return; @@ -748,7 +523,7 @@ PolySplit->setEnabled(true); ResetCont->setEnabled(false); ResetContClip->setEnabled(false); - if (AbsMode->isChecked()) + if (m_posOrigin == Canvas | m_posOrigin == Page) { XSpin->setMinimum(-16777215); YSpin->setMinimum(-16777215); @@ -773,10 +548,52 @@ m_doc->regionsChanged()->update(QRectF()); } -void NodePalette::HaveNode(bool have, bool mov) +void NodePalette::changePosOrigin(int index) { + m_posOrigin = index; + if (m_doc == nullptr) return; + + FPoint zp(0.0, 0.0); + disconnect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + disconnect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + FPointArray Clip; + FPoint np(0.0, 0.0); + + if (EditCont->isChecked()) + Clip = m_doc->m_Selection->itemAt(0)->ContourLine; + else + Clip = m_doc->m_Selection->itemAt(0)->PoLine; + + if (m_doc->nodeEdit.selectionCount() != 0) + np = Clip.point(m_doc->nodeEdit.selNode().at(0)); + + if (m_posOrigin == Canvas || m_posOrigin == Page) + { + XSpin->setMinimum(-16777215); + YSpin->setMinimum(-16777215); + if (m_posOrigin == Canvas) + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos(), m_doc->m_Selection->itemAt(0)->yPos()); + else + zp = FPoint(m_doc->m_Selection->itemAt(0)->xPos() - m_doc->currentPage()->xOffset(), m_doc->m_Selection->itemAt(0)->yPos() - m_doc->currentPage()->yOffset()); + } + else + { + XSpin->setMinimum(0); + YSpin->setMinimum(0); + } + + XSpin->setValue((np.x() + zp.x()) * m_doc->unitRatio()); + YSpin->setValue((np.y() + zp.y()) * m_doc->unitRatio()); + connect(XSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); + connect(YSpin, SIGNAL(valueChanged(double)), this, SLOT(MovePoint())); +} + +void NodePalette::HaveNode(bool have) +{ + if (m_doc == nullptr) + return; bool setter = have; if (m_doc->nodeEdit.selectionCount() > 1) { @@ -788,35 +605,7 @@ XSpin->setEnabled(setter); YSpin->setEnabled(setter); } - if (setter) - { - if (m_doc->nodeEdit.edPoints()) - ResNode->setEnabled(setter); - else - Res1Node->setEnabled(setter); - } - else - { - ResNode->setEnabled(setter); - Res1Node->setEnabled(setter); - } - if (m_doc->nodeEdit.submode() == NodeEditContext::MOVE_POINT) - { - disconnect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); - disconnect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - if (mov) - { - SymMove->setChecked(true); - AsymMove->setChecked(false); - } - else - { - AsymMove->setChecked(true); - SymMove->setChecked(false); - } - connect(AsymMove, SIGNAL(clicked()), this, SLOT(SetAsym())); - connect(SymMove, SIGNAL(clicked()), this, SLOT(SetSym())); - } + if (m_doc->nodeEdit.submode() == NodeEditContext::DEL_POINT) { uint cc; @@ -837,91 +626,106 @@ leaveEd = true; } if (leaveEd) - { MoveN(); - MoveControl->setEnabled(true); - } } } -void NodePalette::MoveK() +void NodePalette::MoveN() { if (m_doc == nullptr) return; + m_doc->nodeEdit.setSubMode(NodeEditContext::MOVE_POINT); - m_doc->nodeEdit.setEdPoints(false); m_doc->nodeEdit.setClre(-1); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); + if (m_doc->m_Selection->count() > 0) m_doc->m_Selection->itemAt(0)->update(); + SymMove->setEnabled(true); AsymMove->setEnabled(true); - Res1Node->setEnabled(false); - ResNode->setEnabled(false); - AddNode->setEnabled(false); - DeleteNode->setEnabled(false); - MoveNode->setChecked(false); - MoveControl->setChecked(true); - DeleteNode->setChecked(false); - AddNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(true); + MoveNode->setChecked(true); } -void NodePalette::MoveN() +void NodePalette::AddN() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::MOVE_POINT); - m_doc->nodeEdit.setEdPoints(true); + + m_doc->nodeEdit.setSubMode(NodeEditContext::ADD_POINT); + SymMove->setEnabled(false); + AsymMove->setEnabled(false); + IndependentMove->setEnabled(false); + AddNode->setChecked(true); +} + +void NodePalette::DelN() +{ + if (m_doc == nullptr) + return; + + m_doc->nodeEdit.setSubMode(NodeEditContext::DEL_POINT); + SymMove->setEnabled(false); + AsymMove->setEnabled(false); + IndependentMove->setEnabled(false); + DeleteNode->setChecked(true); +} + +void NodePalette::SplitPoly() +{ + if (m_doc == nullptr) + return; + + m_doc->nodeEdit.setSubMode(NodeEditContext::SPLIT_PATH); m_doc->nodeEdit.setClre(-1); m_doc->nodeEdit.setClre2(-1); m_doc->nodeEdit.setSegP1(-1); m_doc->nodeEdit.setSegP2(-1); + if (m_doc->m_Selection->count() > 0) m_doc->m_Selection->itemAt(0)->update(); - ResNode->setEnabled(false); - AddNode->setEnabled(true); - DeleteNode->setEnabled(true); + SymMove->setEnabled(false); AsymMove->setEnabled(false); - Res1Node->setEnabled(false); - MoveNode->setChecked(true); - MoveControl->setChecked(false); - DeleteNode->setChecked(false); - AddNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(false); + PolySplit->setChecked(true); } -void NodePalette::AddN() +void NodePalette::editControl() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::ADD_POINT); - m_doc->nodeEdit.setEdPoints(true); + + m_doc->nodeEdit.setSubMode(NodeEditContext::EDIT_POINT); + m_doc->nodeEdit.setClre(-1); + m_doc->nodeEdit.setClre2(-1); + m_doc->nodeEdit.setSegP1(-1); + m_doc->nodeEdit.setSegP2(-1); + SymMove->setEnabled(false); AsymMove->setEnabled(false); - ResNode->setEnabled(false); - Res1Node->setEnabled(false); - AddNode->setChecked(true); - DeleteNode->setChecked(false); - PolySplit->setChecked(false); + IndependentMove->setEnabled(false); + EditControl->setChecked(true); + } -void NodePalette::DelN() +void NodePalette::CloseBezier() { if (m_doc == nullptr) return; - m_doc->nodeEdit.setSubMode(NodeEditContext::DEL_POINT); - m_doc->nodeEdit.setEdPoints(true); - SymMove->setEnabled(false); - AsymMove->setEnabled(false); - ResNode->setEnabled(false); - Res1Node->setEnabled(false); - AddNode->setChecked(false); - DeleteNode->setChecked(true); - PolySplit->setChecked(false); + + MoveN(); + PageItem *currItem = m_doc->m_Selection->itemAt(0); + PageItem* newItem = m_doc->convertItemTo(currItem, PageItem::Polygon); + m_doc->m_Selection->clear(); + m_doc->m_Selection->addItem(newItem); + newItem->update(); + BezierClose->setEnabled(false); + PolySplit->setEnabled(true); + EditCont->setEnabled(true); } void NodePalette::closeEvent(QCloseEvent *ce) @@ -956,7 +760,7 @@ m_doc->nodeEdit.selNode().clear(); m_doc->nodeEdit.setPreviewMode(false); EditCont->setChecked(false); - ToggleConMode(); + ToggleContourMode(); PageItem *currItem = m_doc->m_Selection->itemAt(0); if (currItem && currItem->itemType() == PageItem::PathText) currItem->updatePolyClip(); @@ -979,7 +783,7 @@ m_doc->nodeEdit.selNode().clear(); m_doc->nodeEdit.setPreviewMode(false); EditCont->setChecked(false); - ToggleConMode(); + ToggleContourMode(); PageItem *currItem = m_doc->m_Selection->itemAt(0); // Calculate the difference of the current position and the original @@ -1069,14 +873,16 @@ { IconManager& im = IconManager::instance(); +//<<<<<<< .mine + initPosOriginMenu(); + MoveNode->setIcon(im.loadIcon("bezier-node-move")); AddNode->setIcon(im.loadIcon("bezier-node-add")); DeleteNode->setIcon(im.loadIcon("bezier-node-delete")); - ResNode->setIcon(im.loadIcon("bezier-control-reset-all")); - MoveControl->setIcon(im.loadIcon("bezier-control-move")); AsymMove->setIcon(im.loadIcon("bezier-control-asymetric")); SymMove->setIcon(im.loadIcon("bezier-control-symetric")); - Res1Node->setIcon(im.loadIcon("bezier-control-reset")); + IndependentMove->setIcon(im.loadIcon("bezier-control-independent")); + EditControl->setIcon(im.loadIcon("bezier-node-edit")); PolySplit->setIcon(im.loadIcon("bezier-split")); BezierClose->setIcon(im.loadIcon("bezier-close")); PolyMirrorH->setIcon(im.loadIcon("object-flip-horizontal")); @@ -1091,6 +897,13 @@ Shrink->setIcon(im.loadIcon("bezier-scale-down")); Enlarge->setIcon(im.loadIcon("bezier-scale-up")); Reduce->setIcon(im.loadIcon("bezier-scale-down")); + + EditCont->setIcon(im.loadIcon("path-contour-edit")); + PreviewMode->setIcon(im.loadIcon("path-node-visibility")); + ResetContClip->setIcon(im.loadIcon("path-contour-to-clip")); + ResetShape2Clip->setIcon(im.loadIcon("path-shape-to-clip")); + ResetCont->setIcon(im.loadIcon("path-contour-reset")); + resetDefaultButton->setIcon(im.loadIcon("path-reset-all")); } void NodePalette::languageChange() @@ -1098,28 +911,21 @@ setWindowTitle( tr( "Nodes" ) ); scalePercentage->setSuffix( tr(" %")); - AbsMode->setTitle( tr("&Absolute Coordinates")); - absToCanvas->setText( tr("to Canvas")); - absToPage->setText( tr("to Page")); - TextLabel1->setText( tr("&X-Pos:")); - TextLabel2->setText( tr("&Y-Pos:")); - PreviewMode->setText( tr("Hide Nodes")); - EditCont->setText( tr("Edit &Contour Line")); - ResetCont->setText( tr("&Reset Contour Line")); - ResetContClip->setText( tr("Set Contour to Image Clip")); - ResetShape2Clip->setText( tr("Set Shape to Image Clip")); + initPosOriginMenu(); + + PosOriginLabel->setText( tr("Origin:")); + TextLabel1->setText( tr("&X:")); + TextLabel2->setText( tr("&Y:")); editEditButton->setText( tr("OK")); cancelEditButton->setText( tr("Cancel")); - resetDefaultButton->setText( tr("Reset all edits")); -// editEditButton->setText( tr("&End Editing")); + resetDefaultButton->setToolTip( tr("Reset all edits")); MoveNode->setToolTip( tr("Move Nodes")); - MoveControl->setToolTip( tr("Move Control Points")); AddNode->setToolTip( tr("Add Nodes")); DeleteNode->setToolTip( tr("Delete Nodes")); - AsymMove->setToolTip( tr("Move Control Points Independently")); + AsymMove->setToolTip( tr("Move Control Points Asymetrical")); SymMove->setToolTip( tr("Move Control Points Symmetrical")); - ResNode->setToolTip( tr("Reset Control Points")); - Res1Node->setToolTip( tr("Reset this Control Point")); + IndependentMove->setToolTip( tr("Move Control Points Independently")); + EditControl->setToolTip( tr("Edit Control Points")); PolySplit->setToolTip( tr("Open a Polygon or Cuts a Bezier Curve")); BezierClose->setToolTip( tr("Close this Bezier Curve")); PolyMirrorH->setToolTip( tr("Mirror the Path Horizontally")); @@ -1137,11 +943,12 @@ RotVal->setToolTip( tr("Angle of Rotation")); scalePercentage->setToolTip( tr("% to Enlarge or Shrink By")); scaleDistance->setToolTip( tr("Value to Enlarge or Shrink By")); + PreviewMode->setToolTip( tr("Hide Nodes")); EditCont->setToolTip( tr("Activate Contour Line Editing Mode")); ResetCont->setToolTip( tr("Reset the Contour Line to the Original Shape of the Frame")); ResetContClip->setToolTip( tr("Reset the Contour Line to the Clipping Path of the Image")); ResetShape2Clip->setToolTip( tr("Set the Shape to the Clipping Path of the Image")); - AbsMode->setToolTip( "<qt>" + tr("When checked use coordinates relative to the page, otherwise coordinates are relative to the Object") + "</qt>"); + PosOriginButton->setToolTip( "<qt>" + tr("Use coordinates relative to the canvas, page, or to the Object") + "</qt>"); } void NodePalette::unitChange() Index: scribus/ui/nodeeditpalette.h =================================================================== --- scribus/ui/nodeeditpalette.h (Revision 26704) +++ scribus/ui/nodeeditpalette.h (Arbeitskopie) @@ -25,13 +25,13 @@ #include "scribusapi.h" #include "fpointarray.h" #include "scrpalettebase.h" -#include "scrspinbox.h" +#include "ui_nodeeditpalette.h" class ScribusDoc; class ScribusView; class PageItem; -class SCRIBUS_API NodePalette : public ScrPaletteBase +class SCRIBUS_API NodePalette : public ScrPaletteBase, public Ui::nodePalette { Q_OBJECT @@ -40,60 +40,19 @@ ~NodePalette() {}; void setDefaults(PageItem* currItem); - - QToolButton* MoveNode { nullptr }; - QToolButton* MoveControl { nullptr }; - QToolButton* AddNode { nullptr }; - QToolButton* DeleteNode { nullptr }; - QToolButton* AsymMove { nullptr }; - QToolButton* SymMove { nullptr }; - QToolButton* ResNode { nullptr }; - QToolButton* Res1Node { nullptr }; - QToolButton* PolySplit { nullptr }; - QToolButton* BezierClose { nullptr }; - QToolButton* PolyMirrorH { nullptr }; - QToolButton* PolyMirrorV { nullptr }; - QToolButton* PolyShearL { nullptr }; - QToolButton* PolyShearR { nullptr }; - QToolButton* PolyShearU { nullptr }; - QToolButton* PolyShearD { nullptr }; - QToolButton* RotateCCW { nullptr }; - QToolButton* RotateCW { nullptr }; - QToolButton* Expand { nullptr }; - QToolButton* Shrink { nullptr }; - QToolButton* Enlarge { nullptr }; - QToolButton* Reduce { nullptr }; - QCheckBox* PreviewMode { nullptr }; - QGroupBox* AbsMode { nullptr }; - QRadioButton* absToCanvas { nullptr }; - QRadioButton* absToPage { nullptr }; - QCheckBox* EditCont { nullptr }; - QLabel* TextLabel1 { nullptr }; - ScrSpinBox* YSpin { nullptr }; - QLabel* TextLabel2 { nullptr }; - ScrSpinBox* XSpin { nullptr }; - QSpinBox *RotVal { nullptr }; - QSpinBox *scalePercentage { nullptr }; - ScrSpinBox *scaleDistance { nullptr }; - QPushButton* ResetCont { nullptr }; - QPushButton* ResetContClip { nullptr }; - QPushButton* ResetShape2Clip { nullptr }; - QPushButton* editEditButton { nullptr }; - QPushButton* cancelEditButton { nullptr }; - QPushButton* resetDefaultButton { nullptr }; - void setDoc(ScribusDoc *dc, ScribusView *vi); ScribusDoc* currentDocument() const; + bool isSymetricMove() { return SymMove->isChecked(); }; + private slots: - void MoveK(); void AddN(); void DelN(); void MovePoint(); void SetSym(); void SetAsym(); - void ResetControl(); - void Reset1Control(); + void SetIndependentMove(); + void editControl(); void ResetContour(); void ResetContourToImageClip(); void ResetShapeToImageClip(); @@ -111,14 +70,14 @@ void ShearL(); void ShearU(); void ShearD(); - void ToggleAbsMode(); - void ToggleConMode(); + void ToggleContourMode(); void TogglePreview(); + void changePosOrigin(int index); public slots: void MoveN(); void SetXY(double x, double y); - void HaveNode(bool have, bool mov); + void HaveNode(bool have); void IsOpen(); void PolyStatus(int typ, uint size); void iconSetChange(); @@ -132,19 +91,21 @@ void reject() override; protected: + + enum PositionOrigin { + Canvas = 0, + Page, + Item + }; + void connectSignals(); void disconnectSignals(); + void initPosOriginMenu(); + void changeEvent(QEvent *e) override; void closeEvent(QCloseEvent *) override; - QVBoxLayout *vboxLayout { nullptr }; - QVBoxLayout *vboxLayout1 { nullptr }; - QHBoxLayout *hboxLayout { nullptr }; - QGridLayout *gridLayout { nullptr }; - QGridLayout *gridLayout2 { nullptr }; - QGridLayout *gridLayout3 { nullptr }; - ScribusDoc *m_doc { nullptr }; ScribusView *m_view { nullptr }; @@ -153,6 +114,7 @@ double m_yPos { 0.0 }; FPointArray m_itemPath; FPointArray m_itemContourPath; + int m_posOrigin { Page }; signals: void DocChanged(); Index: scribus/util_math.h =================================================================== --- scribus/util_math.h (Revision 26704) +++ scribus/util_math.h (Arbeitskopie) @@ -19,6 +19,7 @@ #include <QPainterPath> #include <QLineF> +#include "fpoint.h" #include "scribusapi.h" class FPoint; @@ -42,7 +43,7 @@ * @param t * @return */ -QList<QPointF> catmullToBezier(const QList<QPointF>& inputPoints, double t = 1); +QList<QPointF> SCRIBUS_API catmullToBezier(const QList<QPointF>& inputPoints, double t = 1); QPainterPath SCRIBUS_API regularPolygonPath(double w, double h, uint c, bool star, double factor, double rota, double factor2 = 0.0, double innerRot = 0.0, double factor3 = 0.0); QPainterPath SCRIBUS_API spiralPath(double spiralWidth, double spiralHeight, double spiralStartAngle, double spiralEndAngle, double spiralFactor); inline double SCRIBUS_API xy2Deg(double x, double y); @@ -51,6 +52,7 @@ inline double SCRIBUS_API square(double); inline double SCRIBUS_API distance(double, double); inline double SCRIBUS_API manhattanDistance(const QPointF& p1, const QPointF& p2); + /*! \brief Constrains an angle of rotation to 45 degree intervals Will make code simpler and reduce interval or provide as a parameter \param angle angle Angle in degrees @@ -58,6 +60,7 @@ \retval double Constrained angle */ double SCRIBUS_API constrainAngle(double angle, double constrain); +inline FPoint SCRIBUS_API getPointFromRotation(const FPoint &p1, double distance, double angle); /*! \brief Get the rotation angle (in radian) from a transformation matrix Will make code simpler and reduce interval or provide as a parameter \param matrix the transformation matrix @@ -92,6 +95,14 @@ return point.manhattanLength(); } +inline FPoint getPointFromRotation(const FPoint &p1, double distance, double angle) +{ + FPoint p2; + p2.setX(p1.x() + cosd(angle) * distance); + p2.setY(p1.y() + sind(angle) * distance); + return p2; +} + inline double xy2Deg(double x, double y) { return atan2(y,x) * (180.0/M_PI); |
|
i gave it a quick try. it's comfortable to use. some wishes are still open, but they can wait for the toolbar! |
Date Modified | Username | Field | Change |
---|---|---|---|
2025-01-05 21:28 | nitramr | New Issue | |
2025-01-05 21:28 | nitramr | Status | new => assigned |
2025-01-05 21:28 | nitramr | Assigned To | => nitramr |
2025-01-05 21:30 | nitramr | Relationship added | related to 0016547 |
2025-01-05 21:31 | nitramr | Relationship added | related to 0009099 |
2025-01-05 21:31 | nitramr | Relationship added | related to 0000889 |
2025-01-05 21:31 | nitramr | Relationship added | related to 0013990 |
2025-01-05 21:32 | nitramr | Relationship added | related to 0013991 |
2025-01-05 21:32 | nitramr | Relationship added | related to 0013989 |
2025-01-05 21:35 | nitramr | Relationship added | related to 0011676 |
2025-01-05 21:35 | nitramr | Relationship added | related to 0017128 |
2025-01-05 21:35 | nitramr | Relationship added | related to 0007753 |
2025-01-05 21:36 | nitramr | Relationship added | related to 0011976 |
2025-01-05 21:37 | nitramr | Relationship added | related to 0009100 |
2025-01-05 22:07 | utnik | Note Added: 0051877 | |
2025-01-05 22:16 | nitramr | Note Added: 0051878 | |
2025-01-06 07:14 | utnik | Note Added: 0051879 | |
2025-01-06 07:21 | nitramr | Description Updated | |
2025-01-06 07:24 | nitramr | Note Added: 0051880 | |
2025-01-06 08:31 | utnik | Note Added: 0051881 | |
2025-01-06 08:31 | utnik | File Added: Bildschirmfoto 2025-01-06 um 09.06.39.png | |
2025-01-06 14:20 | nitramr | Note Added: 0051882 | |
2025-01-06 14:20 | nitramr | File Added: Bildschirmfoto 2025-01-06 um 15.14.51.png | |
2025-01-06 17:21 | ale | Note Added: 0051883 | |
2025-01-06 17:21 | ale | File Added: Clippy-letter-2.PNG | |
2025-01-06 18:01 | nitramr | Note Added: 0051884 | |
2025-01-06 18:01 | nitramr | File Added: Bildschirmfoto vom 2025-01-06 18-44-48.png | |
2025-01-06 18:09 | nitramr | Note Added: 0051885 | |
2025-01-06 18:09 | nitramr | File Added: customize_toolbar.png | |
2025-01-06 18:12 | ale | Note Added: 0051886 | |
2025-01-06 18:25 | nitramr | Note Added: 0051887 | |
2025-01-07 18:31 | ale | Relationship added | child of 0017365 |
2025-01-09 09:25 | ale | Relationship added | related to 0017369 |
2025-01-11 13:45 | nitramr | Note Added: 0051906 | |
2025-01-11 13:45 | nitramr | File Added: node_editing_palette.png | |
2025-01-11 20:57 | ale | Note Added: 0051907 | |
2025-01-11 21:14 | nitramr | Note Added: 0051908 | |
2025-01-12 09:13 | ale | Note Added: 0051910 | |
2025-01-13 20:46 | nitramr | Note Added: 0051913 | |
2025-01-13 21:00 | nitramr | Note Added: 0051914 | |
2025-01-23 08:25 | ale | Note Added: 0051937 | |
2025-02-07 19:50 | nitramr | Description Updated | |
2025-02-07 20:00 | nitramr | Note Added: 0052022 | |
2025-02-07 20:00 | nitramr | File Added: files.zip | |
2025-02-07 20:00 | nitramr | File Added: nodeedit_2025-02-07_01.patch | |
2025-02-07 20:00 | nitramr | File Added: node_editing.png | |
2025-02-07 20:07 | nitramr | Description Updated | |
2025-02-07 20:09 | nitramr | Product Version | 1.7 milestone => 1.7.1.svn |
2025-02-07 20:09 | nitramr | Patch | No => Yes |
2025-02-08 12:19 | ale | Note Added: 0052026 | |
2025-02-08 13:34 | nitramr | Note Added: 0052028 | |
2025-02-08 18:08 | nitramr | Relationship added | related to 0017411 |
2025-02-08 19:58 | ale | Note Added: 0052029 | |
2025-02-17 18:49 | nitramr | Note Added: 0052065 | |
2025-02-17 18:49 | nitramr | File Added: files-2.zip | |
2025-02-17 18:49 | nitramr | File Added: nodeedit_2025-02-16_01.patch | |
2025-02-17 18:49 | nitramr | File Added: nodeedit_palette.png | |
2025-02-17 18:52 | nitramr | Description Updated | |
2025-02-17 21:14 | ale | Note Added: 0052066 |