/[svn]/gigedit/trunk/src/gigedit/scripteditor.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/scripteditor.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3736 - (show annotations) (download)
Sat Feb 1 19:39:06 2020 UTC (4 years, 2 months ago) by schoenebeck
File size: 32321 byte(s)
* Script Editor: highlight new NKSP 'patch' keyword in pink color.

* Bumped version (1.1.1.svn13).

1 /*
2 Copyright (c) 2014-2020 Christian Schoenebeck
3
4 This file is part of "gigedit" and released under the terms of the
5 GNU General Public License version 2.
6 */
7
8 #include "scripteditor.h"
9 #include "global.h"
10 #include "compat.h"
11 #include <gtk/gtkwidget.h> // for gtk_widget_modify_*()
12 #if defined(__APPLE__)
13 # include "MacHelper.h"
14 #endif
15 #include <math.h> // for log10()
16
17 #if !USE_LS_SCRIPTVM
18
19 static const std::string _keywords[] = {
20 "on", "end", "declare", "while", "if", "or", "and", "not", "else", "case",
21 "select", "to", "const", "polyphonic", "mod", "synchronized"
22 };
23 static int _keywordsSz = sizeof(_keywords) / sizeof(std::string);
24
25 static const std::string _eventNames[] = {
26 "init", "note", "release", "controller"
27 };
28 static int _eventNamesSz = sizeof(_eventNames) / sizeof(std::string);
29
30 static bool isKeyword(const Glib::ustring& s) {
31 for (int i = 0; i < _keywordsSz; ++i)
32 if (_keywords[i] == s) return true;
33 return false;
34 }
35
36 static bool isEvent(const Glib::ustring& s) {
37 for (int i = 0; i < _eventNamesSz; ++i)
38 if (_eventNames[i] == s) return true;
39 return false;
40 }
41
42 #endif // !USE_LS_SCRIPTVM
43
44 static Glib::RefPtr<Gdk::Pixbuf> createIcon(std::string name, const Glib::RefPtr<Gdk::Screen>& screen) {
45 const int targetH = 16;
46 Glib::RefPtr<Gtk::IconTheme> theme = Gtk::IconTheme::get_for_screen(screen);
47 int w = 0;
48 int h = 0; // ignored
49 Gtk::IconSize::lookup(Gtk::ICON_SIZE_SMALL_TOOLBAR, w, h);
50 if (!theme->has_icon(name))
51 return Glib::RefPtr<Gdk::Pixbuf>();
52 Glib::RefPtr<Gdk::Pixbuf> pixbuf = theme->load_icon(name, w, Gtk::ICON_LOOKUP_GENERIC_FALLBACK);
53 if (pixbuf->get_height() != targetH) {
54 pixbuf = pixbuf->scale_simple(targetH, targetH, Gdk::INTERP_BILINEAR);
55 }
56 return pixbuf;
57 }
58
59 static Glib::RefPtr<Gdk::Pixbuf> createIcon(std::vector<std::string> alternativeNames, const Glib::RefPtr<Gdk::Screen>& screen) {
60 for (int i = 0; i < alternativeNames.size(); ++i) {
61 Glib::RefPtr<Gdk::Pixbuf> buf = createIcon(alternativeNames[i], screen);
62 if (buf) return buf;
63 }
64 return Glib::RefPtr<Gdk::Pixbuf>();
65 }
66
67 ScriptEditor::ScriptEditor() :
68 m_statusLabel("", Gtk::ALIGN_START),
69 #if HAS_GTKMM_STOCK
70 m_applyButton(Gtk::Stock::APPLY),
71 m_cancelButton(Gtk::Stock::CANCEL)
72 #else
73 m_applyButton(_("_Apply"), true),
74 m_cancelButton(_("_Cancel"), true)
75 #endif
76 {
77 m_script = NULL;
78 #if USE_LS_SCRIPTVM
79 m_vm = NULL;
80 #endif
81
82 if (!Settings::singleton()->autoRestoreWindowDimension) {
83 set_default_size(800, 700);
84 set_position(Gtk::WIN_POS_MOUSE);
85 }
86
87 // depending on GTK version and installed themes, there may be different
88 // icons, and different names for them, so for each type of icon we use,
89 // we provide a list of possible icon names, the first one found to be
90 // installed on the local system from the list will be used and loaded for
91 // the respective purpose (so order matters in those lists)
92 //
93 // (see https://developer.gnome.org/gtkmm/stable/namespaceGtk_1_1Stock.html for
94 // available icon names)
95 std::vector<std::string> errorIconNames;
96 errorIconNames.push_back("dialog-error");
97 errorIconNames.push_back("media-record");
98 errorIconNames.push_back("process-stop");
99
100 std::vector<std::string> warningIconNames;
101 warningIconNames.push_back("dialog-warning-symbolic");
102 warningIconNames.push_back("dialog-warning");
103
104 std::vector<std::string> successIconNames;
105 successIconNames.push_back("emblem-default");
106 successIconNames.push_back("tools-check-spelling");
107
108 m_errorIcon = createIcon(errorIconNames, get_screen());
109 m_warningIcon = createIcon(warningIconNames, get_screen());
110 m_successIcon = createIcon(successIconNames, get_screen());
111
112 add(m_vbox);
113
114 m_tagTable = Gtk::TextBuffer::TagTable::create();
115
116 m_keywordTag = Gtk::TextBuffer::Tag::create();
117 m_keywordTag->property_foreground() = "#000000"; // black
118 m_keywordTag->property_weight() = PANGO_WEIGHT_BOLD;
119 m_tagTable->add(m_keywordTag);
120
121 m_eventTag = Gtk::TextBuffer::Tag::create();
122 m_eventTag->property_foreground() = "#07c0cf"; // cyan 1
123 m_eventTag->property_weight() = PANGO_WEIGHT_BOLD;
124 m_tagTable->add(m_eventTag);
125
126 m_variableTag = Gtk::TextBuffer::Tag::create();
127 m_variableTag->property_foreground() = "#790cc4"; // magenta
128 m_tagTable->add(m_variableTag);
129
130 m_functionTag = Gtk::TextBuffer::Tag::create();
131 m_functionTag->property_foreground() = "#1ba1dd"; // cyan 2
132 m_tagTable->add(m_functionTag);
133
134 m_numberTag = Gtk::TextBuffer::Tag::create();
135 m_numberTag->property_foreground() = "#c4950c"; // yellow
136 m_tagTable->add(m_numberTag);
137
138 m_stringTag = Gtk::TextBuffer::Tag::create();
139 m_stringTag->property_foreground() = "#c40c0c"; // red
140 m_tagTable->add(m_stringTag);
141
142 m_patchTag = Gtk::TextBuffer::Tag::create();
143 m_patchTag->property_foreground() = "#FF4FF3"; // pink
144 m_patchTag->property_weight() = PANGO_WEIGHT_BOLD;
145 m_tagTable->add(m_patchTag);
146
147 m_commentTag = Gtk::TextBuffer::Tag::create();
148 m_commentTag->property_foreground() = "#9c9c9c"; // gray
149 m_tagTable->add(m_commentTag);
150
151 #define PREPROC_TOKEN_COLOR "#2f8a33" // green
152
153 m_preprocTag = Gtk::TextBuffer::Tag::create();
154 m_preprocTag->property_foreground() = PREPROC_TOKEN_COLOR;
155 m_tagTable->add(m_preprocTag);
156
157 m_preprocCommentTag = Gtk::TextBuffer::Tag::create();
158 m_preprocCommentTag->property_strikethrough() = true;
159 m_preprocCommentTag->property_background() = "#e5e5e5";
160 m_tagTable->add(m_preprocCommentTag);
161
162 m_errorTag = Gtk::TextBuffer::Tag::create();
163 m_errorTag->property_background() = "#ff9393"; // red
164 m_tagTable->add(m_errorTag);
165
166 m_warningTag = Gtk::TextBuffer::Tag::create();
167 m_warningTag->property_background() = "#fffd7c"; // yellow
168 m_tagTable->add(m_warningTag);
169
170 m_lineNrTag = Gtk::TextBuffer::Tag::create();
171 m_lineNrTag->property_foreground() = "#CCCCCC";
172 m_tagTable->add(m_lineNrTag);
173
174 m_metricTag = Gtk::TextBuffer::Tag::create();
175 m_metricTag->property_foreground() = "#000000"; // black
176 m_tagTable->add(m_metricTag);
177
178 m_stdUnitTag = Gtk::TextBuffer::Tag::create();
179 m_stdUnitTag->property_foreground() = "#50BC00"; // greenish
180 m_tagTable->add(m_stdUnitTag);
181
182 // create menu
183 #if USE_GTKMM_BUILDER
184 m_actionGroup = Gio::SimpleActionGroup::create();
185 m_actionGroup->add_action(
186 "Apply", sigc::mem_fun(*this, &ScriptEditor::onButtonApply)
187 );
188 m_actionGroup->add_action(
189 "Close", sigc::mem_fun(*this, &ScriptEditor::onButtonCancel)
190 );
191 m_actionGroup->add_action(
192 "ChangeFont", sigc::mem_fun(*this, &ScriptEditor::onMenuChangeFontSize)
193 );
194 insert_action_group("ScriptEditor", m_actionGroup);
195
196 m_uiManager = Gtk::Builder::create();
197 Glib::ustring ui_info =
198 "<interface>"
199 " <menubar id='MenuBar'>"
200 " <menu id='MenuScript'>"
201 " <section>"
202 " <item id='Apply'>"
203 " <attribute name='label' translatable='yes'>_Apply</attribute>"
204 " <attribute name='action'>ScriptEditor.Apply</attribute>"
205 " <attribute name='accel'>&lt;Primary&gt;s</attribute>"
206 " </item>"
207 " </section>"
208 " <section>"
209 " <item id='Close'>"
210 " <attribute name='label' translatable='yes'>_Close</attribute>"
211 " <attribute name='action'>ScriptEditor.Close</attribute>"
212 " <attribute name='accel'>&lt;Primary&gt;q</attribute>"
213 " </item>"
214 " </section>"
215 " </menu>"
216 " <menu id='MenuEditor'>"
217 " <section>"
218 " <item id='ChangeFont'>"
219 " <attribute name='label' translatable='yes'>_Font Size ...</attribute>"
220 " <attribute name='action'>ScriptEditor.ChangeFont</attribute>"
221 " </item>"
222 " </section>"
223 " </menu>"
224 " </menubar>"
225 "</interface>";
226 m_uiManager->add_from_string(ui_info);
227 /*{
228 auto object = uiManager->get_object("MenuBar");
229 auto gmenu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
230 set_menubar(gmenu);
231 }*/
232 #else
233 m_actionGroup = Gtk::ActionGroup::create();
234 m_actionGroup->add(Gtk::Action::create("MenuScript", _("_Script")));
235 m_actionGroup->add(Gtk::Action::create("Apply", _("_Apply")),
236 Gtk::AccelKey("<control>s"),
237 sigc::mem_fun(*this, &ScriptEditor::onButtonApply));
238 m_actionGroup->add(Gtk::Action::create("Close", _("_Close")),
239 Gtk::AccelKey("<control>q"),
240 sigc::mem_fun(*this, &ScriptEditor::onButtonCancel));
241 m_actionGroup->add(Gtk::Action::create("MenuEditor", _("_Editor")));
242 m_actionGroup->add(Gtk::Action::create("ChangeFont", _("_Font Size ...")),
243 sigc::mem_fun(*this, &ScriptEditor::onMenuChangeFontSize));
244 m_uiManager = Gtk::UIManager::create();
245 m_uiManager->insert_action_group(m_actionGroup);
246 add_accel_group(m_uiManager->get_accel_group());
247 m_uiManager->add_ui_from_string(
248 "<ui>"
249 " <menubar name='MenuBar'>"
250 " <menu action='MenuScript'>"
251 " <menuitem action='Apply'/>"
252 " <separator/>"
253 " <menuitem action='Close'/>"
254 " </menu>"
255 " <menu action='MenuEditor'>"
256 " <menuitem action='ChangeFont'/>"
257 " </menu>"
258 " </menubar>"
259 "</ui>"
260 );
261 #endif
262
263 m_lineNrBuffer = Gtk::TextBuffer::create(m_tagTable);
264 m_lineNrView.set_size_request(22,14);
265 m_lineNrView.set_buffer(m_lineNrBuffer);
266 m_lineNrView.set_left_margin(3);
267 m_lineNrView.set_right_margin(3);
268 m_lineNrView.property_editable() = false;
269 m_lineNrView.property_sensitive() = false;
270 m_lineNrTextViewSpacer.set_size_request(5,14);
271 m_lineNrTextViewSpacer.property_editable() = false;
272 m_lineNrTextViewSpacer.property_sensitive() = false;
273 {
274 #if 1 //(GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
275 Gdk::Color color;
276 #else
277 Gdk::RGBA color;
278 #endif
279 color.set("#F5F5F5");
280 GtkWidget* widget = (GtkWidget*) m_lineNrView.gobj();
281 #if GTK_MAJOR_VERSION < 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION <= 24)
282 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, color.gobj());
283 gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, color.gobj());
284 #endif
285 }
286 {
287 #if 1 //(GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
288 Gdk::Color color;
289 #else
290 Gdk::RGBA color;
291 #endif
292 color.set("#EEEEEE");
293 GtkWidget* widget = (GtkWidget*) m_lineNrTextViewSpacer.gobj();
294 #if GTK_MAJOR_VERSION < 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION <= 24)
295 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, color.gobj());
296 gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, color.gobj());
297 #endif
298 }
299 m_textBuffer = Gtk::TextBuffer::create(m_tagTable);
300 m_textView.set_buffer(m_textBuffer);
301 m_textView.set_left_margin(5);
302 setFontSize(currentFontSize(), false);
303 m_textViewHBox.pack_start(m_lineNrView, Gtk::PACK_SHRINK);
304 m_textViewHBox.pack_start(m_lineNrTextViewSpacer, Gtk::PACK_SHRINK);
305 m_textViewHBox.add(m_textView);
306 m_scrolledWindow.add(m_textViewHBox);
307 m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
308
309 #if USE_GTKMM_BUILDER
310 Gtk::Widget* menuBar = new Gtk::MenuBar(
311 Glib::RefPtr<Gio::Menu>::cast_dynamic(
312 m_uiManager->get_object("MenuBar")
313 )
314 );
315 #else
316 Gtk::Widget* menuBar = m_uiManager->get_widget("/MenuBar");
317 #endif
318
319 m_vbox.pack_start(*menuBar, Gtk::PACK_SHRINK);
320 m_vbox.pack_start(m_scrolledWindow);
321
322 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
323 m_buttonBox.pack_start(m_applyButton);
324 m_buttonBox.pack_start(m_cancelButton);
325 m_applyButton.set_can_default();
326 m_applyButton.set_sensitive(false);
327 m_applyButton.grab_focus();
328
329 #if GTKMM_MAJOR_VERSION < 3
330 m_statusHBox.set_spacing(6);
331 #elif GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 12
332 m_statusImage.set_margin_left(6);
333 m_statusImage.set_margin_right(6);
334 #else
335 m_statusImage.set_margin_start(6);
336 m_statusImage.set_margin_end(6);
337 #endif
338
339 m_statusHBox.pack_start(m_statusImage, Gtk::PACK_SHRINK);
340 m_statusHBox.pack_start(m_statusLabel);
341 #if HAS_GTKMM_SHOW_ALL_CHILDREN
342 m_statusHBox.show_all_children();
343 #endif
344
345 m_footerHBox.pack_start(m_statusHBox);
346 m_footerHBox.pack_start(m_buttonBox, Gtk::PACK_SHRINK);
347
348 m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK);
349
350 m_applyButton.signal_clicked().connect(
351 sigc::mem_fun(*this, &ScriptEditor::onButtonApply)
352 );
353
354 m_cancelButton.signal_clicked().connect(
355 sigc::mem_fun(*this, &ScriptEditor::onButtonCancel)
356 );
357
358 m_textBuffer->signal_insert().connect(
359 sigc::mem_fun(*this, &ScriptEditor::onTextInserted)
360 );
361
362 m_textBuffer->signal_erase().connect(
363 sigc::mem_fun(*this, &ScriptEditor::onTextErased)
364 );
365
366 m_textBuffer->signal_modified_changed().connect(
367 sigc::mem_fun(*this, &ScriptEditor::onModifiedChanged)
368 );
369
370 signal_hide().connect(
371 sigc::mem_fun(*this, &ScriptEditor::onWindowHide)
372 );
373
374 signal_delete_event().connect(
375 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
376 sigc::mem_fun(*this, &ScriptEditor::onWindowDelete)
377 #else
378 sigc::mem_fun(*this, &ScriptEditor::onWindowDeleteP)
379 #endif
380 );
381
382 #if HAS_GTKMM_SHOW_ALL_CHILDREN
383 show_all_children();
384 #endif
385
386 #if !USE_LS_SCRIPTVM
387 // make user aware about gigedit had been compiled without liblinuxsampler support
388 m_statusLabel.set_markup(_("Limited editor features (since Gigedit was compiled without liblinuxsampler support)!"));
389 m_statusImage.set(m_warningIcon);
390 #endif
391 }
392
393 ScriptEditor::~ScriptEditor() {
394 printf("ScriptEditor destruct\n");
395 #if USE_LS_SCRIPTVM
396 if (m_vm) delete m_vm;
397 #endif
398 }
399
400 int ScriptEditor::currentFontSize() const {
401 #if defined(__APPLE__)
402 const int defaultFontSize = 11;
403 #else
404 const int defaultFontSize = 10;
405 #endif
406 const int settingFontSize = Settings::singleton()->scriptEditorFontSize;
407 const int fontSize = (settingFontSize > 0) ? settingFontSize : defaultFontSize;
408 return fontSize;
409 }
410
411 void ScriptEditor::setFontSize(int sizePt, bool save) {
412 //printf("setFontSize(%d,%d)\n", size, save);
413
414 // make sure the real size on the screen for the editor's font is consistent
415 // on all screens (which otherwise may vary between models and DPI settings)
416 const double referenceDPI = 96;
417 double dpi = Gdk::Screen::get_default()->get_resolution();
418 double sizePx = sizePt * dpi / referenceDPI;
419
420 #if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 20)
421 Pango::FontDescription fdesc;
422 fdesc.set_family("monospace");
423 # if defined(__APPLE__)
424 // fixes poor readability of default monospace font on Macs
425 if (macIsMinMac10_6())
426 fdesc.set_family("Menlo");
427 # endif
428 fdesc.set_size(sizePx * PANGO_SCALE);
429 # if GTKMM_MAJOR_VERSION < 3
430 m_lineNrView.modify_font(fdesc);
431 m_textView.modify_font(fdesc);
432 # else
433 m_lineNrView.override_font(fdesc);
434 m_textView.override_font(fdesc);
435 # endif
436 #else
437 Glib::ustring family = "monospace";
438 # if defined(__APPLE__)
439 // fixes poor readability of default monospace font on Macs
440 if (macIsMinMac10_6())
441 family = "Menlo";
442 # endif
443 if (!m_css) {
444 m_css = Gtk::CssProvider::create();
445 m_lineNrView.get_style_context()->add_provider(m_css, GTK_STYLE_PROVIDER_PRIORITY_FALLBACK);
446 m_textView.get_style_context()->add_provider(m_css, GTK_STYLE_PROVIDER_PRIORITY_FALLBACK);
447 }
448 m_css->load_from_data(
449 "* {"
450 " font: " + ToString(sizePt) + "pt " + family + ";"
451 "}"
452 );
453 #endif
454 if (save) Settings::singleton()->scriptEditorFontSize = sizePt;
455 }
456
457 void ScriptEditor::setScript(gig::Script* script) {
458 m_script = script;
459 if (!script) {
460 set_title(_("No Script"));
461 return;
462 }
463
464 set_title(std::string(_("Instrument Script")) + " - \"" + script->Name + "\"");
465
466 std::string txt = script->GetScriptAsText();
467 //printf("text : '%s'\n", txt.c_str());
468 m_textBuffer->set_text(txt);
469 m_textBuffer->set_modified(false);
470
471 // on Gtk 3 the respective text change callback would not be called, so force this update here
472 if (txt.empty())
473 updateLineNumbers();
474 }
475
476 void ScriptEditor::updateLineNumbers() {
477 int n = m_textBuffer->get_line_count();
478 int old = m_lineNrBuffer->get_line_count();
479 if (n == old && old > 1) return;
480 if (n < 1) n = 1;
481 const int digits = log10(n) + 1;
482 const int bufSz = digits + 2;
483 char* buf = new char[bufSz];
484 std::string sFmt1 = "%" + ToString(digits) + "d";
485 std::string sFmt2 = "\n%" + ToString(digits) + "d";
486 Glib::ustring s;
487 for (int i = 0; i < n; ++i) {
488 snprintf(buf, bufSz, i ? sFmt2.c_str() : sFmt1.c_str(), i+1);
489 s += buf;
490 }
491 m_lineNrBuffer->remove_all_tags(m_lineNrBuffer->begin(), m_lineNrBuffer->end());
492 m_lineNrBuffer->set_text(s);
493 m_lineNrBuffer->apply_tag(m_lineNrTag, m_lineNrBuffer->begin(), m_lineNrBuffer->end());
494 if (buf) delete[] buf;
495 }
496
497 void ScriptEditor::onTextInserted(const Gtk::TextBuffer::iterator& itEnd, const Glib::ustring& txt, int length) {
498 //printf("onTextInserted()\n");
499 #if USE_LS_SCRIPTVM
500 m_textBuffer->remove_all_tags(m_textBuffer->begin(), m_textBuffer->end());
501 updateSyntaxHighlightingByVM();
502 updateParserIssuesByVM();
503 updateStatusBar();
504 #else
505 //printf("inserted %d\n", length);
506 Gtk::TextBuffer::iterator itStart = itEnd;
507 itStart.backward_chars(length);
508
509 Gtk::TextBuffer::iterator it = itStart;
510 it.backward_word_start();
511
512 bool eofReached = false;
513 while (it <= itEnd) {
514 Gtk::TextBuffer::iterator itWordStart = it;
515 if (!it.forward_word_end()) {
516 eofReached = true;
517 it = itEnd;
518 }
519
520 Glib::ustring s = m_textBuffer->get_text(itWordStart, it, false);
521 //printf("{%s}\n", s.c_str());
522 if (isKeyword(s))
523 m_textBuffer->apply_tag(m_keywordTag, itWordStart, it);
524 else if (isEvent(s)) {
525 // check if previous word is "on"
526 Gtk::TextBuffer::iterator itPreviousWordStart = itWordStart;
527 if (itPreviousWordStart.backward_word_start()) {
528 Gtk::TextBuffer::iterator itPreviousWordEnd = itPreviousWordStart;
529 itPreviousWordEnd.forward_word_end();
530 if (m_textBuffer->get_text(itPreviousWordStart, itPreviousWordEnd, false) == "on") {
531 m_textBuffer->apply_tag(m_eventTag, itWordStart, it);
532 }
533 }
534 }
535
536 if (eofReached) break;
537
538 while (!it.inside_word())
539 if (!it.forward_char())
540 goto EOF_REACHED;
541 }
542
543 EOF_REACHED:
544 ;
545
546 #endif // USE_LS_SCRIPTVM
547 updateLineNumbers();
548 }
549
550 #if USE_LS_SCRIPTVM
551
552 LinuxSampler::ScriptVM* ScriptEditor::GetScriptVM() {
553 if (!m_vm) m_vm = LinuxSampler::ScriptVMFactory::Create("gig");
554 return m_vm;
555 }
556
557 template<class T>
558 static void getIteratorsForIssue(Glib::RefPtr<Gtk::TextBuffer>& txtbuf, const T& issue, Gtk::TextBuffer::iterator& start, Gtk::TextBuffer::iterator& end) {
559 Gtk::TextBuffer::iterator itLine =
560 txtbuf->get_iter_at_line_index(issue.firstLine - 1, 0);
561 const int charsInLine = itLine.get_bytes_in_line();
562 start = txtbuf->get_iter_at_line_index(
563 issue.firstLine - 1,
564 // check we are not getting past the end of the line here, otherwise Gtk crashes
565 issue.firstColumn - 1 < charsInLine ? issue.firstColumn - 1 : charsInLine - 1
566 );
567 end = start;
568 end.forward_lines(issue.lastLine - issue.firstLine);
569 end.forward_chars(
570 (issue.lastLine != issue.firstLine)
571 ? issue.lastColumn - 1
572 : issue.lastColumn - issue.firstColumn + 1
573 );
574 }
575
576 static void applyCodeTag(Glib::RefPtr<Gtk::TextBuffer>& txtbuf, const LinuxSampler::VMSourceToken& token, Glib::RefPtr<Gtk::TextBuffer::Tag>& tag) {
577 Gtk::TextBuffer::iterator itLine =
578 txtbuf->get_iter_at_line_index(token.firstLine(), 0);
579 const int charsInLine = itLine.get_bytes_in_line();
580 Gtk::TextBuffer::iterator itStart = txtbuf->get_iter_at_line_index(
581 token.firstLine(),
582 // check we are not getting past the end of the line here, otherwise Gtk crashes
583 token.firstColumn() < charsInLine ? token.firstColumn() : charsInLine - 1
584 );
585 Gtk::TextBuffer::iterator itEnd = itStart;
586 const int length = token.text().length();
587 itEnd.forward_chars(length);
588 txtbuf->apply_tag(tag, itStart, itEnd);
589 }
590
591 static void applyCodeTag(Glib::RefPtr<Gtk::TextBuffer>& txtbuf, const LinuxSampler::ParserIssue& issue, Glib::RefPtr<Gtk::TextBuffer::Tag>& tag) {
592 Gtk::TextBuffer::iterator itStart, itEnd;
593 getIteratorsForIssue(txtbuf, issue, itStart, itEnd);
594 txtbuf->apply_tag(tag, itStart, itEnd);
595 }
596
597 static void applyPreprocessorComment(Glib::RefPtr<Gtk::TextBuffer>& txtbuf, const LinuxSampler::CodeBlock& block, Glib::RefPtr<Gtk::TextBuffer::Tag>& tag) {
598 Gtk::TextBuffer::iterator itStart, itEnd;
599 getIteratorsForIssue(txtbuf, block, itStart, itEnd);
600 txtbuf->apply_tag(tag, itStart, itEnd);
601 }
602
603 void ScriptEditor::updateSyntaxHighlightingByVM() {
604 GetScriptVM();
605 const std::string s = m_textBuffer->get_text();
606 if (s.empty()) return;
607 std::vector<LinuxSampler::VMSourceToken> tokens = m_vm->syntaxHighlighting(s);
608
609 for (int i = 0; i < tokens.size(); ++i) {
610 const LinuxSampler::VMSourceToken& token = tokens[i];
611
612 if (token.isKeyword()) {
613 if (token.text() == "patch")
614 applyCodeTag(m_textBuffer, token, m_patchTag);
615 else
616 applyCodeTag(m_textBuffer, token, m_keywordTag);
617 } else if (token.isVariableName()) {
618 applyCodeTag(m_textBuffer, token, m_variableTag);
619 } else if (token.isIdentifier()) {
620 if (token.isEventHandlerName()) {
621 applyCodeTag(m_textBuffer, token, m_eventTag);
622 } else { // a function ...
623 applyCodeTag(m_textBuffer, token, m_functionTag);
624 }
625 } else if (token.isNumberLiteral()) {
626 applyCodeTag(m_textBuffer, token, m_numberTag);
627 } else if (token.isStringLiteral()) {
628 applyCodeTag(m_textBuffer, token, m_stringTag);
629 } else if (token.isComment()) {
630 applyCodeTag(m_textBuffer, token, m_commentTag);
631 } else if (token.isPreprocessor()) {
632 applyCodeTag(m_textBuffer, token, m_preprocTag);
633 } else if (token.isMetricPrefix()) {
634 applyCodeTag(m_textBuffer, token, m_metricTag);
635 } else if (token.isStdUnit()) {
636 applyCodeTag(m_textBuffer, token, m_stdUnitTag);
637 } else if (token.isNewLine()) {
638 }
639 }
640 }
641
642 void ScriptEditor::updateParserIssuesByVM() {
643 GetScriptVM();
644 const std::string s = m_textBuffer->get_text();
645 LinuxSampler::VMParserContext* parserContext = m_vm->loadScript(s);
646 m_issues = parserContext->issues();
647 m_errors = parserContext->errors();
648 m_warnings = parserContext->warnings();
649 m_preprocComments = parserContext->preprocessorComments();
650
651 if (!s.empty()) {
652 for (int i = 0; i < m_issues.size(); ++i) {
653 const LinuxSampler::ParserIssue& issue = m_issues[i];
654
655 if (issue.isErr()) {
656 applyCodeTag(m_textBuffer, issue, m_errorTag);
657 } else if (issue.isWrn()) {
658 applyCodeTag(m_textBuffer, issue, m_warningTag);
659 }
660 }
661 }
662
663 for (int i = 0; i < m_preprocComments.size(); ++i) {
664 applyPreprocessorComment(m_textBuffer, m_preprocComments[i],
665 m_preprocCommentTag);
666 }
667
668 delete parserContext;
669 }
670
671 void ScriptEditor::updateIssueTooltip(GdkEventMotion* e) {
672 int x, y;
673 m_textView.window_to_buffer_coords(Gtk::TEXT_WINDOW_TEXT, int(e->x), int(e->y), x, y);
674
675 Gtk::TextBuffer::iterator it;
676 m_textView.get_iter_at_location(it, x, y);
677
678 const int line = it.get_line();
679 const int column = it.get_line_offset();
680
681 //printf("mouse at l%d c%d\n", line, column);
682
683 for (int i = 0; i < m_issues.size(); ++i) {
684 const LinuxSampler::ParserIssue& issue = m_issues[i];
685 const int firstLine = issue.firstLine - 1;
686 const int firstColumn = issue.firstColumn - 1;
687 const int lastLine = issue.lastLine - 1;
688 const int lastColumn = issue.lastColumn - 1;
689 if (firstLine <= line && line <= lastLine &&
690 (firstLine != line || firstColumn <= column) &&
691 (lastLine != line || lastColumn >= column))
692 {
693 m_textView.set_tooltip_markup(
694 (issue.isErr() ? "<span foreground=\"#ff9393\">ERROR:</span> " : "<span foreground=\"#c4950c\">Warning:</span> ") +
695 issue.txt
696 );
697 return;
698 }
699 }
700
701 for (int i = 0; i < m_preprocComments.size(); ++i) {
702 const LinuxSampler::CodeBlock& block = m_preprocComments[i];
703 const int firstLine = block.firstLine - 1;
704 const int firstColumn = block.firstColumn - 1;
705 const int lastLine = block.lastLine - 1;
706 const int lastColumn = block.lastColumn - 1;
707 if (firstLine <= line && line <= lastLine &&
708 (firstLine != line || firstColumn <= column) &&
709 (lastLine != line || lastColumn >= column))
710 {
711 m_textView.set_tooltip_markup(
712 "Code block filtered out by preceding <span foreground=\"" PREPROC_TOKEN_COLOR "\">preprocessor</span> statement."
713 );
714 return;
715 }
716 }
717
718 m_textView.set_tooltip_markup("");
719 }
720
721 static std::string warningsCountTxt(const std::vector<LinuxSampler::ParserIssue> warnings) {
722 std::string txt = "<span foreground=\"#c4950c\">" + ToString(warnings.size());
723 txt += (warnings.size() == 1) ? " Warning" : " Warnings";
724 txt += "</span>";
725 return txt;
726 }
727
728 static std::string errorsCountTxt(const std::vector<LinuxSampler::ParserIssue> errors) {
729 std::string txt = "<span foreground=\"#c40c0c\">" + ToString(errors.size());
730 txt += (errors.size() == 1) ? " Error" : " Errors";
731 txt += "</span>";
732 return txt;
733 }
734
735 void ScriptEditor::updateStatusBar() {
736 // update status text
737 std::string txt;
738 if (m_issues.empty()) {
739 txt = "No issues with this script.";
740 } else {
741 const char* txtWontLoad = ". Sampler won't load instruments using this script!";
742 txt = "There ";
743 txt += (m_errors.size() <= 1 && m_warnings.size() <= 1) ? "is " : "are ";
744 if (m_errors.empty()) {
745 txt += warningsCountTxt(m_warnings) + ". Script will load, but might not behave as expected!";
746 } else if (m_warnings.empty()) {
747 txt += errorsCountTxt(m_errors) + txtWontLoad;
748 } else {
749 txt += errorsCountTxt(m_errors) + " and " +
750 warningsCountTxt(m_warnings) + txtWontLoad;
751 }
752 }
753 m_statusLabel.set_markup(txt);
754
755 // update status icon
756 m_statusImage.set(
757 m_issues.empty() ? m_successIcon : !m_errors.empty() ? m_errorIcon : m_warningIcon
758 );
759 }
760
761 #endif // USE_LS_SCRIPTVM
762
763 void ScriptEditor::onTextErased(const Gtk::TextBuffer::iterator& itStart, const Gtk::TextBuffer::iterator& itEnd) {
764 //printf("erased\n");
765 #if USE_LS_SCRIPTVM
766 m_textBuffer->remove_all_tags(m_textBuffer->begin(), m_textBuffer->end());
767 updateSyntaxHighlightingByVM();
768 updateParserIssuesByVM();
769 updateStatusBar();
770 #else
771 Gtk::TextBuffer::iterator itStart2 = itStart;
772 if (itStart2.inside_word() || itStart2.ends_word())
773 itStart2.backward_word_start();
774
775 Gtk::TextBuffer::iterator itEnd2 = itEnd;
776 if (itEnd2.inside_word()) itEnd2.forward_word_end();
777
778 m_textBuffer->remove_all_tags(itStart2, itEnd2);
779 #endif // USE_LS_SCRIPTVM
780 updateLineNumbers();
781 }
782
783 bool ScriptEditor::on_motion_notify_event(GdkEventMotion* e) {
784 #if USE_LS_SCRIPTVM
785 //TODO: event throttling would be a good idea here
786 updateIssueTooltip(e);
787 #endif
788 #if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION <= 24)
789 return ManagedWindow::on_motion_notify_event(e);
790 #else
791 Gdk::EventMotion em = Glib::wrap(e, true);
792 return ManagedWindow::on_motion_notify_event(em);
793 #endif
794 }
795
796 void ScriptEditor::onMenuChangeFontSize() {
797 //TODO: for GTKMM >= 3.2 class Gtk::FontChooser could be used instead
798 Gtk::Dialog dialog(_("Font Size"), true /*modal*/);
799 HBox hbox;
800 hbox.set_spacing(6);
801
802 Gtk::Label label(_("Editor's Font Size:"), Gtk::ALIGN_START);
803 hbox.pack_start(label, Gtk::PACK_SHRINK);
804
805 Gtk::SpinButton spinButton;
806 spinButton.set_range(4, 80);
807 spinButton.set_increments(1, 10);
808 spinButton.set_value(currentFontSize());
809 hbox.pack_start(spinButton);
810
811 #if USE_GTKMM_BOX
812 dialog.get_content_area()->pack_start(hbox);
813 #else
814 dialog.get_vbox()->pack_start(hbox);
815 #endif
816 dialog.add_button(_("_OK"), 0);
817 dialog.add_button(_("_Cancel"), 1);
818
819 #if HAS_GTKMM_SHOW_ALL_CHILDREN
820 dialog.show_all_children();
821 #endif
822
823 if (!dialog.run()) { // OK selected ...
824 const int newFontSize = spinButton.get_value_as_int();
825 if (newFontSize >= 4)
826 setFontSize(newFontSize, true);
827 }
828 }
829
830 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
831 bool ScriptEditor::onWindowDelete(Gdk::Event& e) {
832 return onWindowDeleteP(NULL);
833 }
834 #endif
835
836 bool ScriptEditor::onWindowDeleteP(GdkEventAny* /*e*/) {
837 //printf("onWindowDelete\n");
838
839 if (!isModified()) return false; // propagate event further (which will close this window)
840
841 gchar* msg = g_strdup_printf(_("Apply changes to instrument script \"%s\" before closing?"),
842 m_script->Name.c_str());
843 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
844 g_free(msg);
845 dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
846 dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
847 dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
848 dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
849 dialog.set_default_response(Gtk::RESPONSE_YES);
850 int response = dialog.run();
851 dialog.hide();
852
853 // user decided to close script editor without saving
854 if (response == Gtk::RESPONSE_NO)
855 return false; // propagate event further (which will close this window)
856
857 // user cancelled dialog, thus don't close script editor
858 if (response == Gtk::RESPONSE_CANCEL) {
859 show();
860 return true; // drop event (prevents closing this window)
861 }
862
863 // user wants to apply the changes, afterwards close window
864 if (response == Gtk::RESPONSE_YES) {
865 onButtonApply();
866 return false; // propagate event further (which will close this window)
867 }
868
869 // should never ever make it to this point actually
870 return false;
871 }
872
873 bool ScriptEditor::isModified() const {
874 return m_textBuffer->get_modified();
875 }
876
877 void ScriptEditor::onModifiedChanged() {
878 m_applyButton.set_sensitive(isModified());
879 #if USE_LS_SCRIPTVM
880 updateStatusBar();
881 #endif
882 }
883
884 void ScriptEditor::onButtonCancel() {
885 bool dropEvent = onWindowDeleteP(NULL);
886 if (dropEvent) return;
887 hide();
888 }
889
890 void ScriptEditor::onButtonApply() {
891 signal_script_to_be_changed.emit(m_script);
892 m_script->SetScriptAsText(m_textBuffer->get_text());
893 signal_script_changed.emit(m_script);
894 m_textBuffer->set_modified(false);
895 }
896
897 void ScriptEditor::onWindowHide() {
898 delete this; // this is the end, my friend
899 }

  ViewVC Help
Powered by ViewVC