diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b47bf3a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.gitignore b/.gitignore index 6df9964..a10ca8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target/ .mvn mvnw* -docs/.obsidian \ No newline at end of file +docs/.obsidian +*.jar diff --git a/README.md b/README.md index 45a4777..a7d51a5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ - @@ -15,39 +14,97 @@ [![Java CI with Maven](https://github.com/Glimmr-Lang/PicassoCode/actions/workflows/maven.yml/badge.svg)](https://github.com/Glimmr-Lang/PicassoCode/actions/workflows/maven.yml) ## About +<<<<<<< HEAD +<<<<<<< HEAD +Picasso code is an image editor that uses code to create/edit an image. This allows powerful designs to be created with ease and +automation. The editor uses *PiccodeScript* as the scripting language for writing the image editing code. +======= + +Piccasso code is an image editor that uses code to create/edit an image. This allows powerful designs to be created with ease and +automation. The editor uses _glimr_ as the scripting language for writing the image editing code. +>>>>>>> 0064ac3 (fix: corrected a spelling error in the readme file) +======= + Piccasso code is an image editor that uses code to create/edit an image. This allows powerful designs to be created with ease and -automation. The editor uses *glimr* as the scripting language for writing the image editing code. +automation. The editor uses _glimr_ as the scripting language for writing the image editing code. +======= +Picasso code is an image editor that uses code to create/edit an image. This allows powerful designs to be created with ease and +automation. The editor uses *PiccodeScript* as the scripting language for writing the image editing code. +>>>>>>> 455b066 (README: Fixed mistakes in the README) +>>>>>>> 955b7fb (README: Fixed mistakes in the README) ## Download ->> Coming soon + +> > Coming soon ## Building ```sh -$ git clone git@github.com:hexaredecimal/Piccode.git -$ cd Piccode +<<<<<<< HEAD +<<<<<<< HEAD +$ git clone https://github.com/Glimmr-Lang/PicassoCode.git +$ cd PicassoCode $ mvn package ``` ### Test your build ```sh $ java -jar target/Piccode-1.0-SNAPSHOT-jar-with-dependencies.jar +======= +git clone git@github.com:hexaredecimal/Piccode.git +cd Piccode +mvn package +>>>>>>> 0064ac3 (fix: corrected a spelling error in the readme file) +======= +git clone git@github.com:hexaredecimal/Piccode.git +cd Piccode +mvn package +======= +$ git clone https://github.com/Glimmr-Lang/PicassoCode.git +$ cd PicassoCode +$ mvn package +``` +### Test your build +```sh +$ java -jar target/Piccode-1.0-SNAPSHOT-jar-with-dependencies.jar +>>>>>>> 455b066 (README: Fixed mistakes in the README) +>>>>>>> 955b7fb (README: Fixed mistakes in the README) ``` +### Test your build + +```sh +java -jar target/Piccode-1.0-SNAPSHOT-jar-with-dependencies.jar +``` ## Inspired by +<<<<<<< HEAD +<<<<<<< HEAD +PicassoCode is heavily inspired by the [OpenSCAD](https://openscad.org/) program and tries to mimic its functionality +======= + +Piccassocode is heavily inspired by the [OpenSCAD](https://openscad.org/) program and tries to mimic its functionality +>>>>>>> 0064ac3 (fix: corrected a spelling error in the readme file) +======= + Piccassocode is heavily inspired by the [OpenSCAD](https://openscad.org/) program and tries to mimic its functionality +======= +PicassoCode is heavily inspired by the [OpenSCAD](https://openscad.org/) program and tries to mimic its functionality +>>>>>>> 455b066 (README: Fixed mistakes in the README) +>>>>>>> 955b7fb (README: Fixed mistakes in the README) as much as it can while still being an image editor. I was stoked when I tried OpenSCAD for the first time and ended up -challenging myself to start a new project based araound the idea. A friend suggested something that has to do with graphics -and my first though was OpenSCAD, but 2D. The idea quickly grew and the small program became an image editor. +challenging myself to start a new project based around the idea. A friend suggested something that has to do with graphics +and my first though was OpenSCAD, but 2D. The idea quickly grew and the small program became an image editor. ## References + [java image filters](http://www.jhlabs.com/ip/filters/index.html) [Icons8 Pack](https://icons8.com/icons/parakeet--style-parakeet) ## License + ```sh -drawString(" +Render::drawString(" +-----------------------------------+ | ▄▖▘ ▌ | | ▙▌▌▛▘▀▌▛▘▛▘▛▌▛▘▛▌▛▌█▌ | @@ -56,7 +113,5 @@ drawString(" +-----------------------------------+ ", 0, 0) // Released under the MIT LICENSE ``` ->> Thank you for viewing. - - +> > Thank you for viewing. diff --git a/examples/starrect.pics b/examples/starrect.pics new file mode 100644 index 0000000..4e35fa6 --- /dev/null +++ b/examples/starrect.pics @@ -0,0 +1,44 @@ + +IO :: import std.io +Virtual :: import std.virtual +Render :: import piccode.render +Pen :: import piccode.pen + +drawStar :: (ctx, padding=0) = + ctx + |> Pen::drawLine(50+padding, 50+padding, 150-padding, 150-padding) + |> Pen::drawLine(150-padding, 50+padding, 50+padding, 150-padding) + |> Pen::drawLine(50+padding, 100, 150-padding, 100) + |> Pen::drawLine(100, 50+padding, 100, 150-padding) + +drawRect :: (ctx, x, y, w, h) = + ctx + |> Pen::drawLine(x, y, x + w, y) + |> Pen::drawLine(x, y + h, x + w, y + h) + |> Pen::drawLine(x, y, x, y + w) + |> Pen::drawLine(x + w, y, x + w, y + h) + +drawDepth :: (ctx) = + ctx + |> Pen::drawLine(150, 50, 200, 70) + |> Pen::drawLine(150, 150, 200, 170) + |> Pen::drawLine(50, 150, 100, 170) + +drawBackLines :: (ctx) = + ctx + |> Pen::drawLine(100, 170, 200, 170) + |> Pen::drawLine(200, 70, 200, 170) + +main :: () = + let + ctx := Render::getContext() + in + ctx + |> drawRect(50, 50, 100, 100) + |> drawDepth + |> drawBackLines + |> drawStar(10) + + + + diff --git a/piccode/context/ctx.pics b/piccode/context/ctx.pics new file mode 100644 index 0000000..72e3ce3 --- /dev/null +++ b/piccode/context/ctx.pics @@ -0,0 +1,39 @@ + +// Module: Context +// Contains functions for creating contexts +Context :: module { + + // Function: fromRect + // Creates a new context based on the one provided + // + // Parameters: + // ctx - (Reference) The graphic context + // x - (Number) The x position + // y - (Number) The y position + // w - (Number) The width + // h - (Number) The height + // + // Returns: + // - (Reference) A new graphics sub-context derived from the given context. + fromRect :: (ctx, x, y, w, h) = pic_nat_gfx_from_rect(ctx, x, y, w, h) + + // Function: from + // Creates a new context based on the one provided + // + // Parameters: + // ctx - (Reference) The graphic context + // + // Returns: + // - (Reference) A new graphics context derived from the given context. + from :: (ctx) = pic_nat_gfx_from(ctx) + + // Function: drop + // Frees the given context + // + // Parameters: + // ctx - (Reference) The graphic context + // + // Returns: + // - (Unit) Nothing + drop :: (ctx) = pic_nat_gfx_drop(ctx) +} diff --git a/piccode/filters/filter.pics b/piccode/filters/filter.pics new file mode 100644 index 0000000..244eced --- /dev/null +++ b/piccode/filters/filter.pics @@ -0,0 +1,5 @@ + +Filter :: module { + apply :: (filter, image) = pic_nat_filter_apply(filter, image) +} + diff --git a/piccode/filters/metal/brushMetal.pics b/piccode/filters/metal/brushMetal.pics new file mode 100644 index 0000000..4a846a5 --- /dev/null +++ b/piccode/filters/metal/brushMetal.pics @@ -0,0 +1,8 @@ + + +BrushMetal :: module { + new :: () = pic_nat_brush_metal_new() + radius :: (filter, radius) = pic_nat_brush_metal_set_rad(filter, radius) + amount :: (filter, radius) = pic_nat_brush_metal_set_amount(filter, radius) + shine :: (filter, radius) = pic_nat_brush_metal_set_shine(filter, radius) +} diff --git a/piccode/image/image.pics b/piccode/image/image.pics new file mode 100644 index 0000000..a3dd5cf --- /dev/null +++ b/piccode/image/image.pics @@ -0,0 +1,17 @@ + +Image :: module { + new :: (w, h) = pic_nat_image_new(w, h) + newWithType :: (w, h, type) = pic_nat_image_new_typed(w, h, type) + getContext :: (img) = pic_nat_image_get_context(img) + fromPath :: (path) = pic_nat_image_new_from_path(path) + + resize :: (img, w, h) = pic_nat_image_resize(img, w, h) + + Type :: module { + RGB := 1 + ARGB := 2 + ARGB_PRE := 3 + BGR := 4 + } +} + diff --git a/piccode/pen/draw.pics b/piccode/pen/draw.pics new file mode 100644 index 0000000..4e9e1fa --- /dev/null +++ b/piccode/pen/draw.pics @@ -0,0 +1,66 @@ + +Pen :: module { + + // Function: drawLine + // Draws a line inside the specified context, based on the provided coordinates + // + // Parameters: + // ctx - (Reference) The graphic context + // startx - (Number) The start x position + // starty - (Number) The start y position + // endx - (Number) The end x position + // endy - (Number) The end y position + // + // Returns: + // - (Reference) A modified context with the rendered element. + drawLine :: (ctx, startx, starty, endx, endy) = pic_nat_draw_line(ctx, startx, starty, endx, endy) + + // Function: drawRect + // Draws a rect inside the specified context, based on the provided coordinates + // + // Parameters: + // ctx - (Reference) The graphic context + // x - (Number) The x position + // y - (Number) The y position + // w - (Number) The width + // h - (Number) The height + // + // Returns: + // - (Reference) A modified context with the rendered element. + drawRect :: (ctx, x, y, w, h) = pic_nat_draw_rect(ctx, x, y, w, h) + + // Function: drawRoundRect + // Draws a rounded rectangle inside the specified context, based on the provided coordinates + // + // Parameters: + // ctx - (Reference) The graphic context + // x - (Number) The x position + // y - (Number) The y position + // w - (Number) The width + // h - (Number) The height + // aw - (Number) The arc width + // ah - (Number) The arc height + // + // Returns: + // - (Reference) A modified context with the rendered element. + drawRoundRect :: (ctx, x, y, w, h, aw, ah) = pic_nat_draw_round_rect(ctx, x, y, w, h, aw, ah) + + // Function: drawOval + // Draws a rect inside the specified context, based on the provided coordinates + // + // Parameters: + // ctx - (Reference) The graphic context + // x - (Number) The x position + // y - (Number) The y position + // w - (Number) The width + // h - (Number) The height + // + // Returns: + // - (Reference) A modified context with the rendered element. + drawOval :: (ctx, x, y, w, h) = pic_nat_draw_oval(ctx, x, y, w, h) + + drawImage :: (ctx, image, x, y) = pic_nat_draw_image(ctx, image, x, y) + + drawText :: (ctx, text, x, y) = pic_nat_draw_text(ctx, text, x, y) +} + diff --git a/piccode/render/context.pics b/piccode/render/context.pics new file mode 100644 index 0000000..3c791c8 --- /dev/null +++ b/piccode/render/context.pics @@ -0,0 +1,16 @@ + +// Module: Render +// Functions for working with the editor render context +Render :: module { + // Function: getContext + // returns the render context (A handle to the Render window on the screen) + // + // Parameters: + // None + // + // Returns: + // - (Reference) a handler to the Graphics2D. + getContext :: () = pic_nat_get_gfx() + +} + diff --git a/pom.xml b/pom.xml index 79a9f33..4b48f2d 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,15 @@ jitpack.io https://jitpack.io + + + true + always + + + always + + @@ -23,14 +32,14 @@ org.antlr antlr4-runtime - 4.9.3 + 4.13.2 com.formdev flatlaf - 3.6 + 3.6.1 @@ -54,7 +63,7 @@ org.bidib.jbidib.com.vldocking vldocking - 3.0.10 + 3.0.11 @@ -66,7 +75,19 @@ com.github.Glimmr-Lang PiccodeScript - -SNAPSHOT + main-SNAPSHOT + + + + com.github.Glimmr-Lang + JHLabs + main-SNAPSHOT + + + + com.github.Glimmr-Lang + PiccodePlugin + main-SNAPSHOT @@ -76,7 +97,7 @@ org.junit junit-bom - 5.12.2 + 6.0.0 pom import @@ -91,7 +112,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 @@ -104,7 +125,7 @@ org.apache.maven.plugins maven-assembly-plugin - 2.4.1 + 3.7.1 jar-with-dependencies @@ -130,7 +151,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + 3.6.2 enforce-versions @@ -165,7 +186,7 @@ org.graalvm.buildtools native-maven-plugin - 0.10.0 + 0.11.1 true diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2b397c4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,10 @@ +[tool:pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --tb=short --strict-markers +markers = + slow: marks tests as slow (deselect with '-m "not slow"') + integration: marks tests as integration tests + unit: marks tests as unit tests \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..9e14c88 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,4 @@ +pytest>=7.0.0 +pytest-mock>=3.6.1 +requests>=2.25.1 +pathlib2>=2.3.7; python_version < '3.4' \ No newline at end of file diff --git a/run_readme_tests.py b/run_readme_tests.py new file mode 100644 index 0000000..e2d8d2f --- /dev/null +++ b/run_readme_tests.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +""" +Simple test runner for README validation tests. +Run this script to execute all README-related tests. +""" + +import subprocess +import sys +import os + +def main(): + """Run the README validation tests.""" + try: + # Change to the repository root directory + repo_root = os.path.dirname(os.path.abspath(__file__)) + os.chdir(repo_root) + + # Run pytest with verbose output + result = subprocess.run([ + sys.executable, "-m", "pytest", + "tests/test_readme_validation.py", + "-v", "--tb=short" + ], capture_output=False) + + return result.returncode + + except FileNotFoundError: + print("Error: pytest not found. Please install pytest:") + print("pip install -r requirements-test.txt") + return 1 + except Exception as e: + print(f"Error running tests: {e}") + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/src/main/java/org/editor/CanvasFrame.java b/src/main/java/org/editor/CanvasFrame.java index 7315838..a4a3886 100644 --- a/src/main/java/org/editor/CanvasFrame.java +++ b/src/main/java/org/editor/CanvasFrame.java @@ -2,6 +2,7 @@ import com.vlsolutions.swing.docking.DockKey; import com.vlsolutions.swing.docking.Dockable; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; @@ -21,10 +22,12 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.plaf.basic.BasicGraphicsUtils; +import org.editor.theme.ThemeManager; import org.piccode.ast.Ast; import org.piccode.backend.Compiler; import org.piccode.rt.Context; @@ -46,6 +49,7 @@ public class CanvasFrame extends JPanel implements MouseListener, MouseMotionLis private int lastMouseX, lastMouseY; private BufferedImage gridImage; + private BufferedImage render; private Point lastDragPoint; public static Graphics2D gfx = null; private long lastTime; @@ -66,17 +70,13 @@ public class CanvasFrame extends JPanel implements MouseListener, MouseMotionLis private static CanvasFrame _the = null; private DockKey key = new DockKey("canvas"); - public static String code = null; - public static String file = null; - public static boolean start = false; - private CanvasFrame() { super(new BorderLayout()); this.setBackground(new Color(18, 18, 18)); this.addMouseListener(this); this.addMouseMotionListener(this); drawGrid(); - setPreferredSize(new Dimension(getWidth(), getHeight())); + compile(() -> null); Timer timer = new Timer(16, new ActionListener() { public void actionPerformed(ActionEvent e) { repaint(getVisibleRect()); @@ -108,33 +108,21 @@ protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; drawGrid(); - g2.drawImage(gridImage, 0, 0, null); // Smooth rendering g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(Color.BLACK); - gfx = g2; - if (start && file != null && code != null) { - SwingUtilities.invokeLater(() -> { - AccessFrame.msgs.setText(""); - new Thread(() -> compileFrame()) - .start(); - }); - start = false; - } + if (render != null) { + g2.drawImage(render, 0, 0, null); + } drawSelection(g2); if (showHighlight) { drawCrosshair(g2); } } - private PiccodeValue compileFrame() { - var result = Compiler.compile(file, code); - return result; - } - private void drawSelection(Graphics2D g2) { if (selecting && selectionStart != null && selectionEnd != null) { Point start = selectionStart; @@ -267,12 +255,12 @@ private void drawGrid() { gridImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = gridImage.createGraphics(); // Fill background - g2.setColor(Color.WHITE); + g2.setColor(ThemeManager.RENDER_BG); g2.fillRect(0, 0, getWidth(), getHeight()); if (showGrid) { // Draw grid - g2.setColor(new Color(230, 230, 230)); + g2.setColor(ThemeManager.RENDER_GRID); for (int x = -offsetX % SCALE; x < getWidth(); x += SCALE) { g2.drawLine(x, 0, x, getHeight()); } @@ -283,7 +271,7 @@ private void drawGrid() { if (showRuler) { // Draw axis numbers - g2.setColor(Color.GRAY); + g2.setColor(ThemeManager.RENDER_GRID); for (int x = -offsetX % SCALE; x < getWidth(); x += SCALE) { int value = (x + offsetX) / SCALE; g2.drawString(Integer.toString(value * SCALE), x + 2, 12); @@ -299,7 +287,7 @@ private void drawGrid() { g2.setColor(Color.RED); g2.drawString("y", 8, getHeight() - 5); - g2.setColor(Color.GRAY); + g2.setColor(ThemeManager.RENDER_TXT2); int x = (-offsetX % SCALE + offsetX) / SCALE; int y = (-offsetY % SCALE + offsetY) / SCALE; x *= SCALE; @@ -309,8 +297,33 @@ private void drawGrid() { lastGridOffsetX = offsetX; lastGridOffsetY = offsetY; + g2.dispose(); + } + + public void compile(Supplier fx) { + int width = getWidth(); + int height = getHeight(); + if (width <= 0 || height <= 0) { + return; + } + + render = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Get the Graphics2D object + gfx = render.createGraphics(); + + // Enable transparency by drawing a fully transparent background + gfx.setComposite(AlphaComposite.Clear); + gfx.fillRect(0, 0, width, height); + + gfx.setColor(Color.BLACK); + // Switch back to normal composite mode for drawing + gfx.setComposite(AlphaComposite.SrcOver); + + fx.get(); } + @Override public void mouseClicked(MouseEvent e) { } diff --git a/src/main/java/org/editor/CodeEditor.java b/src/main/java/org/editor/CodeEditor.java index d55e319..e37cdc7 100644 --- a/src/main/java/org/editor/CodeEditor.java +++ b/src/main/java/org/editor/CodeEditor.java @@ -30,6 +30,7 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.TextEditorPane; +import org.fife.ui.rsyntaxtextarea.Theme; import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate; import org.fife.ui.rsyntaxtextarea.templates.StaticCodeTemplate; @@ -297,4 +298,15 @@ public DockKey getDockKey() { public Component getComponent() { return this; } + + public void setThemeMode(boolean dark) { + var themeName = dark ? "monokai" : "vs"; + try { + Theme theme = Theme.load(getClass().getResourceAsStream( + "/org/fife/ui/rsyntaxtextarea/themes/" + themeName +".xml")); + theme.apply(textArea); + } catch (IOException ioe) { // Never happens + ioe.printStackTrace(); + } + } } diff --git a/src/main/java/org/editor/EditorWindow.java b/src/main/java/org/editor/EditorWindow.java index be507e1..b43054c 100644 --- a/src/main/java/org/editor/EditorWindow.java +++ b/src/main/java/org/editor/EditorWindow.java @@ -43,6 +43,7 @@ import org.editor.panels.FileTreePanel; import org.editor.panels.PluginsPanel; import org.editor.panels.VCPanel; +import org.editor.theme.ThemeManager; import org.fife.rsta.ui.CollapsibleSectionPanel; //import org.fife.rsta.ui.DocumentMap; @@ -72,8 +73,9 @@ public final class EditorWindow extends JFrame implements SearchListener { private CollapsibleSectionPanel csp; public static FindDialog findDialog; public static ReplaceDialog replaceDialog; - private DockingDesktop desk = new DockingDesktop(); + public static DockingDesktop desk = new DockingDesktop(); private static CodeEditor selected = null; + public static boolean dark = true; public static EditorWindow the() { if (win == null) { @@ -87,17 +89,13 @@ public static EditorWindow the() { public EditorWindow() { super("Piccode - DashBoard"); + ThemeManager.setFlatLaf(dark); DockingUISettings.getInstance().installUI(); customizeDock(); UIManager.put("Tree.collapsedIcon", UIManager.getIcon("Tree.collapsedIcon")); UIManager.put("Tree.expandedIcon", UIManager.getIcon("Tree.expandedIcon")); - try { - UIManager.setLookAndFeel(new FlatLightLaf()); - } catch (Exception ex) { - System.err.println("Failed to initialize LaF"); - } new CodeEditor(); root = getRootPane(); @@ -118,7 +116,7 @@ public EditorWindow() { if (current.getDockable() instanceof CodeEditor ed) { if (event.getFutureState().isClosed()) { - if (removeIfDirty(ed.tabIndex, ed) == false) { + if (removeIfNotDirty(ed.tabIndex, ed) == false) { event.cancel(); } } @@ -229,6 +227,8 @@ public EditorWindow() { win = this; + ThemeManager.updateThemes(dark); + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(width, height); this.setLocationRelativeTo(null); @@ -251,6 +251,8 @@ public static void addTab(ActionEvent e) { editor.requestFocusInWindow(); current_file.setText(file != null ? file.toString() : "[NONE]"); tabEditors.put(index, editor); + ThemeManager.registerEditor(editor); + ThemeManager.updateThemes(dark); // Add first editor normally if (index == 0) { @@ -271,6 +273,8 @@ public static void addTab(Path path, Void e) { editor.requestFocusInWindow(); tabEditors.put(index, editor); + ThemeManager.registerEditor(editor); + ThemeManager.updateThemes(dark); // Add first editor normally if (index == 0) { win.getContentPane().add(editor); @@ -339,7 +343,7 @@ private static Component makeTabHeader(JTabbedPane tabs, String title) { int index = tabs.indexOfTabComponent(tabHeader); if (index != -1) { var ed = tabEditors.get(index); - removeIfDirty(index, ed); + removeIfNotDirty(index, ed); } }); @@ -364,13 +368,13 @@ public static void removeTab() { return; } - removeIfDirty(index, focused); + removeIfNotDirty(index, focused); } public static void removeAllTabs() { var editors = new HashMap<>(tabEditors); // Copy to avoid ConcurrentModificationException for (var entry : editors.entrySet()) { - removeIfDirty(entry.getKey(), entry.getValue()); + removeIfNotDirty(entry.getKey(), entry.getValue()); } } @@ -378,7 +382,7 @@ public static int tabsCount() { return tabEditors.size(); } - private static boolean removeIfDirty(Integer index, CodeEditor ed) { + private static boolean removeIfNotDirty(Integer index, CodeEditor ed) { if (ed.textArea.isDirty()) { int result = JOptionPane.showConfirmDialog(win, "File " + ed.filePathTruncated() + " is modified. Save?"); if (result == JOptionPane.OK_OPTION) { @@ -387,6 +391,7 @@ private static boolean removeIfDirty(Integer index, CodeEditor ed) { } win.desk.remove((Dockable) ed); // Actual removal from docking layout tabEditors.remove(index); + ThemeManager.removeEditor(ed); migrateIndexes(); return true; diff --git a/src/main/java/org/editor/dialogs/EditorSettingsDialog.form b/src/main/java/org/editor/dialogs/EditorSettingsDialog.form new file mode 100644 index 0000000..c619847 --- /dev/null +++ b/src/main/java/org/editor/dialogs/EditorSettingsDialog.form @@ -0,0 +1,224 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/editor/dialogs/EditorSettingsDialog.java b/src/main/java/org/editor/dialogs/EditorSettingsDialog.java new file mode 100644 index 0000000..1e4098a --- /dev/null +++ b/src/main/java/org/editor/dialogs/EditorSettingsDialog.java @@ -0,0 +1,181 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JPanel.java to edit this template + */ +package org.editor.dialogs; + +/** + * + * @author hexaredecimal + */ +public class EditorSettingsDialog extends javax.swing.JPanel { + + /** + * Creates new form EditorSettingsDialog + */ + public EditorSettingsDialog() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + jTextField1 = new javax.swing.JTextField(); + jButton1 = new javax.swing.JButton(); + jLabel2 = new javax.swing.JLabel(); + jSpinner1 = new javax.swing.JSpinner(); + jPanel2 = new javax.swing.JPanel(); + jCheckBox1 = new javax.swing.JCheckBox(); + jCheckBox2 = new javax.swing.JCheckBox(); + jCheckBox3 = new javax.swing.JCheckBox(); + jPanel3 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + jComboBox1 = new javax.swing.JComboBox<>(); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Font")); + + jLabel1.setText("Font"); + + jButton1.setText("Pick Font"); + + jLabel2.setText("Font Size"); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, 92, Short.MAX_VALUE) + .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 226, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton1) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jSpinner1)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jButton1)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("General")); + + jCheckBox1.setText("Show Line Numbers"); + + jCheckBox2.setText("Enable Syntax Highlighter"); + + jCheckBox3.setText("Enable BookMarks"); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jCheckBox2) + .addComponent(jCheckBox1) + .addComponent(jCheckBox3)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jCheckBox2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jCheckBox1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jCheckBox3) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("LookAndFeel")); + + jLabel3.setText("Dark/Light Mode"); + + jComboBox1.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Dark Mode", "Light Mode" })); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 157, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jComboBox1, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JCheckBox jCheckBox1; + private javax.swing.JCheckBox jCheckBox2; + private javax.swing.JCheckBox jCheckBox3; + private javax.swing.JComboBox jComboBox1; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JSpinner jSpinner1; + private javax.swing.JTextField jTextField1; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/org/editor/dialogs/GeneralSettingsDialog.form b/src/main/java/org/editor/dialogs/GeneralSettingsDialog.form new file mode 100644 index 0000000..b450678 --- /dev/null +++ b/src/main/java/org/editor/dialogs/GeneralSettingsDialog.form @@ -0,0 +1,197 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/editor/dialogs/GeneralSettingsDialog.java b/src/main/java/org/editor/dialogs/GeneralSettingsDialog.java new file mode 100644 index 0000000..2883926 --- /dev/null +++ b/src/main/java/org/editor/dialogs/GeneralSettingsDialog.java @@ -0,0 +1,166 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JPanel.java to edit this template + */ +package org.editor.dialogs; + +/** + * + * @author hexaredecimal + */ +public class GeneralSettingsDialog extends javax.swing.JPanel { + + /** + * Creates new form GeneralSettingsDialog + */ + public GeneralSettingsDialog() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + jTextField1 = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + jTextField2 = new javax.swing.JTextField(); + jButton1 = new javax.swing.JButton(); + jPanel2 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + jTextField3 = new javax.swing.JTextField(); + jLabel4 = new javax.swing.JLabel(); + jTextField4 = new javax.swing.JTextField(); + jLabel5 = new javax.swing.JLabel(); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("General")); + + jLabel1.setText("Working Directory"); + + jLabel2.setText("Project Name"); + + jButton1.setLabel("Update"); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jTextField1)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jTextField2, javax.swing.GroupLayout.DEFAULT_SIZE, 349, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(jButton1))) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 15, Short.MAX_VALUE) + .addComponent(jButton1) + .addContainerGap()) + ); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Runtime")); + + jLabel3.setText("Entry File"); + + jTextField3.setText("./main.pics"); + + jLabel4.setText("Arguments"); + + jLabel5.setFont(new java.awt.Font("sansserif", 0, 10)); // NOI18N + jLabel5.setForeground(new java.awt.Color(204, 204, 204)); + jLabel5.setText("NOTE: Arguments are separated by a space"); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, 94, Short.MAX_VALUE) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTextField3) + .addComponent(jTextField4, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel5) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JTextField jTextField1; + private javax.swing.JTextField jTextField2; + private javax.swing.JTextField jTextField3; + private javax.swing.JTextField jTextField4; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/org/editor/dialogs/NewProjectDialog.form b/src/main/java/org/editor/dialogs/NewProjectDialog.form new file mode 100644 index 0000000..fcf699d --- /dev/null +++ b/src/main/java/org/editor/dialogs/NewProjectDialog.form @@ -0,0 +1,376 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/editor/dialogs/NewProjectDialog.java b/src/main/java/org/editor/dialogs/NewProjectDialog.java new file mode 100644 index 0000000..ad09f00 --- /dev/null +++ b/src/main/java/org/editor/dialogs/NewProjectDialog.java @@ -0,0 +1,377 @@ +package org.editor.dialogs; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFileChooser; + +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +/** + * + * @author vulture + */ +public class NewProjectDialog extends javax.swing.JFrame { + + /** + * Creates new form NewProject + */ + + private File selectedFile; + + public NewProjectDialog() { + initComponents(); + this.setResizable(false); + this.setTitle("Create a new project"); + this.hasError = false; + //this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel6 = new javax.swing.JLabel(); + jPanel2 = new javax.swing.JPanel(); + jPanel3 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + jTextFieldProjectName = new javax.swing.JTextField(); + jLabel4 = new javax.swing.JLabel(); + jTextFieldProjectAuthor = new javax.swing.JTextField(); + jLabelProjectNameError = new javax.swing.JLabel(); + jLabelProjectAuthorError = new javax.swing.JLabel(); + jPanel4 = new javax.swing.JPanel(); + jLabel5 = new javax.swing.JLabel(); + jTextFieldProjectLocation = new javax.swing.JTextField(); + jCheckBox1 = new javax.swing.JCheckBox(); + jButton1 = new javax.swing.JButton(); + jLabelProjectLocationError = new javax.swing.JLabel(); + jCheckBox2 = new javax.swing.JCheckBox(); + jButtonFinish = new javax.swing.JButton(); + jButtonHelp = new javax.swing.JButton(); + jButtonCancel = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setAlwaysOnTop(true); + setLocation(new java.awt.Point(400, 400)); + setResizable(false); + + jPanel1.setBackground(new java.awt.Color(255, 255, 255)); + jPanel1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + jLabel1.setFont(new java.awt.Font("Tahoma", 1, 15)); // NOI18N + jLabel1.setText(" STEPS___________"); + + jLabel2.setText(" Add project name
and author(s)"); + + jLabel6.setFont(new java.awt.Font("sansserif", 0, 10)); // NOI18N + jLabel6.setText(" Add project folder name
and create new file system "); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 138, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 138, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 138, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(18, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Project Information")); + + jLabel3.setText("Project name :"); + + jLabel4.setText("Project author :"); + + jLabelProjectNameError.setForeground(new java.awt.Color(255, 0, 0)); + jLabelProjectNameError.setText("*"); + jLabelProjectNameError.setEnabled(false); + + jLabelProjectAuthorError.setForeground(new java.awt.Color(255, 0, 0)); + jLabelProjectAuthorError.setText("*"); + jLabelProjectAuthorError.setEnabled(false); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addComponent(jLabel4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel3) + .addGap(13, 13, 13))) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jTextFieldProjectName) + .addComponent(jTextFieldProjectAuthor, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabelProjectNameError) + .addComponent(jLabelProjectAuthorError)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(jTextFieldProjectName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabelProjectNameError)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(jTextFieldProjectAuthor, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabelProjectAuthorError)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder("Project Filesystem")); + + jLabel5.setText("Project location :"); + + jCheckBox1.setText("Create a new folder"); + + jButton1.setText("Select"); + jButton1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(evt); + } + }); + + jLabelProjectLocationError.setForeground(new java.awt.Color(255, 0, 0)); + jLabelProjectLocationError.setText("Invalid project location"); + jLabelProjectLocationError.setEnabled(false); + + jCheckBox2.setText("Initialize git repo"); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(jCheckBox1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabelProjectLocationError)) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jTextFieldProjectLocation, javax.swing.GroupLayout.PREFERRED_SIZE, 227, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jButton1)) + .addComponent(jCheckBox2)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel5) + .addComponent(jTextFieldProjectLocation, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jButton1)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jCheckBox1) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addComponent(jLabelProjectLocationError) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 4, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 2, Short.MAX_VALUE) + .addComponent(jCheckBox2) + .addContainerGap()) + ); + + jButtonFinish.setText("Finish"); + jButtonFinish.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonFinishActionPerformed(evt); + } + }); + + jButtonHelp.setText("Help"); + + jButtonCancel.setText("Cancel"); + jButtonCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonCancelActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(jButtonCancel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonFinish) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButtonHelp))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButtonFinish) + .addComponent(jButtonHelp) + .addComponent(jButtonCancel)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + pack(); + }//
//GEN-END:initComponents + + private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCancelActionPerformed + this.dispose(); + }//GEN-LAST:event_jButtonCancelActionPerformed + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int option = fileChooser.showOpenDialog(null); + + if (option == JFileChooser.APPROVE_OPTION) { + selectedFile = fileChooser.getSelectedFile(); + jTextFieldProjectLocation.setText(selectedFile.getPath()); + } + }//GEN-LAST:event_jButton1ActionPerformed + + private void jButtonFinishActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonFinishActionPerformed + if (jTextFieldProjectName.getText().isEmpty()) { + jLabelProjectNameError.setEnabled(true); + return; + } else { + jLabelProjectNameError.setEnabled(false); + } + + if (jTextFieldProjectAuthor.getText().isEmpty()) { + jLabelProjectAuthorError.setEnabled(true); + return; + } else { + jLabelProjectAuthorError.setEnabled(false); + } + + if(jTextFieldProjectLocation.getText().isEmpty()) { + jLabelProjectLocationError.setEnabled(true); + jLabelProjectLocationError.setText("Invalid project location"); + } else { + + if (selectedFile != null && selectedFile.exists() == false) { + jLabelProjectLocationError.setEnabled(true); + jLabelProjectLocationError.setText("Selected directory does not exist"); + return; + } else if (selectedFile == null) { + File tmp = new File(jTextFieldProjectLocation.getText()); + if(tmp.exists() == false) { + jLabelProjectLocationError.setEnabled(true); + jLabelProjectLocationError.setText("Selected directory does not exist"); + return; + } + } else { + // continue by disableing the errors + jLabelProjectLocationError.setText("Invalid project location"); + jLabelProjectLocationError.setEnabled(false); + } + } + + String projectName = jTextFieldProjectName.getText(); + String projectAuthor = jTextFieldProjectAuthor.getText(); + String projectPath = jTextFieldProjectLocation.getText(); + + + + }//GEN-LAST:event_jButtonFinishActionPerformed + + private boolean hasError; + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JButton jButtonCancel; + private javax.swing.JButton jButtonFinish; + private javax.swing.JButton jButtonHelp; + private javax.swing.JCheckBox jCheckBox1; + private javax.swing.JCheckBox jCheckBox2; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabelProjectAuthorError; + private javax.swing.JLabel jLabelProjectLocationError; + private javax.swing.JLabel jLabelProjectNameError; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JTextField jTextFieldProjectAuthor; + private javax.swing.JTextField jTextFieldProjectLocation; + private javax.swing.JTextField jTextFieldProjectName; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/org/editor/dialogs/SettingsDialog.form b/src/main/java/org/editor/dialogs/SettingsDialog.form new file mode 100644 index 0000000..a459d89 --- /dev/null +++ b/src/main/java/org/editor/dialogs/SettingsDialog.form @@ -0,0 +1,79 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/editor/dialogs/SettingsDialog.java b/src/main/java/org/editor/dialogs/SettingsDialog.java new file mode 100644 index 0000000..072d6ec --- /dev/null +++ b/src/main/java/org/editor/dialogs/SettingsDialog.java @@ -0,0 +1,82 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JPanel.java to edit this template + */ +package org.editor.dialogs; + +/** + * + * @author hexaredecimal + */ +public class SettingsDialog extends javax.swing.JPanel { + + /** + * Creates new form SettingsDialog + */ + public SettingsDialog() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + jTabbedPane1 = new javax.swing.JTabbedPane(); + jButton1 = new javax.swing.JButton(); + jButton2 = new javax.swing.JButton(); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTabbedPane1) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jTabbedPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 303, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + ); + + jButton1.setText("jButton1"); + + jButton2.setText("jButton2"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(333, Short.MAX_VALUE) + .addComponent(jButton2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton1) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jButton1) + .addComponent(jButton2)) + .addGap(0, 13, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JButton jButton2; + private javax.swing.JPanel jPanel1; + private javax.swing.JTabbedPane jTabbedPane1; + // End of variables declaration//GEN-END:variables +} diff --git a/src/main/java/org/editor/events/AccessEvents.java b/src/main/java/org/editor/events/AccessEvents.java index 5b2f6da..2a2e46d 100644 --- a/src/main/java/org/editor/events/AccessEvents.java +++ b/src/main/java/org/editor/events/AccessEvents.java @@ -29,10 +29,9 @@ public static void compileAndRender(ActionEvent e) { var file = ed.file.toString(); var code = ed.textArea.getText(); - CanvasFrame.file = file; - CanvasFrame.code = code; - CanvasFrame.start = true; + AccessFrame.msgs.setText(""); AccessFrame.writeSuccess("Compilation started: "); + CanvasFrame.the().compile(() -> Compiler.compile(file, code)); } public static void compile(ActionEvent e) { diff --git a/src/main/java/org/editor/events/MenuEvents.java b/src/main/java/org/editor/events/MenuEvents.java index 3b04f33..50a28b8 100644 --- a/src/main/java/org/editor/events/MenuEvents.java +++ b/src/main/java/org/editor/events/MenuEvents.java @@ -23,118 +23,117 @@ */ public class MenuEvents { - public static void gotoLineEvent(ActionEvent e) { - var findDialog = EditorWindow.findDialog; - var replaceDialog = EditorWindow.replaceDialog; - if (findDialog.isVisible()) { - findDialog.setVisible(false); - } - if (replaceDialog.isVisible()) { - replaceDialog.setVisible(false); - } - GoToDialog dialog = new GoToDialog(EditorWindow.win); - - var ed = EditorWindow.getSelectedEditor(); - if (ed == null) { - return; - } + public static void gotoLineEvent(ActionEvent e) { + var findDialog = EditorWindow.findDialog; + var replaceDialog = EditorWindow.replaceDialog; + if (findDialog.isVisible()) { + findDialog.setVisible(false); + } + if (replaceDialog.isVisible()) { + replaceDialog.setVisible(false); + } + GoToDialog dialog = new GoToDialog(EditorWindow.win); + + var ed = EditorWindow.getSelectedEditor(); + if (ed == null) { + return; + } var textArea = ed.textArea; - dialog.setMaxLineNumberAllowed(textArea.getLineCount()); - dialog.setVisible(true); - int line = dialog.getLineNumber(); - if (line > 0) { - try { - textArea.setCaretPosition(textArea.getLineStartOffset(line - 1)); - } catch (BadLocationException ble) { // Never happens - UIManager.getLookAndFeel().provideErrorFeedback(textArea); - ble.printStackTrace(); - } - } - } - - static void replaceEvent(ActionEvent e) { - var replaceDialog = EditorWindow.replaceDialog; - var findDialog = EditorWindow.findDialog; - if (findDialog.isVisible()) { - findDialog.setVisible(false); - } - replaceDialog.setVisible(true); - } - - static void findEvent(ActionEvent e) { - var replaceDialog = EditorWindow.replaceDialog; - var findDialog = EditorWindow.findDialog; - if (replaceDialog.isVisible()) { - replaceDialog.setVisible(false); - } - findDialog.setVisible(true); - } - - public static void aboutDialog(ActionEvent e) { - new AboutDialog(EditorWindow.win); - } - - public static void closeTab(ActionEvent e) { - EditorWindow.removeTab(); - } - - public static void closeAllTabs(ActionEvent e) { - EditorWindow.removeAllTabs(); - } - - static void openFile(ActionEvent e) { - // TODO: Use the System object to get the current pwd - var fileChooser = new JFileChooser("."); - fileChooser.setFileFilter(FileFilter.mdFilter); - fileChooser.setFileFilter(FileFilter.picsFilter); - - int status = fileChooser.showOpenDialog(EditorWindow.win); - if (status != JFileChooser.APPROVE_OPTION) { - return; - } - - var fp = fileChooser.getSelectedFile(); - var path = fp.toPath(); - EditorWindow.addTab(path, null); - } - - static void saveFile(ActionEvent e) { - if (EditorWindow.tabsCount() == 0) { - return; - } - var ed = EditorWindow.getSelectedEditor(); - if (ed == null) { - return; - } - ed.saveFile(); - } - - static void saveFileAs(ActionEvent e) { - if (EditorWindow.tabsCount() == 1) { - return; - } - var ed = EditorWindow.getSelectedEditor(); - if (ed == null) { - return; - } - ed.saveFileAs(); - } - - static void saveAllFiles(ActionEvent e) { - EditorWindow.saveAll(); - } - - static void closeFile(ActionEvent e) { - closeTab(e); - } - - static void quit(ActionEvent e) { - closeAllTabs(e); - System.exit(0); - } - - static void newFile(ActionEvent e) { - // TODO: Use a file creator dialog in the future - EditorWindow.addTab(e); - } + dialog.setMaxLineNumberAllowed(textArea.getLineCount()); + dialog.setVisible(true); + int line = dialog.getLineNumber(); + if (line > 0) { + try { + textArea.setCaretPosition(textArea.getLineStartOffset(line - 1)); + } catch (BadLocationException ble) { // Never happens + UIManager.getLookAndFeel().provideErrorFeedback(textArea); + ble.printStackTrace(); + } + } + } + + static void replaceEvent(ActionEvent e) { + var replaceDialog = EditorWindow.replaceDialog; + var findDialog = EditorWindow.findDialog; + if (findDialog.isVisible()) { + findDialog.setVisible(false); + } + replaceDialog.setVisible(true); + } + + static void findEvent(ActionEvent e) { + var replaceDialog = EditorWindow.replaceDialog; + var findDialog = EditorWindow.findDialog; + if (replaceDialog.isVisible()) { + replaceDialog.setVisible(false); + } + findDialog.setVisible(true); + } + + public static void aboutDialog(ActionEvent e) { + new AboutDialog(EditorWindow.win); + } + + static void closeTab(ActionEvent e) { + EditorWindow.removeTab(); + } + + public static void closeAllTabs(ActionEvent e) { + EditorWindow.removeAllTabs(); + } + + static void openFile(ActionEvent e) { + var fileChooser = new JFileChooser(System.getProperty("user.dir")); + fileChooser.setFileFilter(FileFilter.mdFilter); + fileChooser.setFileFilter(FileFilter.picsFilter); + + int status = fileChooser.showOpenDialog(EditorWindow.win); + if (status != JFileChooser.APPROVE_OPTION) { + return; + } + + var fp = fileChooser.getSelectedFile(); + var path = fp.toPath(); + EditorWindow.addTab(path, null); + } + + static void saveFile(ActionEvent e) { + if (EditorWindow.tabsCount() == 0) { + return; + } + var ed = EditorWindow.getSelectedEditor(); + if (ed == null) { + return; + } + ed.saveFile(); + } + + static void saveFileAs(ActionEvent e) { + if (EditorWindow.tabsCount() == 1) { + return; + } + var ed = EditorWindow.getSelectedEditor(); + if (ed == null) { + return; + } + ed.saveFileAs(); + } + + static void saveAllFiles(ActionEvent e) { + EditorWindow.saveAll(); + } + + static void closeFile(ActionEvent e) { + closeTab(e); + } + + static void quit(ActionEvent e) { + closeAllTabs(e); + System.exit(0); + } + + static void newFile(ActionEvent e) { + // TODO: Use a file creator dialog in the future + EditorWindow.addTab(e); + } } diff --git a/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java b/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java new file mode 100644 index 0000000..5b46f25 --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java @@ -0,0 +1,112 @@ +package org.editor.nativemods; + +import com.jhlabs.image.BrushedMetalFilter; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.HashMap; +import java.util.List; +import org.editor.CanvasFrame; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeBrushedMetalFilterModule { + + public static void addFunctions() { + NativeFunctionFactory.create("brush_metal_new", List.of(), (args, namedArgs, frame) -> { + var bmFilter = new BrushedMetalFilter(); + return new PiccodeReference(bmFilter); + }, null); + + NativeFunctionFactory.create("brush_metal_set_rad", List.of("filter", "rad"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var rad = namedArgs.get("rad"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, rad, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var radius = (int) (double) ((PiccodeNumber) rad).raw(); + + filter.setRadius(radius); + + return new PiccodeReference(_filter_object); + }, null); + + NativeFunctionFactory.create("brush_metal_set_amount", List.of("filter", "amount"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var amount = namedArgs.get("amount"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, amount, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var _amount = (float) (double) ((PiccodeNumber) amount).raw(); + + filter.setAmount(_amount); + + return new PiccodeReference(_filter_object); + }, null); + + NativeFunctionFactory.create("brush_metal_set_shine", List.of("filter", "shine"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var shine = namedArgs.get("shine"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, shine, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var _shine = (float) (double) ((PiccodeNumber) shine).raw(); + + filter.setShine(_shine); + + return new PiccodeReference(_filter_object); + }, null); + + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodeFilterModule.java b/src/main/java/org/editor/nativemods/PiccodeFilterModule.java new file mode 100644 index 0000000..278ae3d --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeFilterModule.java @@ -0,0 +1,57 @@ +package org.editor.nativemods; + +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.util.List; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeFilterModule { + + public static void addFunctions() { + NativeFunctionFactory.create("filter_apply", List.of("filter", "image"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var image = namedArgs.get("image"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, image, Type.REFERENCE); + + var obj = (PiccodeReference) _filter; + var img = (PiccodeReference) image; + var _filter_object = obj.deref(); + var _buffered_image = img.deref(); + + if (!(_filter_object instanceof BufferedImageOp)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + if (!(_buffered_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); + } + + + var filter = (BufferedImageOp) _filter_object; + var _image = (BufferedImage) _buffered_image; + + var result = filter.filter(_image, null); + + return new PiccodeReference(result); + }, null); + + + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodeGfxModule.java b/src/main/java/org/editor/nativemods/PiccodeGfxModule.java new file mode 100644 index 0000000..6218fb8 --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeGfxModule.java @@ -0,0 +1,106 @@ +package org.editor.nativemods; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.HashMap; +import java.util.List; +import org.editor.CanvasFrame; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeGfxModule { + + public static void addFunctions() { + NativeFunctionFactory.create("get_gfx", List.of(), (args, namedArgs, frame) -> { + var gfx = CanvasFrame.gfx; + return new PiccodeReference(gfx); + }, null); + + NativeFunctionFactory.create("gfx_from_rect", List.of("ctx", "x", "y", "w", "h"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + var gfx2 = (Graphics2D) gfx.create(_x, _y, _w, _h); + return new PiccodeReference(gfx2); + }, null); + NativeFunctionFactory.create("gfx_from", List.of("ctx"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + + var gfx2 = (Graphics2D) gfx.create(); + return new PiccodeReference(gfx2); + }, null); + + NativeFunctionFactory.create("gfx_drop", List.of("ctx"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + gfx.dispose(); + return new PiccodeUnit(); + }, null); + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodeImageModule.java b/src/main/java/org/editor/nativemods/PiccodeImageModule.java new file mode 100644 index 0000000..3d895ce --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeImageModule.java @@ -0,0 +1,148 @@ +package org.editor.nativemods; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import javax.imageio.ImageIO; +import org.editor.CanvasFrame; +import org.editor.icons.ImageLoader; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeImageModule { + + public static void addFunctions() { + + NativeFunctionFactory.create("image_new", List.of("w", "h"), (args, namedArgs, frame) -> { + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + var image = new BufferedImage(_w, _h, BufferedImage.TYPE_INT_ARGB); + return new PiccodeReference(image); + }, null); + + NativeFunctionFactory.create("image_new_typed", List.of("w", "h", "type"), (args, namedArgs, frame) -> { + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + var type = namedArgs.get("type"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + PiccodeValue.verifyType(caller, type, Type.NUMBER); + + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + var _type = (int) (double) ((PiccodeNumber) type).raw(); + + var image = new BufferedImage(_w, _h, _type); + return new PiccodeReference(image); + }, null); + + NativeFunctionFactory.create("image_new_from_path", List.of("path"), (args, namedArgs, frame) -> { + var path = namedArgs.get("path"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, path, Type.STRING); + + try { + BufferedImage image = ImageIO.read(new File(path.raw().toString())); + return new PiccodeReference(image); + } catch (IOException ex) { + var def = (BufferedImage) ImageLoader.getImage(-1); + return new PiccodeReference(def); + } + }, null); + + NativeFunctionFactory.create("image_resize", List.of("img" ,"w", "h"), (args, namedArgs, frame) -> { + var img = namedArgs.get("img"); + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, img, Type.REFERENCE); + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var _buffered_image = ((PiccodeReference)img).deref(); + + if (!(_buffered_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); + } + + var bufferedmage = (BufferedImage) _buffered_image; + + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + Image resultingImage = bufferedmage.getScaledInstance(_w, _h, Image.SCALE_DEFAULT); + BufferedImage outputImage = new BufferedImage(_w, _h, BufferedImage.TYPE_INT_RGB); + outputImage.getGraphics().drawImage(resultingImage, 0, 0, null); + + return new PiccodeReference(outputImage); + }, null); + + NativeFunctionFactory.create("image_get_context", List.of("img"), (args, namedArgs, frame) -> { + var img = namedArgs.get("img"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, img, Type.REFERENCE); + + var _buffered_image = ((PiccodeReference)img).deref(); + + if (!(_buffered_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); + } + + var bufferedmage = (BufferedImage) _buffered_image; + var gfx = bufferedmage.createGraphics(); + return new PiccodeReference(gfx); + }, null); + + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodePenModule.java b/src/main/java/org/editor/nativemods/PiccodePenModule.java new file mode 100644 index 0000000..7c91b5e --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodePenModule.java @@ -0,0 +1,235 @@ +package org.editor.nativemods; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.List; +import org.editor.CanvasFrame; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodePenModule { + + public static void addFunctions() { + + NativeFunctionFactory.create("draw_line", List.of("ctx", "x1", "y1", "x2", "y2"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var _x1 = namedArgs.get("x1"); + var _y1 = namedArgs.get("y1"); + var _x2 = namedArgs.get("x2"); + var _y2 = namedArgs.get("y2"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, _x1, Type.NUMBER); + PiccodeValue.verifyType(caller, _y1, Type.NUMBER); + PiccodeValue.verifyType(caller, _x2, Type.NUMBER); + PiccodeValue.verifyType(caller, _y2, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + var x1 = (int) (double) ((PiccodeNumber) _x1).raw(); + var y1 = (int) (double) ((PiccodeNumber) _y1).raw(); + var x2 = (int) (double) ((PiccodeNumber) _x2).raw(); + var y2 = (int) (double) ((PiccodeNumber) _y2).raw(); + + gfx.drawLine(x1, y1, x2, y2); + return obj; + }, null); + + NativeFunctionFactory.create("draw_rect", List.of("ctx", "x", "y", "w", "h"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + gfx.drawRect(_x, _y, _w, _h); + return obj; + }, null); + + NativeFunctionFactory.create("draw_round_rect", List.of("ctx", "x", "y", "w", "h", "aw", "ah"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + var aw = namedArgs.get("aw"); + var ah = namedArgs.get("ah"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + PiccodeValue.verifyType(caller, aw, Type.NUMBER); + PiccodeValue.verifyType(caller, ah, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + var _aw = (int) (double) ((PiccodeNumber) aw).raw(); + var _ah = (int) (double) ((PiccodeNumber) ah).raw(); + + gfx.drawRoundRect(_x, _y, _w, _h, _aw, _ah); + return obj; + }, null); + + NativeFunctionFactory.create("draw_oval", List.of("ctx", "x", "y", "w", "h"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + + var gfx = (Graphics2D) _gfx; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + gfx.drawOval(_x, _y, _w, _h); + return obj; + }, null); + + NativeFunctionFactory.create("draw_image", List.of("ctx", "img", "x", "y"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var _img = namedArgs.get("img"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, _img, Type.REFERENCE); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var imgObj = (PiccodeReference) _img; + var _gfx = obj.deref(); + var _image = imgObj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + if (!(_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Image in not a correct object. Expected a BufferedImage but found" + _image); + } + + var gfx = (Graphics2D) _gfx; + var img = (BufferedImage) _image; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + + gfx.drawImage(img, _x, _y, null); + return obj; + }, null); + NativeFunctionFactory.create("draw_text", List.of("ctx", "text", "x", "y"), (args, namedArgs, frame) -> { + var _ctx = namedArgs.get("ctx"); + var _text = namedArgs.get("text"); + var x = namedArgs.get("x"); + var y = namedArgs.get("y"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _ctx, Type.REFERENCE); + PiccodeValue.verifyType(caller, _text, Type.STRING); + PiccodeValue.verifyType(caller, x, Type.NUMBER); + PiccodeValue.verifyType(caller, y, Type.NUMBER); + + var obj = (PiccodeReference) _ctx; + var _gfx = obj.deref(); + if (!(_gfx instanceof Graphics2D)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Context is not a correct object. Expected Graphics2D"); + } + var gfx = (Graphics2D) _gfx; + var _x = (int) (double) ((PiccodeNumber) x).raw(); + var _y = (int) (double) ((PiccodeNumber) y).raw(); + + gfx.drawString(_text.toString(), _x, _y); + return obj; + }, null); + } + +} diff --git a/src/main/java/org/editor/panels/AboutPanel.java b/src/main/java/org/editor/panels/AboutPanel.java index 5da656d..1793f44 100644 --- a/src/main/java/org/editor/panels/AboutPanel.java +++ b/src/main/java/org/editor/panels/AboutPanel.java @@ -38,11 +38,11 @@ private void initComponents() { jLabel1.setName(""); // NOI18N jLabel4.setFont(new java.awt.Font("sansserif", 1, 13)); // NOI18N - jLabel4.setText("Brought to you by Glimmir developers"); + jLabel4.setText("Brought to you by Solaris Studio developers"); jTextPane1.setEditable(false); jTextPane1.setBackground(new java.awt.Color(255, 255, 255)); - jTextPane1.setText("\ndrawString(\"\n+--------------------------------------------+\n | ▄▖▘ ▌ |\n | ▙▌▌▛▘▀▌▛▘▛▘▛▌▛▘▛▌▛▌█▌ |\n | ▌ ▌▙▖█▌▄▌▄▌▙▌▙▖▙▌▙▌▙▖ |\n | Creativity + Logic + Math |\n+---------------------------------------------+\n\", 0, 0)\n\nBrought to you by glimmr developers\n\nLead developer: Gama Sibusiso Vincent\nAI and Plugin intergration: TODO\nDocumenentation: TODO\n\nCreated 100% in java using java swing, antlr4 and flatlaf. \nIcons pack dowloaded from icons8"); + jTextPane1.setText("\nPen::drawText(\"\n+--------------------------------------------+\n | ▄▖▘ ▌ |\n | ▙▌▌▛▘▀▌▛▘▛▘▛▌▛▘▛▌▛▌█▌ |\n | ▌ ▌▙▖█▌▄▌▄▌▙▌▙▖▙▌▙▌▙▖ |\n | Creativity + Logic + Math |\n+---------------------------------------------+\n\", 0, 0)\n\nBrought to you by glimmr developers\n\nLead developer: Gama Sibusiso Vincent\nAI and Plugin intergration: TODO\nDocumenentation: TODO\n\nCreated 100% in java using java swing, antlr4 and flatlaf. \nIcons pack dowloaded from icons8"); jTextPane1.setCursor(new java.awt.Cursor(java.awt.Cursor.TEXT_CURSOR)); jScrollPane1.setViewportView(jTextPane1); diff --git a/src/main/java/org/editor/panels/PluginsPanel.java b/src/main/java/org/editor/panels/PluginsPanel.java index 0013be2..3a38918 100644 --- a/src/main/java/org/editor/panels/PluginsPanel.java +++ b/src/main/java/org/editor/panels/PluginsPanel.java @@ -1,6 +1,18 @@ package org.editor.panels; +import com.piccode.piccodeplugin.PiccodePluginPanel; import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.jar.JarFile; +import org.editor.EditorWindow; +import org.editor.theme.ThemeManager; /** * @@ -8,10 +20,83 @@ */ public class PluginsPanel extends DockablePanel { + private final HashMap loaders = new HashMap<>(); public PluginsPanel() { super(new BorderLayout(), "Plugins", "Plugins", "Browse community plugins", "plugin"); - // TODO: Implement the code + loadPlugins(); + } + + private void loadPlugins() { + var pluginsDir = Paths.get("./etc/plugins/"); + try { + Class baseClass = Class.forName("com.piccode.piccodeplugin.PiccodePluginPanel"); + Files.walk(pluginsDir) + .filter(p -> p.toString().endsWith(".jar")) + .forEach(jarPath -> { + try { + scanJar(jarPath.toFile(), baseClass); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (IOException | ClassNotFoundException ex) { + System.out.println(ex.getMessage()); + } + } + + private void loadPlugin(Class clazz) { + System.out.println("Loading plugin: " + clazz.getName()); + try { + var instance = (PiccodePluginPanel) clazz.getDeclaredConstructor().newInstance(); + instance.init(); + instance = instance.getMainPanel(); + var name = instance.getPluginName(); + var title = "Plugin: " + name; + var dockable = new DockablePanel(new BorderLayout(), name, title, instance.getDescription(), "plugin"); + dockable.add(instance, BorderLayout.CENTER); + ThemeManager.registerPlugin(instance); + var desk = EditorWindow.desk; + desk.addDockable(dockable); + desk.setAutoHide(dockable, true); + ThemeManager.updateThemes(EditorWindow.dark); + + } catch (Exception ex) { + System.out.println("" + ex.getMessage()); + } + } + + private void scanJar(File jarFile, Class baseClass) throws IOException { + URL[] urls = {jarFile.toURI().toURL()}; + // Use system classloader as parent to share core and app classes + URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); + loaders.put(jarFile, cl); // keep the loader alive + + try (JarFile jar = new JarFile(jarFile)) { + jar.stream() + .filter(e -> e.getName().endsWith(".class") + && !e.getName().equals("module-info.class") + && !e.getName().contains("$") + && !e.getName().contains("META-INF/") + && !e.getName().endsWith("package-info.class")) + .forEach(e -> { + String className = e.getName().replace('/', '.').replaceAll("\\.class$", ""); + try { + // Try loading from app classloader first (fallback pattern) + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException ex) { + clazz = cl.loadClass(className); + } + if (baseClass.isAssignableFrom(clazz) && !clazz.equals(baseClass)) { + loadPlugin(clazz); + } + } catch (Exception ignore) { + System.out.println("Failed to load class " + className + ": " + ignore.getMessage()); + } + }); + } } } diff --git a/src/main/java/org/editor/theme/ThemeManager.java b/src/main/java/org/editor/theme/ThemeManager.java new file mode 100644 index 0000000..d3a6287 --- /dev/null +++ b/src/main/java/org/editor/theme/ThemeManager.java @@ -0,0 +1,67 @@ +package org.editor.theme; + +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.piccode.piccodeplugin.PiccodePluginInterface; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import org.editor.CodeEditor; + +/** + * + * @author hexaredecimal + */ +public class ThemeManager { + + private static List editors = new ArrayList(); + private static List plugins = new ArrayList(); + public static Color RENDER_BG = Color.WHITE; + public static Color RENDER_FG = Color.BLACK; + public static Color RENDER_TXT2 = Color.BLUE; + public static Color RENDER_GRID = Color.GRAY; + + public static void registerEditor(CodeEditor editor) { + editors.add(editor); + } + + public static void removeEditor(CodeEditor editor) { + editors.remove(editor); + } + + public static void registerPlugin(PiccodePluginInterface plugin) { + plugins.add(plugin); + } + + public static void updateThemes(boolean dark) { + editors.forEach(editor -> editor.setThemeMode(dark)); + plugins.forEach(plugin -> plugin.setThemeMode(dark)); + setFlatLaf(dark); + + if (dark) { + RENDER_BG = new Color(18, 18, 18); + RENDER_FG = Color.WHITE; + RENDER_TXT2 = Color.GREEN; + RENDER_GRID = new Color(18 * 5, 18 * 5, 18 * 5); + } else { + RENDER_BG = Color.WHITE; + RENDER_FG = Color.BLACK; + RENDER_TXT2 = Color.BLUE; + RENDER_GRID = new Color(230, 230, 230); + } + } + + public static void setFlatLaf(boolean dark) { + try { + if (dark) { + FlatDarkLaf.setup(); + FlatDarkLaf.updateUI(); + } else { + FlatLightLaf.setup(); + FlatLightLaf.updateUI(); + } + } catch (Exception ex) { + System.err.println("Failed to initialize LaF"); + } + } +} diff --git a/src/main/java/org/piccode/piccode/Piccode.java b/src/main/java/org/piccode/piccode/Piccode.java index 9f2b0c1..3b03ee6 100644 --- a/src/main/java/org/piccode/piccode/Piccode.java +++ b/src/main/java/org/piccode/piccode/Piccode.java @@ -2,6 +2,11 @@ import org.editor.AccessFrame; import org.editor.EditorWindow; +import org.editor.nativemods.PiccodeBrushedMetalFilterModule; +import org.editor.nativemods.PiccodeFilterModule; +import org.editor.nativemods.PiccodeGfxModule; +import org.editor.nativemods.PiccodeImageModule; +import org.editor.nativemods.PiccodePenModule; import org.piccode.backend.Compiler; import org.piccode.piccodescript.ErrorAsciiKind; @@ -15,7 +20,15 @@ public static void main(String[] args) { Compiler.exitOnError = false; Compiler.errorKind = ErrorAsciiKind.EMACS_COMP_STYLE; Compiler.out = AccessFrame.AccessFrameOutputStream.out; - + initializeNativeAppModules(); EditorWindow.the(); } + + private static void initializeNativeAppModules() { + Compiler.addNativeFunctions(PiccodeGfxModule::addFunctions); + Compiler.addNativeFunctions(PiccodePenModule::addFunctions); + Compiler.addNativeFunctions(PiccodeBrushedMetalFilterModule::addFunctions); + Compiler.addNativeFunctions(PiccodeFilterModule::addFunctions); + Compiler.addNativeFunctions(PiccodeImageModule::addFunctions); + } } diff --git a/tests/test_readme_validation.py b/tests/test_readme_validation.py new file mode 100644 index 0000000..d16d2b7 --- /dev/null +++ b/tests/test_readme_validation.py @@ -0,0 +1,427 @@ +import pytest +import re +import requests +from pathlib import Path +from unittest.mock import patch, Mock + + +class TestReadmeValidation: + """Comprehensive test suite for README.md validation and content verification.""" + + @pytest.fixture + def readme_content(self): + """Fixture to load README.md content for testing.""" + readme_path = Path("README.md") + if readme_path.exists(): + return readme_path.read_text(encoding='utf-8') + return "" + + def test_readme_file_exists(self): + """Test that README.md file exists in the repository root.""" + readme_path = Path("README.md") + assert readme_path.exists(), "README.md file should exist in repository root" + assert readme_path.is_file(), "README.md should be a file, not a directory" + + def test_readme_is_not_empty(self, readme_content): + """Test that README.md is not empty and has meaningful content.""" + assert readme_content.strip(), "README.md should not be empty" + assert len(readme_content.strip()) > 100, "README.md should have substantial content" + + def test_project_title_present(self, readme_content): + """Test that the project title 'Piccaso Code' is present in README.""" + assert "Piccaso Code" in readme_content, "Project title 'Piccaso Code' should be present" + # Also test for the correct spelling variant + assert "Piccasso code" in readme_content, "Alternative spelling 'Piccasso code' should be present" + + def test_project_description_present(self, readme_content): + """Test that key project description elements are present.""" + assert "image editor" in readme_content.lower(), "Should mention 'image editor'" + assert "java" in readme_content.lower(), "Should mention 'java' as the programming language" + assert "glimr" in readme_content.lower(), "Should mention 'glimr' scripting language" + assert "code to create/edit an image" in readme_content.lower(), "Should describe core functionality" + + def test_html_table_structure(self, readme_content): + """Test that HTML table structure is valid and well-formed.""" + # Test for table opening and closing tags + assert "
-

