1 /*
2 * Unser-interface related object extension
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * Jon A. Cruz <jon@joncruz.org>
7 * Abhishek Sharma
8 *
9 * This code is in public domain
10 */
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
16 #include "context-menu.h"
17 #include "../xml/repr.h"
18 #include "desktop.h"
19 #include "document.h"
20 #include "message-stack.h"
21 #include "preferences.h"
22 #include "ui/dialog/dialog-manager.h"
24 using Inkscape::DocumentUndo;
26 static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu);
28 /* Append object-specific part to context menu */
30 void
31 sp_object_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu)
32 {
33 GObjectClass *klass;
34 klass = G_OBJECT_GET_CLASS(object);
35 while (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_OBJECT)) {
36 GType type;
37 type = G_TYPE_FROM_CLASS(klass);
38 sp_object_type_menu(type, object, desktop, menu);
39 klass = (GObjectClass*)g_type_class_peek_parent(klass);
40 }
41 }
43 /* Implementation */
45 #include <gtk/gtkmenuitem.h>
47 #include <glibmm/i18n.h>
49 #include "sp-anchor.h"
50 #include "sp-image.h"
51 #include "sp-text.h"
53 #include "document.h"
54 #include "desktop-handles.h"
55 #include "selection.h"
56 #include "selection-chemistry.h"
57 #include "dialogs/item-properties.h"
58 #include "dialogs/object-attributes.h"
59 #include "dialogs/text-edit.h"
60 #include "dialogs/spellcheck.h"
62 #include "sp-path.h"
63 #include "sp-clippath.h"
64 #include "sp-mask.h"
67 static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
68 static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
69 static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
70 static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
71 static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
72 static void sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
74 static void
75 sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu)
76 {
77 static GHashTable *t2m = NULL;
78 void (* handler)(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
79 if (!t2m) {
80 t2m = g_hash_table_new(NULL, NULL);
81 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ITEM), (void*)sp_item_menu);
82 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_GROUP), (void*)sp_group_menu);
83 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ANCHOR), (void*)sp_anchor_menu);
84 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_IMAGE), (void*)sp_image_menu);
85 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_SHAPE), (void*)sp_shape_menu);
86 g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_TEXT), (void*)sp_text_menu);
87 }
88 handler = (void (*)(SPObject*, SPDesktop*, GtkMenu*))g_hash_table_lookup(t2m, GUINT_TO_POINTER(type));
89 if (handler) handler(object, desktop, menu);
90 }
92 /* SPItem */
94 static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item);
95 static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item);
96 static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item);
97 static void sp_set_mask(GtkMenuItem *menuitem, SPItem *item);
98 static void sp_release_mask(GtkMenuItem *menuitem, SPItem *item);
99 static void sp_set_clip(GtkMenuItem *menuitem, SPItem *item);
100 static void sp_release_clip(GtkMenuItem *menuitem, SPItem *item);
101 /* Generate context menu item section */
103 static void
104 sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
105 {
106 SPItem *item;
107 GtkWidget *w;
109 item = (SPItem *) object;
111 /* Item dialog */
112 w = gtk_menu_item_new_with_mnemonic(_("_Object Properties..."));
113 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
114 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_properties), item);
115 gtk_widget_show(w);
116 gtk_menu_append(GTK_MENU(m), w);
117 /* Separator */
118 w = gtk_menu_item_new();
119 gtk_widget_show(w);
120 gtk_menu_append(GTK_MENU(m), w);
121 /* Select item */
122 w = gtk_menu_item_new_with_mnemonic(_("_Select This"));
123 if (sp_desktop_selection(desktop)->includes(item)) {
124 gtk_widget_set_sensitive(w, FALSE);
125 } else {
126 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
127 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_select_this), item);
128 }
129 gtk_widget_show(w);
130 gtk_menu_append(GTK_MENU(m), w);
131 /* Create link */
132 w = gtk_menu_item_new_with_mnemonic(_("_Create Link"));
133 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
134 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_create_link), item);
135 gtk_widget_set_sensitive(w, !SP_IS_ANCHOR(item));
136 gtk_widget_show(w);
137 gtk_menu_append(GTK_MENU(m), w);
138 /* Set mask */
139 w = gtk_menu_item_new_with_mnemonic(_("Set Mask"));
140 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
141 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_set_mask), item);
142 if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) {
143 gtk_widget_set_sensitive(w, FALSE);
144 } else {
145 gtk_widget_set_sensitive(w, TRUE);
146 }
147 gtk_widget_show(w);
148 gtk_menu_append(GTK_MENU(m), w);
149 /* Release mask */
150 w = gtk_menu_item_new_with_mnemonic(_("Release Mask"));
151 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
152 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_release_mask), item);
153 if (item && item->mask_ref && item->mask_ref->getObject()) {
154 gtk_widget_set_sensitive(w, TRUE);
155 } else {
156 gtk_widget_set_sensitive(w, FALSE);
157 }
158 gtk_widget_show(w);
159 gtk_menu_append(GTK_MENU(m), w);
160 /* Set Clip */
161 w = gtk_menu_item_new_with_mnemonic(_("Set _Clip"));
162 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
163 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_set_clip), item);
164 if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) {
165 gtk_widget_set_sensitive(w, FALSE);
166 } else {
167 gtk_widget_set_sensitive(w, TRUE);
168 }
169 gtk_widget_show(w);
170 gtk_menu_append(GTK_MENU(m), w);
171 /* Release Clip */
172 w = gtk_menu_item_new_with_mnemonic(_("Release C_lip"));
173 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
174 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_release_clip), item);
175 if (item && item->clip_ref && item->clip_ref->getObject()) {
176 gtk_widget_set_sensitive(w, TRUE);
177 } else {
178 gtk_widget_set_sensitive(w, FALSE);
179 }
180 gtk_widget_show(w);
181 gtk_menu_append(GTK_MENU(m), w);
183 }
185 static void
186 sp_item_properties(GtkMenuItem *menuitem, SPItem *item)
187 {
188 SPDesktop *desktop;
190 g_assert(SP_IS_ITEM(item));
192 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
193 g_return_if_fail(desktop != NULL);
195 sp_desktop_selection(desktop)->set(item);
197 sp_item_dialog();
198 }
201 static void
202 sp_set_mask(GtkMenuItem *menuitem, SPItem *item)
203 {
204 SPDesktop *desktop;
206 g_assert(SP_IS_ITEM(item));
208 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
209 g_return_if_fail(desktop != NULL);
211 sp_selection_set_mask(desktop, false, false);
212 }
215 static void
216 sp_release_mask(GtkMenuItem *menuitem, SPItem *item)
217 {
218 SPDesktop *desktop;
220 g_assert(SP_IS_ITEM(item));
222 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
223 g_return_if_fail(desktop != NULL);
225 sp_selection_unset_mask(desktop, false);
226 }
229 static void
230 sp_set_clip(GtkMenuItem *menuitem, SPItem *item)
231 {
232 SPDesktop *desktop;
234 g_assert(SP_IS_ITEM(item));
236 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
237 g_return_if_fail(desktop != NULL);
239 sp_selection_set_mask(desktop, true, false);
240 }
243 static void
244 sp_release_clip(GtkMenuItem *menuitem, SPItem *item)
245 {
246 SPDesktop *desktop;
248 g_assert(SP_IS_ITEM(item));
250 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
251 g_return_if_fail(desktop != NULL);
253 sp_selection_unset_mask(desktop, true);
254 }
257 static void
258 sp_item_select_this(GtkMenuItem *menuitem, SPItem *item)
259 {
260 SPDesktop *desktop;
262 g_assert(SP_IS_ITEM(item));
264 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
265 g_return_if_fail(desktop != NULL);
267 sp_desktop_selection(desktop)->set(item);
268 }
270 static void
271 sp_item_create_link(GtkMenuItem *menuitem, SPItem *item)
272 {
273 g_assert(SP_IS_ITEM(item));
274 g_assert(!SP_IS_ANCHOR(item));
276 SPDesktop *desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
277 g_return_if_fail(desktop != NULL);
279 Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
280 Inkscape::XML::Node *repr = xml_doc->createElement("svg:a");
281 SP_OBJECT_REPR(SP_OBJECT_PARENT(item))->addChild(repr, SP_OBJECT_REPR(item));
282 SPObject *object = SP_OBJECT_DOCUMENT(item)->getObjectByRepr(repr);
283 g_return_if_fail(SP_IS_ANCHOR(object));
285 const char *id = SP_OBJECT_REPR(item)->attribute("id");
286 Inkscape::XML::Node *child = SP_OBJECT_REPR(item)->duplicate(xml_doc);
287 SP_OBJECT(item)->deleteObject(false);
288 repr->addChild(child, NULL);
289 child->setAttribute("id", id);
291 Inkscape::GC::release(repr);
292 Inkscape::GC::release(child);
294 DocumentUndo::done(SP_OBJECT_DOCUMENT(object), SP_VERB_NONE,
295 _("Create link"));
297 sp_object_attributes_dialog(object, "SPAnchor");
299 sp_desktop_selection(desktop)->set(SP_ITEM(object));
300 }
302 /* SPGroup */
304 static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group);
306 static void
307 sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu)
308 {
309 SPItem *item=SP_ITEM(object);
310 GtkWidget *w;
312 /* "Ungroup" */
313 w = gtk_menu_item_new_with_mnemonic(_("_Ungroup"));
314 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
315 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_group_ungroup_activate), item);
316 gtk_widget_show(w);
317 gtk_menu_append(GTK_MENU(menu), w);
318 }
320 static void
321 sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group)
322 {
323 SPDesktop *desktop;
324 GSList *children;
326 g_assert(SP_IS_GROUP(group));
328 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
329 g_return_if_fail(desktop != NULL);
331 children = NULL;
332 sp_item_group_ungroup(group, &children);
334 sp_desktop_selection(desktop)->setList(children);
335 g_slist_free(children);
336 }
338 /* SPAnchor */
340 static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor *anchor);
341 static void sp_anchor_link_follow(GtkMenuItem *menuitem, SPAnchor *anchor);
342 static void sp_anchor_link_remove(GtkMenuItem *menuitem, SPAnchor *anchor);
344 static void
345 sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
346 {
347 SPItem *item;
348 GtkWidget *w;
350 item = (SPItem *) object;
352 /* Link dialog */
353 w = gtk_menu_item_new_with_mnemonic(_("Link _Properties..."));
354 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
355 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_properties), item);
356 gtk_widget_show(w);
357 gtk_menu_append(GTK_MENU(m), w);
358 /* Select item */
359 w = gtk_menu_item_new_with_mnemonic(_("_Follow Link"));
360 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_follow), item);
361 gtk_widget_show(w);
362 gtk_menu_append(GTK_MENU(m), w);
363 /* Reset transformations */
364 w = gtk_menu_item_new_with_mnemonic(_("_Remove Link"));
365 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
366 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_remove), item);
367 gtk_widget_show(w);
368 gtk_menu_append(GTK_MENU(m), w);
369 }
371 static void
372 sp_anchor_link_properties(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
373 {
374 sp_object_attributes_dialog(SP_OBJECT(anchor), "Link");
375 }
377 static void
378 sp_anchor_link_follow(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
379 {
380 g_return_if_fail(anchor != NULL);
381 g_return_if_fail(SP_IS_ANCHOR(anchor));
383 /* shell out to an external browser here */
384 }
386 static void
387 sp_anchor_link_remove(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
388 {
389 GSList *children;
391 g_return_if_fail(anchor != NULL);
392 g_return_if_fail(SP_IS_ANCHOR(anchor));
394 children = NULL;
395 sp_item_group_ungroup(SP_GROUP(anchor), &children);
397 g_slist_free(children);
398 }
400 /* Image */
402 static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor *anchor);
403 static void sp_image_image_edit(GtkMenuItem *menuitem, SPAnchor *anchor);
405 static void
406 sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
407 {
408 SPItem *item = SP_ITEM(object);
409 GtkWidget *w;
411 /* Link dialog */
412 w = gtk_menu_item_new_with_mnemonic(_("Image _Properties..."));
413 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
414 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_image_image_properties), item);
415 gtk_widget_show(w);
416 gtk_menu_append(GTK_MENU(m), w);
418 w = gtk_menu_item_new_with_mnemonic(_("Edit Externally..."));
419 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
420 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_image_image_edit), item);
421 gtk_widget_show(w);
422 gtk_menu_append(GTK_MENU(m), w);
423 Inkscape::XML::Node *ir = SP_OBJECT_REPR(object);
424 const gchar *href = ir->attribute("xlink:href");
425 if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) {
426 gtk_widget_set_sensitive( w, FALSE );
427 }
428 }
430 static void
431 sp_image_image_properties(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
432 {
433 sp_object_attributes_dialog(SP_OBJECT(anchor), "Image");
434 }
436 static gchar* getImageEditorName() {
437 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
438 gchar* value = 0;
439 Glib::ustring choices = prefs->getString("/options/bitmapeditor/choices");
440 if (!choices.empty()) {
441 gchar** splits = g_strsplit(choices.data(), ",", 0);
442 gint numIems = g_strv_length(splits);
444 int setting = prefs->getIntLimited("/options/bitmapeditor/value", 0, 0, numIems);
445 value = g_strdup(splits[setting]);
447 g_strfreev(splits);
448 }
450 if (!value) {
451 value = g_strdup("gimp");
452 }
453 return value;
454 }
456 static void sp_image_image_edit(GtkMenuItem *menuitem, SPAnchor *anchor)
457 {
458 SPObject* obj = SP_OBJECT(anchor);
459 Inkscape::XML::Node *ir = SP_OBJECT_REPR(obj);
460 const gchar *href = ir->attribute("xlink:href");
462 GError* errThing = 0;
463 gchar* editorBin = getImageEditorName();
464 gchar const* args[] = {editorBin, href, 0};
465 g_spawn_async(0, // working dir
466 const_cast<gchar **>(args),
467 0, //envp
468 G_SPAWN_SEARCH_PATH,
469 0, // child_setup
470 0, // user_data
471 0, //GPid *child_pid
472 &errThing);
473 if ( errThing ) {
474 g_warning("Problem launching editor (%d). %s", errThing->code, errThing->message);
475 SPDesktop *desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
476 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, errThing->message);
477 g_error_free(errThing);
478 errThing = 0;
479 }
480 g_free(editorBin);
481 }
483 /* Fill and Stroke entry */
485 static void
486 sp_fill_settings(GtkMenuItem *menuitem, SPItem *item)
487 {
488 SPDesktop *desktop;
490 g_assert(SP_IS_ITEM(item));
492 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
493 g_return_if_fail(desktop != NULL);
495 if (sp_desktop_selection(desktop)->isEmpty()) {
496 sp_desktop_selection(desktop)->set(item);
497 }
499 desktop->_dlg_mgr->showDialog("FillAndStroke");
500 }
502 /* SPShape */
504 static void
505 sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
506 {
507 SPItem *item;
508 GtkWidget *w;
510 item = (SPItem *) object;
512 /* Item dialog */
513 w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke..."));
514 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
515 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_fill_settings), item);
516 gtk_widget_show(w);
517 gtk_menu_append(GTK_MENU(m), w);
518 }
520 /* Edit Text entry */
522 static void
523 sp_text_settings(GtkMenuItem *menuitem, SPItem *item)
524 {
525 SPDesktop *desktop;
527 g_assert(SP_IS_ITEM(item));
529 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
530 g_return_if_fail(desktop != NULL);
532 if (sp_desktop_selection(desktop)->isEmpty()) {
533 sp_desktop_selection(desktop)->set(item);
534 }
536 sp_text_edit_dialog();
537 }
539 /* Spellcheck entry */
541 static void
542 sp_spellcheck_settings(GtkMenuItem *menuitem, SPItem *item)
543 {
544 SPDesktop *desktop;
546 g_assert(SP_IS_ITEM(item));
548 desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop");
549 g_return_if_fail(desktop != NULL);
551 if (sp_desktop_selection(desktop)->isEmpty()) {
552 sp_desktop_selection(desktop)->set(item);
553 }
555 sp_spellcheck_dialog();
556 }
558 /* SPText */
560 static void
561 sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
562 {
563 SPItem *item;
564 GtkWidget *w;
566 item = (SPItem *) object;
568 /* Fill and Stroke dialog */
569 w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke..."));
570 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
571 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_fill_settings), item);
572 gtk_widget_show(w);
573 gtk_menu_append(GTK_MENU(m), w);
575 /* Edit Text dialog */
576 w = gtk_menu_item_new_with_mnemonic(_("_Text and Font..."));
577 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
578 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_text_settings), item);
579 gtk_widget_show(w);
580 gtk_menu_append(GTK_MENU(m), w);
582 /* Spellcheck dialog */
583 w = gtk_menu_item_new_with_mnemonic(_("Check Spellin_g..."));
584 gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop);
585 gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_spellcheck_settings), item);
586 gtk_widget_show(w);
587 gtk_menu_append(GTK_MENU(m), w);
588 }
589 /*
590 Local Variables:
591 mode:c++
592 c-file-style:"stroustrup"
593 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
594 indent-tabs-mode:nil
595 fill-column:99
596 End:
597 */
598 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :