1import glib2, gtk2, gdk2, gtksourceview, dialogs, os, pango, osproc, strutils
2import pegs, streams
3import settings, types, cfg, search
4
5{.push callConv:cdecl.}
6
7const
8  NimrodProjectExt = ".nimprj"
9
10var win: types.MainWin
11win.Tabs = @[]
12
13search.win = addr(win)
14
15var lastSession: seq[string] = @[]
16
17var confParseFail = False # This gets set to true
18                          # When there is an error parsing the config
19
20# Load the settings
21try:
22  win.settings = cfg.load(lastSession)
23except ECFGParse:
24  # TODO: Make the dialog show the exception
25  confParseFail = True
26  win.settings = cfg.defaultSettings()
27except EIO:
28  win.settings = cfg.defaultSettings()
29
30proc getProjectTab(): int =
31  for i in 0..high(win.tabs):
32    if win.tabs[i].filename.endswith(NimrodProjectExt): return i
33
34proc saveTab(tabNr: int, startpath: string) =
35  if tabNr < 0: return
36  if win.Tabs[tabNr].saved: return
37  var path = ""
38  if win.Tabs[tabNr].filename == "":
39    path = ChooseFileToSave(win.w, startpath)
40    # dialogs.nim STOCK_OPEN instead of STOCK_SAVE
41  else:
42    path = win.Tabs[tabNr].filename
43
44  if path != "":
45    var buffer = PTextBuffer(win.Tabs[tabNr].buffer)
46    # Get the text from the TextView
47    var startIter: TTextIter
48    buffer.getStartIter(addr(startIter))
49
50    var endIter: TTextIter
51    buffer.getEndIter(addr(endIter))
52
53    var text = buffer.getText(addr(startIter), addr(endIter), False)
54    # Save it to a file
55    var f: TFile
56    if open(f, path, fmWrite):
57      f.write(text)
58      f.close()
59
60      win.tempStuff.lastSaveDir = splitFile(path).dir
61
62      # Change the tab name and .Tabs.filename etc.
63      win.Tabs[tabNr].filename = path
64      win.Tabs[tabNr].saved = True
65      var name = extractFilename(path)
66
67      var cTab = win.Tabs[tabNr]
68      cTab.label.setText(name)
69    else:
70      error(win.w, "Unable to write to file")
71
72proc saveAllTabs() =
73  for i in 0..high(win.tabs):
74    saveTab(i, os.splitFile(win.tabs[i].filename).dir)
75
76# GTK Events
77# -- w(PWindow)
78proc destroy(widget: PWidget, data: pgpointer) {.cdecl.} =
79  # gather some settings
80  win.settings.VPanedPos = PPaned(win.sourceViewTabs.getParent()).getPosition()
81  win.settings.winWidth = win.w.allocation.width
82  win.settings.winHeight = win.w.allocation.height
83
84  # save the settings
85  win.save()
86  # then quit
87  main_quit()
88
89proc delete_event(widget: PWidget, event: PEvent, user_data: pgpointer): bool =
90  var quit = True
91  for i in low(win.Tabs)..len(win.Tabs)-1:
92    if not win.Tabs[i].saved:
93      var askSave = dialogNewWithButtons("", win.w, 0,
94                            STOCK_SAVE, RESPONSE_ACCEPT, STOCK_CANCEL,
95                            RESPONSE_CANCEL,
96                            "Close without saving", RESPONSE_REJECT, nil)
97      askSave.setTransientFor(win.w)
98      # TODO: Make this dialog look better
99      var label = labelNew(win.Tabs[i].filename &
100          " is unsaved, would you like to save it ?")
101      PBox(askSave.vbox).pack_start(label, False, False, 0)
102      label.show()
103
104      var resp = askSave.run()
105      gtk2.destroy(PWidget(askSave))
106      case resp
107      of RESPONSE_ACCEPT:
108        saveTab(i, os.splitFile(win.tabs[i].filename).dir)
109        quit = True
110      of RESPONSE_CANCEL:
111        quit = False
112        break
113      of RESPONSE_REJECT:
114        quit = True
115      else:
116        quit = False
117        break
118
119  # If False is returned the window will close
120  return not quit
121
122proc windowState_Changed(widget: PWidget, event: PEventWindowState,
123                         user_data: pgpointer) =
124  win.settings.winMaximized = (event.newWindowState and
125                               WINDOW_STATE_MAXIMIZED) != 0
126
127# -- SourceView(PSourceView) & SourceBuffer
128proc updateStatusBar(buffer: PTextBuffer){.cdecl.} =
129  # Incase this event gets fired before
130  # bottomBar is initialized
131  if win.bottomBar != nil and not win.tempStuff.stopSBUpdates:
132    var iter: TTextIter
133
134    win.bottomBar.pop(0)
135    buffer.getIterAtMark(addr(iter), buffer.getInsert())
136    var row = getLine(addr(iter)) + 1
137    var col = getLineOffset(addr(iter))
138    discard win.bottomBar.push(0, "Line: " & $row & " Column: " & $col)
139
140proc cursorMoved(buffer: PTextBuffer, location: PTextIter,
141                 mark: PTextMark, user_data: pgpointer){.cdecl.} =
142  updateStatusBar(buffer)
143
144proc onCloseTab(btn: PButton, user_data: PWidget) =
145  if win.sourceViewTabs.getNPages() > 1:
146    var tab = win.sourceViewTabs.pageNum(user_data)
147    win.sourceViewTabs.removePage(tab)
148
149    win.Tabs.delete(tab)
150
151proc onSwitchTab(notebook: PNotebook, page: PNotebookPage, pageNum: guint,
152                 user_data: pgpointer) =
153  if win.Tabs.len()-1 >= pageNum:
154    win.w.setTitle("Aporia IDE - " & win.Tabs[pageNum].filename)
155
156proc createTabLabel(name: string, t_child: PWidget): tuple[box: PWidget,
157                    label: PLabel] =
158  var box = hboxNew(False, 0)
159  var label = labelNew(name)
160  var closebtn = buttonNew()
161  closeBtn.setLabel(nil)
162  var iconSize = iconSizeFromName("tabIconSize")
163  if iconSize == 0:
164     iconSize = iconSizeRegister("tabIconSize", 10, 10)
165  var image = imageNewFromStock(STOCK_CLOSE, iconSize)
166  discard gSignalConnect(closebtn, "clicked", G_Callback(onCloseTab), t_child)
167  closebtn.setImage(image)
168  gtk2.setRelief(closebtn, RELIEF_NONE)
169  box.packStart(label, True, True, 0)
170  box.packEnd(closebtn, False, False, 0)
171  box.showAll()
172  return (box, label)
173
174proc changed(buffer: PTextBuffer, user_data: pgpointer) =
175  # Update the 'Line & Column'
176  #updateStatusBar(buffer)
177
178  # Change the tabs state to 'unsaved'
179  # and add '*' to the Tab Name
180  var current = win.SourceViewTabs.getCurrentPage()
181  var name = ""
182  if win.Tabs[current].filename == "":
183    win.Tabs[current].saved = False
184    name = "Untitled *"
185  else:
186    win.Tabs[current].saved = False
187    name = extractFilename(win.Tabs[current].filename) & " *"
188
189  var cTab = win.Tabs[current]
190  cTab.label.setText(name)
191
192# Other(Helper) functions
193
194proc initSourceView(SourceView: var PWidget, scrollWindow: var PScrolledWindow,
195                    buffer: var PSourceBuffer) =
196  # This gets called by addTab
197  # Each tabs creates a new SourceView
198  # SourceScrolledWindow(ScrolledWindow)
199  scrollWindow = scrolledWindowNew(nil, nil)
200  scrollWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
201  scrollWindow.show()
202
203  # SourceView(gtkSourceView)
204  SourceView = sourceViewNew(buffer)
205  PSourceView(SourceView).setInsertSpacesInsteadOfTabs(True)
206  PSourceView(SourceView).setIndentWidth(win.settings.indentWidth)
207  PSourceView(SourceView).setShowLineNumbers(win.settings.showLineNumbers)
208  PSourceView(SourceView).setHighlightCurrentLine(
209               win.settings.highlightCurrentLine)
210  PSourceView(SourceView).setShowRightMargin(win.settings.rightMargin)
211  PSourceView(SourceView).setAutoIndent(win.settings.autoIndent)
212
213  var font = font_description_from_string(win.settings.font)
214  SourceView.modifyFont(font)
215
216  scrollWindow.add(SourceView)
217  SourceView.show()
218
219  buffer.setHighlightMatchingBrackets(
220      win.settings.highlightMatchingBrackets)
221
222  # UGLY workaround for yet another compiler bug:
223  discard gsignalConnect(buffer, "mark-set",
224                         GCallback(aporia.cursorMoved), nil)
225  discard gsignalConnect(buffer, "changed", GCallback(aporia.changed), nil)
226
227  # -- Set the syntax highlighter scheme
228  buffer.setScheme(win.scheme)
229
230proc addTab(name, filename: string) =
231  ## Adds a tab, if filename is not "" reads the file. And sets
232  ## the tabs SourceViews text to that files contents.
233  assert(win.nimLang != nil)
234  var buffer: PSourceBuffer = sourceBufferNew(win.nimLang)
235
236  if filename != nil and filename != "":
237    var lang = win.langMan.guessLanguage(filename, nil)
238    if lang != nil:
239      buffer.setLanguage(lang)
240    else:
241      buffer.setHighlightSyntax(False)
242
243  var nam = name
244  if nam == "": nam = "Untitled"
245  if filename == "": nam.add(" *")
246  elif filename != "" and name == "":
247    # Disable the undo/redo manager.
248    buffer.begin_not_undoable_action()
249
250    # Load the file.
251    var file: string = readFile(filename)
252    if file != nil:
253      buffer.set_text(file, len(file))
254
255    # Enable the undo/redo manager.
256    buffer.end_not_undoable_action()
257
258    # Get the name.ext of the filename, for the tabs title
259    nam = extractFilename(filename)
260
261  # Init the sourceview
262  var sourceView: PWidget
263  var scrollWindow: PScrolledWindow
264  initSourceView(sourceView, scrollWindow, buffer)
265
266  var (TabLabel, labelText) = createTabLabel(nam, scrollWindow)
267  # Add a tab
268  discard win.SourceViewTabs.appendPage(scrollWindow, TabLabel)
269
270  var nTab: Tab
271  nTab.buffer = buffer
272  nTab.sourceView = sourceView
273  nTab.label = labelText
274  nTab.saved = (filename != "")
275  nTab.filename = filename
276  win.Tabs.add(nTab)
277
278  PTextView(SourceView).setBuffer(nTab.buffer)
279
280# GTK Events Contd.
281# -- TopMenu & TopBar
282
283proc newFile(menuItem: PMenuItem, user_data: pgpointer) =
284  addTab("", "")
285  win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
286
287proc openFile(menuItem: PMenuItem, user_data: pgpointer) =
288  var startpath = ""
289  var currPage = win.SourceViewTabs.getCurrentPage()
290  if currPage <% win.tabs.len:
291    startpath = os.splitFile(win.tabs[currPage].filename).dir
292
293  if startpath.len == 0:
294    # Use lastSavePath as the startpath
295    startpath = win.tempStuff.lastSaveDir
296    if startpath.len == 0:
297      startpath = os.getHomeDir()
298
299  var files = ChooseFilesToOpen(win.w, startpath)
300  if files.len() > 0:
301    for f in items(files):
302      try:
303        addTab("", f)
304      except EIO:
305        error(win.w, "Unable to read from file")
306    # Switch to the newly created tab
307    win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
308
309proc saveFile_Activate(menuItem: PMenuItem, user_data: pgpointer) =
310  var current = win.SourceViewTabs.getCurrentPage()
311  saveTab(current, os.splitFile(win.tabs[current].filename).dir)
312
313proc saveFileAs_Activate(menuItem: PMenuItem, user_data: pgpointer) =
314  var current = win.SourceViewTabs.getCurrentPage()
315  var (filename, saved) = (win.Tabs[current].filename, win.Tabs[current].saved)
316
317  win.Tabs[current].saved = False
318  win.Tabs[current].filename = ""
319  saveTab(current, os.splitFile(filename).dir)
320  # If the user cancels the save file dialog. Restore the previous filename
321  # and saved state
322  if win.Tabs[current].filename == "":
323    win.Tabs[current].filename = filename
324    win.Tabs[current].saved = saved
325
326proc undo(menuItem: PMenuItem, user_data: pgpointer) =
327  var current = win.SourceViewTabs.getCurrentPage()
328  if win.Tabs[current].buffer.canUndo():
329    win.Tabs[current].buffer.undo()
330
331proc redo(menuItem: PMenuItem, user_data: pgpointer) =
332  var current = win.SourceViewTabs.getCurrentPage()
333  if win.Tabs[current].buffer.canRedo():
334    win.Tabs[current].buffer.redo()
335
336proc find_Activate(menuItem: PMenuItem, user_data: pgpointer) =
337  # Get the selected text, and set the findEntry to it.
338  var currentTab = win.SourceViewTabs.getCurrentPage()
339  var insertIter: TTextIter
340  win.Tabs[currentTab].buffer.getIterAtMark(addr(insertIter),
341                                      win.Tabs[currentTab].buffer.getInsert())
342  var insertOffset = addr(insertIter).getOffset()
343
344  var selectIter: TTextIter
345  win.Tabs[currentTab].buffer.getIterAtMark(addr(selectIter),
346                win.Tabs[currentTab].buffer.getSelectionBound())
347  var selectOffset = addr(selectIter).getOffset()
348
349  if insertOffset != selectOffset:
350    var text = win.Tabs[currentTab].buffer.getText(addr(insertIter),
351                                                   addr(selectIter), false)
352    win.findEntry.setText(text)
353
354  win.findBar.show()
355  win.findEntry.grabFocus()
356  win.replaceEntry.hide()
357  win.replaceLabel.hide()
358  win.replaceBtn.hide()
359  win.replaceAllBtn.hide()
360
361proc replace_Activate(menuitem: PMenuItem, user_data: pgpointer) =
362  win.findBar.show()
363  win.findEntry.grabFocus()
364  win.replaceEntry.show()
365  win.replaceLabel.show()
366  win.replaceBtn.show()
367  win.replaceAllBtn.show()
368
369proc settings_Activate(menuitem: PMenuItem, user_data: pgpointer) =
370  settings.showSettings(win)
371
372proc viewBottomPanel_Toggled(menuitem: PCheckMenuItem, user_data: pgpointer) =
373  win.settings.bottomPanelVisible = menuitem.itemGetActive()
374  if win.settings.bottomPanelVisible:
375    win.bottomPanelTabs.show()
376  else:
377    win.bottomPanelTabs.hide()
378
379var
380  pegLineError = peg"{[^(]*} '(' {\d+} ', ' \d+ ') Error:' \s* {.*}"
381  pegLineWarning = peg"{[^(]*} '(' {\d+} ', ' \d+ ') ' ('Warning:'/'Hint:') \s* {.*}"
382  pegOtherError = peg"'Error:' \s* {.*}"
383  pegSuccess = peg"'Hint: operation successful'.*"
384
385proc addText(textView: PTextView, text: string, colorTag: PTextTag = nil) =
386  if text != nil:
387    var iter: TTextIter
388    textView.getBuffer().getEndIter(addr(iter))
389
390    if colorTag == nil:
391      textView.getBuffer().insert(addr(iter), text, len(text))
392    else:
393      textView.getBuffer().insertWithTags(addr(iter), text, len(text), colorTag,
394                                          nil)
395
396proc createColor(textView: PTextView, name, color: string): PTextTag =
397  var tagTable = textView.getBuffer().getTagTable()
398  result = tagTable.tableLookup(name)
399  if result == nil:
400    result = textView.getBuffer().createTag(name, "foreground", color, nil)
401
402when not defined(os.findExe):
403  proc findExe(exe: string): string =
404    ## returns "" if the exe cannot be found
405    result = addFileExt(exe, os.exeExt)
406    if ExistsFile(result): return
407    var path = os.getEnv("PATH")
408    for candidate in split(path, pathSep):
409      var x = candidate / result
410      if ExistsFile(x): return x
411    result = ""
412
413proc GetCmd(cmd, filename: string): string =
414  var f = quoteIfContainsWhite(filename)
415  if cmd =~ peg"\s* '$' y'findExe' '(' {[^)]+} ')' {.*}":
416    var exe = quoteIfContainsWhite(findExe(matches[0]))
417    if exe.len == 0: exe = matches[0]
418    result = exe & " " & matches[1] % f
419  else:
420    result = cmd % f
421
422proc showBottomPanel() =
423  if not win.settings.bottomPanelVisible:
424    win.bottomPanelTabs.show()
425    win.settings.bottomPanelVisible = true
426    PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(true)
427  # Scroll to the end of the TextView
428  # This is stupid, it works sometimes... it's random
429  var endIter: TTextIter
430  win.outputTextView.getBuffer().getEndIter(addr(endIter))
431  discard win.outputTextView.scrollToIter(
432    addr(endIter), 0.25, False, 0.0, 0.0)
433
434proc compileRun(currentTab: int, shouldRun: bool) =
435  if win.Tabs[currentTab].filename.len == 0: return
436  # Clear the outputTextView
437  win.outputTextView.getBuffer().setText("", 0)
438
439  var outp = osProc.execProcess(GetCmd(win.settings.nimrodCmd,
440                                win.Tabs[currentTab].filename))
441  # Colors
442  var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
443  var errorTag = createColor(win.outputTextView, "errorTag", "red")
444  var warningTag = createColor(win.outputTextView, "warningTag", "darkorange")
445  var successTag = createColor(win.outputTextView, "successTag", "darkgreen")
446  for x in outp.splitLines():
447    if x =~ pegLineError / pegOtherError:
448      win.outputTextView.addText("\n" & x, errorTag)
449    elif x=~ pegSuccess:
450      win.outputTextView.addText("\n" & x, successTag)
451
452      # Launch the process
453      if shouldRun:
454        var filename = changeFileExt(win.Tabs[currentTab].filename, os.ExeExt)
455        var output = "\n" & osProc.execProcess(filename)
456        win.outputTextView.addText(output)
457    elif x =~ pegLineWarning:
458      win.outputTextView.addText("\n" & x, warningTag)
459    else:
460      win.outputTextView.addText("\n" & x, normalTag)
461  showBottomPanel()
462
463proc CompileCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
464  saveFile_Activate(nil, nil)
465  compileRun(win.SourceViewTabs.getCurrentPage(), false)
466
467proc CompileRunCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
468  saveFile_Activate(nil, nil)
469  compileRun(win.SourceViewTabs.getCurrentPage(), true)
470
471proc CompileProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
472  saveAllTabs()
473  compileRun(getProjectTab(), false)
474
475proc CompileRunProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
476  saveAllTabs()
477  compileRun(getProjectTab(), true)
478
479proc RunCustomCommand(cmd: string) =
480  saveFile_Activate(nil, nil)
481  var currentTab = win.SourceViewTabs.getCurrentPage()
482  if win.Tabs[currentTab].filename.len == 0 or cmd.len == 0: return
483  # Clear the outputTextView
484  win.outputTextView.getBuffer().setText("", 0)
485  var outp = osProc.execProcess(GetCmd(cmd, win.Tabs[currentTab].filename))
486  var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
487  for x in outp.splitLines():
488    win.outputTextView.addText("\n" & x, normalTag)
489  showBottomPanel()
490
491proc RunCustomCommand1(menuitem: PMenuItem, user_data: pgpointer) =
492  RunCustomCommand(win.settings.customCmd1)
493
494proc RunCustomCommand2(menuitem: PMenuItem, user_data: pgpointer) =
495  RunCustomCommand(win.settings.customCmd2)
496
497proc RunCustomCommand3(menuitem: PMenuItem, user_data: pgpointer) =
498  RunCustomCommand(win.settings.customCmd3)
499
500# -- FindBar
501
502proc nextBtn_Clicked(button: PButton, user_data: pgpointer) = findText(True)
503proc prevBtn_Clicked(button: PButton, user_data: pgpointer) = findText(False)
504
505proc replaceBtn_Clicked(button: PButton, user_data: pgpointer) =
506  var currentTab = win.SourceViewTabs.getCurrentPage()
507  var start, theEnd: TTextIter
508  if not win.Tabs[currentTab].buffer.getSelectionBounds(
509        addr(start), addr(theEnd)):
510    # If no text is selected, try finding a match.
511    findText(True)
512    if not win.Tabs[currentTab].buffer.getSelectionBounds(
513          addr(start), addr(theEnd)):
514      # No match
515      return
516
517  # Remove the text
518  win.Tabs[currentTab].buffer.delete(addr(start), addr(theEnd))
519  # Insert the replacement
520  var text = getText(win.replaceEntry)
521  win.Tabs[currentTab].buffer.insert(addr(start), text, len(text))
522
523proc replaceAllBtn_Clicked(button: PButton, user_data: pgpointer) =
524  var find = getText(win.findEntry)
525  var replace = getText(win.replaceEntry)
526  discard replaceAll(find, replace)
527
528proc closeBtn_Clicked(button: PButton, user_data: pgpointer) =
529  win.findBar.hide()
530
531proc caseSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
532  win.settings.search = "casesens"
533proc caseInSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
534  win.settings.search = "caseinsens"
535proc style_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
536  win.settings.search = "style"
537proc regex_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
538  win.settings.search = "regex"
539proc peg_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
540  win.settings.search = "peg"
541
542proc extraBtn_Clicked(button: PButton, user_data: pgpointer) =
543  var extraMenu = menuNew()
544  var group: PGSList
545
546  var caseSensMenuItem = radio_menu_item_new(group, "Case sensitive")
547  extraMenu.append(caseSensMenuItem)
548  discard signal_connect(caseSensMenuItem, "toggled",
549                          SIGNAL_FUNC(caseSens_Changed), nil)
550  caseSensMenuItem.show()
551  group = caseSensMenuItem.ItemGetGroup()
552
553  var caseInSensMenuItem = radio_menu_item_new(group, "Case insensitive")
554  extraMenu.append(caseInSensMenuItem)
555  discard signal_connect(caseInSensMenuItem, "toggled",
556                          SIGNAL_FUNC(caseInSens_Changed), nil)
557  caseInSensMenuItem.show()
558  group = caseInSensMenuItem.ItemGetGroup()
559
560  var styleMenuItem = radio_menu_item_new(group, "Style insensitive")
561  extraMenu.append(styleMenuItem)
562  discard signal_connect(styleMenuItem, "toggled",
563                          SIGNAL_FUNC(style_Changed), nil)
564  styleMenuItem.show()
565  group = styleMenuItem.ItemGetGroup()
566
567  var regexMenuItem = radio_menu_item_new(group, "Regex")
568  extraMenu.append(regexMenuItem)
569  discard signal_connect(regexMenuItem, "toggled",
570                          SIGNAL_FUNC(regex_Changed), nil)
571  regexMenuItem.show()
572  group = regexMenuItem.ItemGetGroup()
573
574  var pegMenuItem = radio_menu_item_new(group, "Pegs")
575  extraMenu.append(pegMenuItem)
576  discard signal_connect(pegMenuItem, "toggled",
577                          SIGNAL_FUNC(peg_Changed), nil)
578  pegMenuItem.show()
579
580  # Make the correct radio button active
581  case win.settings.search
582  of "casesens":
583    PCheckMenuItem(caseSensMenuItem).ItemSetActive(True)
584  of "caseinsens":
585    PCheckMenuItem(caseInSensMenuItem).ItemSetActive(True)
586  of "style":
587    PCheckMenuItem(styleMenuItem).ItemSetActive(True)
588  of "regex":
589    PCheckMenuItem(regexMenuItem).ItemSetActive(True)
590  of "peg":
591    PCheckMenuItem(pegMenuItem).ItemSetActive(True)
592
593  extraMenu.popup(nil, nil, nil, nil, 0, get_current_event_time())
594
595# GUI Initialization
596
597proc createAccelMenuItem(toolsMenu: PMenu, accGroup: PAccelGroup,
598                         label: string, acc: gint,
599                         action: proc (i: PMenuItem, p: pgpointer)) =
600  var result = menu_item_new(label)
601  result.addAccelerator("activate", accGroup, acc, 0, ACCEL_VISIBLE)
602  ToolsMenu.append(result)
603  show(result)
604  discard signal_connect(result, "activate", SIGNAL_FUNC(action), nil)
605
606proc createSeparator(menu: PMenu) =
607  var sep = separator_menu_item_new()
608  menu.append(sep)
609  sep.show()
610
611proc initTopMenu(MainBox: PBox) =
612  # Create a accelerator group, used for shortcuts
613  # like CTRL + S in SaveMenuItem
614  var accGroup = accel_group_new()
615  add_accel_group(win.w, accGroup)
616
617  # TopMenu(MenuBar)
618  var TopMenu = menuBarNew()
619
620  # FileMenu
621  var FileMenu = menuNew()
622
623  var NewMenuItem = menu_item_new("New") # New
624  FileMenu.append(NewMenuItem)
625  show(NewMenuItem)
626  discard signal_connect(NewMenuItem, "activate",
627                          SIGNAL_FUNC(newFile), nil)
628
629  createSeparator(FileMenu)
630
631  var OpenMenuItem = menu_item_new("Open...") # Open...
632  # CTRL + O
633  OpenMenuItem.add_accelerator("activate", accGroup,
634                  KEY_o, CONTROL_MASK, ACCEL_VISIBLE)
635  FileMenu.append(OpenMenuItem)
636  show(OpenMenuItem)
637  discard signal_connect(OpenMenuItem, "activate",
638                          SIGNAL_FUNC(aporia.openFile), nil)
639
640  var SaveMenuItem = menu_item_new("Save") # Save
641  # CTRL + S
642  SaveMenuItem.add_accelerator("activate", accGroup,
643                  KEY_s, CONTROL_MASK, ACCEL_VISIBLE)
644  FileMenu.append(SaveMenuItem)
645  show(SaveMenuItem)
646  discard signal_connect(SaveMenuItem, "activate",
647                          SIGNAL_FUNC(saveFile_activate), nil)
648
649  var SaveAsMenuItem = menu_item_new("Save As...") # Save as...
650
651  SaveAsMenuItem.add_accelerator("activate", accGroup,
652                  KEY_s, CONTROL_MASK or gdk2.SHIFT_MASK, ACCEL_VISIBLE)
653  FileMenu.append(SaveAsMenuItem)
654  show(SaveAsMenuItem)
655  discard signal_connect(SaveAsMenuItem, "activate",
656                          SIGNAL_FUNC(saveFileAs_Activate), nil)
657
658  var FileMenuItem = menuItemNewWithMnemonic("_File")
659
660  FileMenuItem.setSubMenu(FileMenu)
661  FileMenuItem.show()
662  TopMenu.append(FileMenuItem)
663
664  # Edit menu
665  var EditMenu = menuNew()
666
667  var UndoMenuItem = menu_item_new("Undo") # Undo
668  EditMenu.append(UndoMenuItem)
669  show(UndoMenuItem)
670  discard signal_connect(UndoMenuItem, "activate",
671                          SIGNAL_FUNC(aporia.undo), nil)
672
673  var RedoMenuItem = menu_item_new("Redo") # Undo
674  EditMenu.append(RedoMenuItem)
675  show(RedoMenuItem)
676  discard signal_connect(RedoMenuItem, "activate",
677                          SIGNAL_FUNC(aporia.redo), nil)
678
679  createSeparator(EditMenu)
680
681  var FindMenuItem = menu_item_new("Find") # Find
682  FindMenuItem.add_accelerator("activate", accGroup,
683                  KEY_f, CONTROL_MASK, ACCEL_VISIBLE)
684  EditMenu.append(FindMenuItem)
685  show(FindMenuItem)
686  discard signal_connect(FindMenuItem, "activate",
687                          SIGNAL_FUNC(aporia.find_Activate), nil)
688
689  var ReplaceMenuItem = menu_item_new("Replace") # Replace
690  ReplaceMenuItem.add_accelerator("activate", accGroup,
691                  KEY_h, CONTROL_MASK, ACCEL_VISIBLE)
692  EditMenu.append(ReplaceMenuItem)
693  show(ReplaceMenuItem)
694  discard signal_connect(ReplaceMenuItem, "activate",
695                          SIGNAL_FUNC(aporia.replace_Activate), nil)
696
697  createSeparator(EditMenu)
698
699  var SettingsMenuItem = menu_item_new("Settings...") # Settings
700  EditMenu.append(SettingsMenuItem)
701  show(SettingsMenuItem)
702  discard signal_connect(SettingsMenuItem, "activate",
703                          SIGNAL_FUNC(aporia.Settings_Activate), nil)
704
705  var EditMenuItem = menuItemNewWithMnemonic("_Edit")
706
707  EditMenuItem.setSubMenu(EditMenu)
708  EditMenuItem.show()
709  TopMenu.append(EditMenuItem)
710
711  # View menu
712  var ViewMenu = menuNew()
713
714  win.viewBottomPanelMenuItem = check_menu_item_new("Bottom Panel")
715  PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(
716         win.settings.bottomPanelVisible)
717  win.viewBottomPanelMenuItem.add_accelerator("activate", accGroup,
718                  KEY_f9, CONTROL_MASK, ACCEL_VISIBLE)
719  ViewMenu.append(win.viewBottomPanelMenuItem)
720  show(win.viewBottomPanelMenuItem)
721  discard signal_connect(win.viewBottomPanelMenuItem, "toggled",
722                          SIGNAL_FUNC(aporia.viewBottomPanel_Toggled), nil)
723
724  var ViewMenuItem = menuItemNewWithMnemonic("_View")
725
726  ViewMenuItem.setSubMenu(ViewMenu)
727  ViewMenuItem.show()
728  TopMenu.append(ViewMenuItem)
729
730
731  # Tools menu
732  var ToolsMenu = menuNew()
733
734  createAccelMenuItem(ToolsMenu, accGroup, "Compile current file",
735                      KEY_F4, aporia.CompileCurrent_Activate)
736  createAccelMenuItem(ToolsMenu, accGroup, "Compile & run current file",
737                      KEY_F5, aporia.CompileRunCurrent_Activate)
738  createSeparator(ToolsMenu)
739  createAccelMenuItem(ToolsMenu, accGroup, "Compile project",
740                      KEY_F8, aporia.CompileProject_Activate)
741  createAccelMenuItem(ToolsMenu, accGroup, "Compile & run project",
742                      KEY_F9, aporia.CompileRunProject_Activate)
743  createSeparator(ToolsMenu)
744  createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 1",
745                      KEY_F1, aporia.RunCustomCommand1)
746  createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 2",
747                      KEY_F2, aporia.RunCustomCommand2)
748  createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 3",
749                      KEY_F3, aporia.RunCustomCommand3)
750
751  var ToolsMenuItem = menuItemNewWithMnemonic("_Tools")
752
753  ToolsMenuItem.setSubMenu(ToolsMenu)
754  ToolsMenuItem.show()
755  TopMenu.append(ToolsMenuItem)
756
757  # Help menu
758  MainBox.packStart(TopMenu, False, False, 0)
759  TopMenu.show()
760
761proc initToolBar(MainBox: PBox) =
762  # TopBar(ToolBar)
763  var TopBar = toolbarNew()
764  TopBar.setStyle(TOOLBAR_ICONS)
765
766  var NewFileItem = TopBar.insertStock(STOCK_NEW, "New File",
767                      "New File", SIGNAL_FUNC(aporia.newFile), nil, 0)
768  TopBar.appendSpace()
769  var OpenItem = TopBar.insertStock(STOCK_OPEN, "Open",
770                      "Open", SIGNAL_FUNC(aporia.openFile), nil, -1)
771  var SaveItem = TopBar.insertStock(STOCK_SAVE, "Save",
772                      "Save", SIGNAL_FUNC(saveFile_Activate), nil, -1)
773  TopBar.appendSpace()
774  var UndoItem = TopBar.insertStock(STOCK_UNDO, "Undo",
775                      "Undo", SIGNAL_FUNC(aporia.undo), nil, -1)
776  var RedoItem = TopBar.insertStock(STOCK_REDO, "Redo",
777                      "Redo", SIGNAL_FUNC(aporia.redo), nil, -1)
778
779  MainBox.packStart(TopBar, False, False, 0)
780  TopBar.show()
781
782proc initSourceViewTabs() =
783  win.SourceViewTabs = notebookNew()
784  #win.sourceViewTabs.dragDestSet(DEST_DEFAULT_DROP, nil, 0, ACTION_MOVE)
785  discard win.SourceViewTabs.signalConnect(
786          "switch-page", SIGNAL_FUNC(onSwitchTab), nil)
787  #discard win.SourceViewTabs.signalConnect(
788  #        "drag-drop", SIGNAL_FUNC(svTabs_DragDrop), nil)
789  #discard win.SourceViewTabs.signalConnect(
790  #        "drag-data-received", SIGNAL_FUNC(svTabs_DragDataRecv), nil)
791  #discard win.SourceViewTabs.signalConnect(
792  #        "drag-motion", SIGNAL_FUNC(svTabs_DragMotion), nil)
793  win.SourceViewTabs.set_scrollable(True)
794
795  win.SourceViewTabs.show()
796  if lastSession.len != 0:
797    for i in 0 .. len(lastSession)-1:
798      var splitUp = lastSession[i].split('|')
799      var (filename, offset) = (splitUp[0], splitUp[1])
800      addTab("", filename)
801
802      var iter: TTextIter
803      win.Tabs[i].buffer.getIterAtOffset(addr(iter), offset.parseInt())
804      win.Tabs[i].buffer.moveMarkByName("insert", addr(iter))
805      win.Tabs[i].buffer.moveMarkByName("selection_bound", addr(iter))
806
807      # TODO: Fix this..... :(
808      discard PTextView(win.Tabs[i].sourceView).
809          scrollToIter(addr(iter), 0.25, true, 0.0, 0.0)
810  else:
811    addTab("", "")
812
813  # This doesn't work :\
814  win.Tabs[0].sourceView.grabFocus()
815
816
817proc initBottomTabs() =
818  win.bottomPanelTabs = notebookNew()
819  if win.settings.bottomPanelVisible:
820    win.bottomPanelTabs.show()
821
822  # output tab
823  var tabLabel = labelNew("Output")
824  var outputTab = vboxNew(False, 0)
825  discard win.bottomPanelTabs.appendPage(outputTab, tabLabel)
826  # Compiler tabs, gtktextview
827  var outputScrolledWindow = scrolledwindowNew(nil, nil)
828  outputScrolledWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
829  outputTab.packStart(outputScrolledWindow, true, true, 0)
830  outputScrolledWindow.show()
831
832  win.outputTextView = textviewNew()
833  outputScrolledWindow.add(win.outputTextView)
834  win.outputTextView.show()
835
836  outputTab.show()
837
838proc initTAndBP(MainBox: PBox) =
839  # This init's the HPaned, which splits the sourceViewTabs
840  # and the BottomPanelTabs
841  initSourceViewTabs()
842  initBottomTabs()
843
844  var TAndBPVPaned = vpanedNew()
845  tandbpVPaned.pack1(win.sourceViewTabs, resize=True, shrink=False)
846  tandbpVPaned.pack2(win.bottomPanelTabs, resize=False, shrink=False)
847  MainBox.packStart(TAndBPVPaned, True, True, 0)
848  tandbpVPaned.setPosition(win.settings.VPanedPos)
849  TAndBPVPaned.show()
850
851proc initFindBar(MainBox: PBox) =
852  # Create a fixed container
853  win.findBar = HBoxNew(False, 0)
854  win.findBar.setSpacing(4)
855
856  # Add a Label 'Find'
857  var findLabel = labelNew("Find:")
858  win.findBar.packStart(findLabel, False, False, 0)
859  findLabel.show()
860
861  # Add a (find) text entry
862  win.findEntry = entryNew()
863  win.findBar.packStart(win.findEntry, False, False, 0)
864  discard win.findEntry.signalConnect("activate", SIGNAL_FUNC(
865                                      aporia.nextBtn_Clicked), nil)
866  win.findEntry.show()
867  var rq: TRequisition
868  win.findEntry.sizeRequest(addr(rq))
869
870  # Make the (find) text entry longer
871  win.findEntry.set_size_request(190, rq.height)
872
873  # Add a Label 'Replace'
874  # - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
875  win.replaceLabel = labelNew("Replace:")
876  win.findBar.packStart(win.replaceLabel, False, False, 0)
877  #replaceLabel.show()
878
879  # Add a (replace) text entry
880  # - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
881  win.replaceEntry = entryNew()
882  win.findBar.packStart(win.replaceEntry, False, False, 0)
883  #win.replaceEntry.show()
884  var rq1: TRequisition
885  win.replaceEntry.sizeRequest(addr(rq1))
886
887  # Make the (replace) text entry longer
888  win.replaceEntry.set_size_request(100, rq1.height)
889
890  # Find next button
891  var nextBtn = buttonNew("Next")
892  win.findBar.packStart(nextBtn, false, false, 0)
893  discard nextBtn.signalConnect("clicked",
894             SIGNAL_FUNC(aporia.nextBtn_Clicked), nil)
895  nextBtn.show()
896  var nxtBtnRq: TRequisition
897  nextBtn.sizeRequest(addr(nxtBtnRq))
898
899  # Find previous button
900  var prevBtn = buttonNew("Previous")
901  win.findBar.packStart(prevBtn, false, false, 0)
902  discard prevBtn.signalConnect("clicked",
903             SIGNAL_FUNC(aporia.prevBtn_Clicked), nil)
904  prevBtn.show()
905
906  # Replace button
907  # - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
908  win.replaceBtn = buttonNew("Replace")
909  win.findBar.packStart(win.replaceBtn, false, false, 0)
910  discard win.replaceBtn.signalConnect("clicked",
911             SIGNAL_FUNC(aporia.replaceBtn_Clicked), nil)
912  #replaceBtn.show()
913
914  # Replace all button
915  # - this Is only shown, when the 'Search & Replace'(CTRL + H) is shown
916  win.replaceAllBtn = buttonNew("Replace All")
917  win.findBar.packStart(win.replaceAllBtn, false, false, 0)
918  discard win.replaceAllBtn.signalConnect("clicked",
919             SIGNAL_FUNC(aporia.replaceAllBtn_Clicked), nil)
920  #replaceAllBtn.show()
921
922  # Right side ...
923
924  # Close button - With a close stock image
925  var closeBtn = buttonNew()
926  var closeImage = imageNewFromStock(STOCK_CLOSE, ICON_SIZE_SMALL_TOOLBAR)
927  var closeBox = hboxNew(False, 0)
928  closeBtn.add(closeBox)
929  closeBox.show()
930  closeBox.add(closeImage)
931  closeImage.show()
932  discard closeBtn.signalConnect("clicked",
933             SIGNAL_FUNC(aporia.closeBtn_Clicked), nil)
934  win.findBar.packEnd(closeBtn, False, False, 2)
935  closeBtn.show()
936
937  # Extra button - When clicked shows a menu with options like 'Use regex'
938  var extraBtn = buttonNew()
939  var extraImage = imageNewFromStock(STOCK_PROPERTIES, ICON_SIZE_SMALL_TOOLBAR)
940
941  var extraBox = hboxNew(False, 0)
942  extraBtn.add(extraBox)
943  extraBox.show()
944  extraBox.add(extraImage)
945  extraImage.show()
946  discard extraBtn.signalConnect("clicked",
947             SIGNAL_FUNC(aporia.extraBtn_Clicked), nil)
948  win.findBar.packEnd(extraBtn, False, False, 0)
949  extraBtn.show()
950
951  MainBox.packStart(win.findBar, False, False, 0)
952  win.findBar.show()
953
954proc initStatusBar(MainBox: PBox) =
955  win.bottomBar = statusbarNew()
956  MainBox.packStart(win.bottomBar, False, False, 0)
957  win.bottomBar.show()
958
959  discard win.bottomBar.push(0, "Line: 0 Column: 0")
960
961proc initControls() =
962  # Load up the language style
963  win.langMan = languageManagerGetDefault()
964  var langpaths: array[0..1, cstring] =
965          [cstring(os.getApplicationDir() / langSpecs), nil]
966  win.langMan.setSearchPath(addr(langpaths))
967  var nimLang = win.langMan.getLanguage("nimrod")
968  win.nimLang = nimLang
969
970  # Load the scheme
971  var schemeMan = schemeManagerGetDefault()
972  var schemepaths: array[0..1, cstring] =
973          [cstring(os.getApplicationDir() / styles), nil]
974  schemeMan.setSearchPath(addr(schemepaths))
975  win.scheme = schemeMan.getScheme(win.settings.colorSchemeID)
976
977  # Window
978  win.w = windowNew(gtk2.WINDOW_TOPLEVEL)
979  win.w.setDefaultSize(win.settings.winWidth, win.settings.winHeight)
980  win.w.setTitle("Aporia IDE")
981  if win.settings.winMaximized: win.w.maximize()
982
983  win.w.show() # The window has to be shown before
984               # setting the position of the VPaned so that
985               # it gets set correctly, when the window is maximized.
986
987  discard win.w.signalConnect("destroy", SIGNAL_FUNC(aporia.destroy), nil)
988  discard win.w.signalConnect("delete_event",
989    SIGNAL_FUNC(aporia.delete_event), nil)
990  discard win.w.signalConnect("window-state-event",
991    SIGNAL_FUNC(aporia.windowState_Changed), nil)
992
993  # MainBox (vbox)
994  var MainBox = vboxNew(False, 0)
995  win.w.add(MainBox)
996
997  initTopMenu(MainBox)
998  initToolBar(MainBox)
999  initTAndBP(MainBox)
1000  initFindBar(MainBox)
1001  initStatusBar(MainBox)
1002
1003  MainBox.show()
1004  if confParseFail:
1005    dialogs.warning(win.w, "Error parsing config file, using default settings.")
1006
1007nimrod_init()
1008initControls()
1009main()
1010
1011