Merge lp:~inkscape.dev/inkscape/doc_rotate into lp:~inkscape.dev/inkscape/trunk

Proposed by Jabiertxof
Status: Merged
Merged at revision: 15444
Proposed branch: lp:~inkscape.dev/inkscape/doc_rotate
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 2821 lines (+1359/-546)
31 files modified
share/extensions/hpgl_output.py (+15/-0)
share/extensions/synfig_output.py (+9/-0)
src/attributes.cpp (+1/-0)
src/attributes.h (+1/-0)
src/desktop-events.cpp (+4/-0)
src/display/canvas-temporary-item-list.h (+1/-0)
src/display/sp-canvas.cpp (+292/-3)
src/display/sp-canvas.h (+4/-1)
src/document-undo.cpp (+37/-34)
src/document.cpp (+6/-0)
src/extension/internal/cairo-png-out.cpp (+2/-2)
src/extension/internal/cairo-ps-out.cpp (+5/-2)
src/extension/internal/cairo-renderer-pdf-out.cpp (+3/-1)
src/extension/internal/emf-inout.cpp (+3/-2)
src/extension/internal/javafx-out.cpp (+7/-2)
src/extension/internal/latex-pstricks-out.cpp (+2/-0)
src/extension/internal/odf.cpp (+8/-1)
src/extension/internal/pov-out.cpp (+10/-3)
src/extension/internal/wmf-inout.cpp (+5/-2)
src/file.cpp (+4/-2)
src/print.cpp (+2/-0)
src/sp-namedview.cpp (+97/-1)
src/sp-namedview.h (+8/-3)
src/ui/dialog/export.cpp (+8/-1)
src/ui/tools/tool-base.cpp (+562/-466)
src/ui/tools/tool-base.h (+1/-0)
src/viewbox.cpp (+49/-13)
src/viewbox.h (+7/-0)
src/widgets/desktop-widget.cpp (+202/-6)
src/widgets/desktop-widget.h (+3/-1)
src/widgets/widget-sizes.h (+1/-0)
To merge this branch: bzr merge lp:~inkscape.dev/inkscape/doc_rotate
Reviewer Review Type Date Requested Status
Martin Owens Approve
Inkscape Developers Pending
Review via email: mp+310123@code.launchpad.net

Description of the change

Add rotate canvas feature to Inkscape.
It add the option to rotate in 2 ways:
1. from botton left widget
2. with control+middle button or spacebar

To post a comment you must log in.
lp:~inkscape.dev/inkscape/doc_rotate updated
15175. By Jabiertxof

Fix a bug that allow to enter rotate mode with right click

15176. By Jabiertxof

Update to trunk

15177. By Jabiertxof

Fix some bugs pointed by vlada

15178. By Jabiertxof

Update to trunk

15179. By Jabiertxof

Fix some bugs

15180. By Jabiertxof <jtx@jtx>

fixing to new trunk

15181. By Jabiertxof <jtx@jtx>

Update to trunk

Revision history for this message
Martin Owens (doctormo) wrote :

The namespace should be saved somewhere instead of re-specified each time:

DOCROTATE = "{http://www.inkscape.org/namespaces/inkscape}document_rotation"

for example.

Everything else looks remarkably good for such a large diff.

review: Approve
lp:~inkscape.dev/inkscape/doc_rotate updated
15182. By Jabiertxof

