Merge lp:~gero-bare/scratch/add-linter-support into lp:~elementary-apps/scratch/scratch

Proposed by Gero.Bare
Status: Needs review
Proposed branch: lp:~gero-bare/scratch/add-linter-support
Merge into: lp:~elementary-apps/scratch/scratch
Diff against target: 1764 lines (+1684/-1)
11 files modified
plugins/CMakeLists.txt (+1/-0)
plugins/linter-manager/CMakeLists.txt (+35/-0)
plugins/linter-manager/ConfigurationDialog.vala (+330/-0)
plugins/linter-manager/LinterManagerPlugin.vala (+481/-0)
plugins/linter-manager/Settings.vala (+32/-0)
plugins/linter-manager/icons/check_file.svg (+775/-0)
plugins/linter-manager/icons/linter-manager.gresource.xml (+6/-0)
plugins/linter-manager/linter-manager.plugin (+9/-0)
schemas/CMakeLists.txt (+1/-0)
schemas/org.pantheon.scratch.plugins.linter-manager.gschema.xml (+10/-0)
src/Scratch.vala (+4/-1)
To merge this branch: bzr merge lp:~gero-bare/scratch/add-linter-support
Reviewer Review Type Date Requested Status
Jeremy Wootten ui/ux Needs Fixing
Zisu Andrei (community) Needs Fixing
Review via email: mp+273938@code.launchpad.net

Description of the change

This add linter support, in a generic form.

You need a cli program to do processing and a regex to handle virtually any linter.

The command is simply any command you like plus
The regex uses "named" sub patterns: "line", "column", "type" of warning and the "message".

At the moment this linter manager support out of the box:
*python3 pep8
*npm jshint
*npm csslint
*html tidy

Additional features:
It has an icon. Yay.
Online help documentation (in github) https://github.com/gerito1/misc-docs/blob/master/scratch-plugin-linter-manager/help.md
Idk really...
deleted the "%u" none-sense

Missing features.
* No shortcut.
* No color/error markings in sourceview.

Things to check.

* Accepts files with spaces in the path.
* Doesn't crash messing with the configuration /deleting, resetting profiles.
* The documentation is good enough. (my engrish is not that good)
* Doesn't mess the bottom notebook with the terminal
* Click in the error resume brings up the correct file.
* Closing files close the resume
* Any gui design input ?
* Changing the Syntax highlight brings up the correct linter.

I think is "robust" enough by now.

To post a comment you must log in.
Revision history for this message
Zisu Andrei (matzipan) wrote :

My comments on the code are inlined.

review: Needs Fixing
Revision history for this message
Zisu Andrei (matzipan) wrote :

Ok so I installed it. First of all, I think it should use another icon. Secondly, if I don't have any tabs open, if I click on the icon, I get:

[DEBUG 22:11:33.500717] LinterManagerPlugin.vala:202: Linter Hook on notebook bottom
[FATAL 22:11:35.684459] scratch_services_document_get_is_file_temporary: assertion 'self != NULL' failed
Segmentation fault (core dumped)

Also the configuration window could use a bit of visual improvement. Would it be possible to add it to Scratch's preferences dialog?

Also, maybe there should be an option to run lint on save?

review: Needs Fixing
Revision history for this message
Zisu Andrei (matzipan) wrote :

Lots and lots of comments inline. It's not bad at all, just needs some grooming.

I haven't tested the functionality yet.

review: Needs Fixing
1567. By Gero.Bare

* Several code improvements due previus review.

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

On trying out the html linter I get two error messages, both for Line 0, Column 0:
1) Regex failure
2) Failed to execute child process "tidy" ....

until I installed the application "tidy". It would be better if an informative message or dialog was used to inform the user how to fix the problem.

Some work needs doing on the appearance of the configuration dialog in my opinion - needs UX team input.

review: Needs Fixing (ui/ux)

Unmerged revisions

1567. By Gero.Bare

* Several code improvements due previus review.

1566. By Gero.Bare

Added online documentation
(for now linked to my github repo, later it should be maintained in the elementary side)

1565. By Gero.Bare

* merge with the current branch to fix some conflicts

1564. By Gero.Bare

* Modified the command input, it no longer uses the %u wildcard
* Modified the regex format.
* Added new tooltips
* Fixed some minor bugs.

1563. By Gero.Bare

Re-designed the linter output view in the editor.

1562. By Gero.Bare

Re done the design of the configuration dialog.
Addes some tooltips.

1561. By Gero.Bare

Some minor changes.

1560. By Gero.Bare

* Added Icon for the plugin.
* Added the ability to copy linter messages to the clipboard.
* Fixed a crash.

1559. By Gero.Bare

Add support for html tidy.
Now concatenating stderr and stdout just in case (i.e. html tidy).

1558. By Gero.Bare