Piccaso Code

+

Picasso Code

Creativity + Logic + Math

A code based image editor created 100% in java

" in readme_content, "Should contain opening table tag" + assert "
" in readme_content, "Should contain closing table tag" + + # Test for proper row structure + table_rows = re.findall(r".*?", readme_content, re.DOTALL) + assert len(table_rows) >= 1, "Should have at least one table row" + + # Test for proper cell structure + table_cells = re.findall(r".*?", readme_content, re.DOTALL) + assert len(table_cells) >= 2, "Should have at least two table cells" + + def test_app_icon_reference(self, readme_content): + """Test that app icon image reference is properly formatted.""" + img_pattern = r'' + img_matches = re.findall(img_pattern, readme_content) + + assert len(img_matches) > 0, "Should contain at least one img tag" + + for src, width in img_matches: + assert src.startswith("./"), "Image source should use relative path" + assert "appicon.png" in src, "Should reference the app icon file" + assert width.endswith("%"), "Width should be specified as percentage" + + def test_github_actions_badge(self, readme_content): + """Test that GitHub Actions CI badge is present and properly formatted.""" + badge_pattern = r'\[![^]]+\]\([^)]+\)' + badges = re.findall(badge_pattern, readme_content) + + assert len(badges) > 0, "Should contain at least one badge" + assert "Java CI with Maven" in readme_content, "Should contain Java CI badge text" + assert "actions/workflows/maven.yml" in readme_content, "Should reference Maven workflow" + + def test_required_sections_present(self, readme_content): + """Test that all required sections are present in the README.""" + required_sections = [ + "## About", + "## Download", + "## Building", + "## Inspired by", + "## References", + "## License" + ] + + for section in required_sections: + assert section in readme_content, f"Required section '{section}' should be present" + + def test_build_instructions_valid(self, readme_content): + """Test that build instructions are present and properly formatted.""" + # Test for git clone command + assert "git clone" in readme_content, "Should contain git clone command" + assert "github.com" in readme_content, "Should reference GitHub repository" + + # Test for Maven build command + assert "mvn package" in readme_content, "Should contain Maven package command" + + # Test for Java execution command + assert "java -jar" in readme_content, "Should contain Java execution command" + assert "target/" in readme_content, "Should reference target directory" + assert ".jar" in readme_content, "Should reference JAR file" + + def test_code_blocks_properly_formatted(self, readme_content): + """Test that code blocks are properly formatted with language specifiers.""" + code_block_pattern = r'```(\w+)?\n(.*?)\n```' + code_blocks = re.findall(code_block_pattern, readme_content, re.DOTALL) + + assert len(code_blocks) >= 2, "Should contain at least 2 code blocks" + + # Check for shell/bash code blocks + shell_blocks = [content for lang, content in code_blocks if lang in ['sh', 'bash', 'shell']] + assert len(shell_blocks) >= 1, "Should contain at least one shell code block" + + def test_external_references_format(self, readme_content): + """Test that external references are properly formatted.""" + # Test for OpenSCAD reference + assert "OpenSCAD" in readme_content, "Should reference OpenSCAD" + assert "openscad.org" in readme_content, "Should contain OpenSCAD URL" + + # Test for java image filters reference + assert "jhlabs.com" in readme_content, "Should reference JH Labs filters" + + # Test for Icons8 reference + assert "icons8.com" in readme_content, "Should reference Icons8" + + @pytest.mark.parametrize("url", [ + "https://github.com/Glimmr-Lang/PicassoCode/actions/workflows/maven.yml", + "https://openscad.org/", + "http://www.jhlabs.com/ip/filters/index.html", + "https://icons8.com/icons/parakeet--style-parakeet" + ]) + def test_external_urls_accessible(self, url): + """Test that external URLs mentioned in README are accessible (mocked for testing).""" + with patch('requests.get') as mock_get: + mock_response = Mock() + mock_response.status_code = 200 + mock_response.raise_for_status.return_value = None + mock_get.return_value = mock_response + + response = requests.get(url, timeout=10) + assert response.status_code == 200, f"URL {url} should be accessible" + mock_get.assert_called_once_with(url, timeout=10) + + def test_license_section_format(self, readme_content): + """Test that license section contains proper ASCII art and license info.""" + license_section = readme_content[readme_content.find("## License"):] + + # Test for ASCII art presence + assert "drawString" in license_section, "License section should contain drawString function" + assert "Creativity + Logic + Math" in license_section, "Should contain project motto" + assert "MIT LICENSE" in license_section, "Should mention MIT license" + + # Test for proper code block formatting in license + assert '```sh' in license_section, "License code should be in shell code block" + + def test_tagline_consistency(self, readme_content): + """Test that the project tagline appears consistently throughout.""" + tagline = "Creativity + Logic + Math" + tagline_occurrences = readme_content.count(tagline) + assert tagline_occurrences >= 2, f"Tagline '{tagline}' should appear at least twice" + + def test_spelling_and_consistency(self, readme_content): + """Test for potential spelling inconsistencies in project name.""" + # The README has both "Piccaso" and "Piccasso" - test that both variants are intentional + piccaso_count = readme_content.count("Piccaso") + piccasso_count = readme_content.count("Piccasso") + + assert piccaso_count > 0, "Should contain 'Piccaso' variant" + assert piccasso_count > 0, "Should contain 'Piccasso' variant" + + # Warn about inconsistency (this is more of a documentation test) + total_variants = piccaso_count + piccasso_count + assert total_variants >= 2, "Project name should appear multiple times" + + def test_markdown_syntax_validity(self, readme_content): + """Test basic markdown syntax validity.""" + lines = readme_content.split('\n') + + # Test for proper heading syntax + heading_lines = [line for line in lines if line.startswith('#')] + for heading in heading_lines: + assert re.match(r'^#{1,6}\s+\S+', heading), f"Invalid heading format: {heading}" + + # Test for balanced HTML tags in the content + html_tags = re.findall(r'<(\/?[^>]+)>', readme_content) + opening_tags = [tag for tag in html_tags if not tag.startswith('/')] + closing_tags = [tag[1:] for tag in html_tags if tag.startswith('/')] + + # Basic balance check for common tags + for tag in ['table', 'tr', 'td', 'h3', 'h6', 'p', 'img']: + opening_count = sum(1 for t in opening_tags if t.split()[0] == tag) + closing_count = closing_tags.count(tag) + if tag != 'img': # img is self-closing + assert opening_count == closing_count, f"Unbalanced {tag} tags" + + def test_repository_references_consistency(self, readme_content): + """Test that repository references are consistent.""" + # Extract repository URLs + repo_urls = re.findall(r'github\.com[:/][^/\s)]+/[^/\s)]+', readme_content) + + assert len(repo_urls) > 0, "Should contain GitHub repository references" + + # Check for consistency in repository naming + unique_repos = set(repo_urls) + # There might be slight variations (Glimmr-Lang/PicassoCode vs hexaredecimal/Piccode) + # This test documents the current state + assert len(unique_repos) >= 1, "Should reference at least one repository" + + def test_download_section_placeholder(self, readme_content): + """Test that download section has appropriate placeholder text.""" + download_section_match = re.search(r'## Download.*?## Building', readme_content, re.DOTALL) + assert download_section_match, "Should have Download section before Building section" + + download_content = download_section_match.group(0) + assert "Coming soon" in download_content, "Download section should indicate coming soon" + + def test_file_encoding_and_format(self): + """Test that README file has proper encoding and line endings.""" + readme_path = Path("README.md") + if readme_path.exists(): + # Test that file can be read as UTF-8 + try: + content = readme_path.read_text(encoding='utf-8') + assert len(content) > 0, "File should be readable as UTF-8" + except UnicodeDecodeError: + pytest.fail("README.md should be encoded in UTF-8") + + # Test for reasonable line lengths (soft limit) + lines = content.split('\n') + long_lines = [i for i, line in enumerate(lines, 1) if len(line) > 120] + # This is a soft warning rather than hard failure + if len(long_lines) > 3: + pytest.warn(f"Many lines exceed 120 characters: lines {long_lines[:5]}") + + +class TestReadmeContentQuality: + """Additional tests for README content quality and completeness.""" + + @pytest.fixture + def readme_content(self): + """Fixture to load README.md content for testing.""" + readme_path = Path("README.md") + if readme_path.exists(): + return readme_path.read_text(encoding='utf-8') + return "" + + def test_inspiration_section_completeness(self, readme_content): + """Test that inspiration section provides adequate context.""" + inspiration_match = re.search(r'## Inspired by.*?## References', readme_content, re.DOTALL) + assert inspiration_match, "Should have complete Inspired by section" + + inspiration_content = inspiration_match.group(0) + assert len(inspiration_content.strip()) > 200, "Inspiration section should be substantial" + assert "OpenSCAD" in inspiration_content, "Should reference OpenSCAD as inspiration" + assert "2D" in inspiration_content, "Should mention 2D aspect" + + def test_build_command_completeness(self, readme_content): + """Test that build commands are complete and executable.""" + # Extract all shell commands + shell_commands = re.findall(r'```sh\n(.*?)\n```', readme_content, re.DOTALL) + + build_commands = [] + for command_block in shell_commands: + commands = command_block.strip().split('\n') + build_commands.extend([cmd.strip() for cmd in commands if cmd.strip()]) + + # Should have git clone, cd, and mvn commands + git_commands = [cmd for cmd in build_commands if cmd.startswith('git')] + cd_commands = [cmd for cmd in build_commands if cmd.startswith('cd')] + mvn_commands = [cmd for cmd in build_commands if cmd.startswith('mvn')] + java_commands = [cmd for cmd in build_commands if cmd.startswith('java')] + + assert len(git_commands) >= 1, "Should have git clone command" + assert len(cd_commands) >= 1, "Should have cd command" + assert len(mvn_commands) >= 1, "Should have mvn command" + assert len(java_commands) >= 1, "Should have java execution command" + + def test_visual_elements_present(self, readme_content): + """Test that visual elements enhance the README presentation.""" + # Should have app icon + assert 'src="./src/main/resources/applogo/appicon.png"' in readme_content, "Should reference app icon" + + # Should have CI badge + assert "badge.svg" in readme_content, "Should have CI status badge" + + # Should have ASCII art in license + ascii_art_indicators = ["▄▖▘", "▙▌▌▛", "▌ ▌▙"] + ascii_present = any(indicator in readme_content for indicator in ascii_art_indicators) + assert ascii_present, "Should contain ASCII art in license section" + + def test_thank_you_message(self, readme_content): + """Test that README ends with appropriate thank you message.""" + assert "Thank you for viewing" in readme_content, "Should end with thank you message" + + def test_repository_url_consistency(self, readme_content): + """Test consistency between different repository URLs mentioned.""" + # Extract git clone URL + git_clone_match = re.search(r'git clone (git@github\.com:[^/]+/[^\s]+)', readme_content) + if git_clone_match: + git_url = git_clone_match.group(1) + # Should be consistent naming + assert "Piccode" in git_url, "Git clone URL should reference Piccode repository" + + # Extract badge URL + badge_match = re.search(r'github\.com/([^/]+/[^/]+)/actions', readme_content) + if badge_match: + badge_repo = badge_match.group(1) + assert "/" in badge_repo, "Badge should reference valid GitHub repo format" + + def test_section_ordering(self, readme_content): + """Test that sections appear in logical order.""" + sections = ["## About", "## Download", "## Building", "## Inspired by", "## References", "## License"] + + section_positions = {} + for section in sections: + pos = readme_content.find(section) + if pos != -1: + section_positions[section] = pos + + # Verify sections appear in expected order + ordered_sections = sorted(section_positions.items(), key=lambda x: x[1]) + expected_order = ["## About", "## Download", "## Building", "## Inspired by", "## References", "## License"] + + actual_order = [section for section, _ in ordered_sections] + for i, expected in enumerate(expected_order): + if i < len(actual_order): + assert actual_order[i] == expected, f"Section {expected} should appear before later sections" + + +class TestReadmeEdgeCases: + """Test edge cases and potential issues in README content.""" + + @pytest.fixture + def readme_content(self): + """Fixture to load README.md content for testing.""" + readme_path = Path("README.md") + if readme_path.exists(): + return readme_path.read_text(encoding='utf-8') + return "" + + def test_no_broken_markdown_links(self, readme_content): + """Test that markdown links are properly formatted.""" + # Find all markdown links + link_pattern = r'\[([^\]]+)\]\(([^)]+)\)' + links = re.findall(link_pattern, readme_content) + + for link_text, link_url in links: + assert link_text.strip(), "Link text should not be empty" + assert link_url.strip(), "Link URL should not be empty" + assert not link_url.startswith(' '), "Link URL should not start with space" + assert not link_url.endswith(' '), "Link URL should not end with space" + + def test_html_img_attributes(self, readme_content): + """Test that HTML img tags have required attributes.""" + img_tags = re.findall(r']+>', readme_content) + + for img_tag in img_tags: + assert 'src=' in img_tag, f"IMG tag should have src attribute: {img_tag}" + # Width is optional but should be properly formatted if present + if 'width=' in img_tag: + width_match = re.search(r'width="([^"]+)"', img_tag) + if width_match: + width_value = width_match.group(1) + # Should be percentage or pixel value + assert width_value.endswith('%') or width_value.endswith('px') or width_value.isdigit(), \ + f"Width value should be valid CSS unit: {width_value}" + + def test_code_block_completeness(self, readme_content): + """Test that code blocks are complete and not malformed.""" + # Count opening and closing code block markers + opening_blocks = readme_content.count('```') + assert opening_blocks % 2 == 0, "Code blocks should have matching opening and closing markers" + + # Test that code blocks have content + code_block_pattern = r'```[^\n]*\n(.*?)\n```' + code_blocks = re.findall(code_block_pattern, readme_content, re.DOTALL) + + for block_content in code_blocks: + assert block_content.strip(), "Code blocks should not be empty" + + def test_ascii_art_integrity(self, readme_content): + """Test that ASCII art in license section is properly formatted.""" + license_section = readme_content[readme_content.find("## License"):] + + if "drawString" in license_section: + # Extract the ASCII art + art_match = re.search(r'drawString\("([^"]+)"', license_section, re.DOTALL) + if art_match: + ascii_art = art_match.group(1) + lines = ascii_art.split('\\n') + + # Should have box-drawing characters + box_chars_present = any(char in ascii_art for char in ['▄', '▖', '▘', '▌', '▙', '█']) + assert box_chars_present, "ASCII art should contain Unicode box-drawing characters" + + # Should have consistent line structure for the box + border_lines = [line for line in lines if '+' in line and '-' in line] + assert len(border_lines) >= 2, "ASCII art should have top and bottom borders" + + def test_whitespace_consistency(self, readme_content): + """Test for consistent whitespace usage.""" + lines = readme_content.split('\n') + + # Check for trailing whitespace (common markdown issue) + trailing_whitespace_lines = [i+1 for i, line in enumerate(lines) if line.rstrip() != line] + assert len(trailing_whitespace_lines) < 5, \ + f"Too many lines with trailing whitespace: {trailing_whitespace_lines[:5]}" + + # Check for multiple consecutive blank lines + consecutive_blanks = 0 + max_consecutive_blanks = 0 + + for line in lines: + if line.strip() == '': + consecutive_blanks += 1 + max_consecutive_blanks = max(max_consecutive_blanks, consecutive_blanks) + else: + consecutive_blanks = 0 + + assert max_consecutive_blanks <= 3, "Should not have more than 3 consecutive blank lines" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) \ No newline at end of file