Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db0930b7a6 | |||
|
|
64793c2505 | ||
| 0c8a7dc1dc | |||
| 33ec71e91f | |||
|
|
d5ad1ed82a | ||
|
|
fe6a848346 | ||
| 765f643cd0 | |||
| af51ed63f3 | |||
|
|
6559622dd9 | ||
| 95e1c32d09 | |||
|
|
4259d12d48 | ||
|
|
c1c86a94c6 | ||
|
|
7b0dc1548a | ||
| c12e98f9be | |||
|
|
9b2d70c876 | ||
| 0793287044 | |||
|
|
6bcbcbdea2 | ||
| 66313d9461 | |||
|
|
79ff110708 | ||
| 80730d79ca | |||
|
|
48250b6bd0 | ||
|
|
fd83e1291d | ||
| e0d093fa4a | |||
| 70d4aa76e0 | |||
| 91bba8878c | |||
| c53d5716c3 | |||
|
|
057c1e0074 | ||
|
|
b65ed726c7 | ||
| ecba05e492 | |||
| faddeb4201 | |||
| 909e241c5b | |||
| de8d1e9756 | |||
| ad9ee1c84e | |||
| 0c16ae3f15 | |||
| 22406222f3 | |||
|
|
08f9b2b881 | ||
| 94be84d6e1 | |||
| 075988f327 | |||
| 40d32a5d54 | |||
| 2860db78d7 | |||
| 68bf49f639 | |||
| 690901fd8b | |||
| 28b9dba111 | |||
| e8b34b7164 | |||
| 81c76ae9d4 | |||
| 1cc2c6f4f9 | |||
| 697d2b59a8 | |||
| ed0e3bd871 | |||
| da02308498 | |||
| f89968d9d0 | |||
| e274ac5e87 | |||
| 445155ed33 | |||
| da83d108b2 | |||
| 3b5600eba8 | |||
| f235ab1f75 | |||
|
|
2e397fd971 | ||
| 92fa16891c | |||
| eb7f89c932 | |||
| 505f6872f2 | |||
| 581bb0ad3b | |||
| 02c738e761 | |||
| 6504d3cb3f | |||
| d63a5d90bf | |||
| c865a38349 | |||
| 86b7be7647 | |||
| 33dd146e60 | |||
| 71868ed6e3 | |||
|
|
d2f7ad1828 | ||
| f0a724888d | |||
| d4d6be1930 | |||
| 59a45ba4a5 | |||
| 77ddb56d69 | |||
| 7d40b2608d | |||
| 740a4ee1f7 | |||
| 4e6c12e7c9 | |||
| e892acf3d7 | |||
| 94e81e5e08 | |||
|
|
38ea6a91fe | ||
|
|
6d1794421c | ||
|
|
fcab3df9fc | ||
|
|
1156b827ac | ||
|
|
79e684228f | ||
|
|
509f5224b5 | ||
|
|
06397cc6cf | ||
|
|
be748d9138 | ||
|
|
4a63aa6edf | ||
|
|
ac7069e640 | ||
|
|
382ae5cbbc | ||
|
|
2ecda54d7f | ||
|
|
df41e3df58 | ||
| 3f8482bb78 | |||
| 180627578b |
4
.gitignore
vendored
@@ -10,4 +10,6 @@ target/
|
||||
.idea/
|
||||
|
||||
#VSCode
|
||||
.vscode/
|
||||
.vscode/
|
||||
|
||||
dependency-reduced-pom.xml
|
||||
@@ -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:
|
||||
|
||||
BIN
.gitlab/graphics/FXML-diagram.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
.gitlab/graphics/main-screenshot.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
65
README.md
@@ -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
|
||||
[](https://gitlab.stud.idi.ntnu.no/oysteikt/h20-tdt4100-project/commits/master/)
|
||||
[](http://oysteikt.pages.stud.idi.ntnu.no/h20-tdt4100-project/)
|
||||
[](http://oysteikt.pages.stud.idi.ntnu.no/h20-tdt4100-project/apidocs)
|
||||
[](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
@@ -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
|
||||
|
||||
[](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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
mvn clean package -Dmaven.test.skip=true
|
||||
|
||||
java -jar --enable-preview target/banana-editor-1.0.0.jar
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
15
src/main/java/app/MainLauncher.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,4 @@ package app.events;
|
||||
/**
|
||||
* Base class for any type of event of the eventbus
|
||||
*/
|
||||
abstract class Event {}
|
||||
public abstract class Event {}
|
||||
|
||||
@@ -4,5 +4,5 @@ package app.events;
|
||||
* Event signalizing a shutdown request for the whole applicaton
|
||||
*/
|
||||
public class ExitApplicationEvent extends Event {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
35
src/main/java/app/events/SaveFileEvent.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
84
src/main/java/app/service/DialogBoxes.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
128
src/main/java/app/service/FileOperations.java
Normal 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;
|
||||
}
|
||||
}
|
||||
160
src/main/java/app/service/FiletreeOperations.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
112
src/main/java/app/settings/SettingsProvider.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
15
src/main/java/app/settings/SettingsProviderI.java
Normal 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();
|
||||
|
||||
}
|
||||
3
src/main/resources/BNNsettings/settings.dat
Normal file
@@ -0,0 +1,3 @@
|
||||
- Settings:
|
||||
ProgrammingLanguage = Java
|
||||
Theme = Solarized Light
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]"/>
|
||||
|
||||
BIN
src/main/resources/graphics/filetreeicons/application-7zip.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-acad.png
Normal file
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 691 B |
|
After Width: | Height: | Size: 691 B |
|
After Width: | Height: | Size: 697 B |
|
After Width: | Height: | Size: 745 B |
|
After Width: | Height: | Size: 691 B |
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-dart.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-dwg.png
Normal file
|
After Width: | Height: | Size: 683 B |
BIN
src/main/resources/graphics/filetreeicons/application-dxf.png
Normal file
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 697 B |
BIN
src/main/resources/graphics/filetreeicons/application-excel.png
Normal file
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 989 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 621 B |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-gpx.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-gzip.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1017 B |
BIN
src/main/resources/graphics/filetreeicons/application-java.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-json.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 824 B |
|
After Width: | Height: | Size: 636 B |
BIN
src/main/resources/graphics/filetreeicons/application-msword.png
Normal file
|
After Width: | Height: | Size: 636 B |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 697 B |
BIN
src/main/resources/graphics/filetreeicons/application-ogg.png
Normal file
|
After Width: | Height: | Size: 809 B |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-pdf.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 813 B |
|
After Width: | Height: | Size: 885 B |
|
After Width: | Height: | Size: 885 B |
BIN
src/main/resources/graphics/filetreeicons/application-pgp.png
Normal file
|
After Width: | Height: | Size: 885 B |
BIN
src/main/resources/graphics/filetreeicons/application-pkcs10.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-pkcs12.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 885 B |
BIN
src/main/resources/graphics/filetreeicons/application-pkcs8.png
Normal file
|
After Width: | Height: | Size: 885 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-ram.png
Normal file
|
After Width: | Height: | Size: 729 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/graphics/filetreeicons/application-rtf.png
Normal file
|
After Width: | Height: | Size: 636 B |