1 /** \file
2 * Pencil event context implementation.
3 */
5 /*
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 2000 Lauris Kaplinski
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 * Copyright (C) 2002 Lauris Kaplinski
13 * Copyright (C) 2004 Monash University
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include <gdk/gdkkeysyms.h>
20 #include "pencil-context.h"
21 #include "desktop.h"
22 #include "desktop-handles.h"
23 #include "selection.h"
24 #include "selection-chemistry.h"
25 #include "draw-anchor.h"
26 #include "message-stack.h"
27 #include "message-context.h"
28 #include "modifier-fns.h"
29 #include "sp-path.h"
30 #include "prefs-utils.h"
31 #include "snap.h"
32 #include "pixmaps/cursor-pencil.xpm"
33 #include "display/bezier-utils.h"
34 #include "display/canvas-bpath.h"
35 #include <glibmm/i18n.h>
36 #include "libnr/in-svg-plane.h"
37 #include "libnr/n-art-bpath.h"
38 #include "context-fns.h"
39 #include "sp-namedview.h"
40 #include "xml/repr.h"
41 #include "document.h"
42 #include "desktop-style.h"
43 #include "macros.h"
44 #include "display/curve.h"
46 static void sp_pencil_context_class_init(SPPencilContextClass *klass);
47 static void sp_pencil_context_init(SPPencilContext *pc);
48 static void sp_pencil_context_setup(SPEventContext *ec);
49 static void sp_pencil_context_dispose(GObject *object);
51 static gint sp_pencil_context_root_handler(SPEventContext *event_context, GdkEvent *event);
52 static gint pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &bevent);
53 static gint pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent);
54 static gint pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &revent);
55 static gint pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint const state);
57 static void spdc_set_startpoint(SPPencilContext *pc, NR::Point const p);
58 static void spdc_set_endpoint(SPPencilContext *pc, NR::Point const p);
59 static void spdc_finish_endpoint(SPPencilContext *pc);
60 static void spdc_add_freehand_point(SPPencilContext *pc, NR::Point p, guint state);
61 static void fit_and_split(SPPencilContext *pc);
64 static SPDrawContextClass *pencil_parent_class;
65 static NR::Point pencil_drag_origin_w(0, 0);
66 static bool pencil_within_tolerance = false;
68 /**
69 * Register SPPencilContext class with Gdk and return its type number.
70 */
71 GType
72 sp_pencil_context_get_type()
73 {
74 static GType type = 0;
75 if (!type) {
76 GTypeInfo info = {
77 sizeof(SPPencilContextClass),
78 NULL, NULL,
79 (GClassInitFunc) sp_pencil_context_class_init,
80 NULL, NULL,
81 sizeof(SPPencilContext),
82 4,
83 (GInstanceInitFunc) sp_pencil_context_init,
84 NULL, /* value_table */
85 };
86 type = g_type_register_static(SP_TYPE_DRAW_CONTEXT, "SPPencilContext", &info, (GTypeFlags)0);
87 }
88 return type;
89 }
91 /**
92 * Initialize SPPencilContext vtable.
93 */
94 static void
95 sp_pencil_context_class_init(SPPencilContextClass *klass)
96 {
97 GObjectClass *object_class;
98 SPEventContextClass *event_context_class;
100 object_class = (GObjectClass *) klass;
101 event_context_class = (SPEventContextClass *) klass;
103 pencil_parent_class = (SPDrawContextClass*)g_type_class_peek_parent(klass);
105 object_class->dispose = sp_pencil_context_dispose;
107 event_context_class->setup = sp_pencil_context_setup;
108 event_context_class->root_handler = sp_pencil_context_root_handler;
109 }
111 /**
112 * Callback to initialize SPPencilContext object.
113 */
114 static void
115 sp_pencil_context_init(SPPencilContext *pc)
116 {
117 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
119 event_context->cursor_shape = cursor_pencil_xpm;
120 event_context->hot_x = 4;
121 event_context->hot_y = 4;
123 pc->npoints = 0;
124 pc->state = SP_PENCIL_CONTEXT_IDLE;
125 pc->req_tangent = NR::Point(0, 0);
126 }
128 /**
129 * Callback to setup SPPencilContext object.
130 */
131 static void
132 sp_pencil_context_setup(SPEventContext *ec)
133 {
134 if (prefs_get_int_attribute("tools.freehand.pencil", "selcue", 0) != 0) {
135 ec->enableSelectionCue();
136 }
138 if (((SPEventContextClass *) pencil_parent_class)->setup) {
139 ((SPEventContextClass *) pencil_parent_class)->setup(ec);
140 }
142 SPPencilContext *const pc = SP_PENCIL_CONTEXT(ec);
143 pc->is_drawing = false;
145 pc->anchor_statusbar = false;
146 }
148 static void
149 sp_pencil_context_dispose(GObject *object)
150 {
151 G_OBJECT_CLASS(pencil_parent_class)->dispose(object);
152 }
154 /** Snaps new node relative to the previous node. */
155 static void
156 spdc_endpoint_snap(SPPencilContext const *pc, NR::Point &p, guint const state)
157 {
158 spdc_endpoint_snap_rotation(pc, p, pc->p[0], state);
159 spdc_endpoint_snap_free(pc, p, state);
160 }
162 /**
163 * Callback for handling all pencil context events.
164 */
165 gint
166 sp_pencil_context_root_handler(SPEventContext *const ec, GdkEvent *event)
167 {
168 SPPencilContext *const pc = SP_PENCIL_CONTEXT(ec);
170 gint ret = FALSE;
172 switch (event->type) {
173 case GDK_BUTTON_PRESS:
174 ret = pencil_handle_button_press(pc, event->button);
175 break;
177 case GDK_MOTION_NOTIFY:
178 ret = pencil_handle_motion_notify(pc, event->motion);
179 break;
181 case GDK_BUTTON_RELEASE:
182 ret = pencil_handle_button_release(pc, event->button);
183 break;
185 case GDK_KEY_PRESS:
186 ret = pencil_handle_key_press(pc, get_group0_keyval (&event->key), event->key.state);
187 break;
189 default:
190 break;
191 }
193 if (!ret) {
194 gint (*const parent_root_handler)(SPEventContext *, GdkEvent *)
195 = ((SPEventContextClass *) pencil_parent_class)->root_handler;
196 if (parent_root_handler) {
197 ret = parent_root_handler(ec, event);
198 }
199 }
201 return ret;
202 }
204 static gint
205 pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &bevent)
206 {
207 gint ret = FALSE;
208 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
209 if ( bevent.button == 1 && !event_context->space_panning) {
211 SPDrawContext *dc = SP_DRAW_CONTEXT (pc);
212 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
213 Inkscape::Selection *selection = sp_desktop_selection(desktop);
215 if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) {
216 return TRUE;
217 }
219 NR::Point const button_w(bevent.x, bevent.y);
221 /* Find desktop coordinates */
222 NR::Point p = pc->desktop->w2d(button_w);
224 /* Test whether we hit any anchor. */
225 SPDrawAnchor *anchor = spdc_test_inside(pc, button_w);
227 pencil_drag_origin_w = NR::Point(bevent.x,bevent.y);
228 pencil_within_tolerance = true;
230 switch (pc->state) {
231 case SP_PENCIL_CONTEXT_ADDLINE:
232 /* Current segment will be finished with release */
233 ret = TRUE;
234 break;
235 default:
236 /* Set first point of sequence */
237 if (bevent.state & GDK_CONTROL_MASK) {
238 freehand_create_single_dot(event_context, p, "tools.freehand.pencil", bevent.state);
239 ret = true;
240 break;
241 }
242 if (anchor) {
243 p = anchor->dp;
244 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
245 } else {
247 if (!(bevent.state & GDK_SHIFT_MASK)) {
249 // This is the first click of a new curve; deselect item so that
250 // this curve is not combined with it (unless it is drawn from its
251 // anchor, which is handled by the sibling branch above)
252 selection->clear();
253 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
254 SnapManager &m = desktop->namedview->snap_manager;
255 m.setup(desktop);
256 m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, p);
257 } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) {
258 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
259 }
260 }
261 pc->sa = anchor;
262 spdc_set_startpoint(pc, p);
263 ret = TRUE;
264 break;
265 }
267 pc->is_drawing = true;
268 }
269 return ret;
270 }
272 static gint
273 pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent)
274 {
275 if ((mevent.state & GDK_CONTROL_MASK) && (mevent.state & GDK_BUTTON1_MASK)) {
276 // mouse was accidentally moved during Ctrl+click;
277 // ignore the motion and create a single point
278 pc->is_drawing = false;
279 return TRUE;
280 }
281 gint ret = FALSE;
282 SPDesktop *const dt = pc->desktop;
284 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
285 if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
286 // allow scrolling
287 return FALSE;
288 }
290 if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) {
291 /* Grab mouse, so release will not pass unnoticed */
292 pc->grab = SP_CANVAS_ITEM(dt->acetate);
293 sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
294 GDK_BUTTON_RELEASE_MASK |
295 GDK_POINTER_MOTION_MASK ),
296 NULL, mevent.time);
297 }
299 /* Find desktop coordinates */
300 NR::Point p = dt->w2d(NR::Point(mevent.x, mevent.y));
302 /* Test whether we hit any anchor. */
303 SPDrawAnchor *anchor = spdc_test_inside(pc, NR::Point(mevent.x, mevent.y));
305 if (pencil_within_tolerance) {
306 gint const tolerance = prefs_get_int_attribute_limited("options.dragtolerance",
307 "value", 0, 0, 100);
308 if ( NR::LInfty( NR::Point(mevent.x,mevent.y) - pencil_drag_origin_w ) < tolerance ) {
309 return FALSE; // Do not drag if we're within tolerance from origin.
310 }
311 }
313 // Once the user has moved farther than tolerance from the original location
314 // (indicating they intend to move the object, not click), then always process the
315 // motion notify coordinates as given (no snapping back to origin)
316 pencil_within_tolerance = false;
318 switch (pc->state) {
319 case SP_PENCIL_CONTEXT_ADDLINE:
320 /* Set red endpoint */
321 if (anchor) {
322 p = anchor->dp;
323 } else {
324 spdc_endpoint_snap(pc, p, mevent.state);
325 }
326 spdc_set_endpoint(pc, p);
327 ret = TRUE;
328 break;
329 default:
330 /* We may be idle or already freehand */
331 if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) {
332 pc->state = SP_PENCIL_CONTEXT_FREEHAND;
333 if ( !pc->sa && !pc->green_anchor ) {
334 /* Create green anchor */
335 pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]);
336 }
337 /** \todo
338 * fixme: I am not sure whether we want to snap to anchors
339 * in middle of freehand (Lauris)
340 */
341 if (anchor) {
342 p = anchor->dp;
343 } else if ((mevent.state & GDK_SHIFT_MASK) == 0) {
344 SnapManager &m = dt->namedview->snap_manager;
345 m.setup(dt, NULL);
346 m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, p);
347 }
348 if ( pc->npoints != 0 ) { // buttonpress may have happened before we entered draw context!
349 spdc_add_freehand_point(pc, p, mevent.state);
350 ret = TRUE;
351 }
353 if (anchor && !pc->anchor_statusbar) {
354 pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Release</b> here to close and finish the path."));
355 pc->anchor_statusbar = true;
356 } else if (!anchor && pc->anchor_statusbar) {
357 pc->_message_context->clear();
358 pc->anchor_statusbar = false;
359 } else if (!anchor) {
360 pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path"));
361 }
363 } else {
364 if (anchor && !pc->anchor_statusbar) {
365 pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> to continue the path from this point."));
366 pc->anchor_statusbar = true;
367 } else if (!anchor && pc->anchor_statusbar) {
368 pc->_message_context->clear();
369 pc->anchor_statusbar = false;
370 }
371 }
372 break;
373 }
374 return ret;
375 }
377 static gint
378 pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &revent)
379 {
380 gint ret = FALSE;
382 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
383 if ( revent.button == 1 && pc->is_drawing && !event_context->space_panning) {
384 SPDesktop *const dt = pc->desktop;
386 pc->is_drawing = false;
388 /* Find desktop coordinates */
389 NR::Point p = dt->w2d(NR::Point(revent.x, revent.y));
391 /* Test whether we hit any anchor. */
392 SPDrawAnchor *anchor = spdc_test_inside(pc, NR::Point(revent.x,
393 revent.y));
395 switch (pc->state) {
396 case SP_PENCIL_CONTEXT_IDLE:
397 /* Releasing button in idle mode means single click */
398 /* We have already set up start point/anchor in button_press */
399 if (!(revent.state & GDK_CONTROL_MASK)) {
400 // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed
401 pc->state = SP_PENCIL_CONTEXT_ADDLINE;
402 }
403 ret = TRUE;
404 break;
405 case SP_PENCIL_CONTEXT_ADDLINE:
406 /* Finish segment now */
407 if (anchor) {
408 p = anchor->dp;
409 } else {
410 spdc_endpoint_snap(pc, p, revent.state);
411 }
412 pc->ea = anchor;
413 spdc_set_endpoint(pc, p);
414 spdc_finish_endpoint(pc);
415 pc->state = SP_PENCIL_CONTEXT_IDLE;
416 ret = TRUE;
417 break;
418 case SP_PENCIL_CONTEXT_FREEHAND:
419 /* Finish segment now */
420 /// \todo fixme: Clean up what follows (Lauris)
421 if (anchor) {
422 p = anchor->dp;
423 }
424 pc->ea = anchor;
425 /* Write curves to object */
427 dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand"));
429 spdc_concat_colors_and_flush(pc, FALSE);
430 pc->sa = NULL;
431 pc->ea = NULL;
432 if (pc->green_anchor) {
433 pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
434 }
435 pc->state = SP_PENCIL_CONTEXT_IDLE;
436 ret = TRUE;
437 break;
438 default:
439 break;
440 }
442 if (pc->grab) {
443 /* Release grab now */
444 sp_canvas_item_ungrab(pc->grab, revent.time);
445 pc->grab = NULL;
446 }
448 ret = TRUE;
449 }
450 return ret;
451 }
453 static void
454 pencil_cancel (SPPencilContext *const pc)
455 {
456 if (pc->grab) {
457 /* Release grab now */
458 sp_canvas_item_ungrab(pc->grab, 0);
459 pc->grab = NULL;
460 }
462 pc->is_drawing = false;
464 pc->state = SP_PENCIL_CONTEXT_IDLE;
466 pc->red_curve->reset();
467 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
468 while (pc->green_bpaths) {
469 gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data));
470 pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
471 }
472 pc->green_curve->reset();
473 if (pc->green_anchor) {
474 pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
475 }
477 pc->_message_context->clear();
478 pc->_message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
480 sp_canvas_end_forced_full_redraws(pc->desktop->canvas);
481 }
484 static gint
485 pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint const state)
486 {
487 gint ret = FALSE;
488 switch (keyval) {
489 case GDK_Up:
490 case GDK_Down:
491 case GDK_KP_Up:
492 case GDK_KP_Down:
493 // Prevent the zoom field from activation.
494 if (!mod_ctrl_only(state)) {
495 ret = TRUE;
496 }
497 break;
498 case GDK_Escape:
499 if (pc->npoints != 0) {
500 // if drawing, cancel, otherwise pass it up for deselecting
501 if (pc->is_drawing) {
502 pencil_cancel (pc);
503 ret = TRUE;
504 }
505 }
506 break;
507 case GDK_z:
508 case GDK_Z:
509 if (mod_ctrl_only(state) && pc->npoints != 0) {
510 // if drawing, cancel, otherwise pass it up for undo
511 if (pc->is_drawing) {
512 pencil_cancel (pc);
513 ret = TRUE;
514 }
515 }
516 break;
517 case GDK_g:
518 case GDK_G:
519 if (mod_shift_only(state)) {
520 sp_selection_to_guides();
521 ret = true;
522 }
523 break;
524 default:
525 break;
526 }
527 return ret;
528 }
530 /**
531 * Reset points and set new starting point.
532 */
533 static void
534 spdc_set_startpoint(SPPencilContext *const pc, NR::Point const p)
535 {
536 pc->npoints = 0;
537 pc->red_curve_is_valid = false;
538 if (in_svg_plane(p)) {
539 pc->p[pc->npoints++] = p;
540 }
541 }
543 /**
544 * Change moving endpoint position.
545 * <ul>
546 * <li>Ctrl constrains to moving to H/V direction, snapping in given direction.
547 * <li>Otherwise we snap freely to whatever attractors are available.
548 * </ul>
549 *
550 * Number of points is (re)set to 2 always, 2nd point is modified.
551 * We change RED curve.
552 */
553 static void
554 spdc_set_endpoint(SPPencilContext *const pc, NR::Point const p)
555 {
556 if (pc->npoints == 0) {
557 return;
558 /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad
559 * zoom setting).
560 */
561 }
562 g_return_if_fail( pc->npoints > 0 );
564 pc->red_curve->reset();
565 if ( ( p == pc->p[0] )
566 || !in_svg_plane(p) )
567 {
568 pc->npoints = 1;
569 } else {
570 pc->p[1] = p;
571 pc->npoints = 2;
573 pc->red_curve->moveto(pc->p[0]);
574 pc->red_curve->lineto(pc->p[1]);
575 pc->red_curve_is_valid = true;
577 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
578 }
579 }
581 /**
582 * Finalize addline.
583 *
584 * \todo
585 * fixme: I'd like remove red reset from concat colors (lauris).
586 * Still not sure, how it will make most sense.
587 */
588 static void
589 spdc_finish_endpoint(SPPencilContext *const pc)
590 {
591 if ( ( pc->red_curve->is_empty() )
592 || ( pc->red_curve->first_point() == pc->red_curve->second_point() ) )
593 {
594 pc->red_curve->reset();
595 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
596 } else {
597 /* Write curves to object. */
598 spdc_concat_colors_and_flush(pc, FALSE);
599 pc->sa = NULL;
600 pc->ea = NULL;
601 }
602 }
604 static void
605 spdc_add_freehand_point(SPPencilContext *pc, NR::Point p, guint /*state*/)
606 {
607 g_assert( pc->npoints > 0 );
608 g_return_if_fail(unsigned(pc->npoints) < G_N_ELEMENTS(pc->p));
610 if ( ( p != pc->p[ pc->npoints - 1 ] )
611 && in_svg_plane(p) )
612 {
613 pc->p[pc->npoints++] = p;
614 fit_and_split(pc);
615 }
616 }
618 static inline double
619 square(double const x)
620 {
621 return x * x;
622 }
624 static void
625 fit_and_split(SPPencilContext *pc)
626 {
627 g_assert( pc->npoints > 1 );
629 double const tol = prefs_get_double_attribute_limited("tools.freehand.pencil", "tolerance", 10.0, 1.0, 100.0);
630 double const tolerance_sq = 0.02 * square( NR::expansion(pc->desktop->w2d()) * tol)
631 * exp(0.2*tol - 2);
633 NR::Point b[4];
634 g_assert(is_zero(pc->req_tangent)
635 || is_unit_vector(pc->req_tangent));
636 NR::Point const tHatEnd(0, 0);
637 int const n_segs = sp_bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints,
638 pc->req_tangent, tHatEnd, tolerance_sq, 1);
639 if ( n_segs > 0
640 && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) )
641 {
642 /* Fit and draw and reset state */
643 pc->red_curve->reset();
644 pc->red_curve->moveto(b[0]);
645 pc->red_curve->curveto(b[1], b[2], b[3]);
646 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
647 pc->red_curve_is_valid = true;
648 } else {
649 /* Fit and draw and copy last point */
651 g_assert(!pc->red_curve->is_empty());
653 /* Set up direction of next curve. */
654 {
655 Geom::CubicBezier const * last_seg = dynamic_cast<Geom::CubicBezier const *>(pc->red_curve->last_segment());
656 g_assert( last_seg ); // Relevance: validity of (*last_seg)[2]
657 pc->p[0] = last_seg->finalPoint();
658 pc->npoints = 1;
659 NR::Point const req_vec( pc->p[0] - (*last_seg)[2] );
660 pc->req_tangent = ( ( NR::is_zero(req_vec) || !in_svg_plane(req_vec) )
661 ? NR::Point(0, 0)
662 : NR::unit_vector(req_vec) );
663 }
665 pc->green_curve->append_continuous(pc->red_curve, 0.0625);
666 SPCurve *curve = pc->red_curve->copy();
668 /// \todo fixme:
669 SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve);
670 curve->unref();
671 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
673 pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
675 pc->red_curve_is_valid = false;
676 }
677 }
680 /*
681 Local Variables:
682 mode:c++
683 c-file-style:"stroustrup"
684 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
685 indent-tabs-mode:nil
686 fill-column:99
687 End:
688 */
689 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :