-- ui_gtk.e -- A huge thanks to Irv Mullins for making EuGTK, which made the Linux -- and OSX GTK ports painless. Thanks to Irv for: -- * focus-in-event for checking modified tabs -- * current folder for load and save dialogs -- * window placement taking window theme into account -- * file dialog filters -- * menu accelerator appearance -- * new subs dialog -- Changes: -- fix intermittent hang on quit (found it, caused by putting the program in the -- background using Ctrl-Z and "bg". It blocks on doing something to console -- before exiting, so need to do "fg" to unfreeze and exit normally.) -- font seems to be ok on OSX now, -- needed to strip spaces and "bold", "italic", from the font name. -- Todo: -- fix modifier keys not working on OS X (might be ok now using gtk accelerators) -- menu accelerator labels show up as "-/-" on OS X -- investigate if widgets need to be Destroy'd public include std/machine.e public include std/error.e include std/get.e include std/regex.e include std/sort.e include scintilla.e include EuGTK/GtkEngine.e include EuGTK/GtkEvents.e include wee.exw as wee include weeicon.e -- nifty shortcut, thanks Greg Haberek function callback(sequence name, atom rid = routine_id(name)) if rid = -1 then crash("routine '"&name&"' is not visible") end if return call_back(rid) end function -- check to see if 64-bit callback arguments are broken ifdef BITS64 then function check_callback_func(atom x) if x = 0 then crash("You need a newer 64-bit Euphoria with callback bug fix: 4.1.0 Beta 2 or later") end if return 0 end function c_proc(define_c_proc("", callback("check_callback_func"), {C_LONG}), {#100000000}) end ifdef wee_init() -- initialize global variables x_pos = 100 y_pos = 50 x_size = 500 y_size = 600 constant wee_conf_file = getenv("HOME") & "/.wee_conf" load_wee_conf(wee_conf_file) -- bind the icon so it won't have to be found at runtime; constant wee_icon = gtk_func("gdk_pixbuf_new_from_xpm_data", {P}, {allocate_string_pointer_array(wee_xpm)}) -------------------------------------------------- -- Find dialog constant GTK_RESPONSE_FIND = 1, GTK_RESPONSE_REPLACE = 2, GTK_RESPONSE_REPLACE_ALL = 3 integer find_backward = 0 procedure find_dialog(integer replace_flag) atom dialog, content, row, vbox, hbox, lbl, hedit, find_entry, rep_entry, chk_word, chk_case, chk_backward integer flags, result, pos sequence text text = get_selection() if length(text) then find_phrase = text end if dialog = create(GtkDialog, { {"border width", 5}, {"transient for", win}, {"title", "Find"}, {"modal", TRUE}, {"add button", "gtk-close", GTK_RESPONSE_DELETE_EVENT}}) if replace_flag then set(dialog, "add button", "Replace All", GTK_RESPONSE_REPLACE_ALL) set(dialog, "add button", "Replace", GTK_RESPONSE_REPLACE) end if set(dialog, "add button", "Find Next", GTK_RESPONSE_FIND) set(dialog, "default response", GTK_RESPONSE_FIND) content = gtk:get(dialog, "content area") vbox = create(GtkBox, VERTICAL, 5) set(vbox, "margin bottom", 5) add(content, vbox) hbox = create(GtkBox, HORIZONTAL, 5) pack(vbox, hbox) pack(hbox, create(GtkLabel, "Find What:")) find_entry = create(GtkEntry, { {"activates default", TRUE}, {"text", find_phrase}}) pack(hbox, find_entry, TRUE, TRUE) hedit = tab_hedit() if replace_flag then set(dialog, "default response", GTK_RESPONSE_REPLACE) hbox = create(GtkBox, HORIZONTAL, 5) pack(vbox, hbox) pack(hbox, create(GtkLabel, "Replace With:")) rep_entry = create(GtkEntry, { {"activates default", TRUE}, {"text", replace_phrase}}) pack(hbox, rep_entry, TRUE, TRUE) -- clear the target so that first replace won't reuse old one SSM(hedit, SCI_SETTARGETSTART, 0) SSM(hedit, SCI_SETTARGETEND, 0) end if flags = SSM(hedit, SCI_GETSEARCHFLAGS) chk_word = create(GtkCheckButton, { {"label", "Match whole word only"}, {"active", 0 != and_bits(flags, SCFIND_WHOLEWORD)}}) pack(vbox, chk_word) chk_case = create(GtkCheckButton, { {"label", "Match case"}, {"active", 0 != and_bits(flags, SCFIND_MATCHCASE)}}) pack(vbox, chk_case) chk_backward = create(GtkCheckButton, { {"label", "Search backward"}, {"active", find_backward}}) pack(vbox, chk_backward) show_all(dialog) result = set(dialog, "run") while result != GTK_RESPONSE_DELETE_EVENT do flags = 0 if gtk:get(chk_word, "active") then flags += SCFIND_WHOLEWORD end if if gtk:get(chk_case, "active") then flags += SCFIND_MATCHCASE end if find_backward = gtk:get(chk_backward, "active") SSM(hedit, SCI_SETSEARCHFLAGS, flags) find_phrase = gtk:get(find_entry, "text") if result = GTK_RESPONSE_FIND then if search_find(find_phrase, find_backward) = 0 then Info(dialog, "Find", "Unable to find a match.") end if else replace_phrase = gtk:get(rep_entry, "text") if result = GTK_RESPONSE_REPLACE_ALL then result = search_replace_all(find_phrase, replace_phrase) if result then Info(dialog, "Replace All", sprintf("%d replacements.", {result})) else Info(dialog, "Replace All", "Unable to find a match.") end if else result = search_replace(replace_phrase) if search_find(find_phrase, find_backward) = 0 and result = 0 then Info(dialog, "Replace", "Unable to find a match.") end if end if end if result = set(dialog, "run") end while hide(dialog) return end procedure -------------------------------------------------- -- functions called from menu items function FileNew() new_file() return 0 end function function FileOpen() open_file("", 0) return 0 end function function FileSave() save_if_modified(0) return 0 end function function FileSaveAs() save_file_as() return 0 end function function FileClose() close_tab() return 0 end function function FileQuit() if save_modified_tabs() then save_wee_conf(wee_conf_file) Quit() end if return 0 end function function EditUndo() SSM(tab_hedit(), SCI_UNDO) return 0 end function function EditRedo() SSM(tab_hedit(), SCI_REDO) return 0 end function function EditCut() SSM(tab_hedit(), SCI_CUT) return 0 end function function EditCopy() SSM(tab_hedit(), SCI_COPY) return 0 end function function EditPaste() SSM(tab_hedit(), SCI_PASTE) return 0 end function function EditClear() SSM(tab_hedit(), SCI_CLEAR) return 0 end function function EditSelectAll() SSM(tab_hedit(), SCI_SELECTALL) return 0 end function function EditToggleComment() toggle_comment() return 0 end function function SearchFind() find_dialog(0) return 0 end function function SearchFindNext() if length(find_phrase) = 0 then return SearchFind() end if search_find(find_phrase, 0) return 0 end function function SearchFindPrevious() search_find(find_phrase, 1) return 0 end function function SearchReplace() find_dialog(1) return 0 end function function ViewDecl() view_declaration() return 0 end function function ViewArgs() view_subroutine_arguments() return 0 end function function ViewComp() view_completions() return 0 end function function ViewError() ui_view_error() return 0 end function function GoBack() go_back() return 0 end function function OldViewSubs() sequence text, word, subs, tmp integer pos, result atom dialog, scroll, list, content, row, lbl text = get_edit_text() pos = get_pos() word = word_pos(text, pos) tmp = get_subroutines(parse(text, file_name)) word = word[1] subs = repeat(0, floor(length(tmp)/2)) for i = 1 to length(subs) do subs[i] = {tmp[i*2-1], tmp[i*2]} end for if sorted_subs then subs = sort(subs) end if dialog = create(GtkDialog, { {"border width", 5}, {"default size", 200, 400}, {"add button", "gtk-close", GTK_RESPONSE_CLOSE}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Subroutines"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) content = gtk:get(dialog, "content area") scroll = create(GtkScrolledWindow) pack(content, scroll, TRUE, TRUE) list = create(GtkListBox) add(scroll, list) for i = 1 to length(subs) do lbl = create(GtkLabel, subs[i][1]) set(lbl, "halign", GTK_ALIGN_START) set(list, "insert", lbl, -1) if equal(subs[i][1], word) then row = gtk:get(list, "row at index", i-1) set(list, "select row", row) end if end for show_all(dialog) if set(dialog, "run") = GTK_RESPONSE_OK then row = gtk:get(list, "selected row") --result = gtk:get(row, "index") -- doesn't work? for i = 1 to length(subs) do if row = gtk:get(list, "row at index", i-1) then word = subs[i][1] pos = subs[i][2] goto_pos(pos, length(word)) exit end if end for end if hide(dialog) return 0 end function function RowActivated(atom ctl, atom path, atom col, atom dialog) --? {ctl, path, col, dialog} set(dialog, "response", GTK_RESPONSE_OK) return 0 end function constant row_activated = callback("RowActivated") -- contributed by Irv function ViewSubs() sequence text, word, subs integer pos, result atom dialog, scroll, list, content, row, lbl text = get_edit_text() pos = get_pos() word = word_pos(text, pos) subs = get_subroutines(parse(text, file_name)) word = word[1] dialog = create(GtkDialog, { {"border width", 5}, {"default size", 200, 400}, {"add button", "gtk-close", GTK_RESPONSE_CLOSE}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Subroutines"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) content = gtk:get(dialog, "content area") scroll = create(GtkScrolledWindow) pack(content, scroll, TRUE, TRUE) object routines = subs, data if sorted_subs then routines = sort(routines) end if list = create(GtkTreeView) add(scroll, list) object store = create(GtkListStore, {gSTR, gINT}) set(list, "model", store) object col1 = create(GtkTreeViewColumn) object rend1 = create(GtkCellRendererText) add(col1, rend1) set(col1, "add attribute", rend1, "text", 1) set(col1, "sort indicator", TRUE) set(col1, "max width", 100) set(col1, "title", "Routine Name") set(list, "append columns", col1) object col2 = create(GtkTreeViewColumn) object rend2 = create(GtkCellRendererText) add(col2, rend2) set(col2, "add attribute", rend2, "text", 2) set(list, "append columns", col2) set(store, "data", routines) object selection = gtk:get(list,"selection") set(selection, "mode", GTK_SELECTION_SINGLE) set(col2, "visible", FALSE) set(col1, "sort column id", 1) connect(list, "row-activated", row_activated, dialog) show_all(dialog) if gtk:get(dialog, "run") = GTK_RESPONSE_OK then row = gtk:get(selection, "selected row") data = gtk:get(store, "row data", row) word = data[1] pos = data[2] goto_pos(pos, length(word)) end if hide(dialog) return 0 end function function OptionsFont() atom dialog sequence font, tmp dialog = create(GtkFontChooserDialog, "Font...", win) set(dialog, "font", sprintf("%s %d", {font_name, font_height})) if set(dialog, "run") = MB_OK then font = gtk:get(dialog, "font") for i = length(font) to 1 by -1 do if font[i] = ' ' then tmp = value(font[i+1..$]) if tmp[1] = 0 then font_height = tmp[2] font_name = font[1..i-1] --printf(1, "%s %d\n", {font_name, font_height}) reinit_all_edits() end if exit end if end for end if set(dialog, "hide") return 0 end function function RunColorDialog(integer color) object ccd = create(GtkColorChooserDialog, { {"title", "Select a color"}, {"transient for", win}, {"use alpha", FALSE}, {"rgba", sprintf("#%02x%02x%02x", and_bits(floor(color/{1,#100,#10000}),#FF))}}) if gtk:get(ccd, "run") = MB_OK then color = gtk:get(ccd, "rgba", 2) color = floor(and_bits(color, #FF0000) / #10000) + and_bits(color, #FF00) + and_bits(color, #FF) * #10000 end if set(ccd, "hide") return color end function function ColorButton(atom ctl, atom w) if w = 1 then normal_color = RunColorDialog(normal_color) elsif w = 2 then background_color = RunColorDialog(background_color) elsif w = 3 then comment_color = RunColorDialog(comment_color) elsif w = 4 then string_color = RunColorDialog(string_color) elsif w = 5 then keyword_color = RunColorDialog(keyword_color) elsif w = 6 then builtin_color = RunColorDialog(builtin_color) elsif w = 7 then number_color = RunColorDialog(number_color) elsif w = 8 then bracelight_color = RunColorDialog(bracelight_color) elsif w = 9 then linenumber_color = RunColorDialog(linenumber_color) end if reinit_all_edits() return 0 end function function BoldToggle(atom ctl, atom flag) if gtk:get(ctl, "active") then bold_flags = or_bits(bold_flags, flag) else bold_flags = and_bits(bold_flags, not_bits(flag)) end if reinit_all_edits() return 0 end function constant color_button = callback("ColorButton") constant bold_toggle = callback("BoldToggle") function OptionsColors() atom dialog, grid dialog = create(GtkDialog, { {"border width", 5}, {"default size", 200, 300}, {"add button", "gtk-close", GTK_RESPONSE_CLOSE}, {"transient for", win}, {"title", "Colors"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) grid = create(GtkGrid, VERTICAL) set(grid, "margin bottom", 5) add(gtk:get(dialog, "content area"), grid) set(grid, { {"row spacing", 2}, {"column spacing", 2}, {"attach", create(GtkButton, "Normal", color_button, 1), 1, 2, 1, 1}, {"attach", create(GtkButton, "Background", color_button, 2), 1, 3, 1, 1}, {"attach", create(GtkButton, "Comment", color_button, 3), 1, 4, 1, 1}, {"attach", create(GtkButton, "String", color_button, 4), 1, 5, 1, 1}, {"attach", create(GtkButton, "Keyword", color_button, 5), 1, 6, 1, 1}, {"attach", create(GtkButton, "Built-in", color_button, 6), 1, 7, 1, 1}, {"attach", create(GtkButton, "Number", color_button, 7), 1, 8, 1, 1}, {"attach", create(GtkButton, "Brace Highlight", color_button, 8), 1, 9, 1, 1}, {"attach", create(GtkButton, "Line Number", color_button, 9), 1, 10, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, 1)}}, bold_toggle, 1), 2, 2, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, 2)}}, bold_toggle, 2), 2, 4, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, 4)}}, bold_toggle, 4), 2, 5, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, 8)}}, bold_toggle, 8), 2, 6, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, #10)}}, bold_toggle, #10), 2, 7, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, #20)}}, bold_toggle, #20), 2, 8, 1, 1}, {"attach", create(GtkCheckButton, { {"label", "Bold"}, {"active", 0 != and_bits(bold_flags, #40)}}, bold_toggle, #40), 2, 9, 1, 1} }) show_all(dialog) set(dialog, "run") set(dialog, "hide") return 0 end function function OptionsLineNumbers(atom handle) line_numbers = gtk:get(handle, "active") reinit_all_edits() return 0 end function function OptionsSortedSubs(atom handle) sorted_subs = gtk:get(handle, "active") return 0 end function function OptionsLineWrap(atom handle) line_wrap = gtk:get(handle, "active") reinit_all_edits() return 0 end function function OptionsReopenTabs(atom handle) reopen_tabs = gtk:get(handle, "active") return 0 end function function OptionsCompleteStatements(atom handle) complete_statements = gtk:get(handle, "active") return 0 end function function OptionsCompleteBraces(atom handle) complete_braces = gtk:get(handle, "active") return 0 end function function OptionsErrorIndicators(atom handle) auto_indicator = gtk:get(handle, "active") return 0 end function function OptionsIndent(atom handle) atom dialog, panel, hbox, hedit, indent_entry, tabs_entry, chk_guides, chk_usetabs integer indent_width, tab_width, use_tabs sequence val hedit = tab_hedit() tab_width = SSM(hedit, SCI_GETTABWIDTH) indent_width = SSM(hedit, SCI_GETINDENT) use_tabs = SSM(hedit, SCI_GETUSETABS) dialog = create(GtkDialog, { {"border width", 5}, {"add button", "gtk-close", GTK_RESPONSE_DELETE_EVENT}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Indent"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) panel = create(GtkBox, VERTICAL, 5) set(panel, "margin bottom", 5) add(gtk:get(dialog, "content area"), panel) hbox = create(GtkBox, HORIZONTAL, 5) add(panel, hbox) add(hbox, create(GtkLabel, "Indent Size")) indent_entry = create(GtkEntry, { {"text", sprintf("%d", {indent_width})}}) add(hbox, indent_entry) chk_guides = create(GtkCheckButton, { {"label", "Show indentation guides"}, {"active", indentation_guides}}) pack(panel, chk_guides) chk_usetabs = create(GtkCheckButton, { {"label", "Use tabs in indentation"}, {"active", use_tabs}}) pack(panel, chk_usetabs) hbox = create(GtkBox, HORIZONTAL, 5) add(panel, hbox) add(hbox, create(GtkLabel, "Tab Size")) tabs_entry = create(GtkEntry, { {"text", sprintf("%d", {tab_width})}}) add(hbox, tabs_entry) show_all(dialog) while set(dialog, "run") = GTK_RESPONSE_OK do val = value(gtk:get(indent_entry, "text")) if val[1] = 0 and integer(val[2]) and val[2] >= 1 and val[2] <= 8 then indent_width = val[2] else set(indent_entry, "grab focus") continue end if val = value(gtk:get(tabs_entry, "text")) if val[1] = 0 and integer(val[2]) and val[2] >= 1 and val[2] <= 8 then tab_width = val[2] else set(tabs_entry, "grab focus") continue end if use_tabs = gtk:get(chk_usetabs, "active") indentation_guides = gtk:get(chk_guides, "active") SSM(hedit, SCI_SETTABWIDTH, tab_width) SSM(hedit, SCI_SETINDENT, indent_width) SSM(hedit, SCI_SETUSETABS, use_tabs) reinit_all_edits() exit end while hide(dialog) return 0 end function -- runs the script, maybe in a terminal emulator procedure Run(sequence script) sequence tmp_name = sprintf("/tmp/wee_run_%d.sh", {rand(1000000)}) sequence cmd = tmp_name if length(terminal_program) then if run_waitkey then -- need bash for "read -n" script = "#!/bin/bash\n" & -- don't show "Press Enter" on errors match_replace("eui ", script, "eui -batch ") & "\nread -n1 -s -p \"Press any key...\"\n" end if cmd = terminal_program & ' ' & tmp_name end if -- write script file if write_file(tmp_name, script) = -1 then printf(2, "Failed to write file %s\n", {tmp_name}) return end if -- remove script after running it cmd &= "; rm " & tmp_name if run_background then -- put the command in background cmd = "(" & cmd & ") &" end if -- make script executable and run it system("chmod +x " & tmp_name & "; " & cmd) end procedure function RunStart(atom ctl) object lbl = gtk:get(ctl, "label") sequence cmd = "" if save_if_modified(0) = 0 or length(file_name) = 0 then return 0 -- cancelled, or no name end if run_file_name = file_name cmd = "cd " & dirname(run_file_name) & "\n" if match("Start", lbl) = 1 then reset_ex_err() cmd &= get_eu_bin(interpreter) & ' ' & quote_spaces(file_name) if not equal(lbl, "Start") then -- with arguments if length(get_tab_arguments()) = 0 then if RunSetArguments() then return 0 end if end if cmd &= ' ' & get_tab_arguments() end if Run(cmd) check_ex_err() elsif equal(lbl, "Bind") then cmd &= get_eu_bin("eubind") & ' ' & quote_spaces(file_name) cmd &= "\nwhich upx && upx " & filebase(file_name) if run_testrun then cmd &= "\n./" & filebase(file_name) end if Run(cmd) elsif equal(lbl, "Shroud") then cmd &= get_eu_bin("eushroud") & ' ' & quote_spaces(file_name) if run_testrun then cmd &= "\n./" & filebase(file_name) & ".il" end if Run(cmd) elsif equal(lbl, "Translate and Compile") then cmd &= get_eu_bin("euc") & ' ' & quote_spaces(file_name) cmd &= "\nwhich upx && upx " & filebase(file_name) if run_testrun then cmd &= "\n./" & filebase(file_name) end if Run(cmd) elsif equal(lbl, "Run Terminal Emulator") then Run(cmd & "$SHELL") else crash("Unable to get menu label") end if return 0 end function function RunSetArguments() atom dialog, content, text_entry, result = -1 dialog = create(GtkDialog, { {"border width", 5}, {"add button", "gtk-close", GTK_RESPONSE_DELETE_EVENT}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Arguments"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) content = gtk:get(dialog, "content area") text_entry = create(GtkEntry, { {"activates default", TRUE}, {"text", get_tab_arguments()}, {"margin bottom", 5}}) add(content, text_entry) show_all(dialog) if set(dialog, "run") = GTK_RESPONSE_OK then set_tab_arguments(gtk:get(text_entry, "text")) result = 0 end if hide(dialog) return result end function function ChooseInterpreter(atom ctl, atom text_entry) atom dialog sequence filenames dialog = create(GtkFileChooserDialog, { {"title", "Open..."}, {"transient for", win}, {"action", GTK_FILE_CHOOSER_ACTION_OPEN}, {"add button", "gtk-cancel", GTK_RESPONSE_CLOSE}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"position", GTK_WIN_POS_MOUSE}, {"current folder", pathname(canonical_path(interpreter))}}) if gtk:get(dialog, "run") = GTK_RESPONSE_OK then set(text_entry, "prepend text", gtk:get(dialog, "filename")) set(text_entry, "active", 1) end if set(dialog, "hide") return 0 end function constant choose_interpreter = callback("ChooseInterpreter") function RunSetInterpreter() atom dialog, text_entry, panel, row sequence interpreters = get_interpreters() integer index dialog = create(GtkDialog, { {"border width", 5}, {"add button", "gtk-close", GTK_RESPONSE_DELETE_EVENT}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Choose Interpreter"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) panel = create(GtkBox, VERTICAL, 5) set(panel, "margin bottom", 5) add(gtk:get(dialog, "content area"), panel) pack(panel, create(GtkLabel, `Enter an interpreter to use to run programs, or select one from the list. Leave blank to use the default first item in the list.`)) row = create(GtkBox, HORIZONTAL, 5) pack(panel, row, TRUE) text_entry = create(GtkComboBoxEntry, { {"margin bottom", 5}, {"activates default", TRUE}}) pack(row, text_entry, TRUE, TRUE) if length(interpreters) then add(text_entry, interpreters) index = find(interpreter, interpreters) if index then set(text_entry, "active", index) end if end if pack(row, create(GtkButton, "...", choose_interpreter, text_entry)) show_all(dialog) if set(dialog, "run") = GTK_RESPONSE_OK then interpreter = gtk:get(text_entry, "active text") end if hide(dialog) return 0 end function constant terminals = {"x-terminal-emulator", "urxvt", "rxvt", "terminator", "Eterm", "aterm", "xterm", "gnome-terminal", "roxterm", "xfce4-terminal", "termite", "lxterminal", "mate-terminal", "terminology"} function RunSetTerminal() atom dialog, text_entry, panel sequence text integer n = 0 dialog = create(GtkDialog, { {"border width", 5}, {"add button", "gtk-close", GTK_RESPONSE_DELETE_EVENT}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "Choose Terminal Emulator"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) panel = create(GtkBox, VERTICAL, 5) set(panel, "margin bottom", 5) add(gtk:get(dialog, "content area"), panel) pack(panel, create(GtkLabel, `Enter a terminal emulator to use to run programs, or select one from the list. Leave blank to run in parent terminal.`)) text_entry = create(GtkComboBoxEntry, { {"margin bottom", 5}, {"activates default", TRUE}}) pack(panel, text_entry) for i = 1 to length(terminals) do if system_exec("which " & terminals[i] & " >/dev/null 2>&1") = 0 then text = terminals[i] if find(text, {"gnome-terminal", "xfce4-terminal"}) then text &= " -x" else text &= " -e" end if add(text_entry, {text}) n += 1 if equal(text, terminal_program) then set(text_entry, "active", n) end if end if end for if length(terminal_program) and gtk:get(text_entry, "active") = 0 then set(text_entry, "prepend text", terminal_program) set(text_entry, "active", 1) end if show_all(dialog) if set(dialog, "run") = GTK_RESPONSE_OK then terminal_program = gtk:get(text_entry, "active text") end if hide(dialog) return 0 end function function RunTestRun(atom handle) run_testrun = gtk:get(handle, "active") return 0 end function function RunBackground(atom handle) run_background = gtk:get(handle, "active") return 0 end function function RunWaitKey(atom handle) run_waitkey = gtk:get(handle, "active") return 0 end function function HelpAbout() set(about_dialog, "run") set(about_dialog, "hide") return 0 end function function HelpReleaseNotes() release_notes() return 0 end function function HelpTutorial() open_tutorial() return 0 end function function HelpHelp() context_help() return 0 end function -------------------------------------- -- functions called from window events -- this gets called when window is moved or resized function configure_event(atom w, atom s) atom left_margin, top_margin -- not used, just for show {left_margin, top_margin, x_size, y_size} = gtk:get(gtk:get(w, "window"), "geometry") {x_pos, y_pos} = gtk:get(w, "position") return 0 end function -- called before window is closed, return TRUE to prevent close, otherwise FALSE function delete_event() if save_modified_tabs() then save_wee_conf(wee_conf_file) return FALSE end if return TRUE end function function window_set_focus(atom widget) check_externally_modified_tabs() check_ex_err() return 0 end function ------------------------------------------------------------- constant group = create(GtkAccelGroup), win = create(GtkWindow, { {"border width", 0}, {"add accel group", group}, {"default size", x_size, y_size}, {"move", x_pos, y_pos}}), panel = create(GtkBox, VERTICAL) gtk_func("gtk_window_set_icon", {P,P}, {win, wee_icon}) connect(win, "destroy", main_quit) connect(win, "configure-event", callback("configure_event")) connect(win, "delete-event", callback("delete_event")) connect(win, "focus-in-event", callback("window_set_focus")) add(win, panel) constant about_dialog = create(GtkAboutDialog, { {"transient for", win}, {"name", "about:dialog"}, {"program name", window_title}, {"comments", "A small editor for Euphoria programming."}, {"version", wee:version}, {"authors", {author, "EuGTK by Irv Mullins http://sites.google.com/site/euphoriagtk/Home/"}}, {"website", "https://github.com/peberlein/WEE/"}, {"website label", "Wee on GitHub"}, {"logo", wee_icon} }) constant menubar = create(GtkMenuBar), menuFile = create(GtkMenuItem, "_File"), menuEdit = create(GtkMenuItem, "_Edit"), menuSearch = create(GtkMenuItem, "_Search"), menuView = create(GtkMenuItem, "_View"), menuRun = create(GtkMenuItem, "_Run"), menuOptions = create(GtkMenuItem, "_Options"), menuHelp = create(GtkMenuItem, "_Help"), filemenu = create(GtkMenu, {{"accel group", group}}), editmenu = create(GtkMenu, {{"accel group", group}}), searchmenu = create(GtkMenu, {{"accel group", group}}), viewmenu = create(GtkMenu, {{"accel group", group}}), runmenu = create(GtkMenu, {{"accel group", group}}), optionsmenu = create(GtkMenu, {{"accel group", group}}), helpmenu = create(GtkMenu, {{"accel group", group}}), chooseinterpreter_menu = create(GtkMenu), tabmenu = create(GtkMenu) -- create a menu item with "activate" signal connected to local routine -- and add parsed accelerator key function createmenuitem(sequence text, object r, object key = 0, object check = -1) atom widget, x if equal(check, -1) or sequence(check) then if sequence(key) then widget = create(GtkMenuItem, text, 0, 0, {group, key}) if sequence(check) then -- check could be a second accelerator set(widget, "add accelerator", {group, check}) end if else widget = create(GtkMenuItem, text) end if else widget = create(GtkCheckMenuItem, text) set(widget, "active", check) end if if sequence(r) then x = routine_id(r) if x <= 0 then crash(r &" is not a visible function") end if r = call_back(x) end if connect(widget, "activate", r) return widget end function add(filemenu, { createmenuitem("_New", "FileNew", "N"), createmenuitem("_Open...", "FileOpen", "O"), createmenuitem("_Save", "FileSave", "S"), createmenuitem("Save _As...", "FileSaveAs", "S"), createmenuitem("_Close", "FileClose", "W"), create(GtkSeparatorMenuItem), createmenuitem("_Quit", "FileQuit", "Q") }) set(menuFile, "submenu", filemenu) add(editmenu, { createmenuitem("_Undo", "EditUndo", "Z"), createmenuitem("_Redo", "EditRedo", "Z"), create(GtkSeparatorMenuItem), createmenuitem("_Cut", "EditCut", "X"), createmenuitem("C_opy", "EditCopy", "C"), createmenuitem("_Paste", "EditPaste", "V"), createmenuitem("Clear", "EditClear"), createmenuitem("Select _All", "EditSelectAll", "A"), create(GtkSeparatorMenuItem), createmenuitem("Toggle Comment", "EditToggleComment", "M") }) set(menuEdit, "submenu", editmenu) add(searchmenu, { createmenuitem("Find...", "SearchFind", "F", "F3"), createmenuitem("Find Next", "SearchFindNext", "G", "F3"), createmenuitem("Find Previous", "SearchFindPrevious", "G", "F3"), createmenuitem("Replace...", "SearchReplace", "R") }) set(menuSearch, "submenu", searchmenu) add(viewmenu, { createmenuitem("Subroutines...", "ViewSubs", "F2"), createmenuitem("Declaration", "ViewDecl", "D", "F2"), createmenuitem("Subroutine Arguments...", "ViewArgs", "F2"), createmenuitem("Completions...", "ViewComp", "space"), createmenuitem("Goto Error", "ViewError", "F4"), createmenuitem("Go Back", "GoBack", "Escape") }) set(menuView, "submenu", viewmenu) add(runmenu, { createmenuitem("Start", "RunStart", "F5"), createmenuitem("Start with Arguments", "RunStart", "F5"), createmenuitem("Set Arguments...", "RunSetArguments"), createmenuitem("Run In Background", "RunBackground", 0, run_background), create(GtkSeparatorMenuItem), createmenuitem("Set Interpreter...", "RunSetInterpreter"), createmenuitem("Set Terminal Emulator...", "RunSetTerminal"), createmenuitem("Run Terminal Emulator", "RunStart"), createmenuitem("Wait for Keypress after Run in Terminal", "RunWaitKey", 0, run_waitkey), create(GtkSeparatorMenuItem), createmenuitem("Bind", "RunStart"), createmenuitem("Shroud", "RunStart"), createmenuitem("Translate and Compile", "RunStart", "F9"), create(GtkSeparatorMenuItem), createmenuitem("Test Run after Bind/Shroud/Translate", "RunTestRun", 0, run_testrun) }) set(menuRun, "submenu", runmenu) add(optionsmenu, { createmenuitem("Font...", "OptionsFont"), createmenuitem("Line Numbers", "OptionsLineNumbers", 0, line_numbers), createmenuitem("Sort View Subroutines", "OptionsSortedSubs", 0, sorted_subs), createmenuitem("Colors...", "OptionsColors"), createmenuitem("Line Wrap", "OptionsLineWrap", 0, line_wrap), createmenuitem("Reopen Tabs Next Time", "OptionsReopenTabs", 0, reopen_tabs), createmenuitem("Complete Statements", "OptionsCompleteStatements", 0, complete_statements), createmenuitem("Complete Braces", "OptionsCompleteBraces", 0, complete_braces), createmenuitem("Indent...", "OptionsIndent"), createmenuitem("Error Indicators", "OptionsErrorIndicators", 0, auto_indicator) }) set(menuOptions, "submenu", optionsmenu) add(helpmenu, { createmenuitem("About...", "HelpAbout"), createmenuitem("Release Notes...", "HelpReleaseNotes"), createmenuitem("Tutorial", "HelpTutorial"), createmenuitem("Help", "HelpHelp", "F1") }) set(menuHelp, "submenu", helpmenu) -- popup menu for tab controls add(tabmenu, { createmenuitem("Save", "FileSave"), createmenuitem("Save As...", "FileSaveAs"), createmenuitem("Close", "FileClose") }) show_all(tabmenu) add(menubar, { menuFile, menuEdit, menuSearch, menuView, menuRun, menuOptions, menuHelp}) pack(panel, menubar) ------------------------------------------------- function NotebookSwitchPage(atom nb, atom page, atom page_num) select_tab(page_num + 1) return 0 end function function NotebookButtonPressEvent(atom nb, atom event) integer button = events:button(event) -- right click or middle click if button = 3 or button = 2 then atom x, y, lx, ly, lw, lh {x,y} = events:xy(event) -- get mouse coordinates atom allocation = allocate(4*4) for i = 1 to gtk:get(nb, "n_pages") do atom pg = gtk:get(nb, "nth_page", i-1) atom lbl = gtk:get(nb, "tab_label", pg) gtk_func("gtk_widget_get_allocation", {P,P}, {lbl, allocation}) {lx, ly, lw, lh} = peek4u({allocation, 4}) -- get label rect if x >= lx-10 and x <= lx+lw+10 then if button = 3 then -- right click select_tab(i) set(tabmenu, "popup", NULL, NULL, NULL, NULL, 0, events:time(event)) elsif button = 2 then -- middle click select_tab(i) close_tab() end if exit end if end for free(allocation) return 1 end if return 0 end function -- switch tabs when mouse wheel is scrolled function NotebookScrollEvent(atom nb, atom event) integer dir = events:scroll_dir(event) if dir = 1 then select_tab(get_next_tab()) elsif dir = 0 then select_tab(get_prev_tab()) end if return 0 end function -- detect Tab and Tab function NotebookKeyPressEvent(atom nb, atom event) integer mod = events:state(event) if key(event) = -9 and and_bits(mod, GDK_CONTROL_MASK) then if and_bits(mod, GDK_SHIFT_MASK) then select_tab(get_prev_tab()) else select_tab(get_next_tab()) end if return 1 end if return 0 end function constant status_label = create(GtkLabel, "status"), notebook = create(GtkNotebook, { {"add_events", GDK_SCROLL_MASK}, {"scrollable", TRUE}, {"show border", FALSE}, -- seems to have opposite effect? {"action widget", status_label, GTK_PACK_END}}) pack(panel, notebook, TRUE, TRUE) show(status_label) connect(notebook, "switch-page", callback("NotebookSwitchPage")) connect(notebook, "button-press-event", callback("NotebookButtonPressEvent")) connect(notebook, "scroll-event", callback("NotebookScrollEvent")) connect(notebook, "key-press-event", callback("NotebookKeyPressEvent")) -------------------------------------------------- sequence ui_hedits ui_hedits = {} function tab_hedit() integer tab tab = gtk:get(notebook, "current page") return ui_hedits[tab+1] end function -------------------------------------------------- global procedure ui_update_status(sequence status) set(status_label, "text", status) end procedure function file_open_recent(atom handle, integer idx) open_recent(idx) return 0 end function sequence filemenu_items = {} global procedure ui_refresh_file_menu(sequence items) atom widget if length(filemenu_items) = 0 and length(items) != 0 then add(filemenu, create(GtkSeparatorMenuItem)) end if for i = 1 to length(items) do if i > length(filemenu_items) then widget = create(GtkMenuItem, items[i]) set(widget, "use underline", 0) filemenu_items &= widget add(filemenu, widget) connect(widget, "activate", callback("file_open_recent"), i) else set(filemenu_items[i], "label", items[i]) end if end for end procedure global procedure ui_select_tab(integer tab) set(notebook, "current page", tab - 1) gtk_proc("gtk_widget_grab_focus", {P}, ui_hedits[tab]) end procedure global procedure ui_update_window_title(sequence name) set(win, "title", name & " ~ " & window_title) end procedure global procedure ui_update_tab_name(integer tab, sequence name) set(notebook, "tab label text", ui_hedits[tab], name) set(gtk:get(notebook, "tab label", ui_hedits[tab]), "tooltip text", file_name) end procedure constant sci_notify_cb = callback("sci_notify") global function ui_new_tab(sequence name) atom editor editor = scintilla_new() ui_hedits &= editor init_edit(editor) gtk_proc("gtk_widget_show", {P}, editor) set(notebook, "append page", editor, create(GtkLabel, name)) connect(editor, "sci-notify", sci_notify_cb, 0) return editor end function global procedure ui_close_tab(integer tab) set(notebook, "remove page", tab-1) -- remove the window handle ui_hedits = ui_hedits[1..tab-1] & ui_hedits[tab+1..$] end procedure constant filters = { create(GtkFileFilter, { {"name", "Euphoria files"}, {"add pattern", "*.e"}, {"add pattern", "*.ex"}, {"add pattern", "*.exw"}, {"add pattern", "*.ew"}, {"add pattern", "ex.err"}, {"add pattern", "eu.cfg"}}), create(GtkFileFilter, { {"name", "Text files"}, {"add mime type", "text/*"}}), create(GtkFileFilter, { {"name", "All files"}, {"add pattern", "*"}})} atom current_filter = filters[1] global function ui_get_open_file_name() atom dialog sequence filename dialog = create(GtkFileChooserDialog, { {"title", "Open..."}, {"transient for", win}, {"action", GTK_FILE_CHOOSER_ACTION_OPEN}, {"select multiple", TRUE}, {"add button", "gtk-cancel", GTK_RESPONSE_CLOSE}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"position", GTK_WIN_POS_MOUSE}, {"current folder", pathname(canonical_path(file_name))}}) add(dialog, filters) set(dialog, "filter", current_filter) if gtk:get(dialog, "run") = GTK_RESPONSE_OK then current_filter = gtk:get(dialog, "filter") filename = gtk:get(dialog, "filenames") if length(filename) = 1 then -- single filename selected filename = filename[1] end if else filename = "" end if set(dialog, "hide") return filename end function global function ui_get_save_file_name(sequence filename) atom dialog dialog = create(GtkFileChooserDialog, { {"title", "Save As..."}, {"transient for", win}, {"action", GTK_FILE_CHOOSER_ACTION_SAVE}, {"select multiple", FALSE}, {"do overwrite confirmation", TRUE}, {"add button", "gtk-cancel", GTK_RESPONSE_CLOSE}, {"add button", "gtk-ok", GTK_RESPONSE_OK}, {"filename", filename}, {"position", GTK_WIN_POS_MOUSE}, {"current folder", pathname(canonical_path(file_name))}}) add(dialog, filters) if gtk:get(dialog, "run") = GTK_RESPONSE_OK then current_filter = gtk:get(dialog, "filter") filename = gtk:get(dialog, "filename") else filename = "" end if set(dialog, "hide") return filename end function -- returns yes=1 no=0 global function ui_message_box_yes_no(sequence title, sequence message) integer result result = Question(win, title, "", message) return (result = MB_YES) end function -- returns yes=1 no=0 cancel=-1 global function ui_message_box_yes_no_cancel(sequence title, sequence message) atom dialog, result dialog = create(GtkMessageDialog, { {"title", title}, {"transient for", win}, {"add button", "gtk-cancel", -1}, {"add button", "gtk-no", 0}, {"add button", "gtk-yes", 1}, {"transient for", win}, {"destroy with parent", TRUE}, {"text", message}, {"position", GTK_WIN_POS_CENTER_ON_PARENT}}) result = gtk:get(dialog, "run") set(dialog, "hide") return result end function global function ui_message_box_error(sequence title, sequence message) Error(win, title, , message, GTK_BUTTONS_OK) return 0 end function global procedure ui_view_error() sequence err atom dialog, scroll, list, content, row, lbl integer result err = get_ex_err() if length(err) = 0 then return end if dialog = create(GtkDialog, { {"border width", 5}, {"default size", 200, 400}, {"add button", "gtk-close", GTK_RESPONSE_CLOSE}, {"add button", "Open Ex.Err", GTK_RESPONSE_YES}, {"add button", "Goto Error", GTK_RESPONSE_OK}, {"transient for", win}, {"title", "View Error"}, {"default response", GTK_RESPONSE_OK}, {"modal", TRUE}}) content = gtk:get(dialog, "content area") lbl = create(GtkLabel, err[2]) pack(content, lbl) content = gtk:get(dialog, "content area") scroll = create(GtkScrolledWindow) pack(content, scroll, TRUE, TRUE) list = create(GtkListBox) add(scroll, list) for i = 3 to length(err) do lbl = create(GtkLabel, err[i]) set(lbl, "halign", GTK_ALIGN_START) set(list, "insert", lbl, -1) end for show_all(dialog) result = set(dialog, "run") if result = GTK_RESPONSE_OK then row = gtk:get(list, "selected row") --result = gtk:get(row, "index") -- doesn't work? for i = 0 to length(err)-3 do if row = gtk:get(list, "row at index", i) then goto_error(err, i+1) exit end if end for elsif result = GTK_RESPONSE_YES then open_file(ex_err_name, 1) end if hide(dialog) end procedure -------------------------------------------------- -- help window function HelpActivateLink(atom handle, atom uri, atom userdata) puts(1, peek_string(uri)&"\n") return 1 end function function Hide(atom handle) set(handle,"visible",FALSE) return 1 end function constant helpwin = create(GtkWindow, { {"transient for", win}, {"title", "Help"}, {"default size", 400, 400}, {"border width", 10}, {"deletable", FALSE}, --! {"resizable", FALSE}}) connect(helpwin, "delete-event", callback("Hide")) constant helplbl = create(GtkLabel) add(helpwin,helplbl) connect(helplbl, "activate-link", callback("HelpActivateLink")) function re(sequence txt, sequence rx, sequence rep) return regex:find_replace(regex:new(rx), txt, rep) end function -- FIXME this doesn't work very well function html_to_markup(sequence html) html = re(html, `([A-Za-z0-9. ]*)`, `\1`) html = re(html, `

?`, ``) html = re(html, `

`, ``) html = re(html, ``, ``) html = re(html, `
`, ``)
    html = re(html, `
`, ``) html = re(html, `
`, ``) html = re(html, `
`, ``) html = re(html, `
    `, `\n`) html = re(html, `
`, ``) html = re(html, `
  • `, ` 1. `) html = re(html, `
  • `, `\n`) html = re(html, `
      `, `\n`) html = re(html, `
    `, ``) html = re(html, `\n\n+`, `\n\n`) puts(1, html) return html end function global function ui_show_help(sequence html) set(helplbl,"markup",html_to_markup(html)) show_all(helpwin) return 0 end function global procedure ui_show_uri(sequence uri) show_uri(uri) end procedure -------------------------------------------------- ui_refresh_file_menu(recent_files) -- open files from last time and on command line open_tabs() show_all(win) main()