Put namespace as constant

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'share/extensions/hpgl_output.py'
2--- share/extensions/hpgl_output.py 2016-05-26 09:28:23 +0000
3+++ share/extensions/hpgl_output.py 2017-01-24 17:53:20 +0000
4@@ -20,6 +20,7 @@
5
6 # standard library
7 import sys
8+from inkex import NSS
9 # local libraries
10 import hpgl_encoder
11 import inkex
12@@ -44,10 +45,18 @@
13 self.OptionParser.add_option('--precut', action='store', type='inkbool', dest='precut', default='TRUE', help='Use precut')
14 self.OptionParser.add_option('--flat', action='store', type='float', dest='flat', default=1.2, help='Curve flatness')
15 self.OptionParser.add_option('--autoAlign', action='store', type='inkbool', dest='autoAlign', default='TRUE', help='Auto align')
16+ self.DOCROTATE = "{http://www.inkscape.org/namespaces/inkscape}document_rotation"
17
18 def effect(self):
19 self.options.debug = False
20 # get hpgl data
21+ svg = self.document.getroot()
22+ xpathStr = '//sodipodi:namedview'
23+ nv = svg.xpath(xpathStr, namespaces=NSS)
24+ document_rotate = "0"
25+ if nv != []:
26+ document_rotate = nv[0].get(self.DOCROTATE)
27+ nv[0].set(self.DOCROTATE,"0")
28 myHpglEncoder = hpgl_encoder.hpglEncoder(self)
29 try:
30 self.hpgl, debugObject = myHpglEncoder.getHpgl()
31@@ -56,9 +65,13 @@
32 # issue error if no paths found
33 inkex.errormsg(_("No paths where found. Please convert all objects you want to save into paths."))
34 self.hpgl = ''
35+ if nv != [] and document_rotate:
36+ nv[0].set("inkscape:document_rotation",document_rotate)
37 return
38 else:
39 type, value, traceback = sys.exc_info()
40+ if nv != [] and document_rotate:
41+ nv[0].set("inkscape:document_rotation",document_rotate)
42 raise ValueError, ("", type, value), traceback
43 # convert raw HPGL to HPGL
44 hpglInit = 'IN'
45@@ -67,6 +80,8 @@
46 if self.options.speed > 0:
47 hpglInit += ';VS%d' % self.options.speed
48 self.hpgl = hpglInit + self.hpgl + ';SP0;PU0,0;IN; '
49+ if nv != [] and document_rotate:
50+ nv[0].set("inkscape:document_rotation",document_rotate)
51
52 def output(self):
53 # print to file
54
55=== modified file 'share/extensions/synfig_output.py'
56--- share/extensions/synfig_output.py 2011-11-25 19:00:24 +0000
57+++ share/extensions/synfig_output.py 2017-01-24 17:53:20 +0000
58@@ -1046,6 +1046,11 @@
59 ###### Main Class #########################################
60 class SynfigExport(SynfigPrep):
61 def __init__(self):
62+ svg = self.document.getroot()
63+ xpathStr = '//http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}:namedview'
64+ res = svg.xpath(xpathStr, namespaces=inkex.NSS)
65+ self.document_rotate = res[0].get("inkscape:document_rotation")
66+ res[0].set("inkscape:document_rotation","0")
67 SynfigPrep.__init__(self)
68
69 def effect(self):
70@@ -1073,6 +1078,10 @@
71 root_canvas.append(layer)
72
73 d.get_root_tree().write(sys.stdout)
74+ svg = self.document.getroot()
75+ xpathStr = '//http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}:namedview'
76+ res = svg.xpath(xpathStr, namespaces=inkex.NSS)
77+ res[0].set("inkscape:document_rotation",self.document_rotate)
78
79 def convert_node(self, node, d):
80 """Convert an SVG node to a list of Synfig layers"""
81
82=== modified file 'src/attributes.cpp'
83--- src/attributes.cpp 2016-10-19 11:11:05 +0000
84+++ src/attributes.cpp 2017-01-24 17:53:20 +0000
85@@ -89,6 +89,7 @@
86 {SP_ATTR_INKSCAPE_ZOOM, "inkscape:zoom"},
87 {SP_ATTR_INKSCAPE_CX, "inkscape:cx"},
88 {SP_ATTR_INKSCAPE_CY, "inkscape:cy"},
89+ {SP_ATTR_INKSCAPE_DOCUMENT_ROTATION, "inkscape:document-rotation"},
90 {SP_ATTR_INKSCAPE_WINDOW_WIDTH, "inkscape:window-width"},
91 {SP_ATTR_INKSCAPE_WINDOW_HEIGHT, "inkscape:window-height"},
92 {SP_ATTR_INKSCAPE_WINDOW_X, "inkscape:window-x"},
93
94=== modified file 'src/attributes.h'
95--- src/attributes.h 2016-10-19 11:11:05 +0000
96+++ src/attributes.h 2017-01-24 17:53:20 +0000
97@@ -97,6 +97,7 @@
98 SP_ATTR_INKSCAPE_ZOOM,
99 SP_ATTR_INKSCAPE_CX,
100 SP_ATTR_INKSCAPE_CY,
101+ SP_ATTR_INKSCAPE_DOCUMENT_ROTATION,
102 SP_ATTR_INKSCAPE_WINDOW_WIDTH,
103 SP_ATTR_INKSCAPE_WINDOW_HEIGHT,
104 SP_ATTR_INKSCAPE_WINDOW_X,
105
106=== modified file 'src/desktop-events.cpp'
107--- src/desktop-events.cpp 2016-08-08 23:57:01 +0000
108+++ src/desktop-events.cpp 2017-01-24 17:53:20 +0000
109@@ -155,6 +155,10 @@
110 }
111 }
112
113+ SPNamedView *namedview = desktop->namedview;
114+ if (namedview && namedview->document_rotation) {
115+ normal *= Geom::Rotate(Geom::rad_from_deg(namedview->document_rotation * -1));
116+ }
117 guide = sp_guideline_new(desktop->guides, NULL, event_dt, normal);
118 sp_guideline_set_color(SP_GUIDELINE(guide), desktop->namedview->guidehicolor);
119
120
121=== modified file 'src/display/canvas-temporary-item-list.h'
122--- src/display/canvas-temporary-item-list.h 2014-10-08 02:22:03 +0000
123+++ src/display/canvas-temporary-item-list.h 2017-01-24 17:53:20 +0000
124@@ -14,6 +14,7 @@
125
126 struct SPCanvasItem;
127 class SPDesktop;
128+class SPViewBox;
129
130 namespace Inkscape {
131 namespace Display {
132
133=== modified file 'src/display/sp-canvas.cpp'
134--- src/display/sp-canvas.cpp 2016-12-25 19:26:50 +0000
135+++ src/display/sp-canvas.cpp 2017-01-24 17:53:20 +0000
136@@ -29,18 +29,24 @@
137 #include "helper/sp-marshal.h"
138 #include <2geom/rect.h>
139 #include <2geom/affine.h>
140-#include "display/cairo-utils.h"
141 #include "display/sp-canvas.h"
142 #include "display/sp-canvas-group.h"
143+#include "display/rendermode.h"
144+#include "display/cairo-utils.h"
145+#include "display/cairo-templates.h"
146+#include "display/drawing-context.h"
147+#include "display/drawing-item.h"
148+#include "display/nr-filter-colormatrix.h"
149+#include "display/canvas-arena.h"
150 #include "preferences.h"
151 #include "inkscape.h"
152 #include "sodipodi-ctrlrect.h"
153 #include "cms-system.h"
154-#include "display/rendermode.h"
155-#include "display/cairo-utils.h"
156 #include "debug/gdk-event-latency-tracker.h"
157 #include "desktop.h"
158 #include "color.h"
159+#include <iomanip>
160+#include <glibmm/i18n.h>
161
162 using Inkscape::Debug::GdkEventLatencyTracker;
163
164@@ -1888,6 +1894,19 @@
165 return SP_CANVAS_GROUP(_root);
166 }
167
168+gdouble grayscale_value_matrix[20] = {
169+ 0.21, 0.72, 0.072, 0, 0,
170+ 0.21, 0.72, 0.072, 0, 0,
171+ 0.21, 0.72, 0.072, 0, 0,
172+ 0 , 0 , 0 , 1, 0
173+ };
174+cairo_surface_t *surface_rotated;
175+cairo_surface_t *surface_origin;
176+cairo_surface_t *surface_measure;
177+double start_angle = 0;
178+bool started = false;
179+bool rotated = false;
180+
181 void SPCanvas::scrollTo(double cx, double cy, unsigned int clear, bool is_scrolling)
182 {
183 GtkAllocation allocation;
184@@ -1910,9 +1929,14 @@
185 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
186 // Paint the background
187 cairo_translate(cr, -ix, -iy);
188+ if (rotated) {
189+ cairo_translate(cr, dx, dy);
190+ rotated = false;
191+ }
192 cairo_set_source(cr, _background);
193 cairo_paint(cr);
194 // Copy the old backing store contents
195+
196 cairo_set_source_surface(cr, _backing_store, _x0, _y0);
197 cairo_rectangle(cr, _x0, _y0, allocation.width, allocation.height);
198 cairo_clip(cr);
199@@ -1949,6 +1973,271 @@
200 addIdle();
201 }
202
203+void SPCanvas::startRotateTo(double angle)
204+{
205+ if (!_backing_store || started) {
206+ return;
207+ }
208+ start_angle = angle;
209+ started = true;
210+ GtkAllocation allocation;
211+ gtk_widget_get_allocation(&_widget, &allocation);
212+ int half_w = allocation.width/2;
213+ int half_h = allocation.height/2;
214+ int half_min = std::min(half_w,half_h);
215+
216+ cairo_surface_t *new_backing_store = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
217+ cairo_t *cr = cairo_create(new_backing_store);
218+ cairo_arc(cr, half_w, half_h, half_min-15, 0, 2*M_PI);
219+ cairo_fill(cr);
220+ cairo_set_operator(cr, CAIRO_OPERATOR_IN);
221+ cairo_set_source_surface(cr, _backing_store, 0, 0);
222+ cairo_paint(cr);
223+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
224+ cairo_arc(cr, half_w, half_h, half_min-16, 0, 2*M_PI);
225+ cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
226+ cairo_stroke(cr);
227+ cairo_destroy(cr);
228+ surface_rotated = new_backing_store;
229+
230+ cairo_surface_t *new_backing_store_measure = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
231+ cr = cairo_create(new_backing_store_measure);
232+ cairo_arc(cr, half_w, half_h, half_min-15, 0, 2*M_PI);
233+ cairo_set_source_rgba (cr, 1, 1, 1, 0.2);
234+ cairo_fill(cr);
235+ cairo_translate(cr, half_w, half_h);
236+ cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
237+ cairo_set_font_size(cr, 10.0);
238+ for (gint x = 0; x < 360 ; x++){
239+ gint ang = 360 - x ;//+ 90;
240+ if (ang > 180) {
241+ ang -= 360;
242+ }
243+ double rot = (-180.0 + x)*(M_PI/180.);
244+ double dist = half_min-9;
245+ gint inverse = 1;
246+ if((x) < 91 || (x) > 270) {
247+ inverse = -1;
248+ }
249+ if(x%10 == 0) {
250+ cairo_rotate(cr, -rot);
251+ cairo_text_extents_t extents;
252+ std::string s = std::to_string(ang) + "º";
253+ cairo_text_extents(cr, s.c_str(), &extents);
254+ //std::cout << extents.width/2 << "extents.x_bearing\n";
255+ cairo_translate(cr, (extents.width/2) * inverse * -1, (dist + ((extents.height/2)* inverse)));
256+ if((x) < 91 || (x) > 270) {
257+ cairo_rotate(cr, 180*(M_PI/180.0));
258+ }
259+ cairo_text_path(cr, s.c_str());
260+ if((x) < 91 || (x) > 270) {
261+ cairo_rotate(cr, -180*(M_PI/180.0));
262+ }
263+ cairo_translate(cr, (extents.width/2) * inverse , (dist + ((extents.height/2)* inverse)) * -1);
264+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
265+ cairo_fill(cr);
266+ cairo_rotate(cr, rot);
267+ }
268+ cairo_rotate(cr, x*(M_PI/180.));
269+ if(x%5 == 0) {
270+ cairo_move_to(cr, 0, half_min-30);
271+ cairo_line_to(cr, 0, half_min-17);
272+ } else {
273+ cairo_move_to(cr, 0, half_min-20);
274+ cairo_line_to(cr, 0, half_min-15);
275+ }
276+ cairo_line_to(cr, 0, half_min-15);
277+ cairo_set_source_rgba (cr, 0, 0, 0, 0.4);
278+ cairo_set_line_width (cr,1);
279+ cairo_stroke(cr);
280+ cairo_rotate(cr, -x*(M_PI/180.));
281+ }
282+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
283+ cairo_translate(cr, -half_w, -half_h);
284+ cairo_arc(cr, half_w, half_h, half_min-30, 0, 2*M_PI);
285+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
286+ cairo_fill(cr);
287+ cairo_translate(cr, half_w, half_h);
288+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
289+ cairo_rotate(cr, start_angle*(M_PI/180.));
290+ cairo_move_to(cr, 0, 0);
291+ cairo_line_to(cr, 0, (half_min-17) * -1);
292+ cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
293+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
294+ cairo_set_line_width (cr,5);
295+ cairo_stroke(cr);
296+ cairo_move_to(cr, 0, 0);
297+ cairo_line_to(cr, 0, (half_min-17) * -1);
298+ cairo_set_source_rgba (cr, 1, 0, 0, 0.9);
299+ cairo_set_line_width (cr,1);
300+ cairo_stroke(cr);
301+ cairo_rotate(cr, -start_angle*(M_PI/180.));
302+ cairo_destroy(cr);
303+ surface_measure = new_backing_store_measure;
304+
305+ cairo_surface_t *new_backing_store_grey = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
306+ cr = cairo_create(new_backing_store_grey);
307+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
308+ cairo_set_source_surface(cr, _backing_store, 0, 0);
309+ cairo_paint(cr);
310+ Inkscape::Filters::FilterColorMatrix::ColorMatrixMatrix _grayscale_colormatrix = std::vector<gdouble> (grayscale_value_matrix, grayscale_value_matrix + 20);
311+ cairo_surface_t *out = ink_cairo_surface_create_identical(new_backing_store_grey);
312+ ink_cairo_surface_filter(new_backing_store_grey, out, _grayscale_colormatrix);
313+ cairo_set_source_surface(cr, out, 0, 0);
314+ cairo_surface_destroy(out);
315+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
316+ cairo_paint(cr);
317+ cairo_destroy(cr);
318+ surface_origin = new_backing_store_grey;
319+}
320+
321+bool SPCanvas::endRotateTo()
322+{
323+ if (!_backing_store || !started) {
324+ return false;
325+ }
326+ started = false;
327+ surface_rotated = NULL;
328+ surface_origin = NULL;
329+ gtk_widget_queue_draw(GTK_WIDGET(this));
330+ dirtyAll();
331+ addIdle();
332+ rotated = true;
333+ return true;
334+}
335+
336+void SPCanvas::clearRotateTo()
337+{
338+ if (!started) {
339+ return;
340+ }
341+ gtk_widget_queue_draw(GTK_WIDGET(this));
342+ dirtyAll();
343+ addIdle();
344+}
345+
346+void SPCanvas::rotateTo(double angle)
347+{
348+ if (!_backing_store || !started) {
349+ return;
350+ }
351+ GtkAllocation allocation;
352+ gtk_widget_get_allocation(&_widget, &allocation);
353+ int half_w = allocation.width/2;
354+ int half_h = allocation.height/2;
355+ int half_min = std::min(half_w,half_h);
356+ cairo_surface_t *new_backing_store = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
357+ cairo_t *cr = cairo_create(new_backing_store);
358+ cairo_set_source_surface(cr, surface_origin, 0, 0);
359+ cairo_paint(cr);
360+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
361+ cairo_paint(cr);
362+ cairo_pattern_t *source_pattern;
363+ cairo_matrix_t matrix;
364+ source_pattern = cairo_pattern_create_for_surface (surface_rotated);
365+ cairo_matrix_init_identity (&matrix);
366+ cairo_matrix_translate (&matrix, allocation.width/2.0, allocation.height/2.0);
367+ cairo_matrix_rotate (&matrix, Geom::rad_from_deg(angle - start_angle) * -1);
368+ cairo_matrix_translate (&matrix, -allocation.width/2.0, -allocation.height/2.0);
369+ cairo_pattern_set_matrix (source_pattern, &matrix);
370+ cairo_set_source(cr, source_pattern);
371+ cairo_paint(cr);
372+ cairo_set_source_surface(cr, surface_measure, 0, 0);
373+ cairo_paint(cr);
374+ cairo_translate(cr, half_w, half_h);
375+ cairo_rotate(cr, angle*(M_PI/180.));
376+ cairo_move_to(cr, 0, 0);
377+ cairo_line_to(cr, 0, (half_min-17) * -1);
378+ cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
379+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
380+ cairo_set_line_width (cr,5);
381+ cairo_stroke(cr);
382+ cairo_move_to(cr, 0, 0);
383+ cairo_line_to(cr, 0, (half_min-17) * -1);
384+ cairo_set_source_rgba (cr, 1, 1, 1, 0.9);
385+ cairo_set_line_width (cr,1);
386+ cairo_stroke(cr);
387+ cairo_move_to(cr, 0, 0);
388+ cairo_line_to(cr, 0, (half_min-17) * -1);
389+ cairo_set_source_rgba (cr, 1, 0, 0, 0.9);
390+ const double dashed[] = {6.0, 3.0};
391+ int len = sizeof(dashed) / sizeof(dashed[0]);
392+ cairo_set_dash(cr, dashed, len, 1);
393+ cairo_stroke(cr);
394+ cairo_translate(cr, -half_w, -half_h);
395+ cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
396+ cairo_arc(cr, half_w, half_h, 7, 0, 2*M_PI);
397+ cairo_fill(cr);
398+ cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
399+ cairo_arc(cr, half_w, half_h, 5, 0, 2*M_PI);
400+ cairo_fill(cr);
401+ cairo_translate(cr, half_w, half_h);
402+ cairo_rotate(cr, -angle*(M_PI/180.));
403+ cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
404+ cairo_set_font_size(cr, 15.0);
405+ cairo_text_extents_t extents;
406+ std::ostringstream s;
407+ s << _("Original angle ") << std::fixed << std::setprecision(2) << start_angle << "º";
408+ cairo_text_extents(cr, s.str().c_str(), &extents);
409+ cairo_translate(cr, half_w - extents.width -15 ,-half_h + 25);
410+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
411+ cairo_text_path(cr, s.str().c_str());
412+ cairo_fill(cr);
413+ cairo_translate(cr, (half_w - extents.width -15) *-1 ,(-half_h + 25) *-1);
414+ s.str("");
415+ s << _("New angle ") << std::fixed << std::setprecision(2) << angle << "º";
416+ cairo_text_extents(cr, s.str().c_str(), &extents);
417+ cairo_translate(cr, half_w - extents.width -15 ,-half_h + 45);
418+ cairo_text_path(cr, s.str().c_str());
419+ cairo_fill(cr);
420+ cairo_translate(cr, (half_w - extents.width -15) *-1 ,(-half_h + 45) *-1);
421+ s.str("");
422+ s << _("Gap ") << std::fixed << std::setprecision(2) << std::abs(start_angle-angle) << "º";
423+ cairo_text_extents(cr, s.str().c_str(), &extents);
424+ cairo_translate(cr, half_w - extents.width -15 ,-half_h + 65);
425+ cairo_text_path(cr, s.str().c_str());
426+ cairo_fill(cr);
427+ cairo_translate(cr, (half_w - extents.width -15) *-1 ,(-half_h + 65) *-1);
428+ cairo_translate(cr, -half_w + 10 ,-half_h + 25);
429+ s.str("");
430+ cairo_set_font_size(cr, 12.0);
431+ s << _("Normal mode, 1º round step");
432+ cairo_text_path(cr, s.str().c_str());
433+ cairo_fill(cr);
434+ cairo_translate(cr, (-half_w +10) * -1 ,(-half_h + 25) * -1);
435+ cairo_translate(cr, -half_w + 10 ,-half_h + 40);
436+ s.str("");
437+ s << _("+ALT, Fractional degrees");
438+ cairo_text_path(cr, s.str().c_str());
439+ cairo_fill(cr);
440+ cairo_translate(cr, (-half_w + 10) * -1 ,(-half_h + 40) * -1);
441+ cairo_translate(cr, -half_w + 10 ,-half_h + 55);
442+ s.str("");
443+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
444+ s << _("+CTRL, ") << 180.0/prefs->getInt("/options/rotationsnapsperpi/value", 12) << _("º round step");
445+ cairo_text_path(cr, s.str().c_str());
446+ cairo_fill(cr);
447+ cairo_translate(cr, (-half_w + 10) * -1 ,(-half_h + 55) * -1);
448+ cairo_translate(cr, -half_w + 10 ,-half_h + 70);
449+ s.str("");
450+ s << _("+SHIFT, Reset");
451+ cairo_text_path(cr, s.str().c_str());
452+ cairo_fill(cr);
453+ cairo_translate(cr, (-half_w + 10) * -1 ,(-half_h + 70) * -1);
454+ cairo_translate(cr, -half_w + 10 ,-half_h + 85);
455+ s.str("");
456+ s << _("+CTRL+SHIFT, 0º");
457+ cairo_text_path(cr, s.str().c_str());
458+ cairo_fill(cr);
459+ //cairo_translate(cr, (-half_w + 10) * -1 ,(-half_h + 60) * -1);
460+ cairo_destroy(cr);
461+ cairo_surface_destroy(_backing_store);
462+ _backing_store = new_backing_store;
463+ cairo_pattern_destroy (source_pattern);
464+ gtk_widget_queue_draw(GTK_WIDGET(this));
465+ addIdle();
466+}
467+
468 void SPCanvas::updateNow()
469 {
470 if (_need_update) {
471
472=== modified file 'src/display/sp-canvas.h'
473--- src/display/sp-canvas.h 2016-08-12 04:11:03 +0000
474+++ src/display/sp-canvas.h 2017-01-24 17:53:20 +0000
475@@ -72,7 +72,10 @@
476 struct SPCanvas {
477 /// Scrolls canvas to specific position (cx and cy are measured in screen pixels).
478 void scrollTo(double cx, double cy, unsigned int clear, bool is_scrolling = false);
479-
480+ void startRotateTo(double angle);
481+ void rotateTo(double angle);
482+ bool endRotateTo();
483+ void clearRotateTo();
484 /// Synchronously updates the canvas if necessary.
485 void updateNow();
486
487
488=== modified file 'src/document-undo.cpp'
489--- src/document-undo.cpp 2017-01-24 00:02:46 +0000
490+++ src/document-undo.cpp 2017-01-24 17:53:20 +0000
491@@ -240,49 +240,52 @@
492
493 gboolean Inkscape::DocumentUndo::undo(SPDocument *doc)
494 {
495- using Inkscape::Debug::EventTracker;
496- using Inkscape::Debug::SimpleEvent;
497-
498- gboolean ret;
499-
500- EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
501-
502- g_assert (doc != NULL);
503- g_assert (doc->priv != NULL);
504- g_assert (doc->priv->sensitive);
505-
506- doc->priv->sensitive = FALSE;
507+ using Inkscape::Debug::EventTracker;
508+ using Inkscape::Debug::SimpleEvent;
509+
510+ gboolean ret;
511+
512+ EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
513+
514+ g_assert (doc != NULL);
515+ g_assert (doc->priv != NULL);
516+ g_assert (doc->priv->sensitive);
517+
518+ doc->priv->sensitive = FALSE;
519 doc->priv->seeking = true;
520
521- doc->actionkey.clear();
522-
523- finish_incomplete_transaction(*doc);
524-
525- if (! doc->priv->undo.empty()) {
526- Inkscape::Event *log = doc->priv->undo.back();
527- doc->priv->undo.pop_back();
528- sp_repr_undo_log (log->event);
529- perform_document_update(*doc);
530-
531- doc->priv->redo.push_back(log);
532+ doc->actionkey.clear();
533+
534+ finish_incomplete_transaction(*doc);
535+
536+ if (! doc->priv->undo.empty()) {
537+ Inkscape::Event *log = doc->priv->undo.back();
538+ doc->priv->undo.pop_back();
539+ sp_repr_undo_log (log->event);
540+ perform_document_update(*doc);
541+
542+ doc->priv->redo.push_back(log);
543
544 doc->setModifiedSinceSave();
545 doc->priv->undoStackObservers.notifyUndoEvent(log);
546
547- ret = TRUE;
548- } else {
549- ret = FALSE;
550- }
551-
552- sp_repr_begin_transaction (doc->rdoc);
553-
554- doc->priv->sensitive = TRUE;
555+ ret = TRUE;
556+ } else {
557+ ret = FALSE;
558+ }
559+
560+ sp_repr_begin_transaction (doc->rdoc);
561+
562+ doc->priv->sensitive = TRUE;
563 doc->priv->seeking = false;
564
565- if (ret)
566- INKSCAPE.external_change();
567+ if (ret) INKSCAPE.external_change();
568
569- return ret;
570+ SPObject *updated = doc->getRoot();
571+ if (updated) {
572+ updated->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
573+ }
574+ return ret;
575 }
576
577 gboolean Inkscape::DocumentUndo::redo(SPDocument *doc)
578
579=== modified file 'src/document.cpp'
580--- src/document.cpp 2016-11-28 22:07:00 +0000
581+++ src/document.cpp 2017-01-24 17:53:20 +0000
582@@ -386,6 +386,7 @@
583 if (!bordercolor.empty()) {
584 rnew->setAttribute("bordercolor", bordercolor.data());
585 }
586+ sp_repr_set_svg_double(rnew, "inkscape:document-rotation", 0.);
587 sp_repr_set_svg_double(rnew, "borderopacity",
588 prefs->getDouble("/template/base/borderopacity", 1.0));
589 sp_repr_set_svg_double(rnew, "objecttolerance",
590@@ -407,6 +408,11 @@
591 rroot->addChild(rnew, NULL);
592 // clean up
593 Inkscape::GC::release(rnew);
594+ } else {
595+ Inkscape::XML::Node *nv_repr = sp_item_group_get_child_by_name(document->root, NULL, "sodipodi:namedview")->getRepr();
596+ if (!nv_repr->attribute("inkscape:document-rotation")) {
597+ sp_repr_set_svg_double(nv_repr, "inkscape:document-rotation", 0.);
598+ }
599 }
600
601 // Defs
602
603=== modified file 'src/extension/internal/cairo-png-out.cpp'
604--- src/extension/internal/cairo-png-out.cpp 2014-03-27 01:33:44 +0000
605+++ src/extension/internal/cairo-png-out.cpp 2017-01-24 17:53:20 +0000
606@@ -53,11 +53,10 @@
607 {
608 CairoRenderer *renderer;
609 CairoRenderContext *ctx;
610-
611+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
612 doc->ensureUpToDate();
613
614 /* Start */
615-
616 SPItem *base = doc->getRoot();
617 Inkscape::Drawing drawing;
618 unsigned dkey = SPItem::display_key_new(1);
619@@ -77,6 +76,7 @@
620 renderer->destroyContext(ctx);
621
622 base->invoke_hide(dkey);
623+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
624 /* end */
625 delete renderer;
626
627
628=== modified file 'src/extension/internal/cairo-ps-out.cpp'
629--- src/extension/internal/cairo-ps-out.cpp 2016-06-11 17:25:23 +0000
630+++ src/extension/internal/cairo-ps-out.cpp 2017-01-24 17:53:20 +0000
631@@ -68,6 +68,7 @@
632 ps_print_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level, bool texttopath, bool omittext,
633 bool filtertobitmap, int resolution, const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, bool eps = false)
634 {
635+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
636 doc->ensureUpToDate();
637
638 SPItem *base = NULL;
639@@ -84,9 +85,10 @@
640 pageBoundingBox = !exportDrawing;
641 }
642
643- if (!base)
644+ if (!base) {
645+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
646 return false;
647-
648+ }
649 Inkscape::Drawing drawing;
650 unsigned dkey = SPItem::display_key_new(1);
651 base->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
652@@ -115,6 +117,7 @@
653
654 renderer->destroyContext(ctx);
655 delete renderer;
656+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
657
658 return ret;
659 }
660
661=== modified file 'src/extension/internal/cairo-renderer-pdf-out.cpp'
662--- src/extension/internal/cairo-renderer-pdf-out.cpp 2016-06-11 17:25:23 +0000
663+++ src/extension/internal/cairo-renderer-pdf-out.cpp 2017-01-24 17:53:20 +0000
664@@ -61,6 +61,7 @@
665 bool texttopath, bool omittext, bool filtertobitmap, int resolution,
666 const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px)
667 {
668+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
669 doc->ensureUpToDate();
670
671 /* Start */
672@@ -80,6 +81,7 @@
673 }
674
675 if (!base) {
676+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
677 return false;
678 }
679
680@@ -112,7 +114,7 @@
681
682 renderer->destroyContext(ctx);
683 delete renderer;
684-
685+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
686 return ret;
687 }
688
689
690=== modified file 'src/extension/internal/emf-inout.cpp'
691--- src/extension/internal/emf-inout.cpp 2016-06-11 17:25:23 +0000
692+++ src/extension/internal/emf-inout.cpp 2017-01-24 17:53:20 +0000
693@@ -94,7 +94,7 @@
694 const gchar *oldconst;
695 gchar *oldoutput;
696 unsigned int ret;
697-
698+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
699 doc->ensureUpToDate();
700
701 mod = Inkscape::Extension::get_print(PRINT_EMF);
702@@ -114,6 +114,7 @@
703 /* Print document */
704 ret = mod->begin(doc);
705 if (ret) {
706+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
707 g_free(oldoutput);
708 throw Inkscape::Extension::Output::save_failed();
709 }
710@@ -127,7 +128,7 @@
711
712 mod->set_param_string("destination", oldoutput);
713 g_free(oldoutput);
714-
715+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
716 return;
717 }
718
719
720=== modified file 'src/extension/internal/javafx-out.cpp'
721--- src/extension/internal/javafx-out.cpp 2016-07-14 11:17:21 +0000
722+++ src/extension/internal/javafx-out.cpp 2017-01-24 17:53:20 +0000
723@@ -843,7 +843,8 @@
724 bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8)
725 {
726 reset();
727-
728+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
729+ doc->ensureUpToDate();
730
731 name = Glib::path_get_basename(filename_utf8);
732 int pos = name.find('.');
733@@ -856,12 +857,14 @@
734 //# Lets do the curves first, to get the stats
735
736 if (!doTree(doc)) {
737+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
738 return false;
739 }
740 String curveBuf = outbuf;
741 outbuf.clear();
742
743 if (!doHeader()) {
744+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
745 return false;
746 }
747
748@@ -875,6 +878,7 @@
749 doBody(doc, doc->getRoot());
750
751 if (!doTail()) {
752+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
753 return false;
754 }
755
756@@ -884,6 +888,7 @@
757 FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w");
758 if (!f)
759 {
760+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
761 err("Could open JavaFX file '%s' for writing", filename_utf8);
762 return false;
763 }
764@@ -894,7 +899,7 @@
765 }
766
767 fclose(f);
768-
769+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
770 return true;
771 }
772
773
774=== modified file 'src/extension/internal/latex-pstricks-out.cpp'
775--- src/extension/internal/latex-pstricks-out.cpp 2012-04-18 07:02:24 +0000
776+++ src/extension/internal/latex-pstricks-out.cpp 2017-01-24 17:53:20 +0000
777@@ -49,6 +49,7 @@
778 void LatexOutput::save(Inkscape::Extension::Output * /*mod2*/, SPDocument *doc, gchar const *filename)
779 {
780 SPPrintContext context;
781+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
782 doc->ensureUpToDate();
783
784 Inkscape::Extension::Print *mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_LATEX);
785@@ -76,6 +77,7 @@
786
787 mod->set_param_string("destination", oldoutput);
788 g_free(oldoutput);
789+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
790 }
791
792 #include "clear-n_.h"
793
794=== modified file 'src/extension/internal/odf.cpp'
795--- src/extension/internal/odf.cpp 2016-08-17 07:39:43 +0000
796+++ src/extension/internal/odf.cpp 2017-01-24 17:53:20 +0000
797@@ -72,6 +72,7 @@
798 #include "sp-path.h"
799 #include "sp-text.h"
800 #include "sp-flowtext.h"
801+#include "sp-root.h"
802 #include "svg/svg.h"
803 #include "text-editing.h"
804 #include "util/units.h"
805@@ -2095,7 +2096,8 @@
806 void OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *filename)
807 {
808 reset();
809-
810+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
811+ doc->ensureUpToDate();
812 documentUri = Inkscape::URI(filename);
813
814 ZipFile zf;
815@@ -2104,25 +2106,30 @@
816 if (!writeManifest(zf))
817 {
818 g_warning("Failed to write manifest");
819+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
820 return;
821 }
822
823 if (!writeContent(zf, doc->rroot))
824 {
825 g_warning("Failed to write content");
826+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
827 return;
828 }
829
830 if (!writeMeta(zf))
831 {
832 g_warning("Failed to write metafile");
833+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
834 return;
835 }
836
837 if (!zf.writeFile(filename))
838 {
839+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
840 return;
841 }
842+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
843 }
844
845
846
847=== modified file 'src/extension/internal/pov-out.cpp'
848--- src/extension/internal/pov-out.cpp 2016-07-14 11:17:21 +0000
849+++ src/extension/internal/pov-out.cpp 2017-01-24 17:53:20 +0000
850@@ -616,11 +616,13 @@
851 void PovOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8)
852 {
853 reset();
854-
855+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
856+ doc->ensureUpToDate();
857 //###### SAVE IN POV FORMAT TO BUFFER
858 //# Lets do the curves first, to get the stats
859 if (!doTree(doc))
860 {
861+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
862 err("Could not output curves for %s", filename_utf8);
863 return;
864 }
865@@ -630,6 +632,7 @@
866
867 if (!doHeader())
868 {
869+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
870 err("Could not write header for %s", filename_utf8);
871 return;
872 }
873@@ -638,6 +641,7 @@
874
875 if (!doTail())
876 {
877+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
878 err("Could not write footer for %s", filename_utf8);
879 return;
880 }
881@@ -648,9 +652,11 @@
882 //###### WRITE TO FILE
883 Inkscape::IO::dump_fopen_call(filename_utf8, "L");
884 FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w");
885- if (!f)
886+ if (!f){
887+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
888 return;
889-
890+ }
891+
892 for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); ++iter)
893 {
894 int ch = *iter;
895@@ -658,6 +664,7 @@
896 }
897
898 fclose(f);
899+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
900 }
901
902
903
904=== modified file 'src/extension/internal/wmf-inout.cpp'
905--- src/extension/internal/wmf-inout.cpp 2016-06-11 17:25:23 +0000
906+++ src/extension/internal/wmf-inout.cpp 2017-01-24 17:53:20 +0000
907@@ -95,7 +95,7 @@
908 SPPrintContext context;
909 const gchar *oldconst;
910 gchar *oldoutput;
911-
912+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
913 doc->ensureUpToDate();
914
915 mod = Inkscape::Extension::get_print(PRINT_WMF);
916@@ -115,6 +115,7 @@
917 /* Print document */
918 if (mod->begin(doc)) {
919 g_free(oldoutput);
920+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
921 throw Inkscape::Extension::Output::save_failed();
922 }
923 mod->base->invoke_print(&context);
924@@ -127,7 +128,7 @@
925
926 mod->set_param_string("destination", oldoutput);
927 g_free(oldoutput);
928-
929+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
930 return;
931 }
932
933@@ -135,6 +136,8 @@
934 void
935 Wmf::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
936 {
937+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
938+ doc->ensureUpToDate();
939 Inkscape::Extension::Extension * ext;
940
941 ext = Inkscape::Extension::db.get(PRINT_WMF);
942
943=== modified file 'src/file.cpp'
944--- src/file.cpp 2017-01-24 00:39:06 +0000
945+++ src/file.cpp 2017-01-24 17:53:20 +0000
946@@ -293,10 +293,12 @@
947 bool replace_empty)
948 {
949 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
950+ Inkscape::Display::TemporaryItem *page_border_rotated = NULL;
951 if (desktop) {
952 desktop->setWaitingCursor();
953+ page_border_rotated = sp_document_namedview(desktop->getDocument(), NULL)->page_border_rotated;
954 }
955-
956+
957 SPDocument *doc = NULL;
958 bool cancelled = false;
959 try {
960@@ -315,7 +317,6 @@
961 }
962
963 if (doc) {
964-
965 SPDocument *existing = desktop ? desktop->getDocument() : NULL;
966
967 if (existing && existing->virgin && replace_empty) {
968@@ -323,6 +324,7 @@
969 doc->ensureUpToDate(); // TODO this will trigger broken link warnings, etc.
970 desktop->change_document(doc);
971 doc->emitResizedSignal(doc->getWidth().value("px"), doc->getHeight().value("px"));
972+ desktop->remove_temporary_canvasitem(page_border_rotated);
973 } else {
974 // create a whole new desktop and window
975 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL)); // TODO this will trigger broken link warnings, etc.
976
977=== modified file 'src/print.cpp'
978--- src/print.cpp 2012-02-27 23:49:20 +0000
979+++ src/print.cpp 2017-01-24 17:53:20 +0000
980@@ -79,6 +79,7 @@
981 void
982 sp_print_document(Gtk::Window& parentWindow, SPDocument *doc)
983 {
984+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
985 doc->ensureUpToDate();
986
987 // Build arena
988@@ -88,6 +89,7 @@
989 Inkscape::UI::Dialog::Print printop(doc,base);
990 Gtk::PrintOperationResult res = printop.run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, parentWindow);
991 (void)res; // TODO handle this
992+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
993 }
994
995 void sp_print_document_to_file(SPDocument *doc, gchar const *filename)
996
997=== modified file 'src/sp-namedview.cpp'
998--- src/sp-namedview.cpp 2016-08-03 14:56:48 +0000
999+++ src/sp-namedview.cpp 2017-01-24 17:53:20 +0000
1000@@ -19,7 +19,12 @@
1001 #include "event-log.h"
1002 #include <2geom/transforms.h>
1003
1004+#include "display/sp-canvas-group.h"
1005+#include "display/canvas-bpath.h"
1006+#include "display/canvas-temporary-item.h"
1007+#include "display/canvas-temporary-item-list.h"
1008 #include "display/canvas-grid.h"
1009+#include "display/curve.h"
1010 #include "util/units.h"
1011 #include "svg/svg-color.h"
1012 #include "xml/repr.h"
1013@@ -29,12 +34,15 @@
1014 #include "desktop-events.h"
1015
1016 #include "sp-guide.h"
1017+#include "sp-root.h"
1018 #include "sp-item-group.h"
1019 #include "sp-namedview.h"
1020 #include "preferences.h"
1021 #include "desktop.h"
1022+#include "selection.h"
1023+#include "object-set.h"
1024+#include "inkscape.h"
1025 #include "conn-avoid-ref.h" // for defaultConnSpacing.
1026-#include "sp-root.h"
1027 #include <gtkmm/window.h>
1028
1029 using Inkscape::DocumentUndo;
1030@@ -72,6 +80,7 @@
1031 this->pagecolor = 0;
1032 this->cx = 0;
1033 this->pageshadow = 0;
1034+ this->document_rotation = 0;
1035 this->window_width = 0;
1036 this->window_height = 0;
1037 this->window_maximized = 0;
1038@@ -92,9 +101,13 @@
1039 this->default_layer_id = 0;
1040
1041 this->connector_spacing = defaultConnSpacing;
1042+ this->page_border_rotated = NULL;
1043 }
1044
1045 SPNamedView::~SPNamedView() {
1046+ if(!this->getViewList().empty()) { // >0 Desktops
1047+ this->getViewList()[0]->remove_temporary_canvasitem(this->page_border_rotated);
1048+ }
1049 }
1050
1051 static void sp_namedview_generate_old_grid(SPNamedView * /*nv*/, SPDocument *document, Inkscape::XML::Node *repr) {
1052@@ -212,6 +225,7 @@
1053 this->readAttr( "inkscape:zoom" );
1054 this->readAttr( "inkscape:cx" );
1055 this->readAttr( "inkscape:cy" );
1056+ this->readAttr( "inkscape:document-rotation" );
1057 this->readAttr( "inkscape:window-width" );
1058 this->readAttr( "inkscape:window-height" );
1059 this->readAttr( "inkscape:window-x" );
1060@@ -409,6 +423,11 @@
1061 this->cy = value ? g_ascii_strtod(value, NULL) : HUGE_VAL; // HUGE_VAL means not set
1062 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
1063 break;
1064+ case SP_ATTR_INKSCAPE_DOCUMENT_ROTATION:
1065+ this->document_rotation = value ? g_ascii_strtod(value, NULL) : 0;
1066+ sp_namedview_set_document_rotation(this);
1067+ this->requestModified(SP_OBJECT_MODIFIED_FLAG);
1068+ break;
1069 case SP_ATTR_INKSCAPE_WINDOW_WIDTH:
1070 this->window_width = value? atoi(value) : -1; // -1 means not set
1071 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
1072@@ -939,6 +958,81 @@
1073 }
1074 }
1075
1076+void sp_namedview_doc_rotate_guides(SPNamedView *nv)
1077+{
1078+ bool saved = DocumentUndo::getUndoSensitive(nv->document);
1079+ DocumentUndo::setUndoSensitive(nv->document, false);
1080+ SPRoot * root = nv->document->getRoot();
1081+ Geom::Point page_center = root->viewBox.midpoint() * root->vbt;
1082+ Geom::Affine rot = Geom::identity();
1083+ rot *= Geom::Translate(page_center).inverse();
1084+ rot *= Geom::Rotate(Geom::rad_from_deg((nv->document_rotation - root->get_rotation()) * -1));
1085+ rot *= Geom::Translate(page_center);
1086+ for(std::vector<SPGuide *>::iterator it=nv->guides.begin();it!=nv->guides.end();++it ) {
1087+ Geom::Point const on_line = (*it)->getPoint() * rot ;
1088+ (*it)->moveto(on_line, true);
1089+ Geom::Affine rot_normal_affine = Geom::Rotate(Geom::rad_from_deg((nv->document_rotation - root->get_rotation()) * -1));
1090+ Geom::Point const rot_normal = (*it)->getNormal() * rot_normal_affine;
1091+ (*it)->set_normal(rot_normal, true);
1092+ }
1093+ DocumentUndo::setUndoSensitive(nv->document, saved);
1094+ nv->document->setModifiedSinceSave();
1095+}
1096+
1097+void sp_namedview_set_document_rotation(SPNamedView *nv)
1098+{
1099+ if ( nv->document->getRoot()->get_rotation() == nv->document_rotation) return;
1100+ if(!nv->getViewList().empty()) { // >0 Desktops
1101+ SPDesktop *desktop = nv->getViewList()[0];
1102+ desktop->remove_temporary_canvasitem(nv->page_border_rotated);
1103+ SPRoot * root = nv->document->getRoot();
1104+ SPCurve *c = new SPCurve();
1105+ c->moveto(root->viewBox.min());
1106+ c->lineto(Geom::Point(root->viewBox.max()[Geom::X],root->viewBox.min()[Geom::Y]));
1107+ c->lineto(Geom::Point(root->viewBox.max()[Geom::X],root->viewBox.max()[Geom::Y]));
1108+ c->lineto(Geom::Point(root->viewBox.min()[Geom::X],root->viewBox.max()[Geom::Y]));
1109+ c->closepath();
1110+ Geom::Point page_center = root->viewBox.midpoint();
1111+ Geom::PathVector const box = c->get_pathvector();
1112+ Geom::Affine rot = Geom::identity();
1113+ rot *= Geom::Translate(page_center).inverse();
1114+ rot *= Geom::Rotate(Geom::rad_from_deg(nv->document_rotation * -1));
1115+ rot *= Geom::Translate(page_center);
1116+ if (nv->document_rotation) {
1117+ SPCanvasItem *canvas_border = sp_canvas_bpath_new(desktop->getTempGroup(), c, true);
1118+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvas_border), 0xFF00009A, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
1119+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvas_border), 0, SP_WIND_RULE_NONZERO);
1120+ sp_canvas_item_affine_absolute(canvas_border, rot * root->vbt);
1121+ nv->page_border_rotated = desktop->add_temporary_canvasitem(canvas_border, 0);
1122+ }
1123+ sp_namedview_doc_rotate_guides(nv);
1124+ nv->document->getRoot()->set_rotation(nv->document_rotation);
1125+ c->unref();
1126+ }
1127+ if (nv->document_rotation) {
1128+ nv->showborder = FALSE;
1129+ } else {
1130+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1131+ nv->showborder = prefs->getBool("/template/base/showborder", 1.0);
1132+ }
1133+
1134+ SPDesktop * desktop = SP_ACTIVE_DESKTOP;
1135+ if (desktop) {
1136+//TODO: Remove knots of shapes on selected items
1137+// Inkscape::Selection * sel = desktop->getSelection();
1138+// std::vector<SPItem*> il(sel->items().begin(), sel->items().end());
1139+// for (std::vector<SPItem*>::const_iterator l = il.begin(); l != il.end(); l++){
1140+// SPItem *item = *l;
1141+// sel->remove(item->getRepr());
1142+// sel->add(item->getRepr());
1143+// }
1144+ SPObject *updated = desktop->getDocument()->getRoot();
1145+ if (updated) {
1146+ updated->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1147+ }
1148+ }
1149+}
1150+
1151 static void sp_namedview_show_single_guide(SPGuide* guide, bool show)
1152 {
1153 if (show) {
1154@@ -953,6 +1047,8 @@
1155 guide->set_locked(locked, true);
1156 }
1157
1158+
1159+
1160 void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr)
1161 {
1162 unsigned int v;
1163
1164=== modified file 'src/sp-namedview.h'
1165--- src/sp-namedview.h 2016-06-08 08:35:36 +0000
1166+++ src/sp-namedview.h 2017-01-24 17:53:20 +0000
1167@@ -21,6 +21,7 @@
1168 #include "snap.h"
1169 #include "document.h"
1170 #include "util/units.h"
1171+#include "display/sp-canvas.h"
1172 #include <vector>
1173
1174 namespace Inkscape {
1175@@ -28,6 +29,9 @@
1176 namespace Util {
1177 class Unit;
1178 }
1179+ namespace Display {
1180+ class TemporaryItem;
1181+ }
1182 }
1183
1184 typedef unsigned int guint32;
1185@@ -38,7 +42,7 @@
1186 SP_BORDER_LAYER_TOP
1187 };
1188
1189-class SPNamedView : public SPObjectGroup {
1190+class SPNamedView : public SPObjectGroup{
1191 public:
1192 SPNamedView();
1193 virtual ~SPNamedView();
1194@@ -54,6 +58,7 @@
1195 double zoom;
1196 double cx;
1197 double cy;
1198+ double document_rotation;
1199 int window_width;
1200 int window_height;
1201 int window_x;
1202@@ -66,7 +71,7 @@
1203
1204 Inkscape::Util::Unit const *display_units; // Units used for the UI (*not* the same as units of SVG coordinates)
1205 Inkscape::Util::Unit const *page_size_units; // Only used in "Custom size" part of Document Properties dialog
1206-
1207+ Inkscape::Display::TemporaryItem *page_border_rotated;
1208 GQuark default_layer_id;
1209
1210 double connector_spacing;
1211@@ -121,7 +126,7 @@
1212 void sp_namedview_window_from_document(SPDesktop *desktop);
1213 void sp_namedview_document_from_window(SPDesktop *desktop);
1214 void sp_namedview_update_layers_from_document (SPDesktop *desktop);
1215-
1216+void sp_namedview_set_document_rotation(SPNamedView *nv);
1217 void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr);
1218 void sp_namedview_guides_toggle_lock(SPDocument *doc, Inkscape::XML::Node *repr);
1219 void sp_namedview_show_grids(SPNamedView *namedview, bool show, bool dirty_document);
1220
1221=== modified file 'src/ui/dialog/export.cpp'
1222--- src/ui/dialog/export.cpp 2017-01-04 23:08:02 +0000
1223+++ src/ui/dialog/export.cpp 2017-01-24 17:53:20 +0000
1224@@ -978,7 +978,9 @@
1225
1226 SPNamedView *nv = desktop->getNamedView();
1227 SPDocument *doc = desktop->getDocument();
1228-
1229+ Geom::Affine rot = doc->getRoot()->c2p;
1230+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
1231+ doc->ensureUpToDate();
1232 bool exportSuccessful = false;
1233
1234 bool hide = hide_export.get_active ();
1235@@ -1003,6 +1005,7 @@
1236
1237 if (num < 1) {
1238 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No items selected."));
1239+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
1240 return;
1241 }
1242
1243@@ -1094,6 +1097,7 @@
1244 if (filename.empty()) {
1245 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename."));
1246 sp_ui_error_dialog(_("You have to enter a filename"));
1247+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
1248 return;
1249 }
1250
1251@@ -1110,6 +1114,7 @@
1252 if (!((x1 > x0) && (y1 > y0) && (width > 0) && (height > 0))) {
1253 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The chosen area to be exported is invalid."));
1254 sp_ui_error_dialog(_("The chosen area to be exported is invalid"));
1255+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
1256 return;
1257 }
1258
1259@@ -1132,6 +1137,7 @@
1260
1261 g_free(safeDir);
1262 g_free(error);
1263+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
1264 return;
1265 }
1266
1267@@ -1281,6 +1287,7 @@
1268 }
1269 }
1270 }
1271+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
1272 } // end of sp_export_export_clicked()
1273
1274 /// Called when Browse button is clicked
1275
1276=== modified file 'src/ui/tools/tool-base.cpp'
1277--- src/ui/tools/tool-base.cpp 2016-08-09 09:33:34 +0000
1278+++ src/ui/tools/tool-base.cpp 2017-01-24 17:53:20 +0000
1279@@ -94,6 +94,7 @@
1280 , _grdrag(NULL)
1281 , shape_editor(NULL)
1282 , space_panning(false)
1283+ , rotating_mode(false)
1284 , _delayed_snap_event(NULL)
1285 , _dse_callback_in_process(false)
1286 , desktop(NULL)
1287@@ -327,99 +328,130 @@
1288 static unsigned int panning = 0;
1289 static unsigned int panning_cursor = 0;
1290 static unsigned int zoom_rb = 0;
1291+ static double angle = 0;
1292
1293 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1294
1295 /// @todo REmove redundant /value in preference keys
1296 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1297 bool allow_panning = prefs->getBool("/options/spacebarpans/value");
1298+ int rotation_snap = 180.0/prefs->getInt("/options/rotationsnapsperpi/value", 12);
1299 gint ret = FALSE;
1300
1301 switch (event->type) {
1302- case GDK_2BUTTON_PRESS:
1303- if (panning) {
1304- panning = 0;
1305- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1306- ret = TRUE;
1307- } else {
1308- /* sp_desktop_dialog(); */
1309- }
1310- break;
1311-
1312- case GDK_BUTTON_PRESS:
1313- // save drag origin
1314- xp = (gint) event->button.x;
1315- yp = (gint) event->button.y;
1316- within_tolerance = true;
1317-
1318- button_w = Geom::Point(event->button.x, event->button.y);
1319-
1320- switch (event->button.button) {
1321- case 1:
1322- if (this->space_panning) {
1323- // When starting panning, make sure there are no snap events pending because these might disable the panning again
1324- if (_uses_snap) {
1325- sp_event_context_discard_delayed_snap_event(this);
1326- }
1327- panning = 1;
1328-
1329- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1330- GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
1331- | GDK_POINTER_MOTION_MASK
1332- | GDK_POINTER_MOTION_HINT_MASK, NULL,
1333- event->button.time - 1);
1334-
1335- ret = TRUE;
1336- }
1337- break;
1338-
1339- case 2:
1340- if (event->button.state & GDK_SHIFT_MASK) {
1341- zoom_rb = 2;
1342- } else {
1343- // When starting panning, make sure there are no snap events pending because these might disable the panning again
1344- if (_uses_snap) {
1345- sp_event_context_discard_delayed_snap_event(this);
1346- }
1347- panning = 2;
1348-
1349- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1350- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
1351- | GDK_POINTER_MOTION_HINT_MASK, NULL,
1352- event->button.time - 1);
1353-
1354- }
1355-
1356- ret = TRUE;
1357- break;
1358-
1359- case 3:
1360- if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) {
1361- // When starting panning, make sure there are no snap events pending because these might disable the panning again
1362- if (_uses_snap) {
1363- sp_event_context_discard_delayed_snap_event(this);
1364- }
1365- panning = 3;
1366-
1367- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1368- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
1369- | GDK_POINTER_MOTION_HINT_MASK, NULL,
1370- event->button.time);
1371-
1372- ret = TRUE;
1373- } else {
1374- sp_event_root_menu_popup(desktop, NULL, event);
1375- }
1376- break;
1377-
1378- default:
1379- break;
1380- }
1381- break;
1382-
1383- case GDK_MOTION_NOTIFY:
1384- if (panning) {
1385- if (panning == 4 && !xp && !yp ) {
1386+ case GDK_2BUTTON_PRESS:
1387+ if (panning) {
1388+ panning = 0;
1389+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1390+ ret = TRUE;
1391+ } else {
1392+ /* sp_desktop_dialog(); */
1393+ }
1394+ break;
1395+
1396+ case GDK_BUTTON_PRESS:
1397+ // save drag origin
1398+ xp = (gint) event->button.x;
1399+ yp = (gint) event->button.y;
1400+ within_tolerance = true;
1401+
1402+ button_w = Geom::Point(event->button.x, event->button.y);
1403+ switch (event->button.button) {
1404+ case 1:
1405+ if (this->space_panning) {
1406+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
1407+ if (_uses_snap) {
1408+ sp_event_context_discard_delayed_snap_event(this);
1409+ }
1410+ panning = 1;
1411+
1412+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1413+ GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
1414+ | GDK_POINTER_MOTION_MASK
1415+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
1416+ event->button.time - 1);
1417+
1418+ ret = TRUE;
1419+ }
1420+ desktop->canvas->clearRotateTo();
1421+ this->rotating_mode = false;
1422+ break;
1423+
1424+ case 2:
1425+ if (event->button.state & GDK_CONTROL_MASK) {
1426+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1427+ desktop->canvas->startRotateTo(desktop->namedview->document_rotation);
1428+ this->rotating_mode = true;
1429+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
1430+ _("<b>MMB + mouse move</b> to rotate canvas, use modifiers on screen to change snaps"));
1431+ } else {
1432+ if (event->button.state & GDK_SHIFT_MASK) {
1433+ zoom_rb = 2;
1434+ } else {
1435+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
1436+ if (_uses_snap) {
1437+ sp_event_context_discard_delayed_snap_event(this);
1438+ }
1439+ panning = 2;
1440+
1441+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1442+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
1443+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
1444+ event->button.time - 1);
1445+ }
1446+ ret = TRUE;
1447+ }
1448+ ret = TRUE;
1449+ break;
1450+
1451+ case 3:
1452+ if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) {
1453+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
1454+ if (_uses_snap) {
1455+ sp_event_context_discard_delayed_snap_event(this);
1456+ }
1457+ panning = 3;
1458+
1459+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1460+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
1461+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
1462+ event->button.time);
1463+
1464+ ret = TRUE;
1465+ } else if( !this->space_panning) {
1466+ sp_event_root_menu_popup(desktop, NULL, event);
1467+ }
1468+ ret = TRUE;
1469+ desktop->canvas->clearRotateTo();
1470+ this->rotating_mode = false;
1471+ break;
1472+
1473+ default:
1474+ break;
1475+ }
1476+ break;
1477+
1478+ case GDK_MOTION_NOTIFY:
1479+ if (this->rotating_mode) {
1480+ button_w = Geom::Point(event->motion.x, event->motion.y);
1481+ Geom::Point const motion_dt(desktop->doc2dt(desktop->w2d(button_w)));
1482+ Geom::Rect view = desktop->get_display_area();
1483+ Geom::Point view_center = desktop->doc2dt(view.midpoint());
1484+ Geom::Ray center_ray(motion_dt, view_center);
1485+ angle = Geom::deg_from_rad(center_ray.angle()) - 90;
1486+ if (event->motion.state & GDK_SHIFT_MASK && event->motion.state & GDK_CONTROL_MASK) {
1487+ angle = 0;
1488+ } else if(event->motion.state & GDK_CONTROL_MASK) {
1489+ angle = floor(angle/rotation_snap) * rotation_snap;
1490+ } else if (event->motion.state & GDK_SHIFT_MASK) {
1491+ angle = desktop->namedview->document_rotation;
1492+ } else if (event->motion.state & GDK_MOD1_MASK) {
1493+ //Decimal raw angle
1494+ } else {
1495+ angle = floor(angle);
1496+ }
1497+ desktop->canvas->rotateTo(angle);
1498+ } else if (panning == 4 && !xp && !yp ) {
1499 // <Space> + mouse panning started, save location and grab canvas
1500 xp = event->motion.x;
1501 yp = event->motion.y;
1502@@ -431,401 +463,465 @@
1503 | GDK_POINTER_MOTION_HINT_MASK, NULL,
1504 event->motion.time - 1);
1505 }
1506-
1507- if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
1508- || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
1509- || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
1510- /* Gdk seems to lose button release for us sometimes :-( */
1511- panning = 0;
1512- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1513- ret = TRUE;
1514- } else {
1515+ if (panning && !this->rotating_mode) {
1516+ if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
1517+ || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
1518+ || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
1519+ /* Gdk seems to lose button release for us sometimes :-( */
1520+ panning = 0;
1521+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1522+ ret = TRUE;
1523+ } else {
1524+ if (within_tolerance && (abs((gint) event->motion.x - xp)
1525+ < tolerance) && (abs((gint) event->motion.y - yp)
1526+ < tolerance)) {
1527+ // do not drag if we're within tolerance from origin
1528+ break;
1529+ }
1530+
1531+ // Once the user has moved farther than tolerance from
1532+ // the original location (indicating they intend to move
1533+ // the object, not click), then always process the motion
1534+ // notify coordinates as given (no snapping back to origin)
1535+ within_tolerance = false;
1536+
1537+ // gobble subsequent motion events to prevent "sticking"
1538+ // when scrolling is slow
1539+ gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning
1540+ == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
1541+
1542+ if (panning_cursor == 0) {
1543+ panning_cursor = 1;
1544+ this->sp_event_context_set_cursor(GDK_FLEUR);
1545+ }
1546+
1547+ Geom::Point const motion_w(event->motion.x, event->motion.y);
1548+ Geom::Point const moved_w(motion_w - button_w);
1549+ this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
1550+ ret = TRUE;
1551+ }
1552+ } else if (zoom_rb) {
1553+ Geom::Point const motion_w(event->motion.x, event->motion.y);
1554+ Geom::Point const motion_dt(desktop->w2d(motion_w));
1555+
1556 if (within_tolerance && (abs((gint) event->motion.x - xp)
1557 < tolerance) && (abs((gint) event->motion.y - yp)
1558 < tolerance)) {
1559- // do not drag if we're within tolerance from origin
1560- break;
1561+ break; // do not drag if we're within tolerance from origin
1562 }
1563
1564- // Once the user has moved farther than tolerance from
1565- // the original location (indicating they intend to move
1566- // the object, not click), then always process the motion
1567- // notify coordinates as given (no snapping back to origin)
1568+ // Once the user has moved farther than tolerance from the original location
1569+ // (indicating they intend to move the object, not click), then always process the
1570+ // motion notify coordinates as given (no snapping back to origin)
1571 within_tolerance = false;
1572
1573- // gobble subsequent motion events to prevent "sticking"
1574- // when scrolling is slow
1575- gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning
1576- == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
1577-
1578- if (panning_cursor == 0) {
1579- panning_cursor = 1;
1580- this->sp_event_context_set_cursor(GDK_FLEUR);
1581- }
1582-
1583- Geom::Point const motion_w(event->motion.x, event->motion.y);
1584- Geom::Point const moved_w(motion_w - button_w);
1585- this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
1586+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
1587+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
1588+ } else {
1589+ Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt);
1590+ }
1591+
1592+ if (zoom_rb == 2) {
1593+ gobble_motion_events(GDK_BUTTON2_MASK);
1594+ }
1595+ }
1596+ break;
1597+
1598+ case GDK_BUTTON_RELEASE:
1599+ if (this->rotating_mode && event->button.button == 2) {
1600+ desktop->canvas->clearRotateTo();
1601+ this->rotating_mode = false;
1602 ret = TRUE;
1603- }
1604- } else if (zoom_rb) {
1605- Geom::Point const motion_w(event->motion.x, event->motion.y);
1606- Geom::Point const motion_dt(desktop->w2d(motion_w));
1607-
1608- if (within_tolerance && (abs((gint) event->motion.x - xp)
1609- < tolerance) && (abs((gint) event->motion.y - yp)
1610- < tolerance)) {
1611- break; // do not drag if we're within tolerance from origin
1612- }
1613-
1614- // Once the user has moved farther than tolerance from the original location
1615- // (indicating they intend to move the object, not click), then always process the
1616- // motion notify coordinates as given (no snapping back to origin)
1617- within_tolerance = false;
1618-
1619- if (Inkscape::Rubberband::get(desktop)->is_started()) {
1620- Inkscape::Rubberband::get(desktop)->move(motion_dt);
1621+ if (desktop->canvas->endRotateTo()) {
1622+ sp_repr_set_svg_double(desktop->namedview->getRepr(), "inkscape:document-rotation", angle);
1623+ }
1624 } else {
1625- Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt);
1626- }
1627-
1628- if (zoom_rb == 2) {
1629- gobble_motion_events(GDK_BUTTON2_MASK);
1630- }
1631- }
1632- break;
1633-
1634- case GDK_BUTTON_RELEASE:
1635- xp = yp = 0;
1636-
1637- if (panning_cursor == 1) {
1638- panning_cursor = 0;
1639- GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
1640- gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
1641- }
1642-
1643- if (within_tolerance && (panning || zoom_rb)) {
1644- zoom_rb = 0;
1645+ xp = yp = 0;
1646+ if (panning_cursor == 1) {
1647+ panning_cursor = 0;
1648+ GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
1649+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
1650+ }
1651+
1652+ if (within_tolerance && (panning || zoom_rb)) {
1653+ zoom_rb = 0;
1654+
1655+ if (panning) {
1656+ panning = 0;
1657+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
1658+ event->button.time);
1659+ }
1660+
1661+ Geom::Point const event_w(event->button.x, event->button.y);
1662+ Geom::Point const event_dt(desktop->w2d(event_w));
1663+
1664+ double const zoom_inc = prefs->getDoubleLimited(
1665+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
1666+
1667+ desktop->zoom_relative_keep_point(event_dt, (event->button.state
1668+ & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
1669+
1670+ desktop->updateNow();
1671+ ret = TRUE;
1672+ } else if (panning == event->button.button) {
1673+ panning = 0;
1674+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
1675+ event->button.time);
1676+
1677+ // in slow complex drawings, some of the motion events are lost;
1678+ // to make up for this, we scroll it once again to the button-up event coordinates
1679+ // (i.e. canvas will always get scrolled all the way to the mouse release point,
1680+ // even if few intermediate steps were visible)
1681+ Geom::Point const motion_w(event->button.x, event->button.y);
1682+ Geom::Point const moved_w(motion_w - button_w);
1683+
1684+ this->desktop->scroll_world(moved_w);
1685+ desktop->updateNow();
1686+ ret = TRUE;
1687+ } else if (zoom_rb == event->button.button) {
1688+ zoom_rb = 0;
1689+
1690+ Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
1691+ Inkscape::Rubberband::get(desktop)->stop();
1692+
1693+ if (b && !within_tolerance) {
1694+ desktop->set_display_area(*b, 10);
1695+ }
1696+
1697+ ret = TRUE;
1698+ }
1699+ if (this->rotating_mode && event->button.button != 3) {
1700+ desktop->canvas->clearRotateTo();
1701+ this->rotating_mode = false;
1702+ ret = TRUE;
1703+ desktop->canvas->endRotateTo();
1704+ }
1705+ }
1706+ break;
1707+
1708+ case GDK_KEY_PRESS: {
1709+ double const acceleration = prefs->getDoubleLimited(
1710+ "/options/scrollingacceleration/value", 0, 0, 6);
1711+ int const key_scroll = prefs->getIntLimited("/options/keyscroll/value",
1712+ 10, 0, 1000);
1713+
1714+ if (this->rotating_mode &&
1715+ get_group0_keyval(&event->key) != GDK_KEY_space &&
1716+ get_group0_keyval(&event->key) != GDK_KEY_Shift_L &&
1717+ get_group0_keyval(&event->key) != GDK_KEY_Shift_R &&
1718+ get_group0_keyval(&event->key) != GDK_KEY_Control_L &&
1719+ get_group0_keyval(&event->key) != GDK_KEY_Control_R &&
1720+ get_group0_keyval(&event->key) != GDK_KEY_Alt_L &&
1721+ get_group0_keyval(&event->key) != GDK_KEY_Alt_R )
1722+ {
1723+ desktop->canvas->clearRotateTo();
1724+ this->rotating_mode = false;
1725+ ret = TRUE;
1726+ desktop->canvas->endRotateTo();
1727+ break;
1728+ }
1729+
1730+ switch (get_group0_keyval(&event->key)) {
1731+ // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
1732+ // in the editing window). So we resteal them back and run our regular shortcut
1733+ // invoker on them.
1734+ unsigned int shortcut;
1735+ case GDK_KEY_Tab:
1736+ case GDK_KEY_ISO_Left_Tab:
1737+ case GDK_KEY_F1:
1738+ shortcut = get_group0_keyval(&event->key);
1739+
1740+ if (event->key.state & GDK_SHIFT_MASK) {
1741+ shortcut |= SP_SHORTCUT_SHIFT_MASK;
1742+ }
1743+
1744+ if (event->key.state & GDK_CONTROL_MASK) {
1745+ shortcut |= SP_SHORTCUT_CONTROL_MASK;
1746+ }
1747+
1748+ if (event->key.state & GDK_MOD1_MASK) {
1749+ shortcut |= SP_SHORTCUT_ALT_MASK;
1750+ }
1751+
1752+ ret = sp_shortcut_invoke(shortcut, desktop);
1753+ break;
1754+
1755+ case GDK_KEY_Q:
1756+ case GDK_KEY_q:
1757+ if (desktop->quick_zoomed()) {
1758+ ret = TRUE;
1759+ }
1760+ if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) {
1761+ desktop->zoom_quick(true);
1762+ ret = TRUE;
1763+ }
1764+ break;
1765+
1766+ case GDK_KEY_W:
1767+ case GDK_KEY_w:
1768+ case GDK_KEY_F4:
1769+ /* Close view */
1770+ if (MOD__CTRL_ONLY(event)) {
1771+ sp_ui_close_view(NULL);
1772+ ret = TRUE;
1773+ }
1774+ break;
1775+
1776+ case GDK_KEY_Left: // Ctrl Left
1777+ case GDK_KEY_KP_Left:
1778+ case GDK_KEY_KP_4:
1779+ if (MOD__CTRL_ONLY(event)) {
1780+ int i = (int) floor(key_scroll * accelerate_scroll(event,
1781+ acceleration, desktop->getCanvas()));
1782+
1783+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
1784+ this->desktop->scroll_world(i, 0);
1785+ ret = TRUE;
1786+ }
1787+ break;
1788+
1789+ case GDK_KEY_Up: // Ctrl Up
1790+ case GDK_KEY_KP_Up:
1791+ case GDK_KEY_KP_8:
1792+ if (MOD__CTRL_ONLY(event)) {
1793+ int i = (int) floor(key_scroll * accelerate_scroll(event,
1794+ acceleration, desktop->getCanvas()));
1795+
1796+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
1797+ this->desktop->scroll_world(0, i);
1798+ ret = TRUE;
1799+ }
1800+ break;
1801+
1802+ case GDK_KEY_Right: // Ctrl Right
1803+ case GDK_KEY_KP_Right:
1804+ case GDK_KEY_KP_6:
1805+ if (MOD__CTRL_ONLY(event)) {
1806+ int i = (int) floor(key_scroll * accelerate_scroll(event,
1807+ acceleration, desktop->getCanvas()));
1808+
1809+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
1810+ this->desktop->scroll_world(-i, 0);
1811+ ret = TRUE;
1812+ }
1813+ break;
1814+
1815+ case GDK_KEY_Down: // Ctrl Down
1816+ case GDK_KEY_KP_Down:
1817+ case GDK_KEY_KP_2:
1818+ if (MOD__CTRL_ONLY(event)) {
1819+ int i = (int) floor(key_scroll * accelerate_scroll(event,
1820+ acceleration, desktop->getCanvas()));
1821+
1822+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
1823+ this->desktop->scroll_world(0, -i);
1824+ ret = TRUE;
1825+ }
1826+ break;
1827+
1828+ case GDK_KEY_Menu:
1829+ sp_event_root_menu_popup(desktop, NULL, event);
1830+ ret = TRUE;
1831+ break;
1832+
1833+ case GDK_KEY_F10:
1834+ if (MOD__SHIFT_ONLY(event)) {
1835+ sp_event_root_menu_popup(desktop, NULL, event);
1836+ ret = TRUE;
1837+ }
1838+ break;
1839+
1840+ case GDK_KEY_space:
1841+ if (event->key.state & GDK_CONTROL_MASK) {
1842+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1843+ desktop->canvas->startRotateTo(desktop->namedview->document_rotation);
1844+ this->rotating_mode = true;
1845+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
1846+ _("<b>Space+mouse move</b> to rotate canvas, use modifiers on screen to change snaps"));
1847+ } else {
1848+ within_tolerance = true;
1849+ xp = yp = 0;
1850+ if (!allow_panning) break;
1851+ panning = 4;
1852+ this->space_panning = true;
1853+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
1854+ _("<b>Space+mouse move</b> to pan canvas"));
1855+ }
1856+ ret = TRUE;
1857+ break;
1858+
1859+ case GDK_KEY_z:
1860+ case GDK_KEY_Z:
1861+ if (MOD__ALT_ONLY(event)) {
1862+ desktop->zoom_grab_focus();
1863+ ret = TRUE;
1864+ }
1865+ break;
1866+
1867+ default:
1868+ break;
1869+ }
1870+ }
1871+ break;
1872+
1873+ case GDK_KEY_RELEASE:
1874+ if (this->rotating_mode &&
1875+ get_group0_keyval(&event->key) != GDK_KEY_space &&
1876+ get_group0_keyval(&event->key) != GDK_KEY_Shift_L &&
1877+ get_group0_keyval(&event->key) != GDK_KEY_Shift_R &&
1878+ get_group0_keyval(&event->key) != GDK_KEY_Control_L &&
1879+ get_group0_keyval(&event->key) != GDK_KEY_Control_R &&
1880+ get_group0_keyval(&event->key) != GDK_KEY_Alt_L &&
1881+ get_group0_keyval(&event->key) != GDK_KEY_Alt_R )
1882+ {
1883+ desktop->canvas->clearRotateTo();
1884+ this->rotating_mode = false;
1885+ ret = TRUE;
1886+ desktop->canvas->endRotateTo();
1887+ break;
1888+ }
1889+
1890+ // Stop panning on any key release
1891+ if (this->space_panning) {
1892+ this->space_panning = false;
1893+ this->message_context->clear();
1894+ }
1895
1896 if (panning) {
1897 panning = 0;
1898+ xp = yp = 0;
1899+
1900 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
1901- event->button.time);
1902- }
1903-
1904- Geom::Point const event_w(event->button.x, event->button.y);
1905- Geom::Point const event_dt(desktop->w2d(event_w));
1906-
1907- double const zoom_inc = prefs->getDoubleLimited(
1908- "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
1909-
1910- desktop->zoom_relative_keep_point(event_dt, (event->button.state
1911- & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
1912-
1913- desktop->updateNow();
1914- ret = TRUE;
1915- } else if (panning == event->button.button) {
1916- panning = 0;
1917- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
1918- event->button.time);
1919-
1920- // in slow complex drawings, some of the motion events are lost;
1921- // to make up for this, we scroll it once again to the button-up event coordinates
1922- // (i.e. canvas will always get scrolled all the way to the mouse release point,
1923- // even if few intermediate steps were visible)
1924- Geom::Point const motion_w(event->button.x, event->button.y);
1925- Geom::Point const moved_w(motion_w - button_w);
1926-
1927- this->desktop->scroll_world(moved_w);
1928- desktop->updateNow();
1929- ret = TRUE;
1930- } else if (zoom_rb == event->button.button) {
1931- zoom_rb = 0;
1932-
1933- Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
1934- Inkscape::Rubberband::get(desktop)->stop();
1935-
1936- if (b && !within_tolerance) {
1937- desktop->set_display_area(*b, 10);
1938- }
1939-
1940- ret = TRUE;
1941- }
1942- break;
1943-
1944- case GDK_KEY_PRESS: {
1945- double const acceleration = prefs->getDoubleLimited(
1946- "/options/scrollingacceleration/value", 0, 0, 6);
1947- int const key_scroll = prefs->getIntLimited("/options/keyscroll/value",
1948- 10, 0, 1000);
1949-
1950- switch (get_group0_keyval(&event->key)) {
1951- // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
1952- // in the editing window). So we resteal them back and run our regular shortcut
1953- // invoker on them.
1954- unsigned int shortcut;
1955- case GDK_KEY_Tab:
1956- case GDK_KEY_ISO_Left_Tab:
1957- case GDK_KEY_F1:
1958- shortcut = get_group0_keyval(&event->key);
1959-
1960- if (event->key.state & GDK_SHIFT_MASK) {
1961- shortcut |= SP_SHORTCUT_SHIFT_MASK;
1962- }
1963-
1964- if (event->key.state & GDK_CONTROL_MASK) {
1965- shortcut |= SP_SHORTCUT_CONTROL_MASK;
1966- }
1967-
1968- if (event->key.state & GDK_MOD1_MASK) {
1969- shortcut |= SP_SHORTCUT_ALT_MASK;
1970- }
1971-
1972- ret = sp_shortcut_invoke(shortcut, desktop);
1973- break;
1974-
1975- case GDK_KEY_Q:
1976- case GDK_KEY_q:
1977- if (desktop->quick_zoomed()) {
1978- ret = TRUE;
1979- }
1980- if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) {
1981- desktop->zoom_quick(true);
1982- ret = TRUE;
1983- }
1984- break;
1985-
1986- case GDK_KEY_W:
1987- case GDK_KEY_w:
1988- case GDK_KEY_F4:
1989- /* Close view */
1990- if (MOD__CTRL_ONLY(event)) {
1991- sp_ui_close_view(NULL);
1992- ret = TRUE;
1993- }
1994- break;
1995-
1996- case GDK_KEY_Left: // Ctrl Left
1997- case GDK_KEY_KP_Left:
1998- case GDK_KEY_KP_4:
1999- if (MOD__CTRL_ONLY(event)) {
2000- int i = (int) floor(key_scroll * accelerate_scroll(event,
2001- acceleration, desktop->getCanvas()));
2002-
2003- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
2004- this->desktop->scroll_world(i, 0);
2005- ret = TRUE;
2006- }
2007- break;
2008-
2009- case GDK_KEY_Up: // Ctrl Up
2010- case GDK_KEY_KP_Up:
2011- case GDK_KEY_KP_8:
2012- if (MOD__CTRL_ONLY(event)) {
2013- int i = (int) floor(key_scroll * accelerate_scroll(event,
2014- acceleration, desktop->getCanvas()));
2015-
2016- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
2017- this->desktop->scroll_world(0, i);
2018- ret = TRUE;
2019- }
2020- break;
2021-
2022- case GDK_KEY_Right: // Ctrl Right
2023- case GDK_KEY_KP_Right:
2024- case GDK_KEY_KP_6:
2025- if (MOD__CTRL_ONLY(event)) {
2026- int i = (int) floor(key_scroll * accelerate_scroll(event,
2027- acceleration, desktop->getCanvas()));
2028-
2029- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
2030- this->desktop->scroll_world(-i, 0);
2031- ret = TRUE;
2032- }
2033- break;
2034-
2035- case GDK_KEY_Down: // Ctrl Down
2036- case GDK_KEY_KP_Down:
2037- case GDK_KEY_KP_2:
2038- if (MOD__CTRL_ONLY(event)) {
2039- int i = (int) floor(key_scroll * accelerate_scroll(event,
2040- acceleration, desktop->getCanvas()));
2041-
2042- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
2043- this->desktop->scroll_world(0, -i);
2044- ret = TRUE;
2045- }
2046- break;
2047-
2048- case GDK_KEY_Menu:
2049- sp_event_root_menu_popup(desktop, NULL, event);
2050- ret = TRUE;
2051- break;
2052-
2053- case GDK_KEY_F10:
2054- if (MOD__SHIFT_ONLY(event)) {
2055- sp_event_root_menu_popup(desktop, NULL, event);
2056- ret = TRUE;
2057- }
2058- break;
2059-
2060- case GDK_KEY_space:
2061- within_tolerance = true;
2062- xp = yp = 0;
2063- if (!allow_panning) break;
2064- panning = 4;
2065- this->space_panning = true;
2066- this->message_context->set(Inkscape::INFORMATION_MESSAGE,
2067- _("<b>Space+mouse move</b> to pan canvas"));
2068-
2069- ret = TRUE;
2070- break;
2071-
2072- case GDK_KEY_z:
2073- case GDK_KEY_Z:
2074- if (MOD__ALT_ONLY(event)) {
2075- desktop->zoom_grab_focus();
2076- ret = TRUE;
2077- }
2078- break;
2079-
2080- default:
2081- break;
2082- }
2083- }
2084- break;
2085-
2086- case GDK_KEY_RELEASE:
2087- // Stop panning on any key release
2088- if (this->space_panning) {
2089- this->space_panning = false;
2090- this->message_context->clear();
2091- }
2092-
2093- if (panning) {
2094- panning = 0;
2095- xp = yp = 0;
2096-
2097- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
2098- event->key.time);
2099-
2100- desktop->updateNow();
2101- }
2102-
2103- if (panning_cursor == 1) {
2104- panning_cursor = 0;
2105- GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
2106- gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
2107- }
2108-
2109- switch (get_group0_keyval(&event->key)) {
2110- case GDK_KEY_space:
2111- if (within_tolerance) {
2112- // Space was pressed, but not panned
2113- sp_toggle_selector(desktop);
2114-
2115- // Be careful, sp_toggle_selector will delete ourselves.
2116- // Thus, make sure we return immediately.
2117- return true;
2118- }
2119-
2120- break;
2121-
2122- case GDK_KEY_Q:
2123- case GDK_KEY_q:
2124- if (desktop->quick_zoomed()) {
2125- desktop->zoom_quick(false);
2126- ret = TRUE;
2127- }
2128- break;
2129-
2130- default:
2131- break;
2132- }
2133- break;
2134-
2135- case GDK_SCROLL: {
2136- bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
2137- bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
2138-
2139- int const wheel_scroll = prefs->getIntLimited(
2140- "/options/wheelscroll/value", 40, 0, 1000);
2141-
2142- // Size of smooth-scrolls (only used in GTK+ 3)
2143- gdouble delta_x = 0;
2144- gdouble delta_y = 0;
2145-
2146- /* shift + wheel, pan left--right */
2147- if (event->scroll.state & GDK_SHIFT_MASK) {
2148- switch (event->scroll.direction) {
2149- case GDK_SCROLL_UP:
2150- desktop->scroll_world(wheel_scroll, 0);
2151- break;
2152-
2153- case GDK_SCROLL_DOWN:
2154- desktop->scroll_world(-wheel_scroll, 0);
2155- break;
2156-
2157- default:
2158- break;
2159- }
2160-
2161- /* ctrl + wheel, zoom in--out */
2162- } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
2163- double rel_zoom;
2164- double const zoom_inc = prefs->getDoubleLimited(
2165- "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
2166-
2167- switch (event->scroll.direction) {
2168- case GDK_SCROLL_UP:
2169- rel_zoom = zoom_inc;
2170- break;
2171-
2172- case GDK_SCROLL_DOWN:
2173- rel_zoom = 1 / zoom_inc;
2174- break;
2175-
2176- default:
2177- rel_zoom = 0.0;
2178- break;
2179- }
2180-
2181- if (rel_zoom != 0.0) {
2182- Geom::Point const scroll_dt = desktop->point();
2183- desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
2184- }
2185-
2186- /* no modifier, pan up--down (left--right on multiwheel mice?) */
2187- } else {
2188- switch (event->scroll.direction) {
2189- case GDK_SCROLL_UP:
2190- desktop->scroll_world(0, wheel_scroll);
2191- break;
2192-
2193- case GDK_SCROLL_DOWN:
2194- desktop->scroll_world(0, -wheel_scroll);
2195- break;
2196-
2197- case GDK_SCROLL_LEFT:
2198- desktop->scroll_world(wheel_scroll, 0);
2199- break;
2200-
2201- case GDK_SCROLL_RIGHT:
2202- desktop->scroll_world(-wheel_scroll, 0);
2203- break;
2204-
2205- case GDK_SCROLL_SMOOTH:
2206- gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
2207- desktop->scroll_world(delta_x, delta_y);
2208- break;
2209- }
2210- }
2211- break;
2212- }
2213- default:
2214- break;
2215- }
2216-
2217+ event->key.time);
2218+
2219+ desktop->updateNow();
2220+ }
2221+
2222+ if (panning_cursor == 1) {
2223+ panning_cursor = 0;
2224+ GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
2225+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
2226+ }
2227+
2228+ switch (get_group0_keyval(&event->key)) {
2229+ case GDK_KEY_space:
2230+ if (this->rotating_mode) {
2231+ desktop->canvas->clearRotateTo();
2232+ this->rotating_mode = false;
2233+ ret = TRUE;
2234+ if (desktop->canvas->endRotateTo()) {
2235+ sp_repr_set_svg_double(desktop->namedview->getRepr(), "inkscape:document-rotation", angle);
2236+ }
2237+ }
2238+ if (within_tolerance) {
2239+ // Space was pressed, but not panned
2240+ sp_toggle_selector(desktop);
2241+
2242+ // Be careful, sp_toggle_selector will delete ourselves.
2243+ // Thus, make sure we return immediately.
2244+ return true;
2245+ }
2246+ break;
2247+
2248+ case GDK_KEY_Q:
2249+ case GDK_KEY_q:
2250+ if (desktop->quick_zoomed()) {
2251+ desktop->zoom_quick(false);
2252+ ret = TRUE;
2253+ }
2254+ break;
2255+
2256+ default:
2257+ break;
2258+ }
2259+ break;
2260+
2261+ case GDK_SCROLL: {
2262+ if (this->rotating_mode) {
2263+ desktop->canvas->clearRotateTo();
2264+ this->rotating_mode = false;
2265+ desktop->canvas->endRotateTo();
2266+ }
2267+ bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
2268+ bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
2269+
2270+ int const wheel_scroll = prefs->getIntLimited(
2271+ "/options/wheelscroll/value", 40, 0, 1000);
2272+
2273+ // Size of smooth-scrolls (only used in GTK+ 3)
2274+ gdouble delta_x = 0;
2275+ gdouble delta_y = 0;
2276+
2277+ /* shift + wheel, pan left--right */
2278+ if (event->scroll.state & GDK_SHIFT_MASK) {
2279+ switch (event->scroll.direction) {
2280+ case GDK_SCROLL_UP:
2281+ desktop->scroll_world(wheel_scroll, 0);
2282+ break;
2283+
2284+ case GDK_SCROLL_DOWN:
2285+ desktop->scroll_world(-wheel_scroll, 0);
2286+ break;
2287+
2288+ default:
2289+ break;
2290+ }
2291+
2292+ /* ctrl + wheel, zoom in--out */
2293+ } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
2294+ double rel_zoom;
2295+ double const zoom_inc = prefs->getDoubleLimited(
2296+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
2297+
2298+ switch (event->scroll.direction) {
2299+ case GDK_SCROLL_UP:
2300+ rel_zoom = zoom_inc;
2301+ break;
2302+
2303+ case GDK_SCROLL_DOWN:
2304+ rel_zoom = 1 / zoom_inc;
2305+ break;
2306+
2307+ default:
2308+ rel_zoom = 0.0;
2309+ break;
2310+ }
2311+
2312+ if (rel_zoom != 0.0) {
2313+ Geom::Point const scroll_dt = desktop->point();
2314+ desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
2315+ }
2316+
2317+ /* no modifier, pan up--down (left--right on multiwheel mice?) */
2318+ } else {
2319+ switch (event->scroll.direction) {
2320+ case GDK_SCROLL_UP:
2321+ desktop->scroll_world(0, wheel_scroll);
2322+ break;
2323+
2324+ case GDK_SCROLL_DOWN:
2325+ desktop->scroll_world(0, -wheel_scroll);
2326+ break;
2327+
2328+ case GDK_SCROLL_LEFT:
2329+ desktop->scroll_world(wheel_scroll, 0);
2330+ break;
2331+
2332+ case GDK_SCROLL_RIGHT:
2333+ desktop->scroll_world(-wheel_scroll, 0);
2334+ break;
2335+
2336+ case GDK_SCROLL_SMOOTH:
2337+ gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
2338+ desktop->scroll_world(delta_x, delta_y);
2339+ break;
2340+ }
2341+ }
2342+ break;
2343+ }
2344+ default:
2345+ break;
2346+ }
2347 return ret;
2348 }
2349
2350
2351=== modified file 'src/ui/tools/tool-base.h'
2352--- src/ui/tools/tool-base.h 2015-07-24 19:38:06 +0000
2353+++ src/ui/tools/tool-base.h 2017-01-24 17:53:20 +0000
2354@@ -176,6 +176,7 @@
2355 ShapeEditor* shape_editor;
2356
2357 bool space_panning;
2358+ bool rotating_mode;
2359
2360 DelayedSnapEvent *_delayed_snap_event;
2361 bool _dse_callback_in_process;
2362
2363=== modified file 'src/viewbox.cpp'
2364--- src/viewbox.cpp 2016-08-03 13:29:38 +0000
2365+++ src/viewbox.cpp 2017-01-24 17:53:20 +0000
2366@@ -17,6 +17,8 @@
2367 #include "viewbox.h"
2368 #include "enums.h"
2369 #include "sp-item.h"
2370+#include "inkscape.h"
2371+#include "desktop.h"
2372
2373 SPViewBox::SPViewBox()
2374 : viewBox_set(false)
2375@@ -25,6 +27,11 @@
2376 , aspect_align(SP_ASPECT_XMID_YMID) // Default per spec
2377 , aspect_clip(SP_ASPECT_MEET)
2378 , c2p(Geom::identity())
2379+ , vbt(Geom::identity())
2380+ , rotation(Geom::identity())
2381+ , angle(0)
2382+ , previous_angle(0)
2383+ , rotated(false)
2384 {
2385 }
2386
2387@@ -159,6 +166,16 @@
2388 }
2389 }
2390
2391+double SPViewBox::get_rotation() {
2392+ return this->angle;
2393+}
2394+
2395+void SPViewBox::set_rotation(double angle_val) {
2396+ this->previous_angle = this->angle;
2397+ this->angle = angle_val;
2398+ this->rotated = true;
2399+}
2400+
2401 // Apply scaling from viewbox
2402 void SPViewBox::apply_viewbox(const Geom::Rect& in, double scale_none) {
2403
2404@@ -222,22 +239,41 @@
2405 break;
2406 }
2407 }
2408-
2409 /* Viewbox transform from scale and position */
2410- Geom::Affine q;
2411- q[0] = scale_x;
2412- q[1] = 0.0;
2413- q[2] = 0.0;
2414- q[3] = scale_y;
2415- q[4] = x - scale_x * this->viewBox.left();
2416- q[5] = y - scale_y * this->viewBox.top();
2417-
2418- // std::cout << " q\n" << q << std::endl;
2419-
2420- /* Append viewbox transformation */
2421- this->c2p = q * this->c2p;
2422+ vbt = Geom::identity();
2423+ vbt[0] = scale_x;
2424+ vbt[1] = 0.0;
2425+ vbt[2] = 0.0;
2426+ vbt[3] = scale_y;
2427+ vbt[4] = x - scale_x * this->viewBox.left();
2428+ vbt[5] = y - scale_y * this->viewBox.top();
2429+ /* Append viewbox and turn transformation */
2430+ Geom::Point page_center = this->viewBox.midpoint();
2431+ SPDesktop * desktop = SP_ACTIVE_DESKTOP;
2432+ if (this->angle > 0.0 || this->angle < 0.0 ) { //!0
2433+ if (desktop) {
2434+ rotation = Geom::Translate(page_center).inverse() * Geom::Rotate(Geom::rad_from_deg(angle)) * Geom::Translate(page_center);
2435+ this->c2p = rotation * vbt * this->c2p;
2436+ } else {
2437+ this->c2p = vbt * this->c2p;
2438+ }
2439+ } else {
2440+ this->c2p = vbt * this->c2p;
2441+ }
2442+ if (desktop && this->rotated) {
2443+ Geom::Rect view = desktop->get_display_area();
2444+ Geom::Point view_center = desktop->doc2dt(view.midpoint());
2445+ Geom::Affine center_rotation = Geom::identity();
2446+ center_rotation *= Geom::Translate(page_center * vbt).inverse();
2447+ center_rotation *= Geom::Rotate(Geom::rad_from_deg(this->angle - this->previous_angle));
2448+ center_rotation *= Geom::Translate(page_center * vbt);
2449+ view_center = desktop->dt2doc(view_center * center_rotation);
2450+ desktop->zoom_relative(view_center[Geom::X], view_center[Geom::Y], 1.0);
2451+ this->rotated = false;
2452+ }
2453 }
2454
2455+
2456 SPItemCtx SPViewBox::get_rctx(const SPItemCtx* ictx, double scale_none) {
2457
2458 /* Create copy of item context */
2459
2460=== modified file 'src/viewbox.h'
2461--- src/viewbox.h 2015-02-12 16:17:54 +0000
2462+++ src/viewbox.h 2017-01-24 17:53:20 +0000
2463@@ -36,7 +36,14 @@
2464
2465 /* Child to parent additional transform */
2466 Geom::Affine c2p;
2467+ Geom::Affine vbt;
2468+ Geom::Affine rotation;
2469+ double angle;
2470+ double previous_angle;
2471+ bool rotated;
2472
2473+ double get_rotation();
2474+ void set_rotation(double angle_val);
2475 void set_viewBox(const gchar* value);
2476 void set_preserveAspectRatio(const gchar* value);
2477
2478
2479=== modified file 'src/widgets/desktop-widget.cpp'
2480--- src/widgets/desktop-widget.cpp 2016-11-11 20:04:49 +0000
2481+++ src/widgets/desktop-widget.cpp 2017-01-24 17:53:20 +0000
2482@@ -54,7 +54,7 @@
2483 #include "ui/uxmanager.h"
2484 #include "util/ege-appear-time-tracker.h"
2485 #include "sp-root.h"
2486-
2487+#include "attributes.h"
2488 // We're in the "widgets" directory, so no need to explicitly prefix these:
2489 #include "button.h"
2490 #include "gimp/ruler.h"
2491@@ -62,11 +62,11 @@
2492 #include "spw-utilities.h"
2493 #include "toolbox.h"
2494 #include "widget-sizes.h"
2495-
2496 #include "verbs.h"
2497 #include <gtkmm/cssprovider.h>
2498 #include <gtkmm/paned.h>
2499 #include <gtkmm/messagedialog.h>
2500+#include <iomanip>
2501
2502 #if defined (SOLARIS) && (SOLARIS == 8)
2503 #include "round.h"
2504@@ -105,8 +105,20 @@
2505 static void cms_adjust_toggled( GtkWidget *button, gpointer data );
2506 #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
2507 static void cms_adjust_set_sensitive( SPDesktopWidget *dtw, bool enabled );
2508+static void sp_desktop_widget_rotate_document(GtkSpinButton *spin, SPDesktopWidget *dtw);
2509 static void sp_desktop_widget_adjustment_value_changed (GtkAdjustment *adj, SPDesktopWidget *dtw);
2510
2511+static gint sp_dtw_rotation_input (GtkSpinButton *spin, gdouble *new_val, gpointer data);
2512+static bool sp_dtw_rotation_output (GtkSpinButton *spin, gpointer data);
2513+static void sp_dtw_rotation_populate_popup (GtkEntry *entry, GtkMenu *menu, gpointer data);
2514+static void sp_dtw_rotate_minus_180 (GtkMenuItem *item, SPDesktopWidget * data);
2515+static void sp_dtw_rotate_minus_135 (GtkMenuItem *item, SPDesktopWidget * data);
2516+static void sp_dtw_rotate_minus_90 (GtkMenuItem *item, SPDesktopWidget * data);
2517+static void sp_dtw_rotate_minus_45 (GtkMenuItem *item, SPDesktopWidget * data);
2518+static void sp_dtw_rotate_0 (GtkMenuItem *item, SPDesktopWidget * data);
2519+static void sp_dtw_rotate_45 (GtkMenuItem *item, SPDesktopWidget * data);
2520+static void sp_dtw_rotate_90 (GtkMenuItem *item, SPDesktopWidget * data);
2521+static void sp_dtw_rotate_135 (GtkMenuItem *item, SPDesktopWidget * data);
2522 static gdouble sp_dtw_zoom_value_to_display (gdouble value);
2523 static gdouble sp_dtw_zoom_display_to_value (gdouble value);
2524 static gint sp_dtw_zoom_input (GtkSpinButton *spin, gdouble *new_val, gpointer data);
2525@@ -593,6 +605,34 @@
2526 g_signal_connect (G_OBJECT (dtw->zoom_status), "key-press-event", G_CALLBACK (spinbutton_keypress), dtw->zoom_status);
2527 dtw->zoom_update = g_signal_connect (G_OBJECT (dtw->zoom_status), "value_changed", G_CALLBACK (sp_dtw_zoom_value_changed), dtw);
2528 dtw->zoom_update = g_signal_connect (G_OBJECT (dtw->zoom_status), "populate_popup", G_CALLBACK (sp_dtw_zoom_populate_popup), dtw);
2529+ auto css_provider_spinbutton = Gtk::CssProvider::create();
2530+ css_provider_spinbutton->load_from_data("* { padding-left: 2; padding-right: 2; padding-top: 0; padding-bottom: 0;}");
2531+ auto zoomstat = Glib::wrap(dtw->zoom_status);
2532+ zoomstat->set_name("ZoomStatus");
2533+ auto context_zoom = zoomstat->get_style_context();
2534+ context_zoom->add_provider(css_provider_spinbutton, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2535+
2536+ // Rotate status spinbutton
2537+ dtw->rotation_status = gtk_spin_button_new_with_range (-360.0,360.0, 1.0);
2538+ gtk_widget_set_tooltip_text (dtw->rotation_status, _("Rotation"));
2539+ gtk_widget_set_size_request (dtw->rotation_status, STATUS_ROTATION_WIDTH, -1);
2540+ gtk_entry_set_width_chars (GTK_ENTRY (dtw->rotation_status), 7);
2541+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (dtw->rotation_status), FALSE);
2542+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (dtw->rotation_status), 2);
2543+ gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (dtw->rotation_status), GTK_UPDATE_ALWAYS);
2544+ g_signal_connect (G_OBJECT (dtw->rotation_status), "input", G_CALLBACK (sp_dtw_rotation_input), dtw);
2545+ g_signal_connect (G_OBJECT (dtw->rotation_status), "output", G_CALLBACK (sp_dtw_rotation_output), dtw);
2546+ g_object_set_data (G_OBJECT (dtw->rotation_status), "dtw", dtw->canvas);
2547+ g_signal_connect (G_OBJECT (dtw->rotation_status), "focus-in-event", G_CALLBACK (spinbutton_focus_in), dtw->rotation_status);
2548+ g_signal_connect (G_OBJECT (dtw->rotation_status), "key-press-event", G_CALLBACK (spinbutton_keypress), dtw->rotation_status);
2549+ dtw->rotation_update = g_signal_connect (G_OBJECT (dtw->rotation_status), "value_changed", G_CALLBACK (sp_desktop_widget_rotate_document), dtw);
2550+ dtw->rotation_update = g_signal_connect (G_OBJECT (dtw->rotation_status), "populate_popup", G_CALLBACK (sp_dtw_rotation_populate_popup), dtw);
2551+
2552+ auto rotstat = Glib::wrap(dtw->rotation_status);
2553+ rotstat->set_name("RotationStatus");
2554+ auto context_rotation = rotstat->get_style_context();
2555+ context_rotation->add_provider(css_provider_spinbutton, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2556+
2557
2558 // Cursor coordinates
2559 dtw->coord_status = gtk_grid_new();
2560@@ -619,12 +659,16 @@
2561
2562 auto label_z = gtk_label_new(_("Z:"));
2563 gtk_widget_set_name(label_z, "ZLabel");
2564+ auto label_r = gtk_label_new(_("R:"));
2565+ gtk_widget_set_name(label_r, "RLabel");
2566 gtk_widget_set_halign(dtw->coord_status_x, GTK_ALIGN_END);
2567 gtk_widget_set_halign(dtw->coord_status_y, GTK_ALIGN_END);
2568 gtk_grid_attach(GTK_GRID(dtw->coord_status), dtw->coord_status_x, 2, 0, 1, 1);
2569 gtk_grid_attach(GTK_GRID(dtw->coord_status), dtw->coord_status_y, 2, 1, 1, 1);
2570 gtk_grid_attach(GTK_GRID(dtw->coord_status), label_z, 3, 0, 1, 2);
2571+ gtk_grid_attach(GTK_GRID(dtw->coord_status), label_r, 5, 0, 1, 2);
2572 gtk_grid_attach(GTK_GRID(dtw->coord_status), dtw->zoom_status, 4, 0, 1, 2);
2573+ gtk_grid_attach(GTK_GRID(dtw->coord_status), dtw->rotation_status, 6, 0, 1, 2);
2574
2575 sp_set_font_size_smaller (dtw->coord_status);
2576
2577@@ -692,6 +736,11 @@
2578 g_signal_handlers_disconnect_matched (G_OBJECT (dtw->zoom_status), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, dtw->zoom_status);
2579 g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->zoom_status), (gpointer) G_CALLBACK (sp_dtw_zoom_value_changed), dtw);
2580 g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->zoom_status), (gpointer) G_CALLBACK (sp_dtw_zoom_populate_popup), dtw);
2581+ g_signal_handlers_disconnect_by_func(G_OBJECT (dtw->rotation_status), (gpointer) G_CALLBACK(sp_dtw_rotation_input), dtw);
2582+ g_signal_handlers_disconnect_by_func(G_OBJECT (dtw->rotation_status), (gpointer) G_CALLBACK(sp_dtw_rotation_output), dtw);
2583+ g_signal_handlers_disconnect_matched (G_OBJECT (dtw->rotation_status), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, dtw->rotation_status);
2584+ g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->rotation_status), (gpointer) G_CALLBACK (sp_desktop_widget_rotate_document), dtw);
2585+ g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->rotation_status), (gpointer) G_CALLBACK (sp_dtw_rotation_populate_popup), dtw);
2586 g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->canvas), (gpointer) G_CALLBACK (sp_desktop_widget_event), dtw);
2587 g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->canvas_tbl), (gpointer) G_CALLBACK (canvas_tbl_size_allocate), dtw);
2588
2589@@ -1423,7 +1472,7 @@
2590 } else {
2591 gtk_widget_show_all (dtw->menubar);
2592 }
2593-
2594+
2595 if (!prefs->getBool(pref_root + "commands/state", true)) {
2596 gtk_widget_hide (dtw->commands_toolbox);
2597 } else {
2598@@ -1629,10 +1678,11 @@
2599 dtw->menubar = sp_ui_main_menubar (dtw->desktop);
2600 gtk_widget_set_name(dtw->menubar, "MenuBar");
2601 gtk_widget_show_all (dtw->menubar);
2602- gtk_box_pack_start (GTK_BOX (dtw->vbox), dtw->menubar, FALSE, FALSE, 0);
2603-
2604+ SPNamedView *nv = dtw->desktop->namedview;
2605+ gtk_box_pack_start (GTK_BOX (dtw->vbox), dtw->menubar, TRUE, TRUE, 0);
2606 dtw->layoutWidgets();
2607-
2608+ gtk_spin_button_set_value(GTK_SPIN_BUTTON (dtw->rotation_status), namedview->document_rotation);
2609+ sp_namedview_set_document_rotation(namedview);
2610 std::vector<GtkWidget *> toolboxes;
2611 toolboxes.push_back(dtw->tool_toolbox);
2612 toolboxes.push_back(dtw->aux_toolbox);
2613@@ -1672,6 +1722,8 @@
2614 void SPDesktopWidget::namedviewModified(SPObject *obj, guint flags)
2615 {
2616 SPNamedView *nv=SP_NAMEDVIEW(obj);
2617+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(this->rotation_status), desktop->namedview->document_rotation);
2618+ sp_namedview_set_document_rotation(nv);
2619
2620 if (flags & SP_OBJECT_MODIFIED_FLAG) {
2621 this->dt2r = 1. / nv->display_units->factor;
2622@@ -1723,6 +1775,18 @@
2623 }
2624
2625 static void
2626+sp_desktop_widget_rotate_document(GtkSpinButton *spin, SPDesktopWidget *dtw)
2627+{
2628+ SPNamedView *nv = dtw->desktop->namedview;
2629+ double value = gtk_spin_button_get_value (spin);
2630+ if (!dtw->desktop->getDocument()->getRoot()->rotated && value != nv->document_rotation) {
2631+ sp_repr_set_svg_double(nv->getRepr(), "inkscape:document-rotation", value);
2632+ }
2633+ spinbutton_defocus (GTK_WIDGET(spin));
2634+}
2635+
2636+
2637+static void
2638 sp_desktop_widget_adjustment_value_changed (GtkAdjustment */*adj*/, SPDesktopWidget *dtw)
2639 {
2640 if (dtw->update)
2641@@ -1802,6 +1866,34 @@
2642 return TRUE;
2643 }
2644
2645+static gint
2646+sp_dtw_rotation_input (GtkSpinButton *spin, gdouble *new_val, gpointer /*data*/)
2647+{
2648+ gdouble new_scrolled = gtk_spin_button_get_value (spin);
2649+ const gchar *b = gtk_entry_get_text (GTK_ENTRY (spin));
2650+ gdouble new_typed = atof (b);
2651+
2652+ if (new_scrolled == new_typed) { // the new value is set by scrolling
2653+ *new_val = new_scrolled;
2654+ } else { // the new value is typed in
2655+ *new_val = new_typed;
2656+ }
2657+
2658+ return TRUE;
2659+}
2660+
2661+static bool
2662+sp_dtw_rotation_output (GtkSpinButton *spin, gpointer /*data*/)
2663+{
2664+ gchar b[64];
2665+ double val = gtk_spin_button_get_value (spin);
2666+ std::ostringstream s;
2667+ s.imbue(std::locale(""));;
2668+ s << std::fixed << std::setprecision(2) << val << "º";
2669+ gtk_entry_set_text (GTK_ENTRY (spin), s.str().c_str());
2670+ return TRUE;
2671+}
2672+
2673 static void
2674 sp_dtw_zoom_value_changed (GtkSpinButton *spin, gpointer data)
2675 {
2676@@ -1940,6 +2032,110 @@
2677 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2678 }
2679
2680+
2681+static void
2682+sp_dtw_rotation_populate_popup (GtkEntry */*entry*/, GtkMenu *menu, gpointer data)
2683+{
2684+ GList *children, *iter;
2685+ GtkWidget *item;
2686+ SPDesktopWidget *dtw = static_cast<SPDesktopWidget*>(data);
2687+ children = gtk_container_get_children (GTK_CONTAINER (menu));
2688+ for ( iter = children ; iter ; iter = g_list_next (iter)) {
2689+ gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (iter->data));
2690+ }
2691+ g_list_free (children);
2692+
2693+ item = gtk_menu_item_new_with_label ("-180º");
2694+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_minus_180), dtw);
2695+ gtk_widget_show (item);
2696+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2697+
2698+ item = gtk_menu_item_new_with_label ("-135º");
2699+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_minus_135), dtw);
2700+ gtk_widget_show (item);
2701+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2702+
2703+ item = gtk_menu_item_new_with_label ("-90º");
2704+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_minus_90), dtw);
2705+ gtk_widget_show (item);
2706+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2707+
2708+ item = gtk_menu_item_new_with_label ("-45º");
2709+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_minus_45), dtw);
2710+ gtk_widget_show (item);
2711+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2712+
2713+ item = gtk_menu_item_new_with_label ("0º");
2714+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_0), dtw);
2715+ gtk_widget_show (item);
2716+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2717+
2718+ item = gtk_menu_item_new_with_label ("45º");
2719+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_45), dtw);
2720+ gtk_widget_show (item);
2721+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2722+
2723+
2724+ item = gtk_menu_item_new_with_label ("90º");
2725+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_90), dtw);
2726+ gtk_widget_show (item);
2727+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2728+
2729+
2730+ item = gtk_menu_item_new_with_label ("135º");
2731+ g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (sp_dtw_rotate_135), dtw);
2732+ gtk_widget_show (item);
2733+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2734+}
2735+
2736+static void
2737+sp_dtw_rotate_minus_180 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2738+{
2739+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status),-180);
2740+}
2741+
2742+static void
2743+sp_dtw_rotate_minus_135 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2744+{
2745+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), -135);
2746+}
2747+
2748+static void
2749+sp_dtw_rotate_minus_90 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2750+{
2751+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), -90);
2752+}
2753+
2754+static void
2755+sp_dtw_rotate_minus_45 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2756+{
2757+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), -45);
2758+}
2759+
2760+static void
2761+sp_dtw_rotate_0 (GtkMenuItem */*item*/,SPDesktopWidget * data)
2762+{
2763+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), 0);
2764+}
2765+
2766+static void
2767+sp_dtw_rotate_45 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2768+{
2769+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), 45);
2770+}
2771+
2772+static void
2773+sp_dtw_rotate_90 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2774+{
2775+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), 90);
2776+}
2777+
2778+static void
2779+sp_dtw_rotate_135 (GtkMenuItem */*item*/, SPDesktopWidget * data)
2780+{
2781+ gtk_spin_button_set_value (GTK_SPIN_BUTTON((data)->rotation_status), 135);
2782+}
2783+
2784 static void
2785 sp_dtw_zoom_menu_handler (SPDesktop *dt, gdouble factor)
2786 {
2787
2788=== modified file 'src/widgets/desktop-widget.h'
2789--- src/widgets/desktop-widget.h 2015-12-09 15:13:54 +0000
2790+++ src/widgets/desktop-widget.h 2017-01-24 17:53:20 +0000
2791@@ -78,7 +78,7 @@
2792
2793 GtkWidget *hbox;
2794
2795- GtkWidget *menubar, *statusbar;
2796+ GtkWidget *menubar, *statusbar, *rotatebar;
2797
2798 Inkscape::UI::Dialogs::SwatchesPanel *panels;
2799
2800@@ -97,7 +97,9 @@
2801 GtkWidget *select_status;
2802 GtkWidget *select_status_eventbox;
2803 GtkWidget *zoom_status;
2804+ GtkWidget *rotation_status;
2805 gulong zoom_update;
2806+ gulong rotation_update;
2807
2808 Inkscape::UI::Widget::Dock *dock;
2809
2810
2811=== modified file 'src/widgets/widget-sizes.h'
2812--- src/widgets/widget-sizes.h 2014-03-27 01:33:44 +0000
2813+++ src/widgets/widget-sizes.h 2017-01-24 17:53:20 +0000
2814@@ -28,6 +28,7 @@
2815 #define STATUS_BAR_FONT_SIZE 10000
2816
2817 #define STATUS_ZOOM_WIDTH 57
2818+#define STATUS_ROTATION_WIDTH 57
2819
2820 #define SELECTED_STYLE_SB_WIDTH 48
2821 #define SELECTED_STYLE_WIDTH 190