diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/ui/toolbar/spray-toolbar.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/ui/toolbar/spray-toolbar.cpp | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/ui/toolbar/spray-toolbar.cpp b/src/ui/toolbar/spray-toolbar.cpp new file mode 100644 index 0000000..de6939a --- /dev/null +++ b/src/ui/toolbar/spray-toolbar.cpp @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Spray aux toolbar + */ +/* Authors: + * MenTaLguY <mental@rydia.net> + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Frank Felfe <innerspace@iname.com> + * John Cliff <simarilius@yahoo.com> + * David Turner <novalis@gnu.org> + * Josh Andler <scislac@scislac.com> + * Jon A. Cruz <jon@joncruz.org> + * Maximilian Albert <maximilian.albert@gmail.com> + * Tavmjong Bah <tavmjong@free.fr> + * Abhishek Sharma + * Kris De Gussem <Kris.DeGussem@gmail.com> + * Jabiertxo Arraiza <jabier.arraiza@marker.es> + * + * Copyright (C) 2004 David Turner + * Copyright (C) 2003 MenTaLguY + * Copyright (C) 1999-2015 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spray-toolbar.h" + +#include <glibmm/i18n.h> + +#include <gtkmm/radiotoolbutton.h> +#include <gtkmm/separatortoolitem.h> + +#include "desktop.h" + +#include "ui/icon-names.h" +#include "ui/simple-pref-pusher.h" + +#include "ui/dialog/clonetiler.h" +#include "ui/dialog/dialog-container.h" +#include "ui/dialog/dialog-base.h" + +#include "ui/widget/canvas.h" +#include "ui/widget/spin-button-tool-item.h" + +// Disabled in 0.91 because of Bug #1274831 (crash, spraying an object +// with the mode: spray object in single path) +// Please enable again when working on 1.0 +#define ENABLE_SPRAY_MODE_SINGLE_PATH + +Inkscape::UI::Dialog::CloneTiler *get_clone_tiler_panel(SPDesktop *desktop) +{ + Inkscape::UI::Dialog::DialogBase *dialog = desktop->getContainer()->get_dialog("CloneTiler"); + if (!dialog) { + desktop->getContainer()->new_dialog("CloneTiler"); + return dynamic_cast<Inkscape::UI::Dialog::CloneTiler *>( + desktop->getContainer()->get_dialog("CloneTiler")); + } + return dynamic_cast<Inkscape::UI::Dialog::CloneTiler *>(dialog); +} + +namespace Inkscape { +namespace UI { +namespace Toolbar { +SprayToolbar::SprayToolbar(SPDesktop *desktop) : + Toolbar(desktop) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + /* Mode */ + { + add_label(_("Mode:")); + + Gtk::RadioToolButton::Group mode_group; + + auto copy_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray with copies"))); + copy_mode_btn->set_tooltip_text(_("Spray copies of the initial selection")); + copy_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-copy")); + _mode_buttons.push_back(copy_mode_btn); + + auto clone_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray with clones"))); + clone_mode_btn->set_tooltip_text(_("Spray clones of the initial selection")); + clone_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-clone")); + _mode_buttons.push_back(clone_mode_btn); + +#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH + auto union_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray single path"))); + union_mode_btn->set_tooltip_text(_("Spray objects in a single path")); + union_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-union")); + _mode_buttons.push_back(union_mode_btn); +#endif + + auto eraser_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Delete sprayed items"))); + eraser_mode_btn->set_tooltip_text(_("Delete sprayed items from selection")); + eraser_mode_btn->set_icon_name(INKSCAPE_ICON("draw-eraser")); + _mode_buttons.push_back(eraser_mode_btn); + + int btn_idx = 0; + for (auto btn : _mode_buttons) { + btn->set_sensitive(true); + add(*btn); + btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::mode_changed), btn_idx++)); + } + } + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + { + /* Width */ + std::vector<Glib::ustring> labels = {_("(narrow spray)"), "", "", "", _("(default)"), "", "", "", "", _("(broad spray)")}; + std::vector<double> values = { 1, 3, 5, 10, 15, 20, 30, 50, 75, 100}; + auto width_val = prefs->getDouble("/tools/spray/width", 15); + _width_adj = Gtk::Adjustment::create(width_val, 1, 100, 1.0, 10.0); + auto width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-width", _("Width:"), _width_adj, 1, 0)); + width_item->set_tooltip_text(_("The width of the spray area (relative to the visible canvas area)")); + width_item->set_custom_numeric_menu_data(values, labels); + width_item->set_focus_widget(desktop->canvas); + _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::width_value_changed)); + add(*width_item); + width_item->set_sensitive(true); + } + + /* Use Pressure Width button */ + { + auto pressure_item = add_toggle_button(_("Pressure"), + _("Use the pressure of the input device to alter the width of spray area")); + pressure_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure")); + _usepressurewidth_pusher.reset(new UI::SimplePrefPusher(pressure_item, "/tools/spray/usepressurewidth")); + pressure_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + pressure_item, + "/tools/spray/usepressurewidth")); + } + + { /* Population */ + std::vector<Glib::ustring> labels = {_("(low population)"), "", "", "", _("(default)"), "", _("(high population)")}; + std::vector<double> values = { 5, 20, 35, 50, 70, 85, 100}; + auto population_val = prefs->getDouble("/tools/spray/population", 70); + _population_adj = Gtk::Adjustment::create(population_val, 1, 100, 1.0, 10.0); + _spray_population = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-population", _("Amount:"), _population_adj, 1, 0)); + _spray_population->set_tooltip_text(_("Adjusts the number of items sprayed per click")); + _spray_population->set_custom_numeric_menu_data(values, labels); + _spray_population->set_focus_widget(desktop->canvas); + _population_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::population_value_changed)); + add(*_spray_population); + _spray_population->set_sensitive(true); + } + + /* Use Pressure Population button */ + { + auto pressure_population_item = add_toggle_button(_("Pressure"), + _("Use the pressure of the input device to alter the amount of sprayed objects")); + pressure_population_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure")); + _usepressurepopulation_pusher.reset(new UI::SimplePrefPusher(pressure_population_item, "/tools/spray/usepressurepopulation")); + pressure_population_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + pressure_population_item, + "/tools/spray/usepressurepopulation")); + } + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + { /* Rotation */ + std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(high rotation variation)")}; + std::vector<double> values = { 0, 10, 25, 35, 50, 60, 80, 100}; + auto rotation_val = prefs->getDouble("/tools/spray/rotation_variation", 0); + _rotation_adj = Gtk::Adjustment::create(rotation_val, 0, 100, 1.0, 10.0); + _spray_rotation = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-rotation", _("Rotation:"), _rotation_adj, 1, 0)); + // xgettext:no-c-format + _spray_rotation->set_tooltip_text(_("Variation of the rotation of the sprayed objects; 0% for the same rotation than the original object")); + _spray_rotation->set_custom_numeric_menu_data(values, labels); + _spray_rotation->set_focus_widget(desktop->canvas); + _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::rotation_value_changed)); + add(*_spray_rotation); + _spray_rotation->set_sensitive(); + } + + { /* Scale */ + std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(high scale variation)")}; + std::vector<double> values = { 0, 10, 25, 35, 50, 60, 80, 100}; + auto scale_val = prefs->getDouble("/tools/spray/scale_variation", 0); + _scale_adj = Gtk::Adjustment::create(scale_val, 0, 100, 1.0, 10.0); + _spray_scale = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-scale", C_("Spray tool", "Scale:"), _scale_adj, 1, 0)); + // xgettext:no-c-format + _spray_scale->set_tooltip_text(_("Variation in the scale of the sprayed objects; 0% for the same scale than the original object")); + _spray_scale->set_custom_numeric_menu_data(values, labels); + _spray_scale->set_focus_widget(desktop->canvas); + _scale_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::scale_value_changed)); + add(*_spray_scale); + _spray_scale->set_sensitive(true); + } + + /* Use Pressure Scale button */ + { + _usepressurescale = add_toggle_button(_("Pressure"), + _("Use the pressure of the input device to alter the scale of new items")); + _usepressurescale->set_icon_name(INKSCAPE_ICON("draw-use-pressure")); + _usepressurescale->set_active(prefs->getBool("/tools/spray/usepressurescale", false)); + _usepressurescale->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_pressure_scale)); + } + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + { + /* Standard_deviation */ + std::vector<Glib::ustring> labels = {_("(minimum scatter)"), "", "", "", "", "", _("(default)"), _("(maximum scatter)")}; + std::vector<double> values = { 1, 5, 10, 20, 30, 50, 70, 100}; + auto sd_val = prefs->getDouble("/tools/spray/standard_deviation", 70); + _sd_adj = Gtk::Adjustment::create(sd_val, 1, 100, 1.0, 10.0); + auto sd_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-standard-deviation", C_("Spray tool", "Scatter:"), _sd_adj, 1, 0)); + sd_item->set_tooltip_text(_("Increase to scatter sprayed objects")); + sd_item->set_custom_numeric_menu_data(values, labels); + sd_item->set_focus_widget(desktop->canvas); + _sd_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::standard_deviation_value_changed)); + add(*sd_item); + sd_item->set_sensitive(true); + } + + { + /* Mean */ + std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(maximum mean)")}; + std::vector<double> values = { 0, 5, 10, 20, 30, 50, 70, 100}; + auto mean_val = prefs->getDouble("/tools/spray/mean", 0); + _mean_adj = Gtk::Adjustment::create(mean_val, 0, 100, 1.0, 10.0); + auto mean_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-mean", _("Focus:"), _mean_adj, 1, 0)); + mean_item->set_tooltip_text(_("0 to spray a spot; increase to enlarge the ring radius")); + mean_item->set_custom_numeric_menu_data(values, labels); + mean_item->set_focus_widget(desktop->canvas); + _mean_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::mean_value_changed)); + add(*mean_item); + mean_item->set_sensitive(true); + } + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + /* Over No Transparent */ + { + _over_no_transparent = add_toggle_button(_("Apply over no transparent areas"), + _("Apply over no transparent areas")); + _over_no_transparent->set_icon_name(INKSCAPE_ICON("object-visible")); + _over_no_transparent->set_active(prefs->getBool("/tools/spray/over_no_transparent", true)); + _over_no_transparent->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _over_no_transparent, + "/tools/spray/over_no_transparent")); + } + + /* Over Transparent */ + { + _over_transparent = add_toggle_button(_("Apply over transparent areas"), + _("Apply over transparent areas")); + _over_transparent->set_icon_name(INKSCAPE_ICON("object-hidden")); + _over_transparent->set_active(prefs->getBool("/tools/spray/over_transparent", true)); + _over_transparent->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _over_transparent, + "/tools/spray/over_transparent")); + } + + /* Pick No Overlap */ + { + _pick_no_overlap = add_toggle_button(_("No overlap between colors"), + _("No overlap between colors")); + _pick_no_overlap->set_icon_name(INKSCAPE_ICON("symbol-bigger")); + _pick_no_overlap->set_active(prefs->getBool("/tools/spray/pick_no_overlap", false)); + _pick_no_overlap->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _pick_no_overlap, + "/tools/spray/pick_no_overlap")); + } + + /* Overlap */ + { + _no_overlap = add_toggle_button(_("Prevent overlapping objects"), + _("Prevent overlapping objects")); + _no_overlap->set_icon_name(INKSCAPE_ICON("distribute-randomize")); + _no_overlap->set_active(prefs->getBool("/tools/spray/no_overlap", false)); + _no_overlap->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_no_overlap)); + } + + /* Offset */ + { + std::vector<Glib::ustring> labels = {_("(minimum offset)"), "", "", "", _("(default)"), "", "", _("(maximum offset)")}; + std::vector<double> values = { 0, 25, 50, 75, 100, 150, 200, 1000}; + auto offset_val = prefs->getDouble("/tools/spray/offset", 100); + _offset_adj = Gtk::Adjustment::create(offset_val, 0, 1000, 1, 4); + _offset = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-offset", _("Offset %:"), _offset_adj, 0, 0)); + _offset->set_tooltip_text(_("Increase to segregate objects more (value in percent)")); + _offset->set_custom_numeric_menu_data(values, labels); + _offset->set_focus_widget(desktop->canvas); + _offset_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::offset_value_changed)); + add(*_offset); + } + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + /* Picker */ + { + _picker = add_toggle_button(_("Pick color from the drawing. You can use clonetiler trace dialog for advanced effects. In clone mode original fill or stroke colors must be unset."), + _("Pick color from the drawing. You can use clonetiler trace dialog for advanced effects. In clone mode original fill or stroke colors must be unset.")); + _picker->set_icon_name(INKSCAPE_ICON("color-picker")); + _picker->set_active(prefs->getBool("/tools/spray/picker", false)); + _picker->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_picker)); + } + + /* Pick Fill */ + { + _pick_fill = add_toggle_button(_("Apply picked color to fill"), + _("Apply picked color to fill")); + _pick_fill->set_icon_name(INKSCAPE_ICON("paint-solid")); + _pick_fill->set_active(prefs->getBool("/tools/spray/pick_fill", false)); + _pick_fill->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _pick_fill, + "/tools/spray/pick_fill")); + } + + /* Pick Stroke */ + { + _pick_stroke = add_toggle_button(_("Apply picked color to stroke"), + _("Apply picked color to stroke")); + _pick_stroke->set_icon_name(INKSCAPE_ICON("no-marker")); + _pick_stroke->set_active(prefs->getBool("/tools/spray/pick_stroke", false)); + _pick_stroke->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _pick_stroke, + "/tools/spray/pick_stroke")); + } + + /* Inverse Value Size */ + { + _pick_inverse_value = add_toggle_button(_("Inverted pick value, retaining color in advanced trace mode"), + _("Inverted pick value, retaining color in advanced trace mode")); + _pick_inverse_value->set_icon_name(INKSCAPE_ICON("object-tweak-shrink")); + _pick_inverse_value->set_active(prefs->getBool("/tools/spray/pick_inverse_value", false)); + _pick_inverse_value->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _pick_inverse_value, + "/tools/spray/pick_inverse_value")); + } + + /* Pick from center */ + { + _pick_center = add_toggle_button(_("Pick from center instead of average area."), + _("Pick from center instead of average area.")); + _pick_center->set_icon_name(INKSCAPE_ICON("snap-bounding-box-center")); + _pick_center->set_active(prefs->getBool("/tools/spray/pick_center", true)); + _pick_center->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled), + _pick_center, + "/tools/spray/pick_center")); + } + + gint mode = prefs->getInt("/tools/spray/mode", 1); + _mode_buttons[mode]->set_active(); + show_all(); + init(); +} + +GtkWidget * +SprayToolbar::create(SPDesktop *desktop) +{ + auto toolbar = new SprayToolbar(desktop); + return GTK_WIDGET(toolbar->gobj()); +} + +void +SprayToolbar::width_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/width", + _width_adj->get_value()); +} + +void +SprayToolbar::mean_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/mean", + _mean_adj->get_value()); +} + +void +SprayToolbar::standard_deviation_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/standard_deviation", + _sd_adj->get_value()); +} + +void +SprayToolbar::mode_changed(int mode) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt("/tools/spray/mode", mode); + init(); +} + +void +SprayToolbar::init(){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int mode = prefs->getInt("/tools/spray/mode", 0); + + bool show = true; + if(mode == 3 || mode == 2){ + show = false; + } + _no_overlap->set_visible(show); + _over_no_transparent->set_visible(show); + _over_transparent->set_visible(show); + _pick_no_overlap->set_visible(show); + _pick_stroke->set_visible(show); + _pick_fill->set_visible(show); + _pick_inverse_value->set_visible(show); + _pick_center->set_visible(show); + _picker->set_visible(show); + _offset->set_visible(show); + _pick_fill->set_visible(show); + _pick_stroke->set_visible(show); + _pick_inverse_value->set_visible(show); + _pick_center->set_visible(show); + if(mode == 2){ + show = true; + } + _spray_rotation->set_visible(show); + update_widgets(); +} + +void +SprayToolbar::population_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/population", + _population_adj->get_value()); +} + +void +SprayToolbar::rotation_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/rotation_variation", + _rotation_adj->get_value()); +} + +void +SprayToolbar::update_widgets() +{ + _offset_adj->set_value(100.0); + + bool no_overlap_is_active = _no_overlap->get_active() && _no_overlap->get_visible(); + _offset->set_visible(no_overlap_is_active); + if (_usepressurescale->get_active()) { + _scale_adj->set_value(0.0); + _spray_scale->set_sensitive(false); + } else { + _spray_scale->set_sensitive(true); + } + + bool picker_is_active = _picker->get_active() && _picker->get_visible(); + _pick_fill->set_visible(picker_is_active); + _pick_stroke->set_visible(picker_is_active); + _pick_inverse_value->set_visible(picker_is_active); + _pick_center->set_visible(picker_is_active); +} + +void +SprayToolbar::toggle_no_overlap() +{ + auto prefs = Inkscape::Preferences::get(); + bool active = _no_overlap->get_active(); + prefs->setBool("/tools/spray/no_overlap", active); + update_widgets(); +} + +void +SprayToolbar::scale_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/scale_variation", + _scale_adj->get_value()); +} + +void +SprayToolbar::offset_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/offset", + _offset_adj->get_value()); +} + +void +SprayToolbar::toggle_pressure_scale() +{ + auto prefs = Inkscape::Preferences::get(); + bool active = _usepressurescale->get_active(); + prefs->setBool("/tools/spray/usepressurescale", active); + if(active){ + prefs->setDouble("/tools/spray/scale_variation", 0); + } + update_widgets(); +} + +void +SprayToolbar::toggle_picker() +{ + auto prefs = Inkscape::Preferences::get(); + bool active = _picker->get_active(); + prefs->setBool("/tools/spray/picker", active); + if(active){ + prefs->setBool("/dialogs/clonetiler/dotrace", false); + SPDesktop *dt = _desktop; + if (Inkscape::UI::Dialog::CloneTiler *ct = get_clone_tiler_panel(dt)){ + dt->getContainer()->new_dialog("CloneTiler"); + ct->show_page_trace(); + } + } + update_widgets(); +} + +void +SprayToolbar::on_pref_toggled(Gtk::ToggleToolButton *btn, + const Glib::ustring& path) +{ + auto prefs = Inkscape::Preferences::get(); + bool active = btn->get_active(); + prefs->setBool(path, active); +} + +void +SprayToolbar::set_mode(int mode) +{ + _mode_buttons[mode]->set_active(); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |