92 Commits
Nice ... master

Author SHA1 Message Date
db0930b7a6 Add instructions on how to run the program 2021-04-26 23:22:41 +02:00
Oystein
64793c2505 run configs 2021-04-26 23:21:21 +02:00
0c8a7dc1dc Merge branch 'master' of gitlab.stud.idi.ntnu.no:oysteikt/h20-tdt4100-project 2021-04-26 23:10:28 +02:00
33ec71e91f Add badges 2021-04-26 23:10:23 +02:00
Oystein
d5ad1ed82a Merge branch 'master' of https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project 2021-04-26 23:00:32 +02:00
Oystein
fe6a848346 typo req 2021-04-26 23:00:25 +02:00
765f643cd0 Merge branch 'master' of gitlab.stud.idi.ntnu.no:oysteikt/h20-tdt4100-project 2021-04-26 22:59:56 +02:00
af51ed63f3 Update docs 2021-04-26 22:59:53 +02:00
Oystein
6559622dd9 Last links on req 2021-04-26 22:57:11 +02:00
95e1c32d09 Update requirements 2021-04-26 22:49:45 +02:00
Oystein
4259d12d48 All TODOS done 2021-04-26 22:46:55 +02:00
Oystein
c1c86a94c6 Merge branch 'master' of https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project 2021-04-26 22:11:27 +02:00
Oystein
7b0dc1548a req 2021-04-26 22:11:19 +02:00
c12e98f9be Add docs 2021-04-26 22:07:54 +02:00
Oystein
9b2d70c876 Merge branch 'master' of https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project 2021-04-26 21:47:02 +02:00
0793287044 Change Folder to Directory once again 2021-04-26 21:46:54 +02:00
Oystein
6bcbcbdea2 krav 2021-04-26 21:46:41 +02:00
66313d9461 Merge branch 'tempBranch' 2021-04-26 21:42:13 +02:00
Oystein
79ff110708 krav2 2021-04-26 21:41:30 +02:00
80730d79ca Change all "Folder" to "Directory" 2021-04-26 21:41:11 +02:00
Oystein
48250b6bd0 Merge branch 'master' of https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project 2021-04-26 21:25:07 +02:00
Oystein
fd83e1291d krav 2021-04-26 21:24:49 +02:00
e0d093fa4a Merge branch 'tempBranch' into 'master'
Miscellaneous changes

See merge request oysteikt/h20-tdt4100-project!7
2021-04-26 19:23:25 +00:00
70d4aa76e0 Comment out broken tests 2021-04-26 21:17:58 +02:00
91bba8878c Update tests 2021-04-26 21:15:36 +02:00
c53d5716c3 Add settingsprovider test 2021-04-26 21:14:54 +02:00
Oystein
057c1e0074 Merge branch 'tempBranch' of https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project into tempBranch 2021-04-26 20:09:24 +02:00
Oystein
b65ed726c7 settings 2021-04-26 20:08:46 +02:00
ecba05e492 Remove excess test template 2021-04-26 20:08:43 +02:00
faddeb4201 Fix editorControllerTest 2021-04-26 17:32:17 +02:00
909e241c5b Flush 2021-04-26 16:52:12 +02:00
de8d1e9756 Clean up ProgrammingLanguage code 2021-04-26 14:41:47 +02:00
ad9ee1c84e Update documentation for controllers 2021-04-26 14:35:47 +02:00
0c16ae3f15 Fix save on Exit Event 2021-04-26 13:09:23 +02:00
22406222f3 Remove New Folder feature 2021-04-26 12:46:26 +02:00
Oystein
08f9b2b881 DialogBoxes 2021-04-26 11:36:28 +02:00
94be84d6e1 Fix Save As 2021-04-26 11:30:56 +02:00
075988f327 Test all branches of LanguageChangedEvent 2021-04-26 00:31:33 +02:00
40d32a5d54 Add MarkdownTest 2021-04-26 00:02:45 +02:00
2860db78d7 Remove unused imports 2021-04-26 00:02:37 +02:00
68bf49f639 Add emptyTest 2021-04-25 23:35:30 +02:00
690901fd8b Remove icon from programminLanguage 2021-04-25 23:35:23 +02:00
28b9dba111 Update readme 2021-04-25 23:28:39 +02:00
e8b34b7164 Add model test 2021-04-25 23:13:44 +02:00
81c76ae9d4 Make error output labels consistent 2021-04-25 22:46:07 +02:00
1cc2c6f4f9 Remove unused test 2021-04-25 22:32:07 +02:00
697d2b59a8 extract legal settings to static variable 2021-04-25 22:21:37 +02:00
ed0e3bd871 Add missed branch 2021-04-25 22:21:15 +02:00
da02308498 Add tests for generateTree 2021-04-25 22:21:06 +02:00
f89968d9d0 Add private empty constructor to static classes 2021-04-25 22:14:59 +02:00
e274ac5e87 Fix OpenProjectEvent 2021-04-25 21:27:58 +02:00
445155ed33 fix apidoc 2021-04-25 00:13:21 +02:00
da83d108b2 Add FileTreeOperationTest 2021-04-25 00:06:53 +02:00
3b5600eba8 Add JavaTest 2021-04-25 00:02:55 +02:00
f235ab1f75 Fix language pattern randomness issue 2021-04-24 22:29:52 +02:00
Oystein
2e397fd971 EventTests 2021-04-23 11:29:09 +02:00
92fa16891c Add mimetype based icons 2021-04-23 00:10:33 +02:00
eb7f89c932 Remove old todos 2021-04-23 00:08:58 +02:00
505f6872f2 Error handle filetree generation 2021-04-22 22:02:23 +02:00
581bb0ad3b Replace FileSelectedEvent with OpenFileEvent 2021-04-22 21:49:35 +02:00
02c738e761 Add filename label 2021-04-22 21:48:38 +02:00
6504d3cb3f Squash some bugs 2021-04-21 19:01:51 +02:00
d63a5d90bf Make nullable paths explicitly nullable 2021-04-20 23:42:33 +02:00
c865a38349 Redefine property path 2021-04-20 20:38:57 +02:00
86b7be7647 Remove dependency-reduced-pom 2021-04-20 20:37:45 +02:00
33dd146e60 Readd graphics libraries 2021-04-20 20:37:01 +02:00
71868ed6e3 Fix run command 2021-04-20 20:35:03 +02:00
Oystein
d2f7ad1828 Lot of stuff 2021-04-20 17:37:50 +02:00
f0a724888d Add jar bundler 2021-04-20 15:29:10 +02:00
d4d6be1930 Fix controller test 2021-04-20 12:23:40 +02:00
59a45ba4a5 Merge branch 'extract-file-operations' into 'master'
Extract logic from classes

See merge request oysteikt/h20-tdt4100-project!6
2021-04-20 09:28:45 +00:00
77ddb56d69 Extract logic from classes 2021-04-20 09:28:45 +00:00
7d40b2608d Remove two last todos 2021-04-17 19:05:19 +02:00
740a4ee1f7 add requirements explanation 2021-04-17 19:03:51 +02:00
4e6c12e7c9 Fix api docs 2021-04-15 16:27:40 +02:00
e892acf3d7 Merge branch 'add-test-templates' into 'master'
Add test templates

See merge request oysteikt/h20-tdt4100-project!5
2021-04-15 14:17:46 +00:00
94e81e5e08 Add test templates 2021-04-15 14:17:46 +00:00
Oystein
38ea6a91fe added interfaces for file management 2021-04-12 15:51:57 +02:00
Oystein
6d1794421c comment handleCreateFolder 2021-04-12 10:58:23 +02:00
Oystein
fcab3df9fc exit dialog 2021-04-12 10:51:53 +02:00
Oystein
1156b827ac added some more with writing and reading to file 2021-04-07 12:39:52 +02:00
Oystein
79e684228f added FileStateChanged 2021-04-07 08:34:03 +02:00
Oystein
509f5224b5 if one tries to SaveAs now and projectpatch i selected, it will op there instead of deafult 2021-04-07 07:11:27 +02:00
Oystein
06397cc6cf checked for null in menubar 2021-04-07 06:51:30 +02:00
Oystein
be748d9138 added som comments 2021-04-07 06:42:48 +02:00
Oystein
4a63aa6edf typo 2021-04-07 06:04:44 +02:00
Oystein
ac7069e640 added some icons for fun and updated readme 2021-04-07 06:00:21 +02:00
Oystein
382ae5cbbc can now double click on directory without error 2021-04-07 04:53:36 +02:00
Oystein
2ecda54d7f added som restrictions to filetype and error alert 2021-04-07 03:26:26 +02:00
Oystein
df41e3df58 Added some to saving, add more to exceptions tomorrow 2021-04-06 20:45:15 +02:00
3f8482bb78 Move around checkboxes 2021-03-04 11:46:18 +00:00
180627578b Add some documentation 2021-03-02 18:57:31 +01:00
976 changed files with 2929 additions and 377 deletions

4
.gitignore vendored
View File

@@ -10,4 +10,6 @@ target/
.idea/
#VSCode
.vscode/
.vscode/
dependency-reduced-pom.xml

View File

@@ -3,15 +3,33 @@
# and
# https://gitlab.stud.idi.ntnu.no/tdt4140-staff/examples/-/blob/master/.gitlab-ci.yml
image: maven:3.6.3-openjdk-15
image: maven:3-openjdk-15-slim
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
MAVEN_OPTS: " \
-Dhttps.protocols=TLSv1.2 \
-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository \
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN \
-Dorg.slf4j.simpleLogger.showDateTime=true \
-Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
MAVEN_CLI_OPTS: " \
--batch-mode \
--errors \
--fail-at-end \
--show-version \
-Dprism.verbose=true \
-Dtestfx.robot=glass \
-Dtestfx.headless=true \
-Dglass.platform=Monocle \
-Dprism.order=sw \
-Dprism.text=t2k \
-Dtestfx.setup.timeout=60000"
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
@@ -38,6 +56,8 @@ unittest:
stage: test
needs: [build]
script:
- "apt update"
- "apt install -y openjfx"
- "mvn package $MAVEN_CLI_OPTS"
artifacts:
paths:
@@ -46,12 +66,13 @@ unittest:
reports:
junit:
- target/surefire-reports/TEST-*.xml
- target/failsafe-reports/TEST-*.xml
generate-coverage:
stage: docs
script:
- 'mvn clean jacoco:prepare-agent test jacoco:report'
- "apt update"
- "apt install -y openjfx"
- 'mvn clean jacoco:prepare-agent test $MAVEN_CLI_OPTS jacoco:report'
- 'cat target/site/jacoco/index.html'
coverage: '/Total.*?([0-9]{1,3})%/'
artifacts:

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

