1011 lines
34 KiB
Nim
1011 lines
34 KiB
Nim
import glib2, gtk2, gdk2, gtksourceview, dialogs, os, pango, osproc, strutils
|
|
import pegs, streams
|
|
import settings, types, cfg, search
|
|
|
|
{.push callConv:cdecl.}
|
|
|
|
const
|
|
NimrodProjectExt = ".nimprj"
|
|
|
|
var win: types.MainWin
|
|
win.Tabs = @[]
|
|
|
|
search.win = addr(win)
|
|
|
|
var lastSession: seq[string] = @[]
|
|
|
|
var confParseFail = False # This gets set to true
|
|
# When there is an error parsing the config
|
|
|
|
# Load the settings
|
|
try:
|
|
win.settings = cfg.load(lastSession)
|
|
except ECFGParse:
|
|
# TODO: Make the dialog show the exception
|
|
confParseFail = True
|
|
win.settings = cfg.defaultSettings()
|
|
except EIO:
|
|
win.settings = cfg.defaultSettings()
|
|
|
|
proc getProjectTab(): int =
|
|
for i in 0..high(win.tabs):
|
|
if win.tabs[i].filename.endswith(NimrodProjectExt): return i
|
|
|
|
proc saveTab(tabNr: int, startpath: string) =
|
|
if tabNr < 0: return
|
|
if win.Tabs[tabNr].saved: return
|
|
var path = ""
|
|
if win.Tabs[tabNr].filename == "":
|
|
path = ChooseFileToSave(win.w, startpath)
|
|
# dialogs.nim STOCK_OPEN instead of STOCK_SAVE
|
|
else:
|
|
path = win.Tabs[tabNr].filename
|
|
|
|
if path != "":
|
|
var buffer = PTextBuffer(win.Tabs[tabNr].buffer)
|
|
# Get the text from the TextView
|
|
var startIter: TTextIter
|
|
buffer.getStartIter(addr(startIter))
|
|
|
|
var endIter: TTextIter
|
|
buffer.getEndIter(addr(endIter))
|
|
|
|
var text = buffer.getText(addr(startIter), addr(endIter), False)
|
|
# Save it to a file
|
|
var f: TFile
|
|
if open(f, path, fmWrite):
|
|
f.write(text)
|
|
f.close()
|
|
|
|
win.tempStuff.lastSaveDir = splitFile(path).dir
|
|
|
|
# Change the tab name and .Tabs.filename etc.
|
|
win.Tabs[tabNr].filename = path
|
|
win.Tabs[tabNr].saved = True
|
|
var name = extractFilename(path)
|
|
|
|
var cTab = win.Tabs[tabNr]
|
|
cTab.label.setText(name)
|
|
else:
|
|
error(win.w, "Unable to write to file")
|
|
|
|
proc saveAllTabs() =
|
|
for i in 0..high(win.tabs):
|
|
saveTab(i, os.splitFile(win.tabs[i].filename).dir)
|
|
|
|
# GTK Events
|
|
# -- w(PWindow)
|
|
proc destroy(widget: PWidget, data: pgpointer) {.cdecl.} =
|
|
# gather some settings
|
|
win.settings.VPanedPos = PPaned(win.sourceViewTabs.getParent()).getPosition()
|
|
win.settings.winWidth = win.w.allocation.width
|
|
win.settings.winHeight = win.w.allocation.height
|
|
|
|
# save the settings
|
|
win.save()
|
|
# then quit
|
|
main_quit()
|
|
|
|
proc delete_event(widget: PWidget, event: PEvent, user_data: pgpointer): bool =
|
|
var quit = True
|
|
for i in low(win.Tabs)..len(win.Tabs)-1:
|
|
if not win.Tabs[i].saved:
|
|
var askSave = dialogNewWithButtons("", win.w, 0,
|
|
STOCK_SAVE, RESPONSE_ACCEPT, STOCK_CANCEL,
|
|
RESPONSE_CANCEL,
|
|
"Close without saving", RESPONSE_REJECT, nil)
|
|
askSave.setTransientFor(win.w)
|
|
# TODO: Make this dialog look better
|
|
var label = labelNew(win.Tabs[i].filename &
|
|
" is unsaved, would you like to save it ?")
|
|
PBox(askSave.vbox).pack_start(label, False, False, 0)
|
|
label.show()
|
|
|
|
var resp = askSave.run()
|
|
gtk2.destroy(PWidget(askSave))
|
|
case resp
|
|
of RESPONSE_ACCEPT:
|
|
saveTab(i, os.splitFile(win.tabs[i].filename).dir)
|
|
quit = True
|
|
of RESPONSE_CANCEL:
|
|
quit = False
|
|
break
|
|
of RESPONSE_REJECT:
|
|
quit = True
|
|
else:
|
|
quit = False
|
|
break
|
|
|
|
# If False is returned the window will close
|
|
return not quit
|
|
|
|
proc windowState_Changed(widget: PWidget, event: PEventWindowState,
|
|
user_data: pgpointer) =
|
|
win.settings.winMaximized = (event.newWindowState and
|
|
WINDOW_STATE_MAXIMIZED) != 0
|
|
|
|
# -- SourceView(PSourceView) & SourceBuffer
|
|
proc updateStatusBar(buffer: PTextBuffer){.cdecl.} =
|
|
# Incase this event gets fired before
|
|
# bottomBar is initialized
|
|
if win.bottomBar != nil and not win.tempStuff.stopSBUpdates:
|
|
var iter: TTextIter
|
|
|
|
win.bottomBar.pop(0)
|
|
buffer.getIterAtMark(addr(iter), buffer.getInsert())
|
|
var row = getLine(addr(iter)) + 1
|
|
var col = getLineOffset(addr(iter))
|
|
discard win.bottomBar.push(0, "Line: " & $row & " Column: " & $col)
|
|
|
|
proc cursorMoved(buffer: PTextBuffer, location: PTextIter,
|
|
mark: PTextMark, user_data: pgpointer){.cdecl.} =
|
|
updateStatusBar(buffer)
|
|
|
|
proc onCloseTab(btn: PButton, user_data: PWidget) =
|
|
if win.sourceViewTabs.getNPages() > 1:
|
|
var tab = win.sourceViewTabs.pageNum(user_data)
|
|
win.sourceViewTabs.removePage(tab)
|
|
|
|
win.Tabs.delete(tab)
|
|
|
|
proc onSwitchTab(notebook: PNotebook, page: PNotebookPage, pageNum: guint,
|
|
user_data: pgpointer) =
|
|
if win.Tabs.len()-1 >= pageNum:
|
|
win.w.setTitle("Aporia IDE - " & win.Tabs[pageNum].filename)
|
|
|
|
proc createTabLabel(name: string, t_child: PWidget): tuple[box: PWidget,
|
|
label: PLabel] =
|
|
var box = hboxNew(False, 0)
|
|
var label = labelNew(name)
|
|
var closebtn = buttonNew()
|
|
closeBtn.setLabel(nil)
|
|
var iconSize = iconSizeFromName("tabIconSize")
|
|
if iconSize == 0:
|
|
iconSize = iconSizeRegister("tabIconSize", 10, 10)
|
|
var image = imageNewFromStock(STOCK_CLOSE, iconSize)
|
|
discard gSignalConnect(closebtn, "clicked", G_Callback(onCloseTab), t_child)
|
|
closebtn.setImage(image)
|
|
gtk2.setRelief(closebtn, RELIEF_NONE)
|
|
box.packStart(label, True, True, 0)
|
|
box.packEnd(closebtn, False, False, 0)
|
|
box.showAll()
|
|
return (box, label)
|
|
|
|
proc changed(buffer: PTextBuffer, user_data: pgpointer) =
|
|
# Update the 'Line & Column'
|
|
#updateStatusBar(buffer)
|
|
|
|
# Change the tabs state to 'unsaved'
|
|
# and add '*' to the Tab Name
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
var name = ""
|
|
if win.Tabs[current].filename == "":
|
|
win.Tabs[current].saved = False
|
|
name = "Untitled *"
|
|
else:
|
|
win.Tabs[current].saved = False
|
|
name = extractFilename(win.Tabs[current].filename) & " *"
|
|
|
|
var cTab = win.Tabs[current]
|
|
cTab.label.setText(name)
|
|
|
|
# Other(Helper) functions
|
|
|
|
proc initSourceView(SourceView: var PWidget, scrollWindow: var PScrolledWindow,
|
|
buffer: var PSourceBuffer) =
|
|
# This gets called by addTab
|
|
# Each tabs creates a new SourceView
|
|
# SourceScrolledWindow(ScrolledWindow)
|
|
scrollWindow = scrolledWindowNew(nil, nil)
|
|
scrollWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
|
scrollWindow.show()
|
|
|
|
# SourceView(gtkSourceView)
|
|
SourceView = sourceViewNew(buffer)
|
|
PSourceView(SourceView).setInsertSpacesInsteadOfTabs(True)
|
|
PSourceView(SourceView).setIndentWidth(win.settings.indentWidth)
|
|
PSourceView(SourceView).setShowLineNumbers(win.settings.showLineNumbers)
|
|
PSourceView(SourceView).setHighlightCurrentLine(
|
|
win.settings.highlightCurrentLine)
|
|
PSourceView(SourceView).setShowRightMargin(win.settings.rightMargin)
|
|
PSourceView(SourceView).setAutoIndent(win.settings.autoIndent)
|
|
|
|
var font = font_description_from_string(win.settings.font)
|
|
SourceView.modifyFont(font)
|
|
|
|
scrollWindow.add(SourceView)
|
|
SourceView.show()
|
|
|
|
buffer.setHighlightMatchingBrackets(
|
|
win.settings.highlightMatchingBrackets)
|
|
|
|
# UGLY workaround for yet another compiler bug:
|
|
discard gsignalConnect(buffer, "mark-set",
|
|
GCallback(aporia.cursorMoved), nil)
|
|
discard gsignalConnect(buffer, "changed", GCallback(aporia.changed), nil)
|
|
|
|
# -- Set the syntax highlighter scheme
|
|
buffer.setScheme(win.scheme)
|
|
|
|
proc addTab(name, filename: string) =
|
|
## Adds a tab, if filename is not "" reads the file. And sets
|
|
## the tabs SourceViews text to that files contents.
|
|
assert(win.nimLang != nil)
|
|
var buffer: PSourceBuffer = sourceBufferNew(win.nimLang)
|
|
|
|
if filename != nil and filename != "":
|
|
var lang = win.langMan.guessLanguage(filename, nil)
|
|
if lang != nil:
|
|
buffer.setLanguage(lang)
|
|
else:
|
|
buffer.setHighlightSyntax(False)
|
|
|
|
var nam = name
|
|
if nam == "": nam = "Untitled"
|
|
if filename == "": nam.add(" *")
|
|
elif filename != "" and name == "":
|
|
# Disable the undo/redo manager.
|
|
buffer.begin_not_undoable_action()
|
|
|
|
# Load the file.
|
|
var file: string = readFile(filename)
|
|
if file != nil:
|
|
buffer.set_text(file, len(file))
|
|
|
|
# Enable the undo/redo manager.
|
|
buffer.end_not_undoable_action()
|
|
|
|
# Get the name.ext of the filename, for the tabs title
|
|
nam = extractFilename(filename)
|
|
|
|
# Init the sourceview
|
|
var sourceView: PWidget
|
|
var scrollWindow: PScrolledWindow
|
|
initSourceView(sourceView, scrollWindow, buffer)
|
|
|
|
var (TabLabel, labelText) = createTabLabel(nam, scrollWindow)
|
|
# Add a tab
|
|
discard win.SourceViewTabs.appendPage(scrollWindow, TabLabel)
|
|
|
|
var nTab: Tab
|
|
nTab.buffer = buffer
|
|
nTab.sourceView = sourceView
|
|
nTab.label = labelText
|
|
nTab.saved = (filename != "")
|
|
nTab.filename = filename
|
|
win.Tabs.add(nTab)
|
|
|
|
PTextView(SourceView).setBuffer(nTab.buffer)
|
|
|
|
# GTK Events Contd.
|
|
# -- TopMenu & TopBar
|
|
|
|
proc newFile(menuItem: PMenuItem, user_data: pgpointer) =
|
|
addTab("", "")
|
|
win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
|
|
|
|
proc openFile(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var startpath = ""
|
|
var currPage = win.SourceViewTabs.getCurrentPage()
|
|
if currPage <% win.tabs.len:
|
|
startpath = os.splitFile(win.tabs[currPage].filename).dir
|
|
|
|
if startpath.len == 0:
|
|
# Use lastSavePath as the startpath
|
|
startpath = win.tempStuff.lastSaveDir
|
|
if startpath.len == 0:
|
|
startpath = os.getHomeDir()
|
|
|
|
var files = ChooseFilesToOpen(win.w, startpath)
|
|
if files.len() > 0:
|
|
for f in items(files):
|
|
try:
|
|
addTab("", f)
|
|
except EIO:
|
|
error(win.w, "Unable to read from file")
|
|
# Switch to the newly created tab
|
|
win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
|
|
|
|
proc saveFile_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
saveTab(current, os.splitFile(win.tabs[current].filename).dir)
|
|
|
|
proc saveFileAs_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
var (filename, saved) = (win.Tabs[current].filename, win.Tabs[current].saved)
|
|
|
|
win.Tabs[current].saved = False
|
|
win.Tabs[current].filename = ""
|
|
saveTab(current, os.splitFile(filename).dir)
|
|
# If the user cancels the save file dialog. Restore the previous filename
|
|
# and saved state
|
|
if win.Tabs[current].filename == "":
|
|
win.Tabs[current].filename = filename
|
|
win.Tabs[current].saved = saved
|
|
|
|
proc undo(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[current].buffer.canUndo():
|
|
win.Tabs[current].buffer.undo()
|
|
|
|
proc redo(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[current].buffer.canRedo():
|
|
win.Tabs[current].buffer.redo()
|
|
|
|
proc find_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
# Get the selected text, and set the findEntry to it.
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
var insertIter: TTextIter
|
|
win.Tabs[currentTab].buffer.getIterAtMark(addr(insertIter),
|
|
win.Tabs[currentTab].buffer.getInsert())
|
|
var insertOffset = addr(insertIter).getOffset()
|
|
|
|
var selectIter: TTextIter
|
|
win.Tabs[currentTab].buffer.getIterAtMark(addr(selectIter),
|
|
win.Tabs[currentTab].buffer.getSelectionBound())
|
|
var selectOffset = addr(selectIter).getOffset()
|
|
|
|
if insertOffset != selectOffset:
|
|
var text = win.Tabs[currentTab].buffer.getText(addr(insertIter),
|
|
addr(selectIter), false)
|
|
win.findEntry.setText(text)
|
|
|
|
win.findBar.show()
|
|
win.findEntry.grabFocus()
|
|
win.replaceEntry.hide()
|
|
win.replaceLabel.hide()
|
|
win.replaceBtn.hide()
|
|
win.replaceAllBtn.hide()
|
|
|
|
proc replace_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
win.findBar.show()
|
|
win.findEntry.grabFocus()
|
|
win.replaceEntry.show()
|
|
win.replaceLabel.show()
|
|
win.replaceBtn.show()
|
|
win.replaceAllBtn.show()
|
|
|
|
proc settings_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
settings.showSettings(win)
|
|
|
|
proc viewBottomPanel_Toggled(menuitem: PCheckMenuItem, user_data: pgpointer) =
|
|
win.settings.bottomPanelVisible = menuitem.itemGetActive()
|
|
if win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
else:
|
|
win.bottomPanelTabs.hide()
|
|
|
|
var
|
|
pegLineError = peg"{[^(]*} '(' {\d+} ', ' \d+ ') Error:' \s* {.*}"
|
|
pegLineWarning = peg"{[^(]*} '(' {\d+} ', ' \d+ ') ' ('Warning:'/'Hint:') \s* {.*}"
|
|
pegOtherError = peg"'Error:' \s* {.*}"
|
|
pegSuccess = peg"'Hint: operation successful'.*"
|
|
|
|
proc addText(textView: PTextView, text: string, colorTag: PTextTag = nil) =
|
|
if text != nil:
|
|
var iter: TTextIter
|
|
textView.getBuffer().getEndIter(addr(iter))
|
|
|
|
if colorTag == nil:
|
|
textView.getBuffer().insert(addr(iter), text, len(text))
|
|
else:
|
|
textView.getBuffer().insertWithTags(addr(iter), text, len(text), colorTag,
|
|
nil)
|
|
|
|
proc createColor(textView: PTextView, name, color: string): PTextTag =
|
|
var tagTable = textView.getBuffer().getTagTable()
|
|
result = tagTable.tableLookup(name)
|
|
if result == nil:
|
|
result = textView.getBuffer().createTag(name, "foreground", color, nil)
|
|
|
|
when not defined(os.findExe):
|
|
proc findExe(exe: string): string =
|
|
## returns "" if the exe cannot be found
|
|
result = addFileExt(exe, os.exeExt)
|
|
if ExistsFile(result): return
|
|
var path = os.getEnv("PATH")
|
|
for candidate in split(path, pathSep):
|
|
var x = candidate / result
|
|
if ExistsFile(x): return x
|
|
result = ""
|
|
|
|
proc GetCmd(cmd, filename: string): string =
|
|
var f = quoteIfContainsWhite(filename)
|
|
if cmd =~ peg"\s* '$' y'findExe' '(' {[^)]+} ')' {.*}":
|
|
var exe = quoteIfContainsWhite(findExe(matches[0]))
|
|
if exe.len == 0: exe = matches[0]
|
|
result = exe & " " & matches[1] % f
|
|
else:
|
|
result = cmd % f
|
|
|
|
proc showBottomPanel() =
|
|
if not win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
win.settings.bottomPanelVisible = true
|
|
PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(true)
|
|
# Scroll to the end of the TextView
|
|
# This is stupid, it works sometimes... it's random
|
|
var endIter: TTextIter
|
|
win.outputTextView.getBuffer().getEndIter(addr(endIter))
|
|
discard win.outputTextView.scrollToIter(
|
|
addr(endIter), 0.25, False, 0.0, 0.0)
|
|
|
|
proc compileRun(currentTab: int, shouldRun: bool) =
|
|
if win.Tabs[currentTab].filename.len == 0: return
|
|
# Clear the outputTextView
|
|
win.outputTextView.getBuffer().setText("", 0)
|
|
|
|
var outp = osProc.execProcess(GetCmd(win.settings.nimrodCmd,
|
|
win.Tabs[currentTab].filename))
|
|
# Colors
|
|
var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
|
|
var errorTag = createColor(win.outputTextView, "errorTag", "red")
|
|
var warningTag = createColor(win.outputTextView, "warningTag", "darkorange")
|
|
var successTag = createColor(win.outputTextView, "successTag", "darkgreen")
|
|
for x in outp.splitLines():
|
|
if x =~ pegLineError / pegOtherError:
|
|
win.outputTextView.addText("\n" & x, errorTag)
|
|
elif x=~ pegSuccess:
|
|
win.outputTextView.addText("\n" & x, successTag)
|
|
|
|
# Launch the process
|
|
if shouldRun:
|
|
var filename = changeFileExt(win.Tabs[currentTab].filename, os.ExeExt)
|
|
var output = "\n" & osProc.execProcess(filename)
|
|
win.outputTextView.addText(output)
|
|
elif x =~ pegLineWarning:
|
|
win.outputTextView.addText("\n" & x, warningTag)
|
|
else:
|
|
win.outputTextView.addText("\n" & x, normalTag)
|
|
showBottomPanel()
|
|
|
|
proc CompileCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveFile_Activate(nil, nil)
|
|
compileRun(win.SourceViewTabs.getCurrentPage(), false)
|
|
|
|
proc CompileRunCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveFile_Activate(nil, nil)
|
|
compileRun(win.SourceViewTabs.getCurrentPage(), true)
|
|
|
|
proc CompileProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveAllTabs()
|
|
compileRun(getProjectTab(), false)
|
|
|
|
proc CompileRunProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveAllTabs()
|
|
compileRun(getProjectTab(), true)
|
|
|
|
proc RunCustomCommand(cmd: string) =
|
|
saveFile_Activate(nil, nil)
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[currentTab].filename.len == 0 or cmd.len == 0: return
|
|
# Clear the outputTextView
|
|
win.outputTextView.getBuffer().setText("", 0)
|
|
var outp = osProc.execProcess(GetCmd(cmd, win.Tabs[currentTab].filename))
|
|
var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
|
|
for x in outp.splitLines():
|
|
win.outputTextView.addText("\n" & x, normalTag)
|
|
showBottomPanel()
|
|
|
|
proc RunCustomCommand1(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd1)
|
|
|
|
proc RunCustomCommand2(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd2)
|
|
|
|
proc RunCustomCommand3(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd3)
|
|
|
|
# -- FindBar
|
|
|
|
proc nextBtn_Clicked(button: PButton, user_data: pgpointer) = findText(True)
|
|
proc prevBtn_Clicked(button: PButton, user_data: pgpointer) = findText(False)
|
|
|
|
proc replaceBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
var start, theEnd: TTextIter
|
|
if not win.Tabs[currentTab].buffer.getSelectionBounds(
|
|
addr(start), addr(theEnd)):
|
|
# If no text is selected, try finding a match.
|
|
findText(True)
|
|
if not win.Tabs[currentTab].buffer.getSelectionBounds(
|
|
addr(start), addr(theEnd)):
|
|
# No match
|
|
return
|
|
|
|
# Remove the text
|
|
win.Tabs[currentTab].buffer.delete(addr(start), addr(theEnd))
|
|
# Insert the replacement
|
|
var text = getText(win.replaceEntry)
|
|
win.Tabs[currentTab].buffer.insert(addr(start), text, len(text))
|
|
|
|
proc replaceAllBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var find = getText(win.findEntry)
|
|
var replace = getText(win.replaceEntry)
|
|
discard replaceAll(find, replace)
|
|
|
|
proc closeBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
win.findBar.hide()
|
|
|
|
proc caseSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "casesens"
|
|
proc caseInSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "caseinsens"
|
|
proc style_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "style"
|
|
proc regex_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "regex"
|
|
proc peg_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "peg"
|
|
|
|
proc extraBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var extraMenu = menuNew()
|
|
var group: PGSList
|
|
|
|
var caseSensMenuItem = radio_menu_item_new(group, "Case sensitive")
|
|
extraMenu.append(caseSensMenuItem)
|
|
discard signal_connect(caseSensMenuItem, "toggled",
|
|
SIGNAL_FUNC(caseSens_Changed), nil)
|
|
caseSensMenuItem.show()
|
|
group = caseSensMenuItem.ItemGetGroup()
|
|
|
|
var caseInSensMenuItem = radio_menu_item_new(group, "Case insensitive")
|
|
extraMenu.append(caseInSensMenuItem)
|
|
discard signal_connect(caseInSensMenuItem, "toggled",
|
|
SIGNAL_FUNC(caseInSens_Changed), nil)
|
|
caseInSensMenuItem.show()
|
|
group = caseInSensMenuItem.ItemGetGroup()
|
|
|
|
var styleMenuItem = radio_menu_item_new(group, "Style insensitive")
|
|
extraMenu.append(styleMenuItem)
|
|
discard signal_connect(styleMenuItem, "toggled",
|
|
SIGNAL_FUNC(style_Changed), nil)
|
|
styleMenuItem.show()
|
|
group = styleMenuItem.ItemGetGroup()
|
|
|
|
var regexMenuItem = radio_menu_item_new(group, "Regex")
|
|
extraMenu.append(regexMenuItem)
|
|
discard signal_connect(regexMenuItem, "toggled",
|
|
SIGNAL_FUNC(regex_Changed), nil)
|
|
regexMenuItem.show()
|
|
group = regexMenuItem.ItemGetGroup()
|
|
|
|
var pegMenuItem = radio_menu_item_new(group, "Pegs")
|
|
extraMenu.append(pegMenuItem)
|
|
discard signal_connect(pegMenuItem, "toggled",
|
|
SIGNAL_FUNC(peg_Changed), nil)
|
|
pegMenuItem.show()
|
|
|
|
# Make the correct radio button active
|
|
case win.settings.search
|
|
of "casesens":
|
|
PCheckMenuItem(caseSensMenuItem).ItemSetActive(True)
|
|
of "caseinsens":
|
|
PCheckMenuItem(caseInSensMenuItem).ItemSetActive(True)
|
|
of "style":
|
|
PCheckMenuItem(styleMenuItem).ItemSetActive(True)
|
|
of "regex":
|
|
PCheckMenuItem(regexMenuItem).ItemSetActive(True)
|
|
of "peg":
|
|
PCheckMenuItem(pegMenuItem).ItemSetActive(True)
|
|
|
|
extraMenu.popup(nil, nil, nil, nil, 0, get_current_event_time())
|
|
|
|
# GUI Initialization
|
|
|
|
proc createAccelMenuItem(toolsMenu: PMenu, accGroup: PAccelGroup,
|
|
label: string, acc: gint,
|
|
action: proc (i: PMenuItem, p: pgpointer)) =
|
|
var result = menu_item_new(label)
|
|
result.addAccelerator("activate", accGroup, acc, 0, ACCEL_VISIBLE)
|
|
ToolsMenu.append(result)
|
|
show(result)
|
|
discard signal_connect(result, "activate", SIGNAL_FUNC(action), nil)
|
|
|
|
proc createSeparator(menu: PMenu) =
|
|
var sep = separator_menu_item_new()
|
|
menu.append(sep)
|
|
sep.show()
|
|
|
|
proc initTopMenu(MainBox: PBox) =
|
|
# Create a accelerator group, used for shortcuts
|
|
# like CTRL + S in SaveMenuItem
|
|
var accGroup = accel_group_new()
|
|
add_accel_group(win.w, accGroup)
|
|
|
|
# TopMenu(MenuBar)
|
|
var TopMenu = menuBarNew()
|
|
|
|
# FileMenu
|
|
var FileMenu = menuNew()
|
|
|
|
var NewMenuItem = menu_item_new("New") # New
|
|
FileMenu.append(NewMenuItem)
|
|
show(NewMenuItem)
|
|
discard signal_connect(NewMenuItem, "activate",
|
|
SIGNAL_FUNC(newFile), nil)
|
|
|
|
createSeparator(FileMenu)
|
|
|
|
var OpenMenuItem = menu_item_new("Open...") # Open...
|
|
# CTRL + O
|
|
OpenMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_o, CONTROL_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(OpenMenuItem)
|
|
show(OpenMenuItem)
|
|
discard signal_connect(OpenMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.openFile), nil)
|
|
|
|
var SaveMenuItem = menu_item_new("Save") # Save
|
|
# CTRL + S
|
|
SaveMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_s, CONTROL_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(SaveMenuItem)
|
|
show(SaveMenuItem)
|
|
discard signal_connect(SaveMenuItem, "activate",
|
|
SIGNAL_FUNC(saveFile_activate), nil)
|
|
|
|
var SaveAsMenuItem = menu_item_new("Save As...") # Save as...
|
|
|
|
SaveAsMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_s, CONTROL_MASK or gdk2.SHIFT_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(SaveAsMenuItem)
|
|
show(SaveAsMenuItem)
|
|
discard signal_connect(SaveAsMenuItem, "activate",
|
|
SIGNAL_FUNC(saveFileAs_Activate), nil)
|
|
|
|
var FileMenuItem = menuItemNewWithMnemonic("_File")
|
|
|
|
FileMenuItem.setSubMenu(FileMenu)
|
|
FileMenuItem.show()
|
|
TopMenu.append(FileMenuItem)
|
|
|
|
# Edit menu
|
|
var EditMenu = menuNew()
|
|
|
|
var UndoMenuItem = menu_item_new("Undo") # Undo
|
|
EditMenu.append(UndoMenuItem)
|
|
show(UndoMenuItem)
|
|
discard signal_connect(UndoMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.undo), nil)
|
|
|
|
var RedoMenuItem = menu_item_new("Redo") # Undo
|
|
EditMenu.append(RedoMenuItem)
|
|
show(RedoMenuItem)
|
|
discard signal_connect(RedoMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.redo), nil)
|
|
|
|
createSeparator(EditMenu)
|
|
|
|
var FindMenuItem = menu_item_new("Find") # Find
|
|
FindMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_f, CONTROL_MASK, ACCEL_VISIBLE)
|
|
EditMenu.append(FindMenuItem)
|
|
show(FindMenuItem)
|
|
discard signal_connect(FindMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.find_Activate), nil)
|
|
|
|
var ReplaceMenuItem = menu_item_new("Replace") # Replace
|
|
ReplaceMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_h, CONTROL_MASK, ACCEL_VISIBLE)
|
|
EditMenu.append(ReplaceMenuItem)
|
|
show(ReplaceMenuItem)
|
|
discard signal_connect(ReplaceMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.replace_Activate), nil)
|
|
|
|
createSeparator(EditMenu)
|
|
|
|
var SettingsMenuItem = menu_item_new("Settings...") # Settings
|
|
EditMenu.append(SettingsMenuItem)
|
|
show(SettingsMenuItem)
|
|
discard signal_connect(SettingsMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.Settings_Activate), nil)
|
|
|
|
var EditMenuItem = menuItemNewWithMnemonic("_Edit")
|
|
|
|
EditMenuItem.setSubMenu(EditMenu)
|
|
EditMenuItem.show()
|
|
TopMenu.append(EditMenuItem)
|
|
|
|
# View menu
|
|
var ViewMenu = menuNew()
|
|
|
|
win.viewBottomPanelMenuItem = check_menu_item_new("Bottom Panel")
|
|
PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(
|
|
win.settings.bottomPanelVisible)
|
|
win.viewBottomPanelMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_f9, CONTROL_MASK, ACCEL_VISIBLE)
|
|
ViewMenu.append(win.viewBottomPanelMenuItem)
|
|
show(win.viewBottomPanelMenuItem)
|
|
discard signal_connect(win.viewBottomPanelMenuItem, "toggled",
|
|
SIGNAL_FUNC(aporia.viewBottomPanel_Toggled), nil)
|
|
|
|
var ViewMenuItem = menuItemNewWithMnemonic("_View")
|
|
|
|
ViewMenuItem.setSubMenu(ViewMenu)
|
|
ViewMenuItem.show()
|
|
TopMenu.append(ViewMenuItem)
|
|
|
|
|
|
# Tools menu
|
|
var ToolsMenu = menuNew()
|
|
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile current file",
|
|
KEY_F4, aporia.CompileCurrent_Activate)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile & run current file",
|
|
KEY_F5, aporia.CompileRunCurrent_Activate)
|
|
createSeparator(ToolsMenu)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile project",
|
|
KEY_F8, aporia.CompileProject_Activate)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile & run project",
|
|
KEY_F9, aporia.CompileRunProject_Activate)
|
|
createSeparator(ToolsMenu)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 1",
|
|
KEY_F1, aporia.RunCustomCommand1)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 2",
|
|
KEY_F2, aporia.RunCustomCommand2)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 3",
|
|
KEY_F3, aporia.RunCustomCommand3)
|
|
|
|
var ToolsMenuItem = menuItemNewWithMnemonic("_Tools")
|
|
|
|
ToolsMenuItem.setSubMenu(ToolsMenu)
|
|
ToolsMenuItem.show()
|
|
TopMenu.append(ToolsMenuItem)
|
|
|
|
# Help menu
|
|
MainBox.packStart(TopMenu, False, False, 0)
|
|
TopMenu.show()
|
|
|
|
proc initToolBar(MainBox: PBox) =
|
|
# TopBar(ToolBar)
|
|
var TopBar = toolbarNew()
|
|
TopBar.setStyle(TOOLBAR_ICONS)
|
|
|
|
var NewFileItem = TopBar.insertStock(STOCK_NEW, "New File",
|
|
"New File", SIGNAL_FUNC(aporia.newFile), nil, 0)
|
|
TopBar.appendSpace()
|
|
var OpenItem = TopBar.insertStock(STOCK_OPEN, "Open",
|
|
"Open", SIGNAL_FUNC(aporia.openFile), nil, -1)
|
|
var SaveItem = TopBar.insertStock(STOCK_SAVE, "Save",
|
|
"Save", SIGNAL_FUNC(saveFile_Activate), nil, -1)
|
|
TopBar.appendSpace()
|
|
var UndoItem = TopBar.insertStock(STOCK_UNDO, "Undo",
|
|
"Undo", SIGNAL_FUNC(aporia.undo), nil, -1)
|
|
var RedoItem = TopBar.insertStock(STOCK_REDO, "Redo",
|
|
"Redo", SIGNAL_FUNC(aporia.redo), nil, -1)
|
|
|
|
MainBox.packStart(TopBar, False, False, 0)
|
|
TopBar.show()
|
|
|
|
proc initSourceViewTabs() =
|
|
win.SourceViewTabs = notebookNew()
|
|
#win.sourceViewTabs.dragDestSet(DEST_DEFAULT_DROP, nil, 0, ACTION_MOVE)
|
|
discard win.SourceViewTabs.signalConnect(
|
|
"switch-page", SIGNAL_FUNC(onSwitchTab), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-drop", SIGNAL_FUNC(svTabs_DragDrop), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-data-received", SIGNAL_FUNC(svTabs_DragDataRecv), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-motion", SIGNAL_FUNC(svTabs_DragMotion), nil)
|
|
win.SourceViewTabs.set_scrollable(True)
|
|
|
|
win.SourceViewTabs.show()
|
|
if lastSession.len != 0:
|
|
for i in 0 .. len(lastSession)-1:
|
|
var splitUp = lastSession[i].split('|')
|
|
var (filename, offset) = (splitUp[0], splitUp[1])
|
|
addTab("", filename)
|
|
|
|
var iter: TTextIter
|
|
win.Tabs[i].buffer.getIterAtOffset(addr(iter), offset.parseInt())
|
|
win.Tabs[i].buffer.moveMarkByName("insert", addr(iter))
|
|
win.Tabs[i].buffer.moveMarkByName("selection_bound", addr(iter))
|
|
|
|
# TODO: Fix this..... :(
|
|
discard PTextView(win.Tabs[i].sourceView).
|
|
scrollToIter(addr(iter), 0.25, true, 0.0, 0.0)
|
|
else:
|
|
addTab("", "")
|
|
|
|
# This doesn't work :\
|
|
win.Tabs[0].sourceView.grabFocus()
|
|
|
|
|
|
proc initBottomTabs() =
|
|
win.bottomPanelTabs = notebookNew()
|
|
if win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
|
|
# output tab
|
|
var tabLabel = labelNew("Output")
|
|
var outputTab = vboxNew(False, 0)
|
|
discard win.bottomPanelTabs.appendPage(outputTab, tabLabel)
|
|
# Compiler tabs, gtktextview
|
|
var outputScrolledWindow = scrolledwindowNew(nil, nil)
|
|
outputScrolledWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
|
outputTab.packStart(outputScrolledWindow, true, true, 0)
|
|
outputScrolledWindow.show()
|
|
|
|
win.outputTextView = textviewNew()
|
|
outputScrolledWindow.add(win.outputTextView)
|
|
win.outputTextView.show()
|
|
|
|
outputTab.show()
|
|
|
|
proc initTAndBP(MainBox: PBox) =
|
|
# This init's the HPaned, which splits the sourceViewTabs
|
|
# and the BottomPanelTabs
|
|
initSourceViewTabs()
|
|
initBottomTabs()
|
|
|
|
var TAndBPVPaned = vpanedNew()
|
|
tandbpVPaned.pack1(win.sourceViewTabs, resize=True, shrink=False)
|
|
tandbpVPaned.pack2(win.bottomPanelTabs, resize=False, shrink=False)
|
|
MainBox.packStart(TAndBPVPaned, True, True, 0)
|
|
tandbpVPaned.setPosition(win.settings.VPanedPos)
|
|
TAndBPVPaned.show()
|
|
|
|
proc initFindBar(MainBox: PBox) =
|
|
# Create a fixed container
|
|
win.findBar = HBoxNew(False, 0)
|
|
win.findBar.setSpacing(4)
|
|
|
|
# Add a Label 'Find'
|
|
var findLabel = labelNew("Find:")
|
|
win.findBar.packStart(findLabel, False, False, 0)
|
|
findLabel.show()
|
|
|
|
# Add a (find) text entry
|
|
win.findEntry = entryNew()
|
|
win.findBar.packStart(win.findEntry, False, False, 0)
|
|
discard win.findEntry.signalConnect("activate", SIGNAL_FUNC(
|
|
aporia.nextBtn_Clicked), nil)
|
|
win.findEntry.show()
|
|
var rq: TRequisition
|
|
win.findEntry.sizeRequest(addr(rq))
|
|
|
|
# Make the (find) text entry longer
|
|
win.findEntry.set_size_request(190, rq.height)
|
|
|
|
# Add a Label 'Replace'
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceLabel = labelNew("Replace:")
|
|
win.findBar.packStart(win.replaceLabel, False, False, 0)
|
|
#replaceLabel.show()
|
|
|
|
# Add a (replace) text entry
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceEntry = entryNew()
|
|
win.findBar.packStart(win.replaceEntry, False, False, 0)
|
|
#win.replaceEntry.show()
|
|
var rq1: TRequisition
|
|
win.replaceEntry.sizeRequest(addr(rq1))
|
|
|
|
# Make the (replace) text entry longer
|
|
win.replaceEntry.set_size_request(100, rq1.height)
|
|
|
|
# Find next button
|
|
var nextBtn = buttonNew("Next")
|
|
win.findBar.packStart(nextBtn, false, false, 0)
|
|
discard nextBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.nextBtn_Clicked), nil)
|
|
nextBtn.show()
|
|
var nxtBtnRq: TRequisition
|
|
nextBtn.sizeRequest(addr(nxtBtnRq))
|
|
|
|
# Find previous button
|
|
var prevBtn = buttonNew("Previous")
|
|
win.findBar.packStart(prevBtn, false, false, 0)
|
|
discard prevBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.prevBtn_Clicked), nil)
|
|
prevBtn.show()
|
|
|
|
# Replace button
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceBtn = buttonNew("Replace")
|
|
win.findBar.packStart(win.replaceBtn, false, false, 0)
|
|
discard win.replaceBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.replaceBtn_Clicked), nil)
|
|
#replaceBtn.show()
|
|
|
|
# Replace all button
|
|
# - this Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceAllBtn = buttonNew("Replace All")
|
|
win.findBar.packStart(win.replaceAllBtn, false, false, 0)
|
|
discard win.replaceAllBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.replaceAllBtn_Clicked), nil)
|
|
#replaceAllBtn.show()
|
|
|
|
# Right side ...
|
|
|
|
# Close button - With a close stock image
|
|
var closeBtn = buttonNew()
|
|
var closeImage = imageNewFromStock(STOCK_CLOSE, ICON_SIZE_SMALL_TOOLBAR)
|
|
var closeBox = hboxNew(False, 0)
|
|
closeBtn.add(closeBox)
|
|
closeBox.show()
|
|
closeBox.add(closeImage)
|
|
closeImage.show()
|
|
discard closeBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.closeBtn_Clicked), nil)
|
|
win.findBar.packEnd(closeBtn, False, False, 2)
|
|
closeBtn.show()
|
|
|
|
# Extra button - When clicked shows a menu with options like 'Use regex'
|
|
var extraBtn = buttonNew()
|
|
var extraImage = imageNewFromStock(STOCK_PROPERTIES, ICON_SIZE_SMALL_TOOLBAR)
|
|
|
|
var extraBox = hboxNew(False, 0)
|
|
extraBtn.add(extraBox)
|
|
extraBox.show()
|
|
extraBox.add(extraImage)
|
|
extraImage.show()
|
|
discard extraBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.extraBtn_Clicked), nil)
|
|
win.findBar.packEnd(extraBtn, False, False, 0)
|
|
extraBtn.show()
|
|
|
|
MainBox.packStart(win.findBar, False, False, 0)
|
|
win.findBar.show()
|
|
|
|
proc initStatusBar(MainBox: PBox) =
|
|
win.bottomBar = statusbarNew()
|
|
MainBox.packStart(win.bottomBar, False, False, 0)
|
|
win.bottomBar.show()
|
|
|
|
discard win.bottomBar.push(0, "Line: 0 Column: 0")
|
|
|
|
proc initControls() =
|
|
# Load up the language style
|
|
win.langMan = languageManagerGetDefault()
|
|
var langpaths: array[0..1, cstring] =
|
|
[cstring(os.getApplicationDir() / langSpecs), nil]
|
|
win.langMan.setSearchPath(addr(langpaths))
|
|
var nimLang = win.langMan.getLanguage("nimrod")
|
|
win.nimLang = nimLang
|
|
|
|
# Load the scheme
|
|
var schemeMan = schemeManagerGetDefault()
|
|
var schemepaths: array[0..1, cstring] =
|
|
[cstring(os.getApplicationDir() / styles), nil]
|
|
schemeMan.setSearchPath(addr(schemepaths))
|
|
win.scheme = schemeMan.getScheme(win.settings.colorSchemeID)
|
|
|
|
# Window
|
|
win.w = windowNew(gtk2.WINDOW_TOPLEVEL)
|
|
win.w.setDefaultSize(win.settings.winWidth, win.settings.winHeight)
|
|
win.w.setTitle("Aporia IDE")
|
|
if win.settings.winMaximized: win.w.maximize()
|
|
|
|
win.w.show() # The window has to be shown before
|
|
# setting the position of the VPaned so that
|
|
# it gets set correctly, when the window is maximized.
|
|
|
|
discard win.w.signalConnect("destroy", SIGNAL_FUNC(aporia.destroy), nil)
|
|
discard win.w.signalConnect("delete_event",
|
|
SIGNAL_FUNC(aporia.delete_event), nil)
|
|
discard win.w.signalConnect("window-state-event",
|
|
SIGNAL_FUNC(aporia.windowState_Changed), nil)
|
|
|
|
# MainBox (vbox)
|
|
var MainBox = vboxNew(False, 0)
|
|
win.w.add(MainBox)
|
|
|
|
initTopMenu(MainBox)
|
|
initToolBar(MainBox)
|
|
initTAndBP(MainBox)
|
|
initFindBar(MainBox)
|
|
initStatusBar(MainBox)
|
|
|
|
MainBox.show()
|
|
if confParseFail:
|
|
dialogs.warning(win.w, "Error parsing config file, using default settings.")
|
|
|
|
nimrod_init()
|
|
initControls()
|
|
main()
|
|
|