Minor changes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/CMakeLists.txt'
2--- plugins/CMakeLists.txt 2016-09-03 10:30:53 +0000
3+++ plugins/CMakeLists.txt 2016-10-27 12:57:14 +0000
4@@ -23,3 +23,4 @@
5 add_subdirectory (clipboard-history)
6 add_subdirectory (detect-indent)
7 add_subdirectory (preserve-indent)
8+add_subdirectory (linter-manager)
9
10=== added directory 'plugins/linter-manager'
11=== added file 'plugins/linter-manager/CMakeLists.txt'
12--- plugins/linter-manager/CMakeLists.txt 1970-01-01 00:00:00 +0000
13+++ plugins/linter-manager/CMakeLists.txt 2016-10-27 12:57:14 +0000
14@@ -0,0 +1,35 @@
15+add_definitions(${NORMAL_CFLAGS})
16+include_directories(${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/scratchcore/)
17+link_directories(${NORMAL_LINK_DIRS})
18+
19+set (PLUGIN_NAME "linter-manager")
20+
21+
22+vala_precompile(VALA_C ${PLUGIN_NAME}
23+ LinterManagerPlugin.vala
24+ ConfigurationDialog.vala
25+ Settings.vala
26+PACKAGES
27+ granite
28+ gtksourceview-3.0
29+ scratchcore
30+ gee-0.8
31+ libpeas-1.0
32+OPTIONS
33+ --thread
34+ --vapidir=${CMAKE_BINARY_DIR}
35+ --vapidir=${CMAKE_BINARY_DIR}/src/
36+ --vapidir=${CMAKE_SOURCE_DIR}/vapi/
37+ --vapidir=${CMAKE_BINARY_DIR}/scratchcore/
38+)
39+
40+include (GResource)
41+glib_compile_resources (GLIB_RESOURCES_LINTER_MANAGER SOURCE icons/linter-manager.gresource.xml)
42+
43+add_library(${PLUGIN_NAME} MODULE ${VALA_C} ${GLIB_RESOURCES_LINTER_MANAGER})
44+add_dependencies(${PLUGIN_NAME} ${LIBNAME} scratch)
45+target_link_libraries(${PLUGIN_NAME} ${NORMAL_LIBRARIES})
46+
47+install(TARGETS ${PLUGIN_NAME} DESTINATION ${PLUGINDIR}/${PLUGIN_NAME})
48+install(FILES ${PLUGIN_NAME}.plugin DESTINATION ${PLUGINDIR}/${PLUGIN_NAME})
49+message("-- Linter-manager plugin will be compiled")
50
51=== added file 'plugins/linter-manager/ConfigurationDialog.vala'
52--- plugins/linter-manager/ConfigurationDialog.vala 1970-01-01 00:00:00 +0000
53+++ plugins/linter-manager/ConfigurationDialog.vala 2016-10-27 12:57:14 +0000
54@@ -0,0 +1,330 @@
55+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
56+/***
57+ BEGIN LICENSE
58+
59+ Copyright (C) 2016 Geronimo Bareiro <gero[dot]bare[at]gmail[dot]com>
60+ This program is free software: you can redistribute it and/or modify it
61+ under the terms of the GNU Lesser General Public License version 3, as published
62+ by the Free Software Foundation.
63+
64+ This program is distributed in the hope that it will be useful, but
65+ WITHOUT ANY WARRANTY; without even the implied warranties of
66+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
67+ PURPOSE. See the GNU General Public License for more details.
68+
69+ You should have received a copy of the GNU General Public License along
70+ with this program. If not, see <http://www.gnu.org/licenses/>
71+
72+ END LICENSE
73+***/
74+
75+namespace Scratch.Plugins.LinterManager {
76+
77+ public class ListPopover : Gtk.Popover {
78+ private Gtk.TreeView tree;
79+ private unowned Gtk.Button button;
80+
81+ public ListPopover (Gtk.Button button) {
82+
83+ this.button = button;
84+ set_relative_to (button);
85+
86+ tree = new Gtk.TreeView ();
87+ tree.headers_visible = false;
88+
89+ var store = new Gtk.ListStore (1, typeof (string));
90+ var cell = new Gtk.CellRendererText ();
91+
92+ tree.insert_column_with_attributes (0, null, cell, "text", 0);
93+ tree.set_model (store);
94+ tree.hexpand = true;
95+ tree.get_selection ().mode = Gtk.SelectionMode.SINGLE;
96+ tree.unselect_all ();
97+
98+ Gtk.TreeIter iter;
99+ var manager = Gtk.SourceLanguageManager.get_default ();
100+ foreach (var ids in manager.get_language_ids ()) {
101+ store.append (out iter);
102+ store.set (iter, 0, ids);
103+ }
104+
105+ var scrolled_window = new Gtk.ScrolledWindow (null, null);
106+ scrolled_window.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
107+ scrolled_window.add (tree);
108+ scrolled_window.set_min_content_height (150);
109+ scrolled_window.hexpand = true;
110+
111+ tree.row_activated.connect ((path, column) => {
112+ Gtk.TreeIter iter_val;
113+ store.get_iter (out iter_val, path);
114+ Value val;
115+ store.get_value (iter_val, 0, out val);
116+ button.label = (string) val;
117+ hide ();
118+ });
119+
120+ add (scrolled_window);
121+ show_all ();
122+ }
123+ }
124+
125+ public class LinterRow : Gtk.ListBoxRow {
126+ private Gtk.Button lang_button;
127+ private Gtk.Entry cmd_entry;
128+ private Gtk.Entry regex_entry;
129+ private Gtk.Grid main_grid;
130+ private Gtk.Grid left_grid;
131+ private Gtk.Grid right_grid;
132+
133+ public string language {
134+ get { return lang_button.label; }
135+ set { lang_button.label = value; }
136+ }
137+
138+ public string command {
139+ get { return cmd_entry.text; }
140+ set { cmd_entry.text = value; }
141+ }
142+
143+ public string regex {
144+ get { return regex_entry.text; }
145+ set { regex_entry.text = value; }
146+ }
147+
148+ public LinterRow (string lan, string com, string re) {
149+
150+ var delete_button = new Gtk.Button.from_icon_name ("edit-delete");
151+ delete_button.relief = Gtk.ReliefStyle.NONE;
152+ delete_button.tooltip_text = _("Delete this linter profile");
153+
154+ right_grid = new Gtk.Grid ();
155+ right_grid.orientation = Gtk.Orientation.VERTICAL;
156+ right_grid.add (delete_button);
157+
158+ var lang_label = new Gtk.Label ("<b>%s</b>".printf(_("Language:")));
159+ lang_label.set_use_markup (true);
160+ lang_label.get_style_context ().add_class ("h4");
161+ lang_button = new Gtk.Button.with_label (lan);
162+ lang_button.tooltip_text = _("Click to select the language");
163+ lang_button.relief = Gtk.ReliefStyle.NONE;
164+
165+ var cmd_label = new Gtk.Label ("<b>%s</b>".printf(_("Command:")));
166+ cmd_label.set_use_markup (true);
167+ cmd_entry = new Gtk.Entry ();
168+ cmd_entry.text = com;
169+ cmd_entry.width_request = 480;
170+ cmd_entry.tooltip_text = _("This is the command to call the cli executable used to analize the file");
171+
172+ var regex_label = new Gtk.Label ("<b>%s</b>".printf(_("Regex:")));
173+ regex_label.set_use_markup (true);
174+ regex_entry = new Gtk.Entry ();
175+ regex_entry.text = re;
176+ regex_entry.width_request = 480;
177+ regex_entry.tooltip_text = _("This is the regex used to evaluate the command output");
178+
179+ left_grid = new Gtk.Grid ();
180+ left_grid.width_request = 580;
181+ left_grid.attach (lang_label, 0, 0, 1, 1);
182+ left_grid.attach (lang_button, 1, 0, 3, 1);
183+ left_grid.attach (cmd_label, 0, 1, 1, 1);
184+ left_grid.attach (cmd_entry, 1, 1, 3, 1);
185+
186+ left_grid.attach (regex_label, 0, 2, 1, 1);
187+ left_grid.attach (regex_entry, 1, 2, 3, 1);
188+
189+ main_grid = new Gtk.Grid ();
190+ main_grid.orientation = Gtk.Orientation.HORIZONTAL;
191+ main_grid.add (left_grid);
192+ main_grid.add (right_grid);
193+ main_grid.show_all ();
194+ add (main_grid);
195+
196+ lang_button.clicked.connect (()=> {
197+ var popover = new ListPopover (lang_button);
198+ popover.show ();
199+ });
200+
201+ delete_button.clicked.connect (() => {
202+ parent.remove (this); // Remove itself from the list
203+ });
204+ }
205+
206+ }
207+
208+ public class LinterConfigurationDialog : Gtk.Dialog {
209+
210+ unowned LinterManagerPlugin linter_manager;
211+ unowned GLib.HashTable<string, Linter> linter_list;
212+ Gtk.Button button_add;
213+ Gtk.Button button_reset;
214+ Gtk.ListBox linter_box = null;
215+
216+ public LinterConfigurationDialog (LinterManagerPlugin linter_manager_plugin) {
217+ linter_manager = linter_manager_plugin;
218+ linter_list = linter_manager.linter_list;
219+
220+ title ="Configuration Dialog";
221+ set_default_size (600, 400);
222+
223+ add_button (_("Apply"), Gtk.ResponseType.ACCEPT);
224+ add_button (_("Close"), Gtk.ResponseType.CLOSE);
225+ add_button (_("Help"), Gtk.ResponseType.HELP);
226+
227+ create_widgets();
228+ populate_linterbox ();
229+ connect_signals();
230+ }
231+
232+ void create_widgets () {
233+ if (linter_box != null) {
234+ return;
235+ }
236+
237+ linter_box = new Gtk.ListBox ();
238+ linter_box. selection_mode = Gtk.SelectionMode.NONE;
239+ linter_box.vexpand = true;
240+ linter_box.hexpand = true;
241+
242+ var scrolled_window = new Gtk.ScrolledWindow (null, null);
243+ scrolled_window.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
244+ scrolled_window.add (linter_box);
245+ scrolled_window.set_min_content_height (150);
246+
247+ var botom_grid = new Gtk.Grid ();
248+ button_add = new Gtk.Button.with_label (_("Add"));
249+ button_add.hexpand = false;
250+ button_add.tooltip_text = _("Add a new Linter to the list");
251+
252+ button_reset = new Gtk.Button.with_label (_("Reset Defaults"));
253+ button_reset.hexpand = false;
254+ button_reset.tooltip_text = _("Reset all Linter Manager options to the Default Configuration");
255+
256+ botom_grid.attach (button_add, 0, 0, 1, 1);
257+ botom_grid.attach (button_reset, 2, 0, 1, 1);
258+
259+ var label = new Gtk.Label (_("Profiles"));
260+ var main_grid = new Gtk.Grid ();
261+ main_grid.orientation = Gtk.Orientation.VERTICAL;
262+ main_grid.add (label);
263+ main_grid.add (scrolled_window);
264+ main_grid.add (botom_grid);
265+
266+ Gtk.Box content = get_content_area () as Gtk.Box;
267+ content.pack_start (main_grid, false, true, 0);
268+ main_grid.show_all ();
269+ }
270+
271+ private void populate_linterbox () {
272+ foreach (var language in linter_list.get_keys()) {
273+ var linter_row = new LinterRow (language,
274+ linter_list[language].command, linter_list[language].regex);
275+ linter_box.add (linter_row);
276+ }
277+
278+ linter_box.show_all ();
279+ }
280+
281+ void connect_signals () {
282+ response.connect (on_response);
283+ button_add.clicked.connect (on_add);
284+ button_reset.clicked.connect (on_reset_linter_conf);
285+
286+ }
287+
288+ private void on_add () {
289+ var linter_row = new LinterRow ("", "", "");
290+ linter_box.add (linter_row);
291+ linter_box.show_all ();
292+ debug ("Added new LinterRow");
293+ }
294+
295+ private void on_reset_linter_conf () {
296+ linter_manager.default_conf ();
297+ linter_manager.save_conf ();
298+
299+ linter_box.@foreach ((widget) => {
300+ linter_box.remove (widget);
301+ });
302+
303+ populate_linterbox ();
304+ }
305+
306+ private bool configuration_is_valid () {
307+ //Check we have a sane configuration
308+ var lang_table = new GenericSet<string> (str_hash, str_equal);
309+
310+ foreach (var child in linter_box.get_children ()) {
311+ var child_linter_row = (LinterRow) child;
312+ if (child_linter_row.language.length == 0) {
313+ var msg = new Gtk.MessageDialog (null,
314+ Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING,
315+ Gtk.ButtonsType.OK, _("One or more entries contain the language's field empty.\nPlease select a proper Language.").printf (child_linter_row.language));
316+ msg.response.connect ((response_id) => {
317+ msg.destroy ();
318+ });
319+ msg.run ();
320+ return false;
321+
322+ } else if (lang_table.contains (child_linter_row.language)) {
323+ var msg = new Gtk.MessageDialog (null,
324+ Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING,
325+ Gtk.ButtonsType.OK, _("The %s lanugage has multiple definitions.\nPlease make sure there is only one entry for each language.").printf (child_linter_row.language));
326+ msg.response.connect ((response_id) => {
327+ msg.destroy ();
328+ });
329+ msg.run ();
330+ return false;
331+ } else if (child_linter_row.command.length == 0 || child_linter_row.regex.length == 0) {
332+ var msg = new Gtk.MessageDialog (null,
333+ Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING,
334+ Gtk.ButtonsType.OK, _("The %s language has empty fields.\nPlease check your configuration.").printf (child_linter_row.language));
335+ msg.response.connect ((response_id) => {
336+ msg.destroy ();
337+ });
338+ msg.run ();
339+ return false;
340+ }
341+
342+ lang_table.add (child_linter_row.language.dup ());
343+ }
344+
345+ return true;
346+ }
347+
348+ private void update_configuration () {
349+
350+ if (!configuration_is_valid ()) {
351+ return;
352+ }
353+
354+ // If we are clear, we commit
355+ linter_list.remove_all ();
356+ foreach (var child in linter_box.get_children ()) {
357+ var child_linter_row = (LinterRow) child;
358+ linter_list[child_linter_row.language.dup ()] = new Linter (child_linter_row.command, child_linter_row.regex);
359+ }
360+
361+ linter_manager.save_conf ();
362+ }
363+
364+ private void on_response (Gtk.Dialog source, int response_id) {
365+ switch (response_id) {
366+ case Gtk.ResponseType.ACCEPT:
367+ debug ("Update Configuration");
368+ update_configuration ();
369+ break;
370+ case Gtk.ResponseType.CLOSE:
371+ destroy ();
372+ break;
373+ case Gtk.ResponseType.HELP:
374+ try {
375+ Gtk.show_uri (null, ONLINE_DOCUMENTATION_URL, Gtk.get_current_event_time());
376+ } catch (Error e) {
377+ warning (e.message);
378+ }
379+
380+ break;
381+ }
382+ }
383+ }
384+}
385
386=== added file 'plugins/linter-manager/LinterManagerPlugin.vala'
387--- plugins/linter-manager/LinterManagerPlugin.vala 1970-01-01 00:00:00 +0000
388+++ plugins/linter-manager/LinterManagerPlugin.vala 2016-10-27 12:57:14 +0000
389@@ -0,0 +1,481 @@
390+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
391+/***
392+ BEGIN LICENSE
393+
394+ Copyright (C) 2016 Geronimo Bareiro <gero[dot]bare[at]gmail[dot]com>
395+ This program is free software: you can redistribute it and/or modify it
396+ under the terms of the GNU Lesser General Public License version 3, as published
397+ by the Free Software Foundation.
398+
399+ This program is distributed in the hope that it will be useful, but
400+ WITHOUT ANY WARRANTY; without even the implied warranties of
401+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
402+ PURPOSE. See the GNU General Public License for more details.
403+
404+ You should have received a copy of the GNU General Public License along
405+ with this program. If not, see <http://www.gnu.org/licenses/>
406+
407+ END LICENSE
408+***/
409+
410+public const string NAME = _("Linter Manager");
411+public const string DESCRIPTION = _("Linter support for scratch");
412+
413+namespace Scratch.Plugins.LinterManager {
414+
415+ public class Linter : Object {
416+ public string command { get; construct; }
417+ public string regex { get; construct; }
418+
419+ public Linter (string com, string reg) {
420+ Object (command: com, regex: reg);
421+ }
422+
423+ construct {}
424+ }
425+
426+ enum RESULTS {
427+ LINE,
428+ COLUMN,
429+ MESSAGE
430+ }
431+
432+ public const string ONLINE_DOCUMENTATION_URL = "https://github.com/gerito1/misc-docs/blob/master/scratch-plugin-linter-manager/help.md";
433+ public const string LINTER_RESOURCE_PATH = "/org/pantheon/scratch/plugin/linter-manager/";
434+ private string text_to_copy;
435+
436+ public class LinterManagerPlugin : Peas.ExtensionBase, Peas.Activatable {
437+
438+ public Object object { owned get; construct; }
439+ private Scratch.Services.Interface plugins { owned get { return (Scratch.Services.Interface) object; } }
440+
441+ Settings settings;
442+
443+ MainWindow? window = null;
444+ Gtk.Notebook? bottombar = null;
445+ Gtk.Grid? container = null;
446+ Gtk.TreeView tree_view;
447+ Gtk.ListStore list_store;
448+ Scratch.Widgets.Toolbar? toolbar = null;
449+ Gtk.MenuToolButton? menu_tool_button = null;
450+ Gtk.Menu? context_menu = null;
451+
452+ Scratch.Services.Document document = null;
453+ Scratch.Services.Document current_document = null;
454+
455+ public GLib.HashTable<string, Linter> linter_list;
456+
457+ LinterConfigurationDialog dialog;
458+ GLib.KeyFile keyfile;
459+ const string CONF_FILENAME = "linter.conf";
460+ string file;
461+
462+ public void update_state () {
463+ }
464+
465+ public void activate () {
466+ create_widgets ();
467+
468+ settings = new Settings ();
469+
470+ plugins.hook_document.connect (on_hook_document);
471+ plugins.hook_notebook_bottom.connect (on_hook_notebook_bottom);
472+ plugins.hook_toolbar.connect (on_hook_on_toolbar);
473+
474+ plugins.hook_window.connect ((w) => {
475+ if (window != null) {
476+ return;
477+ }
478+
479+ window = w;
480+ });
481+
482+ linter_list = new GLib.HashTable<string,Linter> (GLib.str_hash, GLib.str_equal);
483+ load_conf();
484+ }
485+
486+ public void deactivate () {
487+ remove_container ();
488+ toolbar.remove (menu_tool_button);
489+
490+ if (menu_tool_button != null) {
491+ menu_tool_button.destroy ();
492+ }
493+
494+ if (container != null) {
495+ container.destroy ();
496+ }
497+
498+ }
499+
500+ void create_widgets () {
501+ var icon = new Gtk.Image.from_resource ("%scheck_file.svg".printf (LINTER_RESOURCE_PATH));
502+ var run_button = new Gtk.ToolButton (icon, null);
503+ run_button.clicked.connect (run_linter);
504+ run_button.tooltip_text = _("Run Linter");
505+
506+ var conf_icon = new Gtk.Image.from_icon_name ("applications-system", Gtk.IconSize.SMALL_TOOLBAR);
507+ var configure_button = new Gtk.ToolButton (conf_icon, null);
508+ configure_button.clicked.connect (on_configure_clicked);
509+ configure_button.tooltip_text = _("Open Linter Manager Configuration");
510+
511+ var close_icon = new Gtk.Image.from_icon_name ("window-close", Gtk.IconSize.SMALL_TOOLBAR);
512+ var close_button = new Gtk.ToolButton (close_icon, null);
513+ close_button.clicked.connect (on_hide_clicked);
514+ close_button.tooltip_text = _("Hide Linter output view");
515+
516+ var toolbar = new Gtk.Toolbar ();
517+ toolbar.orientation = Gtk.Orientation.VERTICAL;
518+ toolbar.add (run_button);
519+ toolbar.add (configure_button);
520+ toolbar.add (close_button);
521+
522+ list_store = new Gtk.ListStore (3, typeof (int), typeof (int), typeof (string));
523+ tree_view = new Gtk.TreeView.with_model (list_store);
524+
525+ var cell = new Gtk.CellRendererText ();
526+ tree_view.insert_column_with_attributes (-1, _("Line"), cell, "text", RESULTS.LINE);
527+ tree_view.insert_column_with_attributes (-1, _("Column"), cell, "text", RESULTS.COLUMN);
528+ tree_view.insert_column_with_attributes (-1, _("Message"), cell, "text", RESULTS.MESSAGE);
529+
530+ var scrolled_window = new Gtk.ScrolledWindow (null, null);
531+ scrolled_window.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
532+ scrolled_window.add (tree_view);
533+ scrolled_window.vexpand = true;
534+ scrolled_window.hexpand = true;
535+
536+ container = new Gtk.Grid ();
537+ container.attach (toolbar, 0, 0, 1, 1);
538+ container.attach (scrolled_window, 1, 0, 1, 1);
539+
540+ var item_copy = new Gtk.MenuItem.with_label (_("Copy to Clipboard"));
541+
542+ context_menu = new Gtk.Menu ();
543+ context_menu.append (item_copy);
544+
545+ item_copy.activate.connect (() => {
546+ var display = window.get_display ();
547+ var clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
548+ clipboard.set_text (text_to_copy, text_to_copy.length);
549+ });
550+
551+ tree_view.button_press_event.connect ((event) => {
552+ if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) {
553+ var path = new Gtk.TreePath ();
554+ int x, y;
555+ tree_view.get_path_at_pos ((int) event.x, (int) event.y, out path, null, out x, out y);
556+
557+ if (path == null) {
558+ return false;
559+ }
560+
561+ Gtk.TreeIter iter;
562+ list_store.get_iter_from_string (out iter, path.to_string ());
563+ list_store.get (iter, RESULTS.MESSAGE, out text_to_copy);
564+
565+ //We manually select the row to give feedback to te user
566+ tree_view.get_selection ().select_path (path);
567+ context_menu.show_all ();
568+ context_menu.popup (null, null, null, event.button, event.time);
569+ return true;
570+ }
571+ return false;
572+ });
573+ }
574+
575+ public void save_conf () {
576+ keyfile = new GLib.KeyFile ();
577+
578+ foreach (string language in linter_list.get_keys ()) {
579+ keyfile.set_string (language, "command", linter_list[language].command);
580+ keyfile.set_string (language, "regex", linter_list[language].regex);
581+ }
582+
583+ try {
584+ debug ("Saving linter configuration");
585+ GLib.FileUtils.set_contents (file, keyfile.to_data ());
586+ } catch (FileError e) {
587+ warning ("FileError: %s", e.message);
588+ }
589+ }
590+
591+ public void default_conf () {
592+ message ("Setting default configuration");
593+ linter_list.remove_all ();
594+
595+ linter_list["js"] = new Linter ("jshint",
596+ ".+:\\s*(line (?<line>\\d+), col (?<col>\\d+), )(?<message>.*)");
597+ linter_list["css"] = new Linter ("csslint --format=compact",
598+ ".+:\\s*(line (?<line>\\d+), col (?<col>\\d+), )?(?:(?<err>Error)|(?<war>Warning)) - (?<message>.*)");
599+ linter_list["python"] = new Linter ("pep8",
600+ ".+:(?<line>\\d+):(?<col>\\d+): (?:(?<err>E)|(?<war>W))\\d+ (?<message>.+)");
601+ linter_list["html"] = new Linter ("tidy -errors -quiet -utf8",
602+ "line (?<line>\\d+) column (?<col>\\d+) - (?:(?<err>Error)|(?<war>Warning)|(?<inf>Info)): (?<message>.+)");
603+ }
604+
605+ public void load_conf () {
606+ keyfile = new GLib.KeyFile ();
607+ debug ("Loading linter configuration");
608+ file = Path.build_filename (Scratch.ScratchApp.instance.data_home_folder, CONF_FILENAME);
609+ try {
610+ keyfile.load_from_file (file, KeyFileFlags.NONE);
611+ var languages = keyfile.get_groups ();
612+
613+ //Recovering Groups and configuration keys
614+ if (languages.length > 0) {
615+ foreach (var language in languages ) {
616+ debug ("Loading language %s", language);
617+ var command = keyfile.get_string (language, "command");
618+ var regex = keyfile.get_string (language, "regex");
619+ linter_list[language] = new Linter (command, regex);
620+ }
621+ } else { //In case of empty file.
622+ default_conf ();
623+ save_conf ();
624+ }
625+ } catch (Error e) {
626+ warning ("Failed to load config: %s", e.message);
627+ default_conf ();
628+ save_conf ();
629+ }
630+ }
631+
632+ void on_hook_document (Scratch.Services.Document doc) {
633+ document = doc;
634+ debug ("Linter Hook on document");
635+ }
636+
637+ void on_hook_notebook_bottom (Gtk.Notebook notebook) {
638+ debug ("Linter Hook on notebook bottom");
639+
640+ if (bottombar == null) {
641+ bottombar = notebook;
642+ }
643+
644+ }
645+
646+ void on_hook_on_toolbar (Scratch.Widgets.Toolbar scratch_toolbar) {
647+ if (toolbar != null) {
648+ return;
649+ }
650+
651+ toolbar = scratch_toolbar;
652+
653+ var icon = new Gtk.Image.from_resource ("%scheck_file.svg".printf(LINTER_RESOURCE_PATH));
654+ icon.icon_size = Gtk.IconSize.LARGE_TOOLBAR;
655+
656+ menu_tool_button = new Gtk.MenuToolButton (icon, null);
657+ menu_tool_button.tooltip_text = _("Run Linter");
658+
659+ var item_configure = new Gtk.MenuItem.with_label (_("Configure"));
660+ item_configure.tooltip_text = _("Open Linter Manager Configuration");
661+ item_configure.activate.connect (on_configure_clicked);
662+
663+ var item_hide = new Gtk.MenuItem.with_label (_("Hide Linter"));
664+ item_hide.tooltip_text = _("Hide Linter output view");
665+ item_hide.activate.connect (on_hide_clicked);
666+
667+ var menu = new Gtk.Menu ();
668+ menu.append (item_configure);
669+ menu.append (item_hide);
670+ menu.show_all ();
671+ menu_tool_button.set_menu (menu);
672+ menu_tool_button.show_all ();
673+
674+ toolbar.pack_end (menu_tool_button);
675+
676+ menu_tool_button.clicked.connect (()=> {
677+ add_container ();
678+ run_linter ();
679+ });
680+ }
681+
682+ void add_container () {
683+ if (bottombar.page_num (container) == -1) {
684+ var new_page = bottombar.append_page (container, new Gtk.Label (_("Linter")));
685+ bottombar.set_current_page (new_page);
686+ container.show_all ();
687+ }
688+ }
689+
690+ void remove_container () {
691+ if (bottombar.page_num (container) != -1) {
692+ bottombar.remove (container);
693+ }
694+
695+ if (window.get_current_document () != null) {
696+ window.get_current_document ().focus ();
697+ }
698+
699+ tree_view.row_activated.disconnect (go_to_line);
700+
701+ if (current_document != null) {
702+ current_document.doc_closed.disconnect (doc_closed);
703+ }
704+
705+ }
706+
707+ void go_to_line (Gtk.TreePath path, Gtk.TreeViewColumn column) {
708+ Gtk.TreeIter iter_val;
709+ list_store.get_iter (out iter_val, path);
710+ Value val;
711+ list_store.get_value (iter_val, RESULTS.LINE, out val);
712+ int line = (int) val;
713+
714+ if (line <= 0) { //The line count should start at 1
715+ debug ("Trying to go a line <= 0 : %d", line);
716+ return;
717+ }
718+
719+ plugins.open_file (current_document.file);
720+ current_document.source_view.go_to_line (line);
721+ }
722+
723+ void run_linter () {
724+ if (document == null) {
725+ return;
726+ }
727+
728+ current_document = document;
729+
730+ //No Tempfiles I want a pathfile and a type extension
731+ if (current_document.is_file_temporary) {
732+ var msg = new Gtk.MessageDialog (null, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, _("You must save the document first."));
733+ msg.response.connect ((response_id) => {
734+ msg.destroy ();
735+ });
736+ msg.run ();
737+ return;
738+ }
739+
740+ var language = current_document.source_view.buffer.language;
741+
742+ current_document.doc_closed.connect (doc_closed);
743+
744+ //First we make sure we reset behavior
745+ list_store.clear ();
746+ tree_view.row_activated.disconnect (go_to_line);
747+
748+ if (language != null && linter_list[language.id] != null) {
749+ current_document.save.begin ();
750+
751+ debug ("Language selected: %s", language.id);
752+ run_regex.begin (language);
753+ } else {
754+ Gtk.TreeIter iter;
755+ list_store.append (out iter);
756+ list_store.set (iter, RESULTS.LINE, 0, RESULTS.COLUMN, 0, RESULTS.MESSAGE, _("There's no valid linter for the file."));
757+ }
758+ }
759+
760+ async void run_regex (Gtk.SourceLanguage language) {
761+ Idle.add(()=> {
762+ try {
763+ var re = new Regex(linter_list[language.id].regex);
764+ string stdout;
765+ string stderr;
766+ int retcode;
767+ string command = "%s \"%s\"".printf (linter_list[language.id].command, current_document.file.get_path ());
768+ GLib.Process.spawn_command_line_sync (command, out stdout, out stderr, out retcode);
769+
770+ MatchInfo match;
771+
772+ //We are concatenating stderr and stdout to analyze, some linters use stderr
773+ //instead of stdout. For example html-tidy. Analyze both is a safe assumption.
774+ //The spaces are necesary because there is a bug in the regex library
775+ //check https://bugzilla.gnome.org/show_bug.cgi?id=773285
776+ //For most cases of the spaces won't affect the program.
777+ var input = " \n%s \n%s".printf (stdout, stderr);
778+ bool match_exist = re.match (input, 0, out match);
779+
780+ Gtk.TreeIter iter;
781+
782+ if (!match_exist) {
783+ if (retcode == 0) {
784+ list_store.append (out iter);
785+ list_store.set (iter, RESULTS.LINE, 0,
786+ RESULTS.COLUMN, 0, RESULTS.MESSAGE, _("No errors found."));
787+ } else {
788+ //There was a problem, either command or regex had failed
789+
790+ list_store.append (out iter);
791+ list_store.set (iter, RESULTS.LINE, 0,
792+ RESULTS.COLUMN, 0, RESULTS.MESSAGE, _("Unexpected result."));
793+ warning ("Current Language: %s", language.id);
794+ warning ("Command used %s", command);
795+ warning ("Regex used %s", linter_list[language.id].regex);
796+ warning ("Return code recieved %d", retcode);
797+ warning ("Return stderr recieved:\n%s", stderr);
798+ warning ("Return stdout recieved:\n%s", stdout);
799+ }
800+ return false;
801+ }
802+
803+ tree_view.row_activated.connect (go_to_line);
804+ while (match_exist) {
805+
806+ list_store.append (out iter);
807+ string type;
808+ if (match.fetch_named ("err") != null && match.fetch_named ("err").length > 0 ) {
809+ type = match.fetch_named ("err");
810+ } else if (match.fetch_named ("war") != null && match.fetch_named ("war").length > 0 ) {
811+ type = match.fetch_named ("war");
812+ }else if (match.fetch_named ("inf") != null && match.fetch_named ("inf").length > 0 ) {
813+ type = match.fetch_named ("inf");
814+ }else {
815+ type = "";
816+ }
817+
818+ var message = "%s%s".printf (type.length > 0 ? type + ": " : "", match.fetch_named ("message") ?? "");
819+ list_store.set (iter, RESULTS.LINE, int.parse(match.fetch_named ("line") ?? "-1"),
820+ RESULTS.COLUMN, int.parse (match.fetch_named ("col") ?? "-1"),
821+ RESULTS.MESSAGE, message);
822+ match_exist = match.next ();
823+ }
824+ } catch (Error e) {
825+ Gtk.TreeIter iter;
826+ list_store.append (out iter);
827+ list_store.set (iter, RESULTS.LINE, 0, RESULTS.COLUMN, 0, RESULTS.MESSAGE, _("Regex failure."));
828+
829+ //Use the treeview to comunicate error to the user.
830+ list_store.append (out iter);
831+ list_store.set (iter, RESULTS.LINE, 0, RESULTS.COLUMN, 0, RESULTS.MESSAGE, e.message);
832+
833+ warning ("Regex failure.\n%s", e.message);
834+ return false;
835+ }
836+ return false;
837+ }, GLib.Priority.DEFAULT_IDLE);
838+ yield;
839+ }
840+
841+ void on_configure_clicked () {
842+ dialog = new LinterConfigurationDialog (this);
843+ dialog.show_all ();
844+ dialog.run ();
845+ }
846+
847+ void on_hide_clicked () {
848+ remove_container ();
849+ window.get_current_document ().focus ();
850+ }
851+
852+ void doc_closed (Scratch.Services.Document doc) {
853+
854+ if (doc == current_document) {
855+ list_store.clear ();
856+ tree_view.row_activated.disconnect (go_to_line);
857+ current_document = null;
858+ }
859+
860+ doc.doc_closed.disconnect (doc_closed);
861+ }
862+ }
863+}
864+
865+[ModuleInit]
866+public void peas_register_types (GLib.TypeModule module) {
867+ var objmodule = module as Peas.ObjectModule;
868+ objmodule.register_extension_type (typeof (Peas.Activatable),
869+ typeof (Scratch.Plugins.LinterManager.LinterManagerPlugin));
870+}
871
872=== added file 'plugins/linter-manager/Settings.vala'
873--- plugins/linter-manager/Settings.vala 1970-01-01 00:00:00 +0000
874+++ plugins/linter-manager/Settings.vala 2016-10-27 12:57:14 +0000
875@@ -0,0 +1,32 @@
876+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
877+/***
878+ BEGIN LICENSE
879+
880+ Copyright (C) 2016 Geronimo Bareiro <gero[dot]bare[at]gmail[dot]com>
881+ This program is free software: you can redistribute it and/or modify it
882+ under the terms of the GNU Lesser General Public License version 3, as published
883+ by the Free Software Foundation.
884+
885+ This program is distributed in the hope that it will be useful, but
886+ WITHOUT ANY WARRANTY; without even the implied warranties of
887+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
888+ PURPOSE. See the GNU General Public License for more details.
889+
890+ You should have received a copy of the GNU General Public License along
891+ with this program. If not, see <http://www.gnu.org/licenses/>
892+
893+ END LICENSE
894+***/
895+
896+namespace Scratch.Plugins.LinterManager {
897+ internal class Settings : Granite.Services.Settings {
898+
899+ private const string SCHEMA = "org.pantheon.scratch.plugins.linter-manager";
900+
901+ public bool auto_run { get; set; }
902+
903+ public Settings () {
904+ base (SCHEMA);
905+ }
906+ }
907+}
908
909=== added directory 'plugins/linter-manager/icons'
910=== added file 'plugins/linter-manager/icons/check_file.svg'
911--- plugins/linter-manager/icons/check_file.svg 1970-01-01 00:00:00 +0000
912+++ plugins/linter-manager/icons/check_file.svg 2016-10-27 12:57:14 +0000
913@@ -0,0 +1,775 @@
914+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
915+<svg
916+ xmlns:dc="http://purl.org/dc/elements/1.1/"
917+ xmlns:cc="http://creativecommons.org/ns#"
918+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
919+ xmlns:svg="http://www.w3.org/2000/svg"
920+ xmlns="http://www.w3.org/2000/svg"
921+ xmlns:xlink="http://www.w3.org/1999/xlink"
922+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
923+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
924+ version="1.0"
925+ width="24"
926+ height="24"
927+ id="svg2"
928+ inkscape:version="0.91 r13725"
929+ sodipodi:docname="check_file.svg">
930+ <sodipodi:namedview
931+ pagecolor="#ffffff"
932+ bordercolor="#666666"
933+ borderopacity="1"
934+ objecttolerance="10"
935+ gridtolerance="10"
936+ guidetolerance="10"
937+ inkscape:pageopacity="0"
938+ inkscape:pageshadow="2"
939+ inkscape:window-width="881"
940+ inkscape:window-height="480"
941+ id="namedview153"
942+ showgrid="true"
943+ showguides="true"
944+ inkscape:zoom="4.9166667"
945+ inkscape:cx="8.9489574"
946+ inkscape:cy="0.46632093"
947+ inkscape:window-x="0"
948+ inkscape:window-y="2"
949+ inkscape:window-maximized="0"
950+ inkscape:current-layer="svg2">
951+ <inkscape:grid
952+ type="xygrid"
953+ id="grid4388" />
954+ </sodipodi:namedview>
955+ <defs
956+ id="defs4">
957+ <radialGradient
958+ cx="605.71429"
959+ cy="486.64789"
960+ r="117.14286"
961+ fx="605.71429"
962+ fy="486.64789"
963+ id="radialGradient4536"
964+ xlink:href="#linearGradient5060"
965+ gradientUnits="userSpaceOnUse"
966+ gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)" />
967+ <linearGradient
968+ id="linearGradient5060">
969+ <stop
970+ id="stop5062"
971+ style="stop-color:#000000;stop-opacity:1"
972+ offset="0" />
973+ <stop
974+ id="stop5064"
975+ style="stop-color:#000000;stop-opacity:0"
976+ offset="1" />
977+ </linearGradient>
978+ <radialGradient
979+ cx="605.71429"
980+ cy="486.64789"
981+ r="117.14286"
982+ fx="605.71429"
983+ fy="486.64789"
984+ id="radialGradient4534"
985+ xlink:href="#linearGradient5060"
986+ gradientUnits="userSpaceOnUse"
987+ gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)" />
988+ <linearGradient
989+ id="linearGradient5048">
990+ <stop
991+ id="stop5050"
992+ style="stop-color:#000000;stop-opacity:0"
993+ offset="0" />
994+ <stop
995+ id="stop5056"
996+ style="stop-color:#000000;stop-opacity:1"
997+ offset="0.5" />
998+ <stop
999+ id="stop5052"
1000+ style="stop-color:#000000;stop-opacity:0"
1001+ offset="1" />
1002+ </linearGradient>
1003+ <linearGradient
1004+ x1="302.85715"
1005+ y1="366.64789"
1006+ x2="302.85715"
1007+ y2="609.50507"
1008+ id="linearGradient4532"
1009+ xlink:href="#linearGradient5048"
1010+ gradientUnits="userSpaceOnUse"
1011+ gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)" />
1012+ <linearGradient
1013+ id="linearGradient3104">
1014+ <stop
1015+ id="stop3106"
1016+ style="stop-color:#969696;stop-opacity:1"
1017+ offset="0" />
1018+ <stop
1019+ id="stop3108"
1020+ style="stop-color:#bebebe;stop-opacity:1"
1021+ offset="1" />
1022+ </linearGradient>
1023+ <linearGradient
1024+ x1="11.536495"
1025+ y1="44.952385"
1026+ x2="11.536495"
1027+ y2="-1.0040783"
1028+ id="linearGradient4196"
1029+ xlink:href="#linearGradient3104"
1030+ gradientUnits="userSpaceOnUse"
1031+ gradientTransform="matrix(0.4857163,0,0,0.489929,0.34281,0.7216336)" />
1032+ <linearGradient
1033+ id="linearGradient3600">
1034+ <stop
1035+ id="stop3602"
1036+ style="stop-color:#f4f4f4;stop-opacity:1"
1037+ offset="0" />
1038+ <stop
1039+ id="stop3604"
1040+ style="stop-color:#dbdbdb;stop-opacity:1"
1041+ offset="1" />
1042+ </linearGradient>
1043+ <linearGradient
1044+ x1="-60.969666"
1045+ y1="13.668072"
1046+ x2="-60.908253"
1047+ y2="114.5354"
1048+ id="linearGradient4194"
1049+ xlink:href="#linearGradient3600"
1050+ gradientUnits="userSpaceOnUse"
1051+ gradientTransform="matrix(0.2187514,1.3312651e-4,-1.3318587e-4,0.2186539,23.078109,-2.7267432)" />
1052+ <linearGradient
1053+ id="linearGradient3191-577">
1054+ <stop
1055+ id="stop2613"
1056+ style="stop-color:#dbce48;stop-opacity:1"
1057+ offset="0" />
1058+ <stop
1059+ id="stop2615"
1060+ style="stop-color:#c5b625;stop-opacity:1"
1061+ offset="1" />
1062+ </linearGradient>
1063+ <linearGradient
1064+ x1="5.5178981"
1065+ y1="37.371799"
1066+ x2="9.5220556"
1067+ y2="41.391716"
1068+ id="linearGradient3857"
1069+ xlink:href="#linearGradient3191-577"
1070+ gradientUnits="userSpaceOnUse"
1071+ gradientTransform="matrix(0.3763801,0.03615261,0.03669995,0.374874,-2.2182805,-1.1331002)" />
1072+ <linearGradient
1073+ id="linearGradient3290-678">
1074+ <stop
1075+ id="stop2607"
1076+ style="stop-color:#ece5a5;stop-opacity:1"
1077+ offset="0" />
1078+ <stop
1079+ id="stop2609"
1080+ style="stop-color:#fcfbf2;stop-opacity:1"
1081+ offset="1" />
1082+ </linearGradient>
1083+ <linearGradient
1084+ x1="9"
1085+ y1="29.056757"
1086+ x2="9"
1087+ y2="26.02973"
1088+ id="linearGradient3855"
1089+ xlink:href="#linearGradient3290-678"
1090+ gradientUnits="userSpaceOnUse"
1091+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.602268,-17.636692,0.462492)" />
1092+ <linearGradient
1093+ id="linearGradient3223-699">
1094+ <stop
1095+ id="stop2599"
1096+ style="stop-color:#b1b1b1;stop-opacity:1"
1097+ offset="0" />
1098+ <stop
1099+ id="stop2601"
1100+ style="stop-color:#ffffff;stop-opacity:1"
1101+ offset="0.5" />
1102+ <stop
1103+ id="stop2603"
1104+ style="stop-color:#8f8f8f;stop-opacity:1"
1105+ offset="1" />
1106+ </linearGradient>
1107+ <linearGradient
1108+ x1="30.037716"
1109+ y1="24.989594"
1110+ x2="30.037716"
1111+ y2="30.000141"
1112+ id="linearGradient3853"
1113+ xlink:href="#linearGradient3223-699"
1114+ gradientUnits="userSpaceOnUse"
1115+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.177369,-2.1969969e-4)" />
1116+ <linearGradient
1117+ id="linearGradient3240-907">
1118+ <stop
1119+ id="stop2591"
1120+ style="stop-color:#565656;stop-opacity:1"
1121+ offset="0" />
1122+ <stop
1123+ id="stop2593"
1124+ style="stop-color:#9a9a9a;stop-opacity:1"
1125+ offset="0.5" />
1126+ <stop
1127+ id="stop2595"
1128+ style="stop-color:#545454;stop-opacity:1"
1129+ offset="1" />
1130+ </linearGradient>
1131+ <linearGradient
1132+ x1="30.037716"
1133+ y1="24.989594"
1134+ x2="30.037716"
1135+ y2="30.000141"
1136+ id="linearGradient3851"
1137+ xlink:href="#linearGradient3240-907"
1138+ gradientUnits="userSpaceOnUse"
1139+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.061661,-0.1164056)" />
1140+ <linearGradient
1141+ id="linearGradient3223-768">
1142+ <stop
1143+ id="stop2583"
1144+ style="stop-color:#b1b1b1;stop-opacity:1"
1145+ offset="0" />
1146+ <stop
1147+ id="stop2585"
1148+ style="stop-color:#ffffff;stop-opacity:1"
1149+ offset="0.5" />
1150+ <stop
1151+ id="stop2587"
1152+ style="stop-color:#8f8f8f;stop-opacity:1"
1153+ offset="1" />
1154+ </linearGradient>
1155+ <linearGradient
1156+ x1="30.037716"
1157+ y1="24.989594"
1158+ x2="30.037716"
1159+ y2="30.000141"
1160+ id="linearGradient3849"
1161+ xlink:href="#linearGradient3223-768"
1162+ gradientUnits="userSpaceOnUse"
1163+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.581392,0.4054707)" />
1164+ <linearGradient
1165+ id="linearGradient3240-686">
1166+ <stop
1167+ id="stop2575"
1168+ style="stop-color:#565656;stop-opacity:1"
1169+ offset="0" />
1170+ <stop
1171+ id="stop2577"
1172+ style="stop-color:#9a9a9a;stop-opacity:1"
1173+ offset="0.5" />
1174+ <stop
1175+ id="stop2579"
1176+ style="stop-color:#545454;stop-opacity:1"
1177+ offset="1" />
1178+ </linearGradient>
1179+ <linearGradient
1180+ x1="30.037716"
1181+ y1="24.989594"
1182+ x2="30.037716"
1183+ y2="30.000141"
1184+ id="linearGradient3847"
1185+ xlink:href="#linearGradient3240-686"
1186+ gradientUnits="userSpaceOnUse"
1187+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.465684,0.2892868)" />
1188+ <linearGradient
1189+ id="linearGradient3223-789">
1190+ <stop
1191+ id="stop2567"
1192+ style="stop-color:#b1b1b1;stop-opacity:1"
1193+ offset="0" />
1194+ <stop
1195+ id="stop2569"
1196+ style="stop-color:#ffffff;stop-opacity:1"
1197+ offset="0.5" />
1198+ <stop
1199+ id="stop2571"
1200+ style="stop-color:#8f8f8f;stop-opacity:1"
1201+ offset="1" />
1202+ </linearGradient>
1203+ <linearGradient
1204+ x1="30.037716"
1205+ y1="24.989594"
1206+ x2="30.037716"
1207+ y2="30.000141"
1208+ id="linearGradient3845"
1209+ xlink:href="#linearGradient3223-789"
1210+ gradientUnits="userSpaceOnUse"
1211+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.983472,0.8092126)" />
1212+ <linearGradient
1213+ id="linearGradient3240-279">
1214+ <stop
1215+ id="stop2559"
1216+ style="stop-color:#565656;stop-opacity:1"
1217+ offset="0" />
1218+ <stop
1219+ id="stop2561"
1220+ style="stop-color:#9a9a9a;stop-opacity:1"
1221+ offset="0.5" />
1222+ <stop
1223+ id="stop2563"
1224+ style="stop-color:#545454;stop-opacity:1"
1225+ offset="1" />
1226+ </linearGradient>
1227+ <linearGradient
1228+ x1="30.037716"
1229+ y1="24.989594"
1230+ x2="30.037716"
1231+ y2="30.000141"
1232+ id="linearGradient3843"
1233+ xlink:href="#linearGradient3240-279"
1234+ gradientUnits="userSpaceOnUse"
1235+ gradientTransform="matrix(0.4040235,-0.4056919,0.6073752,0.6022679,-17.867764,0.6930272)" />
1236+ <linearGradient
1237+ id="linearGradient3155-40">
1238+ <stop
1239+ id="stop2541"
1240+ style="stop-color:#181818;stop-opacity:1"
1241+ offset="0" />
1242+ <stop
1243+ id="stop2543"
1244+ style="stop-color:#dbdbdb;stop-opacity:1"
1245+ offset="0.13482948" />
1246+ <stop
1247+ id="stop2545"
1248+ style="stop-color:#a4a4a4;stop-opacity:1"
1249+ offset="0.20224422" />
1250+ <stop
1251+ id="stop2547"
1252+ style="stop-color:#ffffff;stop-opacity:1"
1253+ offset="0.26965895" />
1254+ <stop
1255+ id="stop2549"
1256+ style="stop-color:#8d8d8d;stop-opacity:1"
1257+ offset="0.44650277" />
1258+ <stop
1259+ id="stop2551"
1260+ style="stop-color:#959595;stop-opacity:1"
1261+ offset="0.57114136" />
1262+ <stop
1263+ id="stop2553"
1264+ style="stop-color:#cecece;stop-opacity:1"
1265+ offset="0.72038066" />
1266+ <stop
1267+ id="stop2555"
1268+ style="stop-color:#181818;stop-opacity:1"
1269+ offset="1" />
1270+ </linearGradient>
1271+ <linearGradient
1272+ x1="23.575972"
1273+ y1="25.356892"
1274+ x2="23.575972"
1275+ y2="31.210939"
1276+ id="linearGradient3841"
1277+ xlink:href="#linearGradient3155-40"
1278+ gradientUnits="userSpaceOnUse"
1279+ gradientTransform="matrix(0.415777,-0.4174938,0.518983,0.5146192,-15.747227,2.6503673)"
1280+ spreadMethod="pad" />
1281+ <linearGradient
1282+ x1="71.204407"
1283+ y1="6.2375584"
1284+ x2="71.204407"
1285+ y2="44.340794"
1286+ id="linearGradient3316"
1287+ xlink:href="#linearGradient4011-5"
1288+ gradientUnits="userSpaceOnUse"
1289+ gradientTransform="matrix(0.35135136,0,0,0.35135136,-16.20367,0.0907)" />
1290+ <linearGradient
1291+ id="linearGradient4011-5">
1292+ <stop
1293+ id="stop4013-6"
1294+ style="stop-color:#ffffff;stop-opacity:1"
1295+ offset="0" />
1296+ <stop
1297+ id="stop4015-1"
1298+ style="stop-color:#ffffff;stop-opacity:0.23529412"
1299+ offset="0.507761" />
1300+ <stop
1301+ id="stop4017-4"
1302+ style="stop-color:#ffffff;stop-opacity:0.15686275"
1303+ offset="0.83456558" />
1304+ <stop
1305+ id="stop4019-3"
1306+ style="stop-color:#ffffff;stop-opacity:0.39215687"
1307+ offset="1" />
1308+ </linearGradient>
1309+ </defs>
1310+ <metadata
1311+ id="metadata7">
1312+ <rdf:RDF>
1313+ <cc:Work
1314+ rdf:about="">
1315+ <dc:format>image/svg+xml</dc:format>
1316+ <dc:type
1317+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
1318+ <dc:title></dc:title>
1319+ </cc:Work>
1320+ </rdf:RDF>
1321+ </metadata>
1322+ <g
1323+ style="display:inline;stroke-width:1.88256383;stroke-miterlimit:4;stroke-dasharray:none"
1324+ id="g3490"
1325+ transform="matrix(0.5004324,0,0,0.5168524,0.1147464,-0.5781821)">
1326+ <g
1327+ style="display:inline;stroke-width:104.96237946;stroke-miterlimit:4;stroke-dasharray:none"
1328+ id="g5022"
1329+ transform="matrix(0.02165152,0,0,0.01485743,43.0076,42.68539)">
1330+ <rect
1331+ style="display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#linearGradient4532);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:104.96237946;marker:none"
1332+ id="rect4173"
1333+ y="-150.69685"
1334+ x="-1559.2523"
1335+ height="478.35718"
1336+ width="1339.6335" />
1337+ <path
1338+ inkscape:connector-curvature="0"
1339+ style="display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#radialGradient4534);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:104.96237946;marker:none"
1340+ id="path5058"
1341+ d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z" />
1342+ <path
1343+ inkscape:connector-curvature="0"
1344+ style="display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#radialGradient4536);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:104.96237946;marker:none"
1345+ id="path5018"
1346+ d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z" />
1347+ </g>
1348+ </g>
1349+ <path
1350+ inkscape:connector-curvature="0"
1351+ style="display:inline;fill:url(#linearGradient4194);fill-opacity:1;stroke:url(#linearGradient4196);stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
1352+ id="path2855"
1353+ d="m 3.5133625,0.49999968 c 4.1393458,0.002518 12.8294425,0.007809 16.9687885,0.010328 -0.0037,6.04861402 0.02118,15.96248032 0.01751,21.98982332 -5.666565,-0.0034 -11.3331314,-0.0069 -16.9996976,-0.01035 0.00447,-7.329933 0.00893,-14.659869 0.013397,-21.98980333 z" />
1354+ <g
1355+ style="fill:#999999"
1356+ id="g4198"
1357+ transform="matrix(1.0195441,0,0,1,32.167123,7.773339)">
1358+ <path
1359+ inkscape:connector-curvature="0"
1360+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1361+ id="path6035"
1362+ d="m -25.665515,-2.7733386 1.124072,0 0,1 -1.124072,0 0,-1 z" />
1363+ <path
1364+ inkscape:connector-curvature="0"
1365+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1366+ id="path6033"
1367+ d="m -24.388326,-2.7733386 1.060612,0 0,1 -1.060612,0 0,-1 z" />
1368+ <path
1369+ inkscape:connector-curvature="0"
1370+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1371+ id="path6031"
1372+ d="m -23.174597,-2.7733386 0.933684,0 0,1 -0.933684,0 0,-1 z" />
1373+ <path
1374+ inkscape:connector-curvature="0"
1375+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1376+ id="path6029"
1377+ d="m -22.087797,-2.7733386 0.410114,0 0,1 -0.410114,0 0,-1 z" />
1378+ <path
1379+ inkscape:connector-curvature="0"
1380+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1381+ id="path6027"
1382+ d="m -21.524561,-2.7733386 0.917815,0 0,1 -0.917815,0 0,-1 z" />
1383+ <path
1384+ inkscape:connector-curvature="0"
1385+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1386+ id="path6025"
1387+ d="m -20.453624,-2.7733386 2.377462,0 0,1 -2.377462,0 0,-1 z" />
1388+ <path
1389+ inkscape:connector-curvature="0"
1390+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1391+ id="path6023"
1392+ d="m -17.923045,-2.7733386 1.806297,0 0,1 -1.806297,0 0,-1 z" />
1393+ <path
1394+ inkscape:connector-curvature="0"
1395+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1396+ id="path6017"
1397+ d="m -25.665515,3.2266614 1.124072,0 0,1 -1.124072,0 0,-1 z" />
1398+ <path
1399+ inkscape:connector-curvature="0"
1400+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1401+ id="path6015"
1402+ d="m -24.388327,3.2266614 1.060612,0 0,1 -1.060612,0 0,-1 z" />
1403+ <path
1404+ inkscape:connector-curvature="0"
1405+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1406+ id="path6013"
1407+ d="m -23.174598,3.2266614 0.933683,0 0,1 -0.933683,0 0,-1 z" />
1408+ <path
1409+ inkscape:connector-curvature="0"
1410+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1411+ id="path6011"
1412+ d="m -22.087799,3.2266614 0.410114,0 0,1 -0.410114,0 0,-1 z" />
1413+ <path
1414+ inkscape:connector-curvature="0"
1415+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1416+ id="path6009"
1417+ d="m -21.524563,3.2266614 0.917814,0 0,1 -0.917814,0 0,-1 z" />
1418+ <path
1419+ inkscape:connector-curvature="0"
1420+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1421+ id="path6007"
1422+ d="m -20.453627,3.2266614 2.377461,0 0,1 -2.377461,0 0,-1 z" />
1423+ <path
1424+ inkscape:connector-curvature="0"
1425+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1426+ id="path6005"
1427+ d="m -17.923049,3.2266614 1.806296,0 0,1 -1.806296,0 0,-1 z" />
1428+ <path
1429+ inkscape:connector-curvature="0"
1430+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1431+ id="path6003"
1432+ d="m -15.963631,3.2266614 0.552901,0 0,1 -0.552901,0 0,-1 z" />
1433+ <path
1434+ inkscape:connector-curvature="0"
1435+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1436+ id="path6001"
1437+ d="m -15.257608,3.2266614 1.092339,0 0,1 -1.092339,0 0,-1 z" />
1438+ <path
1439+ inkscape:connector-curvature="0"
1440+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1441+ id="path5999"
1442+ d="m -25.665515,-0.77333856 1.742834,0 0,1 -1.742834,0 0,-1 z" />
1443+ <path
1444+ inkscape:connector-curvature="0"
1445+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1446+ id="path5997"
1447+ d="m -23.76361,-0.77333856 0.838487,0 0,1 -0.838487,0 0,-1 z" />
1448+ <path
1449+ inkscape:connector-curvature="0"
1450+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1451+ id="path5995"
1452+ d="m -22.766052,-0.77333856 0.394245,0 0,1 -0.394245,0 0,-1 z" />
1453+ <path
1454+ inkscape:connector-curvature="0"
1455+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1456+ id="path5993"
1457+ d="m -22.212735,-0.77333856 0.759159,0 0,1 -0.759159,0 0,-1 z" />
1458+ <path
1459+ inkscape:connector-curvature="0"
1460+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1461+ id="path5991"
1462+ d="m -21.29451,-0.77333856 0.759159,0 0,1 -0.759159,0 0,-1 z" />
1463+ <path
1464+ inkscape:connector-curvature="0"
1465+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1466+ id="path5989"
1467+ d="m -20.376281,-0.77333856 1.219264,0 0,1 -1.219264,0 0,-1 z" />
1468+ <path
1469+ inkscape:connector-curvature="0"
1470+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1471+ id="path5987"
1472+ d="m -18.99795,-0.77333856 1.600045,0 0,1 -1.600045,0 0,-1 z" />
1473+ <path
1474+ inkscape:connector-curvature="0"
1475+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1476+ id="path5985"
1477+ d="m -17.238834,-0.77333856 1.076471,0 0,1 -1.076471,0 0,-1 z" />
1478+ <path
1479+ inkscape:connector-curvature="0"
1480+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1481+ id="path5983"
1482+ d="m -16.003291,-0.77333856 0.251453,0 0,1 -0.251453,0 0,-1 z" />
1483+ <path
1484+ inkscape:connector-curvature="0"
1485+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1486+ id="path5981"
1487+ d="m -25.665515,1.2266614 1.996685,0 0,1 -1.996685,0 0,-1 z" />
1488+ <path
1489+ inkscape:connector-curvature="0"
1490+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1491+ id="path5979"
1492+ d="m -23.512309,1.2266614 2.139471,0 0,1 -2.139471,0 0,-1 z" />
1493+ <path
1494+ inkscape:connector-curvature="0"
1495+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1496+ id="path5977"
1497+ d="m -21.216317,1.2266614 0.854355,0 0,1 -0.854355,0 0,-1 z" />
1498+ <path
1499+ inkscape:connector-curvature="0"
1500+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1501+ id="path5975"
1502+ d="m -20.205441,1.2266614 2.012548,0 0,1 -2.012548,0 0,-1 z" />
1503+ <path
1504+ inkscape:connector-curvature="0"
1505+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1506+ id="path5973"
1507+ d="m -16.501932,1.2266614 0.537037,0 0,1 -0.537037,0 0,-1 z" />
1508+ <path
1509+ inkscape:connector-curvature="0"
1510+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1511+ id="path5971"
1512+ d="m -18.036372,1.2266614 1.377919,0 0,1 -1.377919,0 0,-1 z" />
1513+ <path
1514+ inkscape:connector-curvature="0"
1515+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1516+ id="path5969"
1517+ d="m -15.808375,1.2266614 0.314918,0 0,1 -0.314918,0 0,-1 z" />
1518+ <path
1519+ inkscape:connector-curvature="0"
1520+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1521+ id="path5967"
1522+ d="m -15.336936,1.2266614 0.854355,0 0,1 -0.854355,0 0,-1 z" />
1523+ <path
1524+ inkscape:connector-curvature="0"
1525+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1526+ id="path5965"
1527+ d="m -25.665515,9.2266614 1.996686,0 0,0.9999996 -1.996686,0 0,-0.9999996 z" />
1528+ <path
1529+ inkscape:connector-curvature="0"
1530+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1531+ id="path5963"
1532+ d="m -23.512309,9.2266614 2.139473,0 0,0.9999996 -2.139473,0 0,-0.9999996 z" />
1533+ <path
1534+ inkscape:connector-curvature="0"
1535+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1536+ id="path5961"
1537+ d="m -21.216315,9.2266614 0.854355,0 0,0.9999996 -0.854355,0 0,-0.9999996 z" />
1538+ <path
1539+ inkscape:connector-curvature="0"
1540+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1541+ id="path5959"
1542+ d="m -20.205439,9.2266614 2.012549,0 0,0.9999996 -2.012549,0 0,-0.9999996 z" />
1543+ <path
1544+ inkscape:connector-curvature="0"
1545+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1546+ id="path5957"
1547+ d="m -16.501929,9.2266614 0.537037,0 0,0.9999996 -0.537037,0 0,-0.9999996 z" />
1548+ <path
1549+ inkscape:connector-curvature="0"
1550+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1551+ id="path5955"
1552+ d="m -18.036369,9.2266614 1.377919,0 0,0.9999996 -1.377919,0 0,-0.9999996 z" />
1553+ <path
1554+ inkscape:connector-curvature="0"
1555+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1556+ id="path5953"
1557+ d="m -15.808371,9.2266614 0.314918,0 0,0.9999996 -0.314918,0 0,-0.9999996 z" />
1558+ <path
1559+ inkscape:connector-curvature="0"
1560+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1561+ id="path5951"
1562+ d="m -15.336932,9.2266614 0.854355,0 0,0.9999996 -0.854355,0 0,-0.9999996 z" />
1563+ <path
1564+ inkscape:connector-curvature="0"
1565+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1566+ id="path5949"
1567+ d="m -25.665515,5.2266614 1.441384,0 0,1 -1.441384,0 0,-1 z" />
1568+ <path
1569+ inkscape:connector-curvature="0"
1570+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1571+ id="path5947"
1572+ d="m -24.071009,5.2266614 2.393326,0 0,1 -2.393326,0 0,-1 z" />
1573+ <path
1574+ inkscape:connector-curvature="0"
1575+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1576+ id="path5945"
1577+ d="m -21.524561,5.2266614 1.155799,0 0,1 -1.155799,0 0,-1 z" />
1578+ <path
1579+ inkscape:connector-curvature="0"
1580+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1581+ id="path5943"
1582+ d="m -18.986049,5.2266614 0.917816,0 0,1 -0.917816,0 0,-1 z" />
1583+ <path
1584+ inkscape:connector-curvature="0"
1585+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1586+ id="path5941"
1587+ d="m -17.915112,5.2266614 1.473117,0 0,1 -1.473117,0 0,-1 z" />
1588+ <path
1589+ inkscape:connector-curvature="0"
1590+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1591+ id="path5939"
1592+ d="m -16.288879,5.2266614 2.393331,0 0,1 -2.393331,0 0,-1 z" />
1593+ <path
1594+ inkscape:connector-curvature="0"
1595+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1596+ id="path5937"
1597+ d="m -20.215641,5.2266614 1.076476,0 0,1 -1.076476,0 0,-1 z" />
1598+ <path
1599+ inkscape:connector-curvature="0"
1600+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1601+ id="path5935"
1602+ d="m -25.665515,7.2266614 1.806297,0 0,1 -1.806297,0 0,-1 z" />
1603+ <path
1604+ inkscape:connector-curvature="0"
1605+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1606+ id="path5933"
1607+ d="m -23.710065,7.2266614 0.56877,0 0,1 -0.56877,0 0,-1 z" />
1608+ <path
1609+ inkscape:connector-curvature="0"
1610+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1611+ id="path5931"
1612+ d="m -22.992143,7.2266614 1.298596,0 0,1 -1.298596,0 0,-1 z" />
1613+ <path
1614+ inkscape:connector-curvature="0"
1615+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1616+ id="path5929"
1617+ d="m -21.544395,7.2266614 0.886087,0 0,1 -0.886087,0 0,-1 z" />
1618+ <path
1619+ inkscape:connector-curvature="0"
1620+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1621+ id="path5927"
1622+ d="m -20.509155,7.2266614 0.886082,0 0,1 -0.886082,0 0,-1 z" />
1623+ <path
1624+ inkscape:connector-curvature="0"
1625+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1626+ id="path5925"
1627+ d="m -19.473916,7.2266614 1.282728,0 0,1 -1.282728,0 0,-1 z" />
1628+ <path
1629+ inkscape:connector-curvature="0"
1630+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1631+ id="path5923"
1632+ d="m -18.042041,7.2266614 0.267326,0 0,1 -0.267326,0 0,-1 z" />
1633+ <path
1634+ inkscape:connector-curvature="0"
1635+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1636+ id="path5921"
1637+ d="m -17.625563,7.2266614 1.663505,0 0,1 -1.663505,0 0,-1 z" />
1638+ <path
1639+ inkscape:connector-curvature="0"
1640+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1641+ id="path5919"
1642+ d="m -15.812906,7.2266614 0.886087,0 0,1 -0.886087,0 0,-1 z" />
1643+ <path
1644+ inkscape:connector-curvature="0"
1645+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1646+ id="path5917"
1647+ d="m -25.665515,11.226661 0.727427,0 0,1 -0.727427,0 0,-1 z" />
1648+ <path
1649+ inkscape:connector-curvature="0"
1650+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1651+ id="path5915"
1652+ d="m -24.749835,11.226661 2.647178,0 0,1 -2.647178,0 0,-1 z" />
1653+ <path
1654+ inkscape:connector-curvature="0"
1655+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1656+ id="path6838"
1657+ d="m -18.374424,11.226661 -0.727427,0 0,1 0.727427,0 0,-1 z" />
1658+ <path
1659+ inkscape:connector-curvature="0"
1660+ style="display:inline;opacity:0.7;fill:#999999;fill-opacity:1;stroke:none"
1661+ id="path6840"
1662+ d="m -19.290104,11.226661 -2.647178,0 0,1 2.647178,0 0,-1 z" />
1663+ </g>
1664+ <g
1665+ transform="matrix(0.81408898,0,0,0.81408898,10.177436,10.177436)"
1666+ id="g4236">
1667+ <path
1668+ d="M 9,1.5 C 4.86176,1.5 1.5,4.86176 1.5,9 c 0,4.13824 3.36176,7.5 7.5,7.5 4.13824,0 7.5,-3.36176 7.5,-7.5 C 16.5,4.86176 13.13824,1.5 9,1.5 Z"
1669+ id="path2555-7"
1670+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:#008000;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
1671+ inkscape:connector-curvature="0" />
1672+ <path
1673+ d="M 15.5,8.99977 C 15.5,12.58974 12.58964,15.5 9.00008,15.5 5.4102,15.5 2.5,12.5897 2.5,8.99977 2.5,5.40997 5.4102,2.5 9.00008,2.5 12.58964,2.5 15.5,5.40997 15.5,8.99977 l 0,0 z"
1674+ id="path8655-6"
1675+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.5;fill:none;stroke:url(#linearGradient3316);stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
1676+ inkscape:connector-curvature="0" />
1677+ <path
1678+ d="m 14.03125,4.15625 a 0.94895148,0.94895148 0 0 0 -0.09375,0.0625 l -4.96875,5.4375 -2.3125,-2.3125 a 0.94895148,0.94895148 0 0 0 -1.3125,0 l -2,2 A 0.94895148,0.94895148 0 0 0 3.40625,10.75 l 5,4 A 0.94895148,0.94895148 0 0 0 9.6875,14.65625 l 6.21875,-6.6875 c -0.218501,-1.473701 -0.888095,-2.7885095 -1.875,-3.8125 z"
1679+ id="path4318"
1680+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#115710;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84323651;marker:none;enable-background:accumulate"
1681+ inkscape:connector-curvature="0" />
1682+ <path
1683+ d="M 14.625,4.875 9,11 6,8 4,10 9,14 15.65625,6.84375 Z"
1684+ id="path2922-6-6-0"
1685+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84323651;marker:none;enable-background:accumulate"
1686+ inkscape:connector-curvature="0" />
1687+ </g>
1688+</svg>
1689
1690=== added file 'plugins/linter-manager/icons/linter-manager.gresource.xml'
1691--- plugins/linter-manager/icons/linter-manager.gresource.xml 1970-01-01 00:00:00 +0000
1692+++ plugins/linter-manager/icons/linter-manager.gresource.xml 2016-10-27 12:57:14 +0000
1693@@ -0,0 +1,6 @@
1694+<?xml version="1.0" encoding="UTF-8"?>
1695+<gresources>
1696+ <gresource prefix="/org/pantheon/scratch/plugin/linter-manager">
1697+ <file compressed="true" preprocess="xml-stripblanks">check_file.svg</file>
1698+ </gresource>
1699+</gresources>
1700
1701=== added file 'plugins/linter-manager/linter-manager.plugin'
1702--- plugins/linter-manager/linter-manager.plugin 1970-01-01 00:00:00 +0000
1703+++ plugins/linter-manager/linter-manager.plugin 2016-10-27 12:57:14 +0000
1704@@ -0,0 +1,9 @@
1705+[Plugin]
1706+Module=linter-manager
1707+Loader=C
1708+IAge=2
1709+Name=Linter Manager
1710+Description=Some linter support for scratch
1711+Authors=gero-bare <gero[dot]bare[at]gmail[dot]com>
1712+Copyright=Copyright © 2016 Scratch Developers
1713+Website=http://launchpad.net/scratch
1714
1715=== modified file 'schemas/CMakeLists.txt'
1716--- schemas/CMakeLists.txt 2015-09-10 00:54:45 +0000
1717+++ schemas/CMakeLists.txt 2016-10-27 12:57:14 +0000
1718@@ -3,4 +3,5 @@
1719 add_schema("org.pantheon.scratch.plugins.folder-manager.gschema.xml")
1720 add_schema("org.pantheon.scratch.plugins.file-manager.gschema.xml")
1721 add_schema("org.pantheon.scratch.plugins.terminal.gschema.xml")
1722+add_schema("org.pantheon.scratch.plugins.linter-manager.gschema.xml")
1723 add_schema("org.pantheon.scratch.plugins.spell.gschema.xml")
1724
1725=== added file 'schemas/org.pantheon.scratch.plugins.linter-manager.gschema.xml'
1726--- schemas/org.pantheon.scratch.plugins.linter-manager.gschema.xml 1970-01-01 00:00:00 +0000
1727+++ schemas/org.pantheon.scratch.plugins.linter-manager.gschema.xml 2016-10-27 12:57:14 +0000
1728@@ -0,0 +1,10 @@
1729+<?xml version="1.0" encoding="UTF-8"?>
1730+<schemalist>
1731+ <schema path="/org/pantheon/scratch/plugins/linter-manager/" id="org.pantheon.scratch.plugins.linter-manager" gettext-domain="scratch">
1732+ <key name="auto-run" type="b">
1733+ <default>false</default>
1734+ <summary>Run the linter after every save.</summary>
1735+ <description>true to run the linter every time the file is saved.</description>
1736+ </key>
1737+ </schema>
1738+</schemalist>
1739
1740=== modified file 'src/Scratch.vala'
1741--- src/Scratch.vala 2016-09-01 04:13:38 +0000
1742+++ src/Scratch.vala 2016-10-27 12:57:14 +0000
1743@@ -27,9 +27,11 @@
1744
1745 public class ScratchApp : Granite.Application {
1746 public string app_cmd_name { get { return _app_cmd_name; } }
1747+ public string data_home_folder { get { return _data_home_folder; } }
1748 public string data_home_folder_unsaved { get { return _data_home_folder_unsaved; } }
1749 public string default_font { get; set; }
1750 private static string _app_cmd_name;
1751+ private static string _data_home_folder;
1752 private static string _data_home_folder_unsaved;
1753 private static string _cwd;
1754 private static bool print_version = false;
1755@@ -87,7 +89,8 @@
1756 services = new ServicesSettings ();
1757
1758 // Init data home folder for unsaved text files
1759- _data_home_folder_unsaved = Environment.get_user_data_dir () + "/" + exec_name + "/unsaved/";
1760+ _data_home_folder = "%s/%s/".printf (Environment.get_user_data_dir (), exec_name);
1761+ _data_home_folder_unsaved ="%s/%s/%s/".printf (Environment.get_user_data_dir (), exec_name, "unsaved");
1762 }
1763
1764 public static ScratchApp _instance = null;

Subscribers

People subscribed via source and target branches