View File

@@ -4,37 +4,64 @@
<img src="src/main/resources/graphics/logo.svg" width="160" height="130">
Probably tastes better than any Apple editor and NetBeans combined.
--> [See project requirement details here][requirements.md] <--
In order to run the program, use run.bat if using Windows or run.sh if using UNIX based OS
<img src=".gitlab/graphics/main-screenshot.png">
</div>
---
## TODO:
- [X] ~~Tabs~~
- [X] Modeline w/ linenumbers
- [X] Syntax highlighting
- [ ] Filetree
- [X] Line numbers
- [X] Shortcuts
- [X] Change languages
- [X] Toggle line comment
- [X] Soft wrap
- [X] Darkmode/Lightmode or color themes
- [ ] Add icons for files/folders in filetree
- [X] Add [code coverage check](https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html) to verify quality of unit tests
[![Pipeline Status](https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/badges/master/pipeline.svg)](https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/commits/master/)
[![Coverage](https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/badges/master/coverage.svg?)](http://oysteikt.pages.stud.idi.ntnu.no/h20-tdt4100-project/)
[![Documentation](https://img.shields.io/badge/Documentation-pages-blue)](http://oysteikt.pages.stud.idi.ntnu.no/h20-tdt4100-project/apidocs)
[![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
## Maybe TODO?
- [ ] List Chooser
- [ ] Search
- [ ] And replace
## TODO:
- [x] ~~Tabs~~
- [x] Modeline w/ linenumbers
- [x] Syntax highlighting
- [x] Filetree
- [x] Line numbers
- [x] Shortcuts
- [x] Change languages
- [x] Toggle line comment
- [x] Soft wrap
- [x] Darkmode/Lightmode or color themes
- [x] Add icons for files/folders in filetree
- [x] Add [code coverage check](https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html) to verify quality of unit tests
- [ ] ~~Search~~
- [ ] ~~And replace~~
## Credits/Sources
- [james-d/SimpleMVP](https://github.com/james-d/SimpleMVP/tree/master/src/examples/mvp) -> Demonstration of JavaFX Model/View/Controller application
- [FXMisc/RichTextFX](https://github.com/FXMisc/RichTextFX) -> Library for handling rich text and line numbers
- [richtextfx-demos](https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos/README.md) -> Examples and demos of how to use RichTextFX, including [Java Keywords Demo](https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/JavaKeywordsAsyncDemo.java)
- [Pixabay/311788](https://pixabay.com/vectors/bananas-fruits-pair-yellow-bananas-311788/) -> Banana logo
- [Guava Event Bus](https://github.com/google/guava/wiki/EventBusExplained) -> Library for handling communication between controllers
- [JFX Moderna](https://github.com/openjdk/jfx/blob/master/modules/javafx.controls/src/main/resources/com/sun/javafx/scene/control/skin/modena/modena.css) -> CSS reference
- [devicons/devicon](https://github.com/devicons/devicon) -> icons for files and languages
<!---
- [devicons/devicon](https://github.com/devicons/devicon) -> Icon languages
-->
- [Papirus](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme) -> Icons for filetree
- [StackOverflow/38278601](https://stackoverflow.com/questions/38278601/javafx-treeview-directory-listing) ->
Example of how to generate and show a filetree recursively.
- [Youtube/ProgrammingKnowledge](https://www.youtube.com/watch?v=RY_Rb2UVQKQ) -> Introduction to the fundamentals
to create a filetree.
- [GenuineCoder](https://www.genuinecoder.com/save-files-javafx-filechooser/) -> Examples of how to use FileChooser
and setting extensionfilters. Also included a nice method to save files.
- [Tutorialspoint](https://www.tutorialspoint.com/how-to-save-files-using-a-file-chooser-in-javafx) -> Example
of how to use the openSaveDialog with FileChooser.
- [mkyoung](https://mkyong.com/java/how-to-create-directory-in-java/) -> Example of how to create a directory/folder.
- [Youtube/Cool IT Help](https://www.youtube.com/watch?v=gnXRI3pHxrU&t=727s) -> Showing how to cast (Stage). Example of how FXML, listener/event and DirectyChooser nicely can cooperate.
- [Code Makery](https://code.makery.ch/blog/javafx-dialogs-official/) -> Examples of many different javafx dialogs.
- [Oracle](https://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html#button) -> How to use JOption.
[requirements.md]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/REQUIREMENTS.md

204
REQUIREMENTS.md Normal file
View File

@@ -0,0 +1,204 @@
# Krav til applikasjonen
## Delbeskrivelser
### Del 1 - Grunnklasser og brukergrensesnitt
```
I denne delen skal dere lage de grunnleggende klassene i appen deres. Klassene skal realisere
hovedfunksjonaliteten i appen, men dere trenger ikke å implementere feilhåndtering og lagring i
denne delen (det kommer senere). Tilstanden til objektene i appen deres skal være innkapslet,
og et objekt skal administrere sin egen tilstand.
I tillegg til grunnklassene skal dere lage et brukergrensesnitt i FXML. Vi anbefaler at dere
tenker litt over dette grensesnittet, gjerne skissér det på forhånd for å se at det virker greit å
bruke. Om dere har tatt eller tar TDT4180 - Menneske-maskin-interaksjon kan det være lærerikt
å bruke noen av konseptene dere har lært der (men merk at dere ikke vurderes ut i fra det i dette
faget).
Dere skal også lage Controller- og App-klasser som starter appen og kobler sammen brukergrensesnitt
og underliggende klasser. Merk at appen deres skal bygges etter Model-ViewController-prinsippet.
Det betyr at det skal være et klart skille mellom modell (model), grensesnitt (view), og
kontroller (controller) i appen. Her er grensesnittet definert av FXML-filene deres,
og modellene er de underliggende klassene. Kontrollerens jobb er å binde sammen grensesnitt og
modeller, altså å holde verdiene i grensesnittet oppdatert i forhold til modellene, og å reagere på
brukerinput i grensesnittet og kalle passende metoder i modellene.
```
### Del 2 - Filbehandling
```
Her skal dere utvide appen til å kunne lese fra og skrive til filer. Dere må selv bestemme hva som
skal lagres, og formatet det skal lagres på. Altså må dere finne en måte å strukturere tilstanden
i objektene deres som en tekstfil.
For å gjøre det enklere å bytte filformat på et senere tidspunkt, eller å gjøre det mulig å lagre
i forskjellige formater, skal dere lage et grensesnitt (interface) med metoder for lesing fra og
skriving til fil. Deretter må dere lage minimum en ny klasse (altså ikke en av klassene dere har
lagd tidligere i prosjektet) som implementerer grensesnittet og realiserer funksjonaliteten.
```
### Del 3 - Feilhåndtering
```
I de aller fleste programmer vil det være mulighet for at feil skjer under kjøring, uavhengig av
om koden er skrevet riktig. Det kan f.eks. være ved ugyldig input fra brukere, filer som ikke
eksisterer, manglende internett, eller lignende. Om denne type feil ikke håndteres, vil det enten
føre til at appen oppfører seg feil, eller at den krasjer.
I denne delen av prosjektet skal dere utvide appen deres til å håndtere feil på passende måter.
For å gjøre dette må dere finne ut:
• Hvor i appen deres det kan oppstå feil.
• Hvordan appen bør respondere på slik feil.
Deretter må dere implementere feilhånderingen dere har funnet ut at er hensiktsmessig.
```
### Del 4 - Testing
```
Siste del av prosjektet handler om å skrive enhetstester til appen deres. Dere bør teste alle
metoder med funksjonalitet utover helt enkle get- og set-metoder. Dere trenger heller ikke å
teste Controller- og App-klassene deres, da det ikke skal være logikk i disse. Har dere logikk
liggende i Controller-klassen bør dere flytte dette til en passende underliggende klasse før dere
begynner på denne delen.
Når dere skriver testene bør dere først tenke over hvilke deler av koden det er viktig å sjekke
at fungerer som den skal. Deretter bør dere prøve å komme på (kombinasjoner av) input-verdier
og tilstander som kan føre til uventet oppførsel i disse delene. Slike edge-cases bør det skrives
tester for, i tillegg til å teste for at funksjonaliteten fungerer med ”snillere” verdier. Til slutt er
det viktig at testene skal sjekke at koden fungerer, ikke motsatt. Om dere er sikre på at en testen
er riktig, men den feiler, så må dere sannsynligvis endre koden i klassen som testes ikke testen
deres.
```
## Detaljerte krav
### Del 1 - Grunnklasser og brukergrensesnitt
1. **Appen skal bestå av minimum to interagerende klasser, i tillegg til Controller- og Appklassene, og brukergrensesnittet laget i FXML.**
Andre klasser:
- [service/][service-folder]
- [model/][model-folder]
App-klasse:
- [Main.java][Main-file]
Controller klasser:
- [MainController.java][MainController-file]
- [controllers/][controllers-folder]
Brukergrensesnitt:
[resources/fxml/][fxml-folder]
I denne delen har vi laget én Application-klasse som laster én hoved-FXML-fil + kontroller, som internt linker til flere andre fxml filer med hver sine kontrollere. Kontrollerne binder sammen sine relevante grensesnittsparametere med en global [`Model`][Model-file]-klasse og setter opp event-lyttere på de relevante FXML-elementene, i tillegg til å sette opp en eventbus mellom seg og de andre kontrollerne for kommunikasjon dem imellom. Eventbus bruker dataklasser arvet fra [`Event`][Event-file]-klassen, som beskrivelse på hvilken type melding som blir sendt. Basert på typen av meldingen skiller eventbusen hvilke funksjoner som skal fyres av. Dette er en variant av observatør-observert metoden.
Her er et diagram over koblingene i programmet:
<div align="center">
<img src=".gitlab/graphics/FXML-diagram.png">
</div>
2. **Minimum en av klassene må ha noe funksjonalitet utover ren datalagring, en form for kalkulasjoner (i en utvidet betydning av begrepet).**
Klassene som blir brukt for kalkulasjon har vi lagt i [app/service][service-folder]
3. **Det skal implementeres korrekt innkapsling og validering for tilstandene til objektene i appen.**
Ettersom vi bruker en eventbus, så ligger innkapslingen og valideringen for tilstandene i events og i funksjonene som tar input fra brukergrensesnittet.
Eksempler på dette kan dere se her:
- [events/LanguageChangedEvent.java][LanguageChangedEvent-file]
- [events/EditorChangedEvent.java][EditorChangedEvent-file]
- [events/ThemeChangedEvent.java][ThemeChangedEvent-file]
4. **Appen skal organiseres etter Model-View-Controller prinsippet, som beskrevet ovenfor.**
Hovedbitene med state som skal være global for hele applikasjonen ligger i en klasse som heter [Model][Model-file]. All annen tilstand er lokal til FXML-elementene hvor den hører hjemme, og ved enhver oppdatering av den tilstanden vil det bli sent ut relevante events til alle andre kontrollere for at de skal oppdatere sine FXML-elementer sin indre tilstand. Dermed er Model, View, og Controllers separert.
### Del 2 - Filbehandling
1. **Et grensesnitt som minimum har en metode for lesing fra og en for skriving til fil.**
Dette finner dere i [settings/SettingsProviderI.java][interface-file]
Her er metodene for lesing fra og skriving til fil som innebærer programtilstandene.
2. **Minimum en ny klasse (altså ikke en av klassene dere har lagd tidligere i prosjketet) som implementerer grensesnittet. Klassen skal altså lagre (deler av) tilstanden til appen deres til et valgfritt format og lese inn det samme formatet til appen.**
Dette finner dere i [settings/SettingsProvider.java][settings-file]
SettingsProvider-filen implementerer grensesnittet og realiserer funksjonaliteten. Her blir tilstandene til innstillings-objektene strukturert over til et tekstformat.
3. **Brukergrensesnittet i appen må utvides med mulighet for å skrive tilstanden til og lese fra fil.**
Dette finner dere i [MenubarController.java][MenubarController-file]
### Del 3 - Feilhåndtering
1. **Det skal være implementert hensiktsmessig feilhåndtering i alle utsatte deler av appen deres.**
På lik måte som det er lagt inn innkapsling der hvor programmet får input ifra brukeren, er det er det meste av feilhåndteringen blir gjort. Et eksempel på dette finner dere i [service/FileTreeOperations.java][FileTreeOperations-file]
I FileOperations er det lagt inn omfattende feilhåndtering for filer.
Dette finner dere i [service/FileOperations.java][FileOperations-file]
### Del 4 - Testing
[![Coverage](https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/badges/master/coverage.svg?)](https://oysteikt.pages.stud.idi.ntnu.no/h20-tdt4100-project/)
1. **Alle relevante deler av koden deres skal enhetstestes.**
Dere finner testene i [src/test/java/app][tests-folder]
**OBS: se nederst angående `FileOperations` og `DialogBoxes`**
2. **Enhetstestene skal skrives i JUnit 5.**
Alle testene er skrevet i JUnit 5, med hjelp fra et par andre biblioteker og plugins som bygger på toppen av JUnit.
Disse inluderer:
- [TestFX](http://testfx.github.io/TestFX/)
- [Hamcrest](http://hamcrest.org/JavaHamcrest/)
- [Mockito](https://site.mockito.org/)
- [Monocle](https://wiki.openjdk.java.net/display/OpenJFX/Monocle)
- [Jacoco](https://www.eclemma.org/jacoco/)
På grunn av hvordan noen av klassene er skrevet statisk, så mangler vi to tester for [`FileOperations`][FileOperations-file] og [`DialogBoxes`][DialogBoxes-file]
Grunnen til at vi ikke fikk testet disse er fordi at Mockito selv ikke kan teste kode som inneholder `static`/`final` properties, eller inline bruk av konstruktør. For disse brukstilfellene har det vært vanlig å bruke [Powermock][powermock], men dette støttet ikke JUnit5 enda. I [`FileOperationsTest.java`][FileOperationsTest-file] finner dere kommentert ut kode som ville vært hvordan vi hadde skrevet koden om powermock var tilgjengelig for JUnit5
Se [aggarwal-rohan17.medium.com](https://aggarwal-rohan17.medium.com/simplifying-junit-mockito-and-powermock-d1392059ce87#48d4) for detaljer
[Model-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/model/Model.java
[Event-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/events/Event.java
[model-folder]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/java/app/model
[service-folder]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/java/app/service
[Main-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/java/app/Main.java
[MainController-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/java/app/MainController.java
[controllers-folder]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/java/app/controllers
[fxml-folder]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/main/resources/fxml/
[tests-folder]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/tree/master/src/test/java/app
[interface-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/settings/SettingsProviderI.java
[settings-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/settings/SettingsProvider.java
[MenubarController-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/controllers/MenubarController.java
[LanguageChangedEvent-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/events/LanguageChangedEvent.java
[EditorChangedEvent-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/events/EditorChangedEvent.java
[ThemeChangedEvent-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/events/ThemeChangedEvent.java
[FileOperations-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/service/FileOperations.java
[FileTreeOperations-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/service/FiletreeOperations.java
[DialogBoxes-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/main/java/app/service/DialogBoxes.java
[powermock]: https://github.com/powermock/powermock
[FileOperationsTest-file]: https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/-/blob/master/src/test/java/app/service/FileOperationsTest.java

92
pom.xml
View File

@@ -1,5 +1,4 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<!-- Please read https://maven.apache.org/guides/introduction/introduction-to-the-pom.html -->
@@ -30,9 +29,9 @@
<!-- RichTextFX -->
<dependency>
<groupId>org.fxmisc.richtext</groupId>
<artifactId>richtextfx</artifactId>
<version>0.10.5</version>
<groupId>org.fxmisc.richtext</groupId>
<artifactId>richtextfx</artifactId>
<version>0.10.5</version>
</dependency>
<!-- Guava Eventbus -->
@@ -42,13 +41,12 @@
<version>3.2.0-01</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.0-M1</version>
<scope>test</scope>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.0-M1</version>
<scope>test</scope>
</dependency>
<!-- TestFX -->
@@ -65,6 +63,36 @@
<scope>test</scope>
</dependency>
<!-- Hamcrest - Matchers to help testing the JavaFX UI -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
<!-- Monocle - Headless UI testing in CI -->
<dependency>
<groupId>org.testfx</groupId>
<artifactId>openjfx-monocle</artifactId>
<version>jdk-12.0.1+2</version>
<scope>test</scope>
</dependency>
<!-- Mockito - Mocking Library for performing isolated unit tests -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>2.23.0</version>
<scope>test</scope>
</dependency>
<!-- JavaDoc -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>
@@ -73,6 +101,26 @@
<type>maven-plugin</type>
</dependency>
<!-- JavaFX Graphics - For completeness when packaging uber-jar -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>17-ea+7</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>17-ea+7</version>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>17-ea+7</version>
<classifier>mac</classifier>
</dependency>
</dependencies>
<build>
<plugins>
@@ -90,7 +138,7 @@
</compilerArgs>
</configuration>
</plugin>
<!-- Plugin to test all junit tests with 'surefire:test' -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
@@ -112,6 +160,7 @@
</configuration>
</plugin>
<!-- Plugin to generate code coverage reports -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
@@ -133,6 +182,27 @@
</executions>
</plugin>
<!-- Plugin to bundle dependencies to generate a jar file -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>app.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

7
run.bat Normal file
View File

@@ -0,0 +1,7 @@
@ECHO OFF
call mvn clean package -Dmaven.test.skip=true
java -jar --enable-preview target/banana-editor-1.0.0.jar
pause

5
run.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
mvn clean package -Dmaven.test.skip=true
java -jar --enable-preview target/banana-editor-1.0.0.jar

View File

@@ -1,6 +1,7 @@
package app;
import java.io.IOException;
import java.util.Optional;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
@@ -9,9 +10,9 @@ import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import app.events.FileSaveStateChangedEvent;
import app.events.LanguageChangedEvent;
import app.events.ThemeChangedEvent;
import app.model.Model;
import app.service.DialogBoxes;
import app.settings.SettingsProvider;
public class Main extends Application {
@@ -24,6 +25,8 @@ public class Main extends Application {
/**
* Boilerplate function to launch the application.
*
* @param args Additional arguments from commandline
*/
public static void main(String[] args) {
launch(args);
@@ -34,17 +37,25 @@ public class Main extends Application {
*/
private void setupWindow(Stage window) {
window.setTitle(TITLE);
window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH)));
if (window.getIcons().isEmpty())
window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH)));
}
/**
* Loads all FXML documents of the main UI and initializes all correlated subcontrollers
* Loads all FXML documents of the main UI and initializes all correlated
* subcontrollers
*
* @throws IOException
*/
private void loadFXML() throws IOException {
// TODO: Error handle this function.
this.fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
this.fxmlRoot = fxmlLoader.load();
private void loadFXML() {
try {
this.fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
this.fxmlRoot = fxmlLoader.load();
} catch (Exception e) {
DialogBoxes.showErrorMessage("There is an error within the program. Please try to reinstall.");
System.exit(1);
}
}
/**
@@ -52,6 +63,7 @@ public class Main extends Application {
*/
private void createScene() {
this.scene = new Scene(fxmlRoot);
this.scene.setUserData(this.fxmlLoader);
Model.setScene(scene);
}
@@ -62,14 +74,17 @@ public class Main extends Application {
scene.getStylesheets().setAll("", "");
MainController mainController = fxmlLoader.getController();
mainController.getEventBus().post(new LanguageChangedEvent("Java"));
mainController.getEventBus().post(new ThemeChangedEvent("Monokai"));
SettingsProvider SP = new SettingsProvider(mainController.getEventBus());
SP.loadSettings();
Model.setActiveFilePath(Optional.empty());
Model.setProjectPath(Optional.empty());
mainController.getEventBus().post(new FileSaveStateChangedEvent(true));
mainController.setHostServices(getHostServices());
}
/**
* The entrypoint of the application.
*
* @param window The primary window of the application
*/
@Override

View File

@@ -4,6 +4,8 @@ import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javax.swing.JOptionPane;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
@@ -18,6 +20,9 @@ import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
/**
* An FXML controller that controls the application and all subcontrollers
*/
public class MainController implements Initializable {
@FXML
@@ -64,8 +69,16 @@ public class MainController implements Initializable {
return hostServices;
}
/**
* @return All subcontrollers of this controller
*/
public List<Controller> getInnerControllers() {
return List.of(editorController, filetreeController, modelineController, menubarController);
}
/**
* Set a reference to the global Host Services API
*
* @param hostServices The JavaFX HostServices object
* @see #getHostServices()
*/
@@ -75,52 +88,69 @@ public class MainController implements Initializable {
/**
* Replace a CSS file in a specific location in the application CSS array
*
* @param position The position of the CSS file to replace
* @param cssPath The path in resources to the new CSS file
* @param cssPath The path in resources to the new CSS file
*/
private void setCSSAt(int position, String cssPath) {
//TODO: Error check that position in range 0 to 1
if ((position != 0) && (position != 1)) {
throw new IllegalArgumentException("Range of position must be either 0 or 1");
}
String nextStyleSheet = getClass().getResource(cssPath).toExternalForm();
Model.getScene().getStylesheets().set(position, nextStyleSheet);
}
/* ------------------------------------------------------------------------ */
/* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */
/**
* Change the CSS according to which language is being used
*
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
public void handle(LanguageChangedEvent event) {
this.setCSSAt(1, "/styling/languages/" + event.getLanguage().toLowerCase() + ".css");
}
/**
* Change the CSS according to which theme the user chooses
*
* @param event
*/
@Subscribe
private void handle(ThemeChangedEvent event) {
public void handle(ThemeChangedEvent event) {
this.setCSSAt(0, "/styling/themes/" + event.getTheme().toLowerCase().replace(" ", "-") + ".css");
}
/**
* Open a link in the browser.
*
* @param event
*/
@Subscribe
private void handle(OpenLinkInBrowserEvent event) {
public void handle(OpenLinkInBrowserEvent event) {
this.getHostServices().showDocument(event.getLink());
}
/**
* Handle an exit request for the whole program
* Handle an exit request for the whole program. Checking if all is saved before
* closing the app. The user can either choose to exit or go back to the
* application and save.
*
* @param event
*/
@Subscribe
private void handle(ExitApplicationEvent event) {
// TODO: send save file event, exit application safely.
public void handle(ExitApplicationEvent event) {
if (!Model.getFileIsSaved()) {
int g = JOptionPane.showConfirmDialog(null, "Your files are not saved.\nSave before exit?", "Exit",
JOptionPane.YES_NO_OPTION);
if (g == JOptionPane.YES_OPTION)
this.editorController.saveCodeArea(Model.getActiveFilePath().isEmpty());
}
Platform.exit();
}
}
}

View File

@@ -0,0 +1,15 @@
package app;
/**
* A launcher class to point towards as the start point for a packaged JAR
*/
public class MainLauncher {
/**
* The root function of the call stack
*
* @param args Commandline arguments
*/
public static void main(String[] args) {
Main.main(args);
}
}

View File

@@ -3,12 +3,12 @@ package app.controllers;
import com.google.common.eventbus.EventBus;
/**
* Interface to make a controller that contains an EventBus
* Interface describing a JavaFX controller that contains an EventBus
*/
public interface Controller {
/**
* Registers the main EventBus into the controller.
* @param eventBus the main EventBus
* @param eventBus
*/
public void setEventBus(EventBus eventBus);
}

View File

@@ -1,11 +1,8 @@
package app.controllers;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.Scanner;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
@@ -19,19 +16,26 @@ import org.fxmisc.richtext.model.TwoDimensional.Position;
import app.events.CopyEvent;
import app.events.CutEvent;
import app.events.EditorChangedEvent;
import app.events.LanguageChangedEvent;
import app.events.OpenFileEvent;
import app.events.PasteEvent;
import app.events.RedoEvent;
import app.events.SaveFileEvent;
import app.events.ToggleCommentEvent;
import app.events.ToggleWrapTextEvent;
import app.events.UndoEvent;
import app.events.FileSaveStateChangedEvent;
import app.events.FileSelectedEvent;
import app.model.Model;
import app.service.FileOperations;
import app.service.LanguageOperations;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.stage.Stage;
/**
* An FXML controller that controls the CodeArea
*/
public class EditorController implements Initializable, Controller {
@FXML
@@ -59,7 +63,7 @@ public class EditorController implements Initializable, Controller {
/**
* Applies highlighting to the editor.
*
* @param highlighting highlighting data
* @param highlighting Syntax highlighting data
*/
private void setHighlighting(StyleSpans<Collection<String>> highlighting) {
this.editor.setStyleSpans(0, highlighting);
@@ -73,8 +77,12 @@ public class EditorController implements Initializable, Controller {
}
/**
* Uses Model.language to determine whether the current line/selection is
* commented or not, and toggles the comment.
* Uses the {@link app.model.ProgrammingLanguage ProgrammingLanguage} in
* {@link app.model.Model Model} to determine whether the current line/selection
* is commented or not, and toggles the comment.
*
* @see app.model.ProgrammingLanguage#commentLine(String)
* ProgrammingLanguage.commentLine(line)
*/
private void toggleComment() {
if (editor.getSelectedText().equals("")) {
@@ -101,6 +109,11 @@ public class EditorController implements Initializable, Controller {
}
}
/**
* Updates the wraptext setting of the code area
*
* @param isWrapText The updated setting value
*/
private void setWrapText(boolean isWrapText) {
this.editor.setWrapText(isWrapText);
}
@@ -120,70 +133,143 @@ public class EditorController implements Initializable, Controller {
}
/**
* Refreshes the editor whenever the language is changed.
* Updates the content of the editor.
*
* @param newContent The String to be inserted into the editor
*/
private void setEditorContent(String newContent) {
editor.clear();
editor.appendText(newContent);
}
/**
* Saving/Writing to the file based on the active filepath in {@link app.model.Model Model}
* if it is a new File. Otherwise it will open a dialog to ask the user where to save the file.
*
* @param isNewFile Whether or not the file already has a path
*/
public void saveCodeArea(boolean isNewFile) {
Stage stage = (Stage) editor.getScene().getWindow();
if (isNewFile && FileOperations.saveFileWithDialog(stage, editor.getText())) {
this.eventBus.post(new OpenFileEvent(Model.getActiveFilePath()));
this.eventBus.post(new FileSaveStateChangedEvent(true));
}
else if (FileOperations.saveFile(Model.getActiveFilePath().orElseThrow(), editor.getText())) {
this.eventBus.post(new FileSaveStateChangedEvent(true));
}
}
/* ------------------------------------------------------------------------ */
/* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */
/**
* Updates the CodeArea whenever a new file is opened.
*
* @param event
*/
@Subscribe
private void handle(FileSelectedEvent event) {
try (Scanner sc = new Scanner(new File(event.getPath()))) {
editor.clear();
while (sc.hasNextLine()) {
editor.appendText(sc.nextLine());
editor.appendText("\n");
}
} catch (FileNotFoundException ex) {
System.out.println(event.getPath());
}
public void handle(OpenFileEvent event) {
String newContent =
event
.getPath()
.map(path -> FileOperations.readFile(path))
.orElse("");
this.setEditorContent(newContent);
}
/**
* Saves the editor content to a file
*
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
public void handle(SaveFileEvent event) {
this.saveCodeArea(event.getIsNewFile());
}
/**
* Refreshes the syntax highlighting when the Programming language is changed
*
* @param event
*/
@Subscribe
public void handle(LanguageChangedEvent event) {
this.refreshHighlighting();
}
/**
* Toggles a language specific comment of the line/selection
* Toggles a comment based on the editor state
*
* @param event
*/
@Subscribe
private void handle(ToggleCommentEvent event) {
public void handle(ToggleCommentEvent event) {
this.toggleComment();
}
/**
* Toggles the WrapText setting
*
* @param event
*/
@Subscribe
private void handle(ToggleWrapTextEvent event) {
public void handle(ToggleWrapTextEvent event) {
this.setWrapText(event.getIsWrapped());
}
/**
* Undo if focused
*
* @param event
*/
@Subscribe
private void handle(UndoEvent event) {
public void handle(UndoEvent event) {
if (this.editor.isFocused())
this.editor.undo();
}
/**
* Redo if focused
*
* @param event
*/
@Subscribe
private void handle(RedoEvent event) {
public void handle(RedoEvent event) {
if (this.editor.isFocused())
this.editor.redo();
}
/**
* Copy selected content if focused
*
* @param event
*/
@Subscribe
private void handle(CopyEvent event) {
public void handle(CopyEvent event) {
if (this.editor.isFocused())
this.editor.copy();
}
/**
* Cut selected content if focused
*
* @param event
*/
@Subscribe
private void handle(CutEvent event) {
public void handle(CutEvent event) {
if (this.editor.isFocused())
this.editor.cut();
}
/**
* Paste from clipboard if focused
*
* @param event
*/
@Subscribe
private void handle(PasteEvent event) {
public void handle(PasteEvent event) {
if (this.editor.isFocused())
this.editor.paste();
}

View File

@@ -4,42 +4,39 @@ import javafx.fxml.FXML;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.events.FileSelectedEvent;
import app.events.OpenFileEvent;
import app.events.OpenProjectEvent;
import app.events.SaveFileEvent;
import app.model.Model;
import app.service.DialogBoxes;
import app.service.FiletreeOperations;
import javafx.fxml.Initializable;
/**
* An FXML controller that controls the Filetree
*/
public class FiletreeController implements Initializable, Controller {
private EventBus eventBus;
// Creating the images for the icons.
Image folder = new Image(getClass().getResourceAsStream("/graphics/folder.png"));
Image md = new Image(getClass().getResourceAsStream("/graphics/md.png"));
Image java = new Image(getClass().getResourceAsStream("/graphics/java.png"));
Image placeholder = new Image(getClass().getResourceAsStream("/graphics/placeholder.png"));
// Creating the variable.
@FXML
private TreeView<String> filetree;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//
}
public void initialize(URL url, ResourceBundle resourceBundle) {}
@Override
public void setEventBus(EventBus eventBus) {
@@ -48,106 +45,78 @@ public class FiletreeController implements Initializable, Controller {
}
/**
* The displaying of the fileTree. The inputChosen(the path) is aquired from the
* eventBus (OpeFileProjectEvent). The root is created as a CheckBoxItems and
* sends it to generateTree, and after that setting it to the root.
* Generate a tree structure of a directory, and set the filetree to
* show the new tree
*
* @param rootDir Path to the directory to be the root of the tree
*/
private void showTree(String inputChosen) {
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>(inputChosen);
private void showTree(Path rootDir) {
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>(rootDir.getFileName().toString());
filetree.setShowRoot(false);
File fileInputChosen = new File(inputChosen);
File fileInputChosen = rootDir.toFile();
generateTree(fileInputChosen, root);
try {
FiletreeOperations.generateTree(fileInputChosen, root);
filetree.setRoot(root);
}
/**
* The method to generate the fileTree recursively. If it is a directory a
* CheckBoxStringItem is created and the method is called again. It goes through
* all until every directory or file inside the orginal CheckBoxItem is made. If
* the item is a file it sends it to the help function checkExtension which is
* described below.
*/
private void generateTree(File file, CheckBoxTreeItem<String> parent) {
if (file.isDirectory()) {
CheckBoxTreeItem<String> element = new CheckBoxTreeItem<>(file.getName(), new ImageView(folder));
parent.getChildren().add(element);
List<File> dirList = new ArrayList<>();
List<File> fileList = new ArrayList<>();
sortFiles(dirList, fileList, file);
for (File f : dirList) {
generateTree(f, element);
}
} else {
checkExtensions(file, parent);
filetree.setRoot(root);
} catch (Exception e) {
Model.setProjectPath(Optional.empty());
DialogBoxes.showErrorMessage(
"Could not open directory.\n\n"
+ "Do you have the right permissions for this directory?\n"
+ "Or does the directory contain any shortcut to somewhere within itself?"
);
}
}
/**
* A helping function to sort the files/folders in the fileTree so that it shows
* in the correct order.
*/
private void sortFiles(List<File> dirList, List<File> fileList, File file) {
for (File f : file.listFiles()) {
if (f.isDirectory())
dirList.add(f);
else {
fileList.add(f);
}
}
dirList.addAll(fileList);
}
/**
* A help function to check if the extensions match .java or.md to insert the
* spesific icons and sending it to another help funtion createExtension to
* create the new CheckboxTreeItem<String> that will add to the parent.
*/
private void checkExtensions(File file, CheckBoxTreeItem<String> parent) {
String name = file.getName();
String ext = (name.substring(file.getName().lastIndexOf(".") + 1, file.getName().length()));
if ("java".equals(ext))
createExtension(name, java, parent);
else if ("md".equals(ext))
createExtension(name, md, parent);
else
createExtension(name, placeholder, parent);
}
private void createExtension(String name, Image image, CheckBoxTreeItem<String> parent) {
CheckBoxTreeItem<String> element = new CheckBoxTreeItem<>(name, new ImageView(image));
parent.getChildren().add(element);
}
* Handles opening a file whenever a filetree item is clicked twice. */
@FXML
private void handleMouseClick(MouseEvent event) {
if (event.getClickCount() == 2) {
TreeItem<String> item = filetree.getSelectionModel().getSelectedItem();
String root = Model.getProjectPath().getFileName().toString();
String path = "";
while (!root.equals(item.getValue())) {
path = File.separator + item.getValue() + path;
item = item.getParent();
}
try {
Path path = FiletreeOperations.getPathOfTreeItem(item);
path = Model.getProjectPath() + path;
this.eventBus.post(new FileSelectedEvent(path));
if (!Files.isDirectory(path)) {
this.eventBus.post(new OpenFileEvent(Optional.ofNullable(path)));
}
} catch (FileNotFoundException e) {
System.err.println("[ERROR] Could not find filepath from filetree");
System.err.print(e);
}
}
}
/* ------------------------------------------------------------------------ */
/* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */
/**
* Updates the filetree whenever a new project is opened
*
* @param event
*/
@Subscribe
private void handle(OpenProjectEvent event) {
this.showTree(event.getPath());
event.getPath().ifPresentOrElse(
path -> this.showTree(path),
() -> System.err.println("[ERROR] OpenProjectEvent was empty")
);
}
/**
* Updates the filetree whenever a new file gets saved
*
* @param event
*/
@Subscribe
private void handle(SaveFileEvent event) {
if (event.getIsNewFile())
Model
.getProjectPath()
.ifPresent(path -> this.showTree(path));
}
}

View File

@@ -1,7 +1,9 @@
package app.controllers;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus;
@@ -10,17 +12,19 @@ import com.google.common.eventbus.Subscribe;
import app.events.CopyEvent;
import app.events.CutEvent;
import app.events.ExitApplicationEvent;
import app.events.FileSelectedEvent;
import app.events.LanguageChangedEvent;
import app.events.OpenFileEvent;
import app.events.OpenLinkInBrowserEvent;
import app.events.OpenProjectEvent;
import app.events.PasteEvent;
import app.events.RedoEvent;
import app.events.SaveFileEvent;
import app.events.ThemeChangedEvent;
import app.events.ToggleCommentEvent;
import app.events.ToggleWrapTextEvent;
import app.events.UndoEvent;
import app.model.Model;
import app.service.DialogBoxes;
import app.service.FileOperations;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
@@ -28,10 +32,11 @@ import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.MenuBar;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ToggleGroup;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
/**
* A FXML controller that controls the MenuBar
*/
public class MenubarController implements Initializable, Controller {
private EventBus eventBus;
@@ -42,9 +47,11 @@ public class MenubarController implements Initializable, Controller {
@FXML
private ToggleGroup languageToggleGroup;
@FXML
private ToggleGroup themeToggleGroup;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// TODO: implement
}
@Override
@@ -53,53 +60,75 @@ public class MenubarController implements Initializable, Controller {
this.eventBus.register(this);
}
/* ------------------------------------------------------------------------ */
/* FILE */
/* ------------------------------------------------------------------------ */
@FXML
public void handleOpenFile() {
FileChooser fc = new FileChooser();
fc.setTitle("Open File");
Stage stage = (Stage) menubar.getScene().getWindow();
File chosenFile = fc.showOpenDialog(stage);
String correctFormat = chosenFile.getAbsolutePath().replace("\\", "\\\\");
Model.setActiveFilePath(chosenFile.toPath());
this.eventBus.post(new FileSelectedEvent(correctFormat));
}
@FXML
private void handleOpenProject() {
DirectoryChooser dc = new DirectoryChooser();
dc.setTitle("Open Project");
Stage stage = (Stage) menubar.getScene().getWindow();
File chosenDir = dc.showDialog(stage);
String correctFormat = chosenDir.getAbsolutePath().replace("\\", "\\\\");
Model.setProjectPath(chosenDir.toPath());
this.eventBus.post(new OpenProjectEvent(correctFormat));
}
// @FXML
// public void handleSaveFile() {
// FileChooser fc = new FileChooser();
// fc.setTitle("Save File");
// Stage stage = (Stage) menubar.getScene().getWindow();
// FileChooser.ExtensionFilter extentionjava = new
// FileChooser.ExtensionFilter(".java");
// FileChooser.ExtensionFilter extentionmd = new
// FileChooser.ExtensionFilter(".md");
// fc.getExtensionFilters().addAll(extentionjava, extentionmd);
// fc.showOpenDialog(stage);
// }
/* ---------------------------------- File ---------------------------------- */
/**
* Handles the event where the language was change from the menu.
* Handles whenever the New File button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleNewFile() {
this.eventBus.post(new OpenFileEvent(Optional.empty()));
}
/**
* Handles whenever the Open File button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleOpenFile() {
Stage stage = (Stage) menubar.getScene().getWindow();
try {
File file = FileOperations.openFileWithDialog(stage);
this.eventBus.post(new OpenFileEvent(Optional.ofNullable(file.toPath())));
} catch (FileNotFoundException e) {
DialogBoxes.showErrorMessage("File not found!");
}
}
/**
* Handles whenever the Open Project button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleOpenProject() {
Stage stage = (Stage) menubar.getScene().getWindow();
try {
File dir = FileOperations.openDirectoryWithDialog(stage);
this.eventBus.post(new OpenProjectEvent(Optional.of(dir.toPath())));
} catch (FileNotFoundException e) {}
}
/**
* Handles whenever the Save button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleSaveFile() {
this.eventBus.post(new SaveFileEvent());
}
/**
* Handles whenever the Save as button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleSaveAsFile() {
this.eventBus.post(new SaveFileEvent(true));
}
/**
* Handles whenever the programming language is changed from the menubar.
*
* @param event
*/
@@ -108,19 +137,29 @@ public class MenubarController implements Initializable, Controller {
this.eventBus.post(new LanguageChangedEvent(((RadioMenuItem) event.getSource()).getText()));
}
/**
* Handles whenever the wraptext togglebutton is pressed in the menubar
*
* @param event
*/
@FXML
private void handleToggleWraptext(ActionEvent event) {
var isSelected = ((CheckMenuItem) event.getSource()).selectedProperty().get();
this.eventBus.post(new ToggleWrapTextEvent(isSelected));
}
/**
* Handles whenever the theme is changed from the menubar
*
* @param event
*/
@FXML
private void handleThemeChange(ActionEvent event) {
this.eventBus.post(new ThemeChangedEvent(((RadioMenuItem) event.getSource()).getText()));
}
/**
* Handles the event where there was an exit request from the menu.
* Handles whenever the exit button is pressed in the menubar
*
* @param event
*/
@@ -129,44 +168,75 @@ public class MenubarController implements Initializable, Controller {
this.eventBus.post(new ExitApplicationEvent());
}
/* ------------------------------------------------------------------------ */
/* EDIT */
/* ------------------------------------------------------------------------ */
/* ---------------------------------- Edit ---------------------------------- */
/**
* Handles whenever the undo button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleUndo(ActionEvent event) {
this.eventBus.post(new UndoEvent());
}
/**
* Handles whenever the redo button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleRedo(ActionEvent event) {
this.eventBus.post(new RedoEvent());
}
/**
* Handles whenever the copy button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleCopy(ActionEvent event) {
this.eventBus.post(new CopyEvent());
}
/**
* Handles whenever the cut button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleCut(ActionEvent event) {
this.eventBus.post(new CutEvent());
}
/**
* Handles whenever the paste button is pressed in the menubar
*
* @param event
*/
@FXML
private void handlePaste(ActionEvent event) {
this.eventBus.post(new PasteEvent());
}
/**
* Handles whenever the Toggle Comment button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleToggleComment(ActionEvent event) {
this.eventBus.post(new ToggleCommentEvent());
}
/* ------------------------------------------------------------------------ */
/* ABOUT */
/* ------------------------------------------------------------------------ */
/* ---------------------------------- About --------------------------------- */
/**
* Handles whenever the About button is pressed in the menubar
*
* @param event
*/
@FXML
private void handleAbout(ActionEvent event) {
String aboutLink = "https://gitlab.stud.idi.ntnu.no/oysteikt/tdt4100-project-2021v/-/blob/master/README.md";
@@ -183,9 +253,33 @@ public class MenubarController implements Initializable, Controller {
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
this.languageToggleGroup.getToggles().stream().map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getLanguage())).findFirst().orElseThrow().setSelected(true);
public void handle(LanguageChangedEvent event) {
this.languageToggleGroup
.getToggles()
.stream()
.map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getLanguage()))
.findFirst()
// This should never happen!
.orElseThrow(() -> new IllegalStateException("Language button missing: " + event.getLanguage()))
.setSelected(true);
}
/**
* Updates menubuttons whenever the theme is changed
*
* @param event
*/
@Subscribe
public void handle(ThemeChangedEvent event) {
this.themeToggleGroup
.getToggles()
.stream()
.map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getTheme().replace(" ", "_")))
.findFirst()
// This should never happen!
.orElseThrow(() -> new IllegalStateException("Theme button missing: " + event.getTheme()))
.setSelected(true);
}
}

View File

@@ -8,12 +8,19 @@ import com.google.common.eventbus.Subscribe;
import app.events.EditorChangedEvent;
import app.events.LanguageChangedEvent;
import app.events.OpenFileEvent;
import app.events.FileSaveStateChangedEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
/**
* A FXML controller that controls the Modeline
*/
public class ModelineController implements Initializable, Controller {
@FXML
private Label filename;
@FXML
private Label saveState;
@@ -46,34 +53,50 @@ public class ModelineController implements Initializable, Controller {
this.columnrow.setText(String.format("[%d:%d]", row, column));
}
/* ------------------------------------------------------------------------ */
/* SUBSCRIPTIONS */
/* ------------------------------------------------------------------------ */
/**
* Updates the column-row number display whenever the editor cursor
* changes position.
*
* @param event
*/
@Subscribe
private void handle(EditorChangedEvent event) {
public void handle(EditorChangedEvent event) {
this.setColumnRow(event.getColumn(), event.getLine());
}
/**
* Updates the saveState label whenever the file either is saved or modified
*
* @param event
*/
@Subscribe
private void handle(FileSaveStateChangedEvent event) {
// TODO: Add CSS styleclass for coloring the saveState label
// whenever it changes
public void handle(FileSaveStateChangedEvent event) {
this.saveState.setText(event.getIsSaved() ? "Saved!" : "Modified");
}
/**
* Updates the modeline to display a new language
* whenever it is changed.
* Updates the modeline to display a new language when changed.
*
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
this.language.setText(event.getLanguage());
}
/**
* Updates the modeline to display the name of the current file when changed
*
* @param event
*/
@Subscribe
private void handle(OpenFileEvent event) {
this.filename.setText(
event.getPath().map(path -> path.getFileName().toString()).orElse("New file")
);
}
}

View File

@@ -3,4 +3,4 @@ package app.events;
/**
* Base class for any type of event of the eventbus
*/
abstract class Event {}
public abstract class Event {}

View File

@@ -4,5 +4,5 @@ package app.events;
* Event signalizing a shutdown request for the whole applicaton
*/
public class ExitApplicationEvent extends Event {
}

View File

@@ -1,29 +0,0 @@
package app.events;
/**
* Event signalizing that a file was selected in the filetree.
*
* Not to be confused with {@link OpenFileEvent}
*/
public class FileSelectedEvent extends Event {
private String path;
/**
* Event signalizing that a file was selected in the filetree.
*
* Not to be confused with {@link OpenFileEvent}
* @param path The path of the selected file
*/
public FileSelectedEvent(String path) {
this.path = path;
}
/**
* @return The path of the selected file
*/
public String getPath() {
return this.path;
}
}

View File

@@ -17,18 +17,22 @@ public class LanguageChangedEvent extends Event {
public LanguageChangedEvent(String language) {
this.language = language;
switch (language) {
switch (language.toLowerCase()) {
case "Java":
case "java":
Model.setLanguage(new Java());
break;
case "Markdown":
case "markdown":
Model.setLanguage(new Markdown());
break;
default:
case "empty":
Model.setLanguage(new Empty());
break;
default:
throw new IllegalArgumentException("Could not recognize language: " + language);
}
}

View File

@@ -1,27 +1,31 @@
package app.events;
import java.nio.file.Path;
import java.util.Optional;
import app.model.Model;
/**
* Event signalizing that a file outside the current project is supposed to be opened in the editor.
*
* Not to be confused with {@link FileSelectedEvent}
*/
public class OpenFileEvent extends Event {
private String path;
private Optional<Path> path;
/**
* Event signalizing that a file outside the current project is supposed to be opened in the editor.
* @param path The path of the file to be opened
*/
public OpenFileEvent(String path) {
this.path = path;
}
/**
* Event signalizing that a file outside the current project is supposed to be opened in the editor.
* @param path The path of the file to be opened
*/
public OpenFileEvent(Optional<Path> path) {
this.path = path;
Model.setActiveFilePath(path);
}
/**
* @return The path of the file to be opened
*/
public String getPath() {
return this.path;
}
/**
* @return The path of the file to be opened
*/
public Optional<Path> getPath() {
return this.path;
}
}

View File

@@ -1,24 +1,30 @@
package app.events;
import java.nio.file.Path;
import java.util.Optional;
import app.model.Model;
/**
* Event signalizing that a folder is supposed to be opened in the filetree.
* Event signalizing that a directory is supposed to be opened in the filetree.
*/
public class OpenProjectEvent extends Event {
private String path;
private Optional<Path> path;
/**
* Event signalizing that a folder is supposed to be opened in the filetree.
* @param path The path of the folder to be opened
* Event signalizing that a directory is supposed to be opened in the filetree.
* @param path The path of the directory to be opened
*/
public OpenProjectEvent(String path) {
public OpenProjectEvent(Optional<Path> path) {
this.path = path;
Model.setProjectPath(path);
}
/**
* @return The path of the folder to be opened
* @return The path of the directory to be opened
*/
public String getPath() {
public Optional<Path> getPath() {
return this.path;
}
}

View File

@@ -0,0 +1,35 @@
package app.events;
import app.model.Model;
/**
* Event signalizing that the current file should be saved to disk
*/
public class SaveFileEvent extends Event {
private boolean isNewFile;
/**
* Event signalizing that a file is to be saved.
*/
public SaveFileEvent() {
this.isNewFile = Model.getActiveFilePath().isEmpty();
}
/**
* Event signalizing that a file is to be saved.
*
* @param isNewFile The path of the selected file
*/
public SaveFileEvent(boolean isNewFile) {
this.isNewFile = isNewFile;
}
/**
* @return Whether or not
*/
public boolean getIsNewFile() {
return this.isNewFile;
}
}

View File

@@ -1,5 +1,7 @@
package app.events;
import app.model.Model;
/**
* Event signalizing that the theme of the applicaton has been changed
*/
@@ -9,12 +11,14 @@ public class ThemeChangedEvent extends Event {
/**
* Event signalizing that the theme of the applicaton has been changed
*
* @param theme The name of the theme
*/
public ThemeChangedEvent(String theme) {
Model.setTheme(theme);
this.theme = theme;
}
/**
* @return The name of the theme
*/

View File

@@ -1,7 +1,9 @@
package app.model;
import java.nio.file.Path;
import java.util.Optional;
import app.settings.SettingsProvider;
import javafx.scene.Scene;
/**
@@ -12,44 +14,68 @@ import javafx.scene.Scene;
*/
public class Model {
private static boolean fileIsSaved;
private static Path activeFilePath;
private static Path currentProjectPath;
private static ProgrammingLanguage currentProgrammingLanguage;
private static Optional<Path> activeFilePath;
private static Optional<Path> activeProjectPath;
private static ProgrammingLanguage activeProgrammingLanguage;
private static String theme;
private static Scene scene;
private static SettingsProvider settings;
public static Path getActiveFilePath() {
public static Optional<Path> getActiveFilePath() {
return activeFilePath;
}
public static void setActiveFilePath(Path path) {
public static void setActiveFilePath(Optional<Path> path) {
if (path == null)
throw new IllegalArgumentException("path can not be null");
Model.activeFilePath = path;
}
public static Path getProjectPath() {
return currentProjectPath;
public static Optional<Path> getProjectPath() {
return activeProjectPath;
}
public static void setProjectPath(Path path) {
Model.currentProjectPath = path;
public static void setProjectPath(Optional<Path> path) {
if (path == null)
throw new IllegalArgumentException("path can not be null");
Model.activeProjectPath = path;
}
public static ProgrammingLanguage getLanguage() {
return currentProgrammingLanguage;
return activeProgrammingLanguage;
}
public static Scene getScene() {
return scene;
}
public static String getTheme() {
return theme;
}
public static boolean getFileIsSaved() {
return fileIsSaved;
}
public static SettingsProvider getSettingsProvider() {
return settings;
}
public static void setTheme(String theme) {
if (theme == null)
throw new IllegalArgumentException("theme can not be null");
Model.theme = theme;
}
public static void setLanguage(ProgrammingLanguage language) {
Model.currentProgrammingLanguage = language;
if (language == null)
throw new IllegalArgumentException("language can not be null");
Model.activeProgrammingLanguage = language;
}
public static void setScene(Scene scene) {
if (scene == null)
throw new IllegalArgumentException("scene can not be null");
Model.scene = scene;
}
@@ -57,4 +83,10 @@ public class Model {
Model.fileIsSaved = fileIsSaved;
}
public static void setSettingsProvider(SettingsProvider settings) {
if (settings == null)
throw new IllegalArgumentException("settings can not be null");
Model.settings = settings;
}
}

View File

@@ -1,37 +1,66 @@
package app.model;
import java.net.URL;
import java.util.Map;
import java.util.regex.Pattern;
/**
* An interface describing functions required for a class to
* provide language specific details and functionality.
*/
public interface ProgrammingLanguage {
/**
* The name of the programming language
* @return The name of the programming language
*/
public String getName();
/**
* The icon of the programming language
* @return The path of the icon
*/
public URL getIcon();
/**
* The map containing the regex and corresponding style-classes to be used for syntax highlighting
* @return The map containing the regexes and corresponding style-classes to be used for syntax highlighting
*/
public Map<Pattern,String> getPatternMap();
/**
* The pattern containing all regexes for syntax highlighting
* @return A combined regex for syntax highlighting
*/
public Pattern getPattern();
/**
* Comment out a line
* @param line The text of the line to comment out
* @return The commented line
*/
public String commentLine(String line);
/**
* Uncomment a line
* @param line The text of the line to uncomment
* @return The uncommented line
*/
public String unCommentLine(String line);
/**
* @param line The text of the line
* @return Whether or not a line is commented
*/
public boolean isCommentedLine(String line);
/**
* Comment out an area of text
* @param selection The text of the area to comment out
* @return The commented area
*/
public String commentSelection(String selection);
/**
* Uncomment an area of text
* @param selection The text of the area to uncomment
* @return The uncommented area
*/
public String unCommentSelection(String selection);
/**
* @param selection The content of the area
* @return Whether or not an area of text is commented
*/
public boolean isCommentedSelection(String selection);
}

View File

@@ -1,6 +1,5 @@
package app.model.languages;
import java.net.URL;
import java.util.regex.Pattern;
import java.util.Map;
@@ -9,15 +8,11 @@ import app.model.ProgrammingLanguage;
public class Empty implements ProgrammingLanguage {
private String name = "?";
private URL iconPath = this.getClass().getResource("");
public String getName() {
return this.name;
}
public URL getIcon() {
return this.iconPath;
}
public Map<Pattern, String> getPatternMap() {
return Map.of();

View File

@@ -1,9 +1,10 @@
package app.model.languages;
import java.net.URL;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -14,7 +15,7 @@ import app.model.ProgrammingLanguage;
public class Java implements ProgrammingLanguage {
private String name = "Java";
private URL iconPath = this.getClass().getResource("");
private static Map<Pattern, String> pattern;
private static final String[] keywords = new String[] {
"abstract", "assert", "boolean", "break", "byte",
@@ -30,11 +31,12 @@ public class Java implements ProgrammingLanguage {
};
private static Entry<Pattern, String> e(String k, String v) {
return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v);
return new SimpleEntry<>(Pattern.compile(k), v);
}
private static final Map<Pattern, String> pattern =
Map.ofEntries(
private static final List<Entry<Pattern, String>> patternList =
List.of(
e("\"([^\"\\\\]|\\\\.)*\"", "string"),
e("\\bthis\\b", "this"),
e("\\btrue\\b", "true"),
@@ -50,16 +52,23 @@ public class Java implements ProgrammingLanguage {
"keyword"),
e("(?://.*)|/\\*(?:\\n|.)*?\\*/", "comment")
);
public Java() {
this.initializePatternMap();
}
private void initializePatternMap() {
pattern = new LinkedHashMap<>();
patternList
.forEach(e -> pattern.put(e.getKey(), e.getValue()));
}
public String getName() {
return this.name;
}
public URL getIcon() {
return this.iconPath;
}
public Map<Pattern, String> getPatternMap() {
return Java.pattern;
}

View File

@@ -1,8 +1,9 @@
package app.model.languages;
import java.net.URL;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
@@ -13,14 +14,14 @@ import app.model.ProgrammingLanguage;
public class Markdown implements ProgrammingLanguage {
private String name = "Markdown";
private URL iconPath = this.getClass().getResource("");
private static Map<Pattern, String> pattern;
private static Entry<Pattern, String> e(String k, String v) {
return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v);
}
private static final Map<Pattern, String> pattern =
Map.ofEntries(
private static final List<Entry<Pattern, String>> patternList =
List.of(
e("<!--(?:.|\n)*-->", "comment"),
e("##### .*", "ssssheader"),
e("#### .*", "sssheader"),
@@ -38,12 +39,19 @@ public class Markdown implements ProgrammingLanguage {
e("\\[\\d+\\]: .*", "source")
);
public String getName() {
return this.name;
public Markdown() {
this.initializePatternMap();
}
public URL getIcon() {
return this.iconPath;
private void initializePatternMap() {
pattern = new LinkedHashMap<>();
patternList
.forEach(e -> pattern.put(e.getKey(), e.getValue()));
}
public String getName() {
return this.name;
}
public Map<Pattern, String> getPatternMap() {
@@ -64,7 +72,7 @@ public class Markdown implements ProgrammingLanguage {
String cStart = "<!-- ";
String cEnd = " -->";
String cStartRegex = "<!-- ?";
String cEndRegex = " ?-->\\s{0,}";
String cEndRegex = " ?-->\\s{0,}$";
public String commentLine(String line) {
return cStart + line + cEnd;

View File

@@ -0,0 +1,84 @@
package app.service;
import java.io.File;
import app.model.Model;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
/**
* Class containing static methods for different kinds of popup window interactions
* with the user.
*/
public class DialogBoxes {
private DialogBoxes() {}
private static FileChooser fc = new FileChooser();
private static DirectoryChooser dc = new DirectoryChooser();
private static Alert error = new Alert(AlertType.ERROR);
/**
* Shows a specified message to the user with an error icon.
*
* @param errorMessage The message to show the user
*/
public static void showErrorMessage(String errorMessage) {
error.setContentText(errorMessage);
error.showAndWait();
}
/**
* Shows an OS specific file chooser to choose a file on the disk
*
* @param stage The JavaFX stage to connect to the dialog box. This is needed
* for the window to be able to run on the JavaFX thread.
*
* @return The file chosen through the dialog window
*/
public static File showopenFileWithDialog(Stage stage) {
fc.setTitle("Open File");
File chosenFile = fc.showOpenDialog(stage);
return chosenFile;
}
/**
* Shows an OS specific directory chooser to choose a directory on the disk
*
* @param stage The JavaFX stage to connect to the dialog box. This is needed
* for the window to be able to run on the JavaFX thread.
*
* @return The file chosen through the dialog window
*/
public static File showOpenDirectoryWithDialog(Stage stage) {
dc.setTitle("Open Project");
File dir = dc.showDialog(stage);
return dir;
}
/**
* Shows an OS specific file chooser to specifyy a new path on the disk
*
* @param stage The JavaFX stage to connect to the dialog box. This is needed
* for the window to be able to run on the JavaFX thread.
*
* @return The filepath chosen through the dialog window
*/
public static File showSaveFileWithDialog(Stage stage) {
FileChooser fc = new FileChooser();
fc.setTitle("Save as");
Model
.getProjectPath()
.ifPresent(path -> fc.setInitialDirectory(path.toFile()));
File chosenLocation = fc.showSaveDialog(stage);
return chosenLocation;
}
}

View File

@@ -0,0 +1,128 @@
package app.service;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Scanner;
import app.model.Model;
import javafx.stage.Stage;
/**
* A class containing operations and logic for choosing, reading and writing to files.
*/
public class FileOperations {
private FileOperations() {}
/**
* A function to get a file through a dialog
*
* @param stage A JavaFX stage is required to show the dialog
* @return The chosen file
* @throws FileNotFoundException if the dialog was canceled
*/
public static File openFileWithDialog(Stage stage) throws FileNotFoundException {
File chosenFile = DialogBoxes.showopenFileWithDialog(stage);
if (chosenFile == null)
throw new FileNotFoundException();
return chosenFile;
}
/**
* A function to get directory through a dialog
*
* @param stage A JavaFX stage is required to show the dialog
* @return The chosen directory
* @throws FileNotFoundException if the dialog was canceled
*/
public static File openDirectoryWithDialog(Stage stage) throws FileNotFoundException {
File dir = DialogBoxes.showOpenDirectoryWithDialog(stage);
if (dir == null)
throw new FileNotFoundException();
return dir;
}
/**
* Saves a file a the specified filepath with the specified content.
* Shows an error message to the user if the file was not found at the specified location.
*
* @param filepath The path of the file to save the content into
* @param content The text content to be saved
* @return Whether the file was sucessfully saved
*/
public static boolean saveFile(Path filepath, String content) {
try (PrintWriter writer = new PrintWriter(filepath.toFile())) {
writer.println(content);
return true;
} catch (FileNotFoundException ex) {
DialogBoxes.showErrorMessage("Could not save file at \n" + filepath.toString());
return false;
}
}
/**
* Lets the user choose a file to save the specified content into
*
* @param stage A JavaFX stage is needed in order to show the file chooser
* @param content The content to be saved
*
* @return Whether the file was sucessfully saved
*/
public static boolean saveFileWithDialog(Stage stage, String content) {
File chosenLocation;
try {
chosenLocation = DialogBoxes.showSaveFileWithDialog(stage);
} catch (NoSuchElementException e) {
return false;
}
if (chosenLocation == null) return false;
if (saveFile(chosenLocation.toPath(), content)) {
Model.setActiveFilePath(Optional.of(chosenLocation.toPath()));
return true;
}
return false;
}
/**
* Tries to read the contents of a file in the specified filepath.
* If it fails, it shows the user a corresponding error message.
*
* @param filePath The path of the file to be read
*
* @return The text content of the file
*/
public static String readFile(Path filePath) {
if (filePath == null)
return "";
String result = "";
try (Scanner sc = new Scanner(filePath.toFile())) {
while (sc.hasNextLine()) {
result += (sc.nextLine() + "\n");
}
} catch (FileNotFoundException e) {
DialogBoxes.showErrorMessage("This file could not be opened!");
System.err.println("[ERROR] File could not be opened: " + filePath.toString());
System.err.print(e);
}
return result;
}
}

View File

@@ -0,0 +1,160 @@
package app.service;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import app.model.Model;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
/**
* A class containing operations for handling files withing a GUI filetree
*/
public class FiletreeOperations {
private static int iconSize = 20;
private FiletreeOperations() {}
/**
* Generate a filetree recursively, find icons for every file and append them to
* the root tree node.
*
* @param file The root directory to be at the top of the tree
* @param parent The root tree item to append children to
*/
public static void generateTree(File file, CheckBoxTreeItem<String> parent) {
Image folder = new Image(FiletreeOperations.class.getResourceAsStream("/graphics/folder.png"));
if (file.isDirectory()) {
ImageView icon = new ImageView(folder);
icon.setFitHeight(iconSize);
icon.setFitWidth(iconSize);
CheckBoxTreeItem<String> element = new CheckBoxTreeItem<>(file.getName(), icon);
parent.getChildren().add(element);
List<File> dirList = new ArrayList<>();
List<File> fileList = new ArrayList<>();
sortFiles(dirList, fileList, file);
for (File f : dirList) {
generateTree(f, element);
}
} else {
try {
ImageView icon = new ImageView(getIconForFile(file));
icon.setFitHeight(iconSize);
icon.setFitWidth(iconSize);
CheckBoxTreeItem<String> element = new CheckBoxTreeItem<>(file.getName(), icon);
parent.getChildren().add(element);
} catch (Exception e) {
System.err.println("[ERROR] Default file icon not found");
System.err.println(e);
}
}
}
/**
* A helping function to sort the files/directories in the fileTree so that it shows
* in the correct order.
*
* @param dirList The list of directories to append all directories to
* @param fileList The list of files to append all the files to
* @param file The directory of which children to sort
*/
private static void sortFiles(List<File> dirList, List<File> fileList, File file) {
for (File f : file.listFiles()) {
if (f.isDirectory())
dirList.add(f);
else
fileList.add(f);
}
dirList.addAll(fileList);
}
/**
* A function to get an icon for a file based on its mimetype.
* If no such icon is found, the function will return a default icon
*
* @param file The file to probe for a mimetype
* @return An image containing the icon
* @throws IOException if the default icon was not found. This should not happen
*/
private static Image getIconForFile(File file) throws IOException {
Image icon;
try {
String mimeType = Files.probeContentType(file.toPath());
if (mimeType == null) throw new IOException();
String iconPath = "/graphics/filetreeicons/" + mimeType.replace('/', '-') + ".png";
InputStream imageData = FiletreeOperations.class.getResourceAsStream(iconPath);
if (imageData == null) throw new IOException();
icon = new Image(imageData);
} catch (IOException e) {
System.err.println("[WARNING] Icon not found: " + file.getPath());
// String iconPath = "/graphics/filetreeicons/file.png";
String iconPath = "/graphics/filetreeicons/unknown.png";
InputStream imageData = FileOperations.class.getResourceAsStream(iconPath);
if (imageData == null) throw new IOException();
icon = new Image(imageData);
}
return icon;
}
/**
* Determine the absolute path of a file within the filetree.
* @param item The treeitem that represents the file of which path is needed
* @return The absolute path of the file that the treeitem represents
* @throws FileNotFoundException if no path was found within the filetree such that the
* item could be connected with the root tree item. This should not happen
*/
public static Path getPathOfTreeItem(TreeItem<String> item) throws FileNotFoundException {
Path projectPath =
Model
.getProjectPath()
.orElseThrow(() -> new IllegalStateException());
final String rootDirName =
projectPath
.getFileName()
.toString();
String path = "";
while (!rootDirName.equals(item.getValue())) {
path = File.separator + item.getValue() + path;
item = item.getParent();
if (item == null)
throw new FileNotFoundException();
}
path = projectPath.toString() + path;
Path pathToString = Paths.get(path);
return pathToString;
}
}

View File

@@ -9,7 +9,13 @@ import org.fxmisc.richtext.model.StyleSpansBuilder;
import app.model.ProgrammingLanguage;
public class LanguageOperations {
/**
* Common static operations that can be executed on any class
* that implements {@link app.model.ProgrammingLanguage ProgrammingLanguage}
*/
public final class LanguageOperations {
private LanguageOperations() {}
/**
* Use a matcher to find the styleclass of the next match

View File

@@ -0,0 +1,112 @@
package app.settings;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.events.LanguageChangedEvent;
import app.events.ThemeChangedEvent;
import app.model.Model;
/**
* A provider for permanent storage of the settings of the application.
*/
public class SettingsProvider implements SettingsProviderI {
private EventBus eventBus;
private String settingsPath =
(System.getProperty("os.name").startsWith("Windows"))
? System.getProperty("user.home") + "\\AppData\\Roaming\\/BNNsettings.dat"
: System.getProperty("user.home") + System.getProperty("file.separator") + ".BNNsettings.dat";
private List<String> legalSettings =
Arrays.asList("Java", "Markdown", "Monokai", "Solarized Light");
/** Only for testing purposes */
protected void setSettingsPath(String settingsPath) {
this.settingsPath = settingsPath;
}
public SettingsProvider(EventBus eB) {
setEventBus(eB);
Model.setSettingsProvider(this);
}
public void setEventBus(EventBus eB) {
eventBus = eB;
eventBus.register(this);
}
@Override
public void loadSettings() {
List<String> settings = new ArrayList<>();
try (Scanner sc = new Scanner(new File(settingsPath))) {
while (sc.hasNextLine()) {
var nextLine = sc.nextLine().trim();
if (nextLine.isEmpty() || nextLine.startsWith("-")) {
continue;
} else {
settings.add(nextLine.substring(nextLine.indexOf("=") + 2));
}
}
if (legalSettings.containsAll(settings)) {
eventBus.post(new LanguageChangedEvent(settings.get(0)));
eventBus.post(new ThemeChangedEvent(settings.get(1)));
} else {
throw new IOException();
}
} catch (IOException e) {
System.err.println("[WARNING] Couldn't find settings file. Using defaults");
eventBus.post(new LanguageChangedEvent("Java"));
eventBus.post(new ThemeChangedEvent("Monokai"));
}
}
@Override
public void saveSettings() {
try (PrintWriter writer = new PrintWriter(new File(settingsPath))) {
writer.println("- Settings:");
writer.println("Programming Language = " + Model.getLanguage().getName());
writer.println("Theme = " + Model.getTheme());
} catch (IOException e) {
System.err.println("[ERROR] Couldn't write to settings file.");
System.err.println(e);
}
}
/**
* Update the settings whenever the theme gets changed
*
* @param event
*/
@Subscribe
private void handle(ThemeChangedEvent event) {
saveSettings();
}
/**
* Update the settings whenever the language gets changed
*
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
saveSettings();
}
}

View File

@@ -0,0 +1,15 @@
package app.settings;
public interface SettingsProviderI {
/**
* Load settings from disk, and fire events to update the program state
*/
void loadSettings();
/**
* Save the state from {@link app.model.Model Model} to disk.
*/
void saveSettings();
}

View File

@@ -0,0 +1,3 @@
- Settings:
ProgrammingLanguage = Java
Theme = Solarized Light

View File

@@ -10,7 +10,8 @@
prefHeight="400"
xmlns="http://javafx.com/javafx/8.0.65"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="app.MainController">
fx:controller="app.MainController"
fx:id="root">
<top>
<!-- Menubar -->

View File

@@ -18,14 +18,13 @@
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" text="New File" accelerator="Shortcut+n"/>
<MenuItem mnemonicParsing="false" text="New Folder" accelerator="Shortcut+Shift+N"/>
<MenuItem mnemonicParsing="false" text="New File" accelerator="Shortcut+n" onAction="#handleNewFile"/>
<SeparatorMenuItem/>
<MenuItem mnemonicParsing="false" text="Open File" accelerator="Shortcut+o" onAction="#handleOpenFile"/>
<MenuItem mnemonicParsing="false" text="Open Project" accelerator="Shortcut+Shift+O" onAction="#handleOpenProject"/>
<SeparatorMenuItem/>
<MenuItem mnemonicParsing="false" text="Save" accelerator="Shortcut+s"/>
<MenuItem mnemonicParsing="false" text="Save as" accelerator="Shortcut+Shift+S"/>
<MenuItem mnemonicParsing="false" text="Save" accelerator="Shortcut+s" onAction="#handleSaveFile"/>
<MenuItem mnemonicParsing="false" text="Save as" accelerator="Shortcut+Shift+S" onAction="#handleSaveAsFile"/>
<SeparatorMenuItem/>
<fx:define>
@@ -34,7 +33,6 @@
<Menu mnemonicParsing="false" text="Change programming language">
<items>
<!-- TODO: Generate buttons based on classes -->
<RadioMenuItem text="Java"
fx:id="toggleJava"
onAction="#handleLanguageChange"
@@ -56,12 +54,12 @@
<Menu mnemonicParsing="false" text="Change color theme">
<items>
<!-- TODO: Generate buttons based on classes -->
<RadioMenuItem text="Monokai"
selected="true"
fx:id="toggleMonokai"
onAction="#handleThemeChange"
toggleGroup="$themeToggleGroup"/>
<RadioMenuItem text="Solarized Light"
fx:id="toggleSolarized_Light"
onAction="#handleThemeChange"
toggleGroup="$themeToggleGroup"/>
</items>

View File

@@ -11,6 +11,7 @@
fx:controller="app.controllers.ModelineController"
alignment="CENTER_LEFT">
<Label text="Modeline :)"/>
<Label fx:id="filename" text="New file"/>
<Region HBox.hgrow="ALWAYS"/>
<Label fx:id="saveState" text="Saved!"/>
<Label fx:id="columnrow" text="[y:x]"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Some files were not shown because too many files have changed in this diff Show More