├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.core.resources.prefs ├── FxEditor Feature Matrix.xlsx ├── LICENSE ├── README.md ├── TODO.txt ├── build.xml ├── doc ├── 2017-0401 selection shapes.jpg └── screenshot.png ├── screenshots ├── 2016-1102-192959-948.png ├── 2016-1126-172643-252.png ├── 2017-0422-143012-998.png ├── 2017-0730-180136-872.png └── 2017-0820-154716-966.png ├── src ├── demo │ └── edit │ │ ├── Conf.java │ │ ├── DemoColorEditorModel.java │ │ ├── DemoGrowingModel.java │ │ ├── DemoSyntax.java │ │ ├── FxEditorApp.java │ │ ├── MainPane.java │ │ ├── MainWindow.java │ │ ├── Segment.java │ │ ├── Styles.java │ │ └── example.txt └── goryachev │ ├── common │ ├── io │ │ ├── BitReader.java │ │ ├── BitStream.java │ │ ├── BitStreamCommon.java │ │ ├── BitStreamReader.java │ │ ├── BitStreamWriter.java │ │ ├── BitWriter.java │ │ ├── BufferedFile.java │ │ ├── CIOTools.java │ │ ├── CReader.java │ │ ├── CSVParser.java │ │ ├── CSVReader.java │ │ ├── CSVWriter.java │ │ ├── CWriter.java │ │ ├── CommaDelimitedParser.java │ │ ├── DByteArrayInputStream.java │ │ ├── DContainer.java │ │ ├── DReader.java │ │ ├── DWriter.java │ │ ├── DWriterBytes.java │ │ ├── IBitStream.java │ │ ├── LimitingInputStream.java │ │ ├── PositionTrackingInputStream.java │ │ ├── RandomAccessByteBuffer.java │ │ ├── RandomAccessFileOutputStreamWrapper.java │ │ ├── StreamingInput.java │ │ ├── StreamingOutput.java │ │ └── WebReader.java │ ├── log │ │ ├── AppenderBase.java │ │ ├── IAppender.java │ │ ├── ILogConfig.java │ │ ├── ILogEventFormatter.java │ │ ├── Log.java │ │ ├── LogLevel.java │ │ ├── LogUtil.java │ │ ├── SimpleLogConfig.java │ │ └── internal │ │ │ ├── ConsoleAppender.java │ │ │ ├── FormatField.java │ │ │ └── LogEventFormatter.java │ ├── test │ │ ├── After.java │ │ ├── AfterClass.java │ │ ├── Before.java │ │ ├── BeforeClass.java │ │ ├── TF.java │ │ ├── Test.java │ │ ├── TestCase.java │ │ ├── TestException.java │ │ └── TestRunner.java │ └── util │ │ ├── ASCII.java │ │ ├── ASettingsStore.java │ │ ├── Activable.java │ │ ├── Assert.java │ │ ├── BKey.java │ │ ├── Base64.java │ │ ├── BasicFileFilter.java │ │ ├── BloomFilterMurmur3.java │ │ ├── BooleanArray.java │ │ ├── ByteArrayClassLoader.java │ │ ├── ByteDataBuffer.java │ │ ├── CCalendar.java │ │ ├── CComparator.java │ │ ├── CDateFormat.java │ │ ├── CDigest.java │ │ ├── CEncoding.java │ │ ├── CField.java │ │ ├── CFileLock.java │ │ ├── CFileLockedException.java │ │ ├── CFileSettings.java │ │ ├── CJob.java │ │ ├── CKit.java │ │ ├── CList.java │ │ ├── CLookup.java │ │ ├── CMap.java │ │ ├── CMethod.java │ │ ├── CMultiMap.java │ │ ├── CMultiSet.java │ │ ├── CPlatform.java │ │ ├── CProperty.java │ │ ├── CSet.java │ │ ├── CSettings.java │ │ ├── CSorter.java │ │ ├── CStringList.java │ │ ├── CSystem.java │ │ ├── CTask.java │ │ ├── CTimeZone.java │ │ ├── CalendarTools.java │ │ ├── CancelledException.java │ │ ├── Choices.java │ │ ├── CircularBuffer.java │ │ ├── Clearable.java │ │ ├── ClipPlayer.java │ │ ├── Committable.java │ │ ├── CompoundFileFilter.java │ │ ├── CompoundKey.java │ │ ├── Copyright.java │ │ ├── D.java │ │ ├── DelayedAction.java │ │ ├── DotSeparatedVersion.java │ │ ├── Dump.java │ │ ├── EQueue.java │ │ ├── ElasticArray.java │ │ ├── ElasticByteArray.java │ │ ├── ElasticIntArray.java │ │ ├── ElasticLongArray.java │ │ ├── Ex.java │ │ ├── FH.java │ │ ├── FileScanner.java │ │ ├── FileSettingsProvider.java │ │ ├── FileTools.java │ │ ├── FixedMemCache.java │ │ ├── GUID.java │ │ ├── GlobalSettings.java │ │ ├── GlobalSettingsProvider.java │ │ ├── HasDisplayName.java │ │ ├── HasDump.java │ │ ├── HasName.java │ │ ├── HasObjectValue.java │ │ ├── HasPrompts.java │ │ ├── HasProperty.java │ │ ├── HasStringValue.java │ │ ├── HasText.java │ │ ├── Hex.java │ │ ├── HiddenData.java │ │ ├── IDisconnectable.java │ │ ├── IFormat.java │ │ ├── IntHashtable.java │ │ ├── JavaVersion.java │ │ ├── JsonDump.java │ │ ├── Keep.java │ │ ├── LongArray.java │ │ ├── LongHashtable.java │ │ ├── Lookup.java │ │ ├── LowMemoryException.java │ │ ├── Mailbox.java │ │ ├── MurmurHash3.java │ │ ├── NamedEntry.java │ │ ├── NamedObjects.java │ │ ├── NaturalSort.java │ │ ├── NetTools.java │ │ ├── Obj.java │ │ ├── ObjectCounter.java │ │ ├── PTable.java │ │ ├── ParallelExecutor.java │ │ ├── Parsers.java │ │ ├── Progress.java │ │ ├── RFileFilter.java │ │ ├── RFilterPattern.java │ │ ├── RandomIndexGenerator.java │ │ ├── Randomizer.java │ │ ├── Reflector.java │ │ ├── SB.java │ │ ├── SKey.java │ │ ├── SStream.java │ │ ├── SW.java │ │ ├── SequenceNumbers.java │ │ ├── SettingsProviderBase.java │ │ ├── StandardLicense.java │ │ ├── SystemTask.java │ │ ├── TextSplitter.java │ │ ├── TextTools.java │ │ ├── TriConsumer.java │ │ ├── UrlStreamFactory.java │ │ ├── UserException.java │ │ ├── ValueGenerator.java │ │ ├── WeakList.java │ │ ├── WeakVector.java │ │ ├── XDataSet.java │ │ ├── Xoroshiro128Plus.java │ │ ├── api │ │ ├── IMessageDigest.java │ │ └── IMessageDigestBlake2b.java │ │ ├── html │ │ ├── AbstractHtmlParser.java │ │ ├── HTML4.java │ │ ├── Html4SymbolEntities.java │ │ └── HtmlTools.java │ │ ├── platform │ │ ├── ApplicationSupport.java │ │ ├── CPlatformLinux.java │ │ ├── CPlatformMac.java │ │ ├── CPlatformUnix.java │ │ ├── CPlatformWindows.java │ │ └── SysInfo.java │ │ └── text │ │ ├── AccentedCharacters.java │ │ ├── CharCounter.java │ │ ├── FindOperation.java │ │ ├── FindOperationResult.java │ │ ├── IBreakIterator.java │ │ ├── QuerySegment.java │ │ ├── SimpleWordCounter.java │ │ ├── ZQuery.java │ │ └── ZQueryParser.java │ ├── fx │ ├── BasicStyledText.java │ ├── CPane.java │ ├── ClosingWindowOperation.java │ ├── CommonStyles.java │ ├── Converters.java │ ├── CssID.java │ ├── CssLoader.java │ ├── CssPseudo.java │ ├── CssStyle.java │ ├── FX.java │ ├── FlatButton.java │ ├── FlatToggleButton.java │ ├── FlowBox.java │ ├── Formatters.java │ ├── FxAction.java │ ├── FxApplication.java │ ├── FxBoolean.java │ ├── FxBooleanBinding.java │ ├── FxButton.java │ ├── FxButtonPane.java │ ├── FxChangeListener.java │ ├── FxCheckBox.java │ ├── FxCheckMenuItem.java │ ├── FxComboBox.java │ ├── FxCtl.java │ ├── FxDateFormatter.java │ ├── FxDecimalFormatter.java │ ├── FxDialog.java │ ├── FxDialogResponse.java │ ├── FxDisconnector.java │ ├── FxDouble.java │ ├── FxDump.java │ ├── FxFileChooser.java │ ├── FxFlags.java │ ├── FxFormatter.java │ ├── FxFramework.java │ ├── FxIconBuilder.java │ ├── FxInt.java │ ├── FxLong.java │ ├── FxMenu.java │ ├── FxMenuBar.java │ ├── FxMenuItem.java │ ├── FxObject.java │ ├── FxPath.java │ ├── FxPopupMenu.java │ ├── FxRadioToggleButton.java │ ├── FxSize.java │ ├── FxSplitMenuButton.java │ ├── FxSplitPane.java │ ├── FxString.java │ ├── FxStyleSheet.java │ ├── FxTabPane.java │ ├── FxTask.java │ ├── FxThread.java │ ├── FxTimer.java │ ├── FxToggleButton.java │ ├── FxToggleGroup.java │ ├── FxToolBar.java │ ├── FxWindow.java │ ├── GlobalBooleanProperty.java │ ├── GlobalDoubleProperty.java │ ├── GlobalIntProperty.java │ ├── GlobalProperties.java │ ├── GlobalProperty.java │ ├── GlobalStringProperty.java │ ├── HPane.java │ ├── HasSettings.java │ ├── HotKey.java │ ├── IStyledText.java │ ├── IconBase.java │ ├── KeyMap.java │ ├── SSConverter.java │ ├── ShutdownChoice.java │ ├── SimpleStyledText.java │ ├── StyledTextFlow.java │ ├── TextCellMetrics.java │ ├── TextCellStyle.java │ ├── Theme.java │ ├── VPane.java │ ├── XScrollBar.java │ ├── icon │ │ ├── ClearIcon.java │ │ ├── CloseIcon.java │ │ ├── EmptyIcon.java │ │ ├── FindIcon.java │ │ ├── GalleryIcon.java │ │ ├── HamburgerIcon.java │ │ ├── ProcessingIcon.java │ │ └── StarIcon.java │ ├── internal │ │ ├── ColorMixer.java │ │ ├── CssHack.java │ │ ├── CssTools.java │ │ ├── DisconnectableIntegerListener.java │ │ ├── FxCssProp.java │ │ ├── FxStyleHandler.java │ │ ├── GlyphCache.java │ │ ├── ParentWindow.java │ │ ├── StandardFxProperties.java │ │ ├── StandardThemes.java │ │ ├── TextStyleFlags.java │ │ └── WeakAnimation.java │ ├── settings │ │ ├── FxSettingsSchema.java │ │ ├── LocalSettings.java │ │ └── WindowMonitor.java │ ├── table │ │ ├── CanvasTextTableCell.java │ │ ├── FxTable.java │ │ ├── FxTableCellRenderer.java │ │ ├── FxTableColumn.java │ │ ├── FxTextTable.java │ │ ├── FxTreeTable.java │ │ ├── FxTreeTableCellFactory.java │ │ ├── FxTreeTableCellValueFactory.java │ │ ├── FxTreeTableColumn.java │ │ └── ICellRenderer.java │ └── util │ │ ├── FxPathBuilder.java │ │ ├── FxTools.java │ │ └── TextPainter.java │ └── fxeditor │ ├── AbstractPlainTextEditorModel.java │ ├── CTextFlow.java │ ├── ClipboardHandlerBase.java │ ├── Edit.java │ ├── EditablePlainTextEditorModel.java │ ├── EditorSelection.java │ ├── FindPane.java │ ├── FxEditor.java │ ├── FxEditorLayout.java │ ├── FxEditorModel.java │ ├── FxEditorModelListener.java │ ├── FxEditorMouseHandler.java │ ├── FxEditorStyles.java │ ├── LineBox.java │ ├── LoadStatus.java │ ├── Marker.java │ ├── PlainTextClipboardHandler.java │ ├── SelectionController.java │ ├── SelectionSegment.java │ ├── SimpleStyledTextModel.java │ ├── SimpleWordSelector.java │ ├── StyledTextPane.java │ ├── StyledTextPaneMouseController.java │ ├── TStyle.java │ ├── TextFlowWithHighlights.java │ ├── VFlow.java │ └── internal │ ├── CaretLocation.java │ ├── EditorTools.java │ ├── Markers.java │ └── SelectionHelper.java └── test └── research ├── fx └── edit │ └── TestTextFlowApp.java ├── md └── Test.md └── unicode └── TestUnicode.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ~* 2 | /build/ 3 | /*.conf 4 | hs_err_pid* 5 | /*.jar 6 | /*.log 7 | /out/ 8 | /user.home*/ 9 | /reference/reference.css 10 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | FxEditor 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /FxEditor Feature Matrix.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/FxEditor Feature Matrix.xlsx -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | FIX 2 | === 3 | - focus border is hidden behind scroll bars 4 | - line number text is slightly off vertically 5 | - adjust offsetx on resize if sufficient space exists 6 | 7 | 8 | 9 | TODO 10 | ==== 11 | - editable 12 | - layout: skip resizing if same width 13 | - click handler 14 | - temporarily set editable to false and do copy() in a background thread (with an error handler) if the model is large 15 | - focused border 16 | - model: highlighters 17 | - model: get side bar highlights 18 | - model: allows background access 19 | - javadoc how model get text and decorated must be consistent 20 | - find feature (log viewer) 21 | - is visible marker 22 | - side bar highlights in model 23 | - popup menu 24 | - get word at position 25 | - get text line at position 26 | - split selection into lines 27 | - split selection into words 28 | - next word next word in camel case 29 | - to uppercase lowercase 30 | - replace 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/2017-0401 selection shapes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/doc/2017-0401 selection shapes.jpg -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/doc/screenshot.png -------------------------------------------------------------------------------- /screenshots/2016-1102-192959-948.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/screenshots/2016-1102-192959-948.png -------------------------------------------------------------------------------- /screenshots/2016-1126-172643-252.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/screenshots/2016-1126-172643-252.png -------------------------------------------------------------------------------- /screenshots/2017-0422-143012-998.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/screenshots/2017-0422-143012-998.png -------------------------------------------------------------------------------- /screenshots/2017-0730-180136-872.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/screenshots/2017-0730-180136-872.png -------------------------------------------------------------------------------- /screenshots/2017-0820-154716-966.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andy-goryachev/FxEditor/7b2b2107e447bb61055f4f3d777b841a37e3be8c/screenshots/2017-0820-154716-966.png -------------------------------------------------------------------------------- /src/demo/edit/Conf.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package demo.edit; 3 | 4 | 5 | /** 6 | * Demo Configuration. 7 | */ 8 | public class Conf 9 | { 10 | public static final int LINE_COUNT = 10_000; // Integer.MAX_VALUE; 11 | } 12 | -------------------------------------------------------------------------------- /src/demo/edit/DemoColorEditorModel.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package demo.edit; 3 | import goryachev.common.util.CKit; 4 | import goryachev.common.util.CList; 5 | import goryachev.fxeditor.AbstractPlainTextEditorModel; 6 | import goryachev.fxeditor.Edit; 7 | import goryachev.fxeditor.FxEditorModel.LoadInfo; 8 | import goryachev.fxeditor.LineBox; 9 | import javafx.scene.text.Text; 10 | 11 | 12 | /** 13 | * test plain text model with up to 2 billion rows 14 | */ 15 | public class DemoColorEditorModel 16 | extends AbstractPlainTextEditorModel 17 | { 18 | private final int lineCount; 19 | private final String[] lines; 20 | 21 | 22 | public DemoColorEditorModel(int lineCount) 23 | { 24 | this.lineCount = lineCount; 25 | this.lines = readFile(); 26 | } 27 | 28 | 29 | private static String[] readFile() 30 | { 31 | String resource = "example.txt"; 32 | try 33 | { 34 | String[] ss = CKit.readLines(DemoColorEditorModel.class, resource); 35 | return ss; 36 | } 37 | catch(Exception e) 38 | { 39 | return new String[] { "error reading resource " + resource }; 40 | } 41 | } 42 | 43 | 44 | public LineBox getLineBox(int line) 45 | { 46 | String text = getPlainText(line); 47 | CList ss = new DemoSyntax(text).generateSegments(); 48 | 49 | LineBox box = new LineBox(); 50 | if(ss.size() == 0) 51 | { 52 | // needs at least one empty Text child to compute height properly 53 | box.addText(new Text("")); 54 | } 55 | else 56 | { 57 | for(Segment s: ss) 58 | { 59 | Text t = new Text(s.text); 60 | t.setFill(s.color); 61 | 62 | box.addText(t); 63 | } 64 | } 65 | return box; 66 | } 67 | 68 | 69 | public String getPlainText(int line) 70 | { 71 | int ix = line % lines.length; 72 | return lines[ix]; 73 | } 74 | 75 | 76 | public LoadInfo getLoadInfo() 77 | { 78 | return null; 79 | } 80 | 81 | 82 | public int getLineCount() 83 | { 84 | return lineCount; 85 | } 86 | 87 | 88 | public Edit edit(Edit ed) throws Exception 89 | { 90 | throw new Exception(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/demo/edit/FxEditorApp.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package demo.edit; 3 | import goryachev.common.log.Log; 4 | import goryachev.common.util.FileSettingsProvider; 5 | import goryachev.common.util.GlobalSettings; 6 | import goryachev.fx.CssLoader; 7 | import java.io.File; 8 | import javafx.application.Application; 9 | import javafx.stage.Stage; 10 | 11 | 12 | /** 13 | * Test FxEditor app. 14 | */ 15 | public class FxEditorApp 16 | extends Application 17 | { 18 | public static void main(String[] args) 19 | { 20 | launch(args); 21 | } 22 | 23 | 24 | public void init() throws Exception 25 | { 26 | // TODO change to something visible in Documents? platform-specific? 27 | File baseDir = new File(System.getProperty("user.home"), ".goryachev.com/FxEditorDemo"); 28 | 29 | // TODO 30 | //File logFolder = new File(baseDir, "logs"); 31 | Log.initConsoleForDebug(); 32 | 33 | File settingsFile = new File(baseDir, "settings.conf"); 34 | FileSettingsProvider p = new FileSettingsProvider(settingsFile); 35 | GlobalSettings.setProvider(p); 36 | p.loadQuiet(); 37 | } 38 | 39 | 40 | public void start(Stage stage) throws Exception 41 | { 42 | new MainWindow().open(); 43 | 44 | // init styles 45 | CssLoader.setStyles(() -> new Styles()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/demo/edit/MainPane.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package demo.edit; 3 | import goryachev.fx.CPane; 4 | import goryachev.fx.CssStyle; 5 | import goryachev.fx.FX; 6 | import goryachev.fxeditor.FindPane; 7 | import goryachev.fxeditor.FxEditor; 8 | import goryachev.fxeditor.FxEditorModel; 9 | import javafx.util.Duration; 10 | 11 | 12 | /** 13 | * Main Pane. 14 | */ 15 | public class MainPane 16 | extends CPane 17 | { 18 | public static final CssStyle PANE = new CssStyle("MainPane_PANE"); 19 | public final FxEditor editor; 20 | 21 | 22 | public MainPane() 23 | { 24 | FX.style(this, PANE); 25 | 26 | editor = new FxEditor(); 27 | editor.setContentPadding(FX.insets(2, 4)); 28 | editor.setBlinkRate(Duration.millis(600)); 29 | editor.setMultipleSelectionEnabled(true); 30 | 31 | setCenter(editor); 32 | 33 | showFindPane(); 34 | } 35 | 36 | 37 | public void setModel(FxEditorModel m) 38 | { 39 | editor.setModel(m); 40 | } 41 | 42 | 43 | public void showFindPane() 44 | { 45 | FindPane p = new FindPane(); 46 | setBottom(p); 47 | 48 | FX.later(() -> p.focusSearch()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/demo/edit/Segment.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package demo.edit; 3 | 4 | import javafx.scene.paint.Color; 5 | 6 | /** 7 | * Segment. 8 | */ 9 | public class Segment 10 | { 11 | public final String text; 12 | public final Color color; 13 | 14 | 15 | public Segment(Color color, String text) 16 | { 17 | this.text = text; 18 | this.color = color; 19 | } 20 | } -------------------------------------------------------------------------------- /src/demo/edit/Styles.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package demo.edit; 3 | import goryachev.fx.CommonStyles; 4 | import goryachev.fx.FxStyleSheet; 5 | import goryachev.fx.Theme; 6 | import goryachev.fxeditor.FxEditor; 7 | 8 | 9 | /** 10 | * this is how a style sheet is generated. 11 | */ 12 | public class Styles 13 | extends FxStyleSheet 14 | { 15 | public Styles() 16 | { 17 | Theme theme = Theme.current(); 18 | 19 | add 20 | ( 21 | // common fx styles 22 | new CommonStyles(), 23 | 24 | selector(MainPane.PANE, FxEditor.PANE).defines 25 | ( 26 | fontSize("120%") 27 | ) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitReader.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.ByteArrayInputStream; 4 | 5 | 6 | public class BitReader 7 | extends BitStreamReader 8 | { 9 | public BitReader(byte[] bytes) 10 | { 11 | super(new ByteArrayInputStream(bytes)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitStream.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.EOFException; 4 | 5 | 6 | public class BitStream 7 | implements IBitStream 8 | { 9 | private byte[] bytes; 10 | private int index; 11 | private static final int[] MASK = 12 | { 13 | 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 14 | }; 15 | 16 | 17 | public BitStream(byte[] bytes) 18 | { 19 | this.bytes = bytes; 20 | } 21 | 22 | 23 | public int getIndex() 24 | { 25 | return index; 26 | } 27 | 28 | 29 | public boolean hasMoreBits() 30 | { 31 | return (index < (bytes.length * 8)); 32 | } 33 | 34 | 35 | public int nextBits(int count) throws Exception 36 | { 37 | int d = 0; 38 | while(count > 0) 39 | { 40 | d = (d << 1) | nextBit(); 41 | --count; 42 | } 43 | return d; 44 | } 45 | 46 | 47 | public int nextBit() throws Exception 48 | { 49 | int byteIndex = index/8; 50 | if(byteIndex >= bytes.length) 51 | { 52 | throw new EOFException(); 53 | } 54 | 55 | int offset = index - (byteIndex * 8); 56 | index++; 57 | if((MASK[offset] & bytes[byteIndex]) == 0) 58 | { 59 | return 0; 60 | } 61 | else 62 | { 63 | return 1; 64 | } 65 | } 66 | 67 | 68 | public void skip(int bits) 69 | { 70 | if(bits < 0) 71 | { 72 | throw new IllegalArgumentException(); 73 | } 74 | 75 | index += bits; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitStreamCommon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | 4 | 5 | public class BitStreamCommon 6 | { 7 | protected static final int BITS_PER_BYTE = 8; 8 | protected static final int MASK[] = 9 | { 10 | 0x00000000, 11 | 0x00000001, 12 | 0x00000003, 13 | 0x00000007, 14 | 0x0000000f, 15 | 0x0000001f, 16 | 0x0000003f, 17 | 0x0000007f, 18 | 0x000000ff, 19 | 0x000001ff, 20 | 0x000003ff, 21 | 0x000007ff, 22 | 0x00000fff, 23 | 0x00001fff, 24 | 0x00003fff, 25 | 0x00007fff, 26 | 0x0000ffff, 27 | 0x0001ffff, 28 | 0x0003ffff, 29 | 0x0007ffff, 30 | 0x000fffff, 31 | 0x001fffff, 32 | 0x003fffff, 33 | 0x007fffff, 34 | 0x00ffffff, 35 | 0x01ffffff, 36 | 0x03ffffff, 37 | 0x07ffffff, 38 | 0x0fffffff, 39 | 0x1fffffff, 40 | 0x3fffffff, 41 | 0x7fffffff, 42 | 0xffffffff 43 | }; 44 | 45 | 46 | public static int getMask(int bits) 47 | { 48 | return MASK[bits]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitStreamReader.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CKit; 4 | import java.io.Closeable; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | 9 | public class BitStreamReader 10 | extends BitStreamCommon 11 | implements Closeable 12 | { 13 | private InputStream inp; 14 | private int count; 15 | private int bits; 16 | 17 | 18 | public BitStreamReader(InputStream inp) 19 | { 20 | this.inp = inp; 21 | } 22 | 23 | 24 | public void close() throws IOException 25 | { 26 | CKit.close(inp); 27 | } 28 | 29 | 30 | public int readBits(int bitCount) throws Exception 31 | { 32 | if((bitCount <= 0) || (bitCount > 31)) 33 | { 34 | throw new IllegalArgumentException("invalid bitCount: " + bitCount); 35 | } 36 | 37 | int rv = 0; 38 | if(inp == null) 39 | { 40 | return -1; 41 | } 42 | 43 | while(bitCount > count) 44 | { 45 | rv |= (bits << (bitCount - count)); 46 | bitCount -= count; 47 | 48 | if((bits = inp.read()) == -1) 49 | { 50 | return -1; 51 | } 52 | 53 | count = BITS_PER_BYTE; 54 | } 55 | 56 | if(bitCount > 0) 57 | { 58 | rv |= (bits >> (count - bitCount)); 59 | bits &= MASK[count - bitCount]; 60 | count -= bitCount; 61 | } 62 | 63 | return rv; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitStreamWriter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CKit; 4 | import java.io.Closeable; 5 | import java.io.Flushable; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | 9 | 10 | public class BitStreamWriter 11 | extends BitStreamCommon 12 | implements Closeable, Flushable 13 | { 14 | protected OutputStream out; 15 | private int bits; 16 | private int count = BITS_PER_BYTE; 17 | 18 | 19 | public BitStreamWriter(OutputStream out) 20 | { 21 | this.out = out; 22 | } 23 | 24 | 25 | protected BitStreamWriter() 26 | { 27 | } 28 | 29 | 30 | protected void setOutputStream(OutputStream out) 31 | { 32 | this.out = out; 33 | } 34 | 35 | 36 | public void write(int bitCount, int value) throws Exception 37 | { 38 | if((bitCount <= 0) || (bitCount > 31)) 39 | { 40 | throw new IllegalArgumentException("invalid bitCount: " + bitCount); 41 | } 42 | 43 | value &= MASK[bitCount]; 44 | 45 | while(bitCount >= count) 46 | { 47 | bits = (bits << count) | (value >> (bitCount - count)); 48 | 49 | out.write(bits); 50 | 51 | value &= MASK[bitCount - count]; 52 | bitCount -= count; 53 | count = BITS_PER_BYTE; 54 | bits = 0; 55 | } 56 | 57 | if(bitCount > 0) 58 | { 59 | bits = ((bits << bitCount) | value); 60 | count -= bitCount; 61 | } 62 | } 63 | 64 | 65 | public void flush() throws IOException 66 | { 67 | if(count != BITS_PER_BYTE) 68 | { 69 | out.write((bits << count)); 70 | bits = 0; 71 | count = BITS_PER_BYTE; 72 | } 73 | 74 | out.flush(); 75 | } 76 | 77 | 78 | public void close() throws IOException 79 | { 80 | try 81 | { 82 | flush(); 83 | } 84 | finally 85 | { 86 | CKit.close(out); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/goryachev/common/io/BitWriter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CKit; 4 | import java.io.ByteArrayOutputStream; 5 | 6 | 7 | public class BitWriter 8 | extends BitStreamWriter 9 | { 10 | private ByteArrayOutputStream stream; 11 | 12 | 13 | public BitWriter() 14 | { 15 | this(32); 16 | } 17 | 18 | 19 | public BitWriter(int sizeInBytes) 20 | { 21 | stream = new ByteArrayOutputStream(sizeInBytes); 22 | setOutputStream(stream); 23 | } 24 | 25 | 26 | /** invokes close() and returns the resulting array */ 27 | public byte[] toByteArray() 28 | { 29 | CKit.close(this); 30 | 31 | return stream.toByteArray(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/goryachev/common/io/CReader.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CKit; 4 | import java.io.BufferedReader; 5 | import java.io.ByteArrayInputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.io.Reader; 11 | import java.io.StringReader; 12 | import java.nio.charset.Charset; 13 | 14 | 15 | public class CReader 16 | extends BufferedReader 17 | { 18 | public CReader(File f, Charset cs) throws Exception 19 | { 20 | this(new FileInputStream(f), cs); 21 | } 22 | 23 | 24 | public CReader(String filename, Charset cs) throws Exception 25 | { 26 | this(new FileInputStream(filename), cs); 27 | } 28 | 29 | 30 | public CReader(InputStream in, Charset cs) throws Exception 31 | { 32 | super(new InputStreamReader(in, (cs == null ? CKit.CHARSET_UTF8 : cs))); 33 | } 34 | 35 | 36 | public CReader(File f) throws Exception 37 | { 38 | this(f, CKit.CHARSET_UTF8); 39 | } 40 | 41 | 42 | public CReader(String text) 43 | { 44 | super(text == null ? new StringReader("") : new StringReader(text)); 45 | } 46 | 47 | 48 | public CReader(InputStream in) throws Exception 49 | { 50 | this(in, CKit.CHARSET_UTF8); 51 | } 52 | 53 | 54 | public CReader(byte[] b) throws Exception 55 | { 56 | this(new ByteArrayInputStream(b)); 57 | } 58 | 59 | 60 | public CReader(Reader rd) 61 | { 62 | super(rd); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/goryachev/common/io/CWriter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CKit; 4 | import goryachev.common.util.FileTools; 5 | import java.io.BufferedWriter; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | import java.io.OutputStreamWriter; 11 | import java.nio.charset.Charset; 12 | 13 | 14 | public class CWriter 15 | extends BufferedWriter 16 | { 17 | public CWriter(File file) throws IOException 18 | { 19 | this(file, CKit.CHARSET_UTF8); 20 | } 21 | 22 | 23 | public CWriter(File file, boolean append) throws IOException 24 | { 25 | this(file, CKit.CHARSET_UTF8, append); 26 | } 27 | 28 | 29 | public CWriter(String filename) throws IOException 30 | { 31 | this(filename, CKit.CHARSET_UTF8); 32 | } 33 | 34 | 35 | public CWriter(File file, Charset cs) throws IOException 36 | { 37 | this(file, cs, false); 38 | } 39 | 40 | 41 | public CWriter(File file, Charset cs, boolean append) throws IOException 42 | { 43 | this(new FileOutputStream(ensureParent(file), append), cs); 44 | } 45 | 46 | 47 | public CWriter(String filename, Charset cs) throws IOException 48 | { 49 | this(new FileOutputStream(ensureParent(new File(filename))), cs); 50 | } 51 | 52 | 53 | public CWriter(OutputStream in, Charset cs) throws IOException 54 | { 55 | super(new OutputStreamWriter(in, cs)); 56 | } 57 | 58 | 59 | public CWriter(OutputStream in) throws IOException 60 | { 61 | super(new OutputStreamWriter(in, CKit.CHARSET_UTF8)); 62 | } 63 | 64 | 65 | public void nl() throws IOException 66 | { 67 | write("\n"); 68 | } 69 | 70 | 71 | private static File ensureParent(File f) 72 | { 73 | FileTools.ensureParentFolder(f); 74 | return f; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/goryachev/common/io/DByteArrayInputStream.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.ByteArrayInputStream; 4 | 5 | 6 | /** 7 | * ByteArrayInputStream with getPosition(); 8 | */ 9 | public class DByteArrayInputStream 10 | extends ByteArrayInputStream 11 | { 12 | public DByteArrayInputStream(byte[] b) 13 | { 14 | super(b); 15 | } 16 | 17 | 18 | public int getPosition() 19 | { 20 | return pos; 21 | } 22 | } -------------------------------------------------------------------------------- /src/goryachev/common/io/DContainer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import goryachev.common.util.CList; 4 | 5 | 6 | public class DContainer 7 | { 8 | public static class ObjectMarker 9 | { 10 | public final String type; 11 | 12 | public ObjectMarker(String type) 13 | { 14 | this.type = type; 15 | } 16 | 17 | public String getType() 18 | { 19 | return type; 20 | } 21 | 22 | public String toString() 23 | { 24 | return "Object:" + getType(); 25 | } 26 | } 27 | 28 | // 29 | 30 | private CList data; 31 | 32 | 33 | public DContainer(DContainer x) 34 | { 35 | data = new CList<>(x.data); 36 | } 37 | 38 | 39 | public DContainer() 40 | { 41 | data = new CList<>(); 42 | } 43 | 44 | 45 | public Object clone() 46 | { 47 | return copyObjectList(); 48 | } 49 | 50 | 51 | public DContainer copyObjectList() 52 | { 53 | return new DContainer(this); 54 | } 55 | 56 | 57 | public Object[] toArray() 58 | { 59 | return data.toArray(); 60 | } 61 | 62 | 63 | public int size() 64 | { 65 | return data.size(); 66 | } 67 | 68 | 69 | public Object get(int ix) 70 | { 71 | return data.get(ix); 72 | } 73 | 74 | 75 | public void add(Object x) 76 | { 77 | data.add(x); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/goryachev/common/io/DWriterBytes.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.ByteArrayOutputStream; 4 | 5 | 6 | /** convenient DWriter backed by a ByteArrayOutputStream */ 7 | public class DWriterBytes 8 | extends DWriter 9 | { 10 | public DWriterBytes() 11 | { 12 | super(new ByteArrayOutputStream()); 13 | } 14 | 15 | 16 | public byte[] toByteArray() 17 | { 18 | return getByteArrayOutputStream().toByteArray(); 19 | } 20 | 21 | 22 | private ByteArrayOutputStream getByteArrayOutputStream() 23 | { 24 | return (ByteArrayOutputStream)out; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/goryachev/common/io/IBitStream.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | 4 | 5 | public interface IBitStream 6 | { 7 | public boolean hasMoreBits(); 8 | 9 | 10 | public int getIndex(); 11 | 12 | 13 | public int nextBits(int count) throws Exception; 14 | 15 | 16 | public int nextBit() throws Exception; 17 | 18 | 19 | public void skip(int bits); 20 | } 21 | -------------------------------------------------------------------------------- /src/goryachev/common/io/LimitingInputStream.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | 7 | /** allows to read a chunk from an input stream */ 8 | public class LimitingInputStream 9 | extends InputStream 10 | { 11 | private final InputStream stream; 12 | private final long length; 13 | private long read; 14 | 15 | 16 | public LimitingInputStream(InputStream stream, long length) 17 | { 18 | this.stream = stream; 19 | this.length = length; 20 | } 21 | 22 | 23 | public int read() throws IOException 24 | { 25 | if(read < length) 26 | { 27 | int c = stream.read(); 28 | read++; 29 | return c; 30 | } 31 | else 32 | { 33 | return -1; 34 | } 35 | } 36 | 37 | 38 | public int read(byte b[], int off, int len) throws IOException 39 | { 40 | int rv; 41 | long toread = length - read; 42 | if(toread >= len) 43 | { 44 | rv = stream.read(b, off, len); 45 | if(rv >= 0) 46 | { 47 | read += rv; 48 | } 49 | } 50 | else 51 | { 52 | if(toread > 0) 53 | { 54 | rv = stream.read(b, off, (int)toread); 55 | if(rv >= 0) 56 | { 57 | read += rv; 58 | } 59 | } 60 | else 61 | { 62 | rv = -1; 63 | } 64 | } 65 | return rv; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/goryachev/common/io/PositionTrackingInputStream.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | 7 | /** an input stream which keeps track of the current position, useful to wrap around a BufferedInputStream */ 8 | public class PositionTrackingInputStream 9 | extends InputStream 10 | { 11 | private final InputStream in; 12 | private long pos; 13 | 14 | 15 | public PositionTrackingInputStream(InputStream in) 16 | { 17 | this.in = in; 18 | } 19 | 20 | 21 | public long getPosition() 22 | { 23 | return pos; 24 | } 25 | 26 | 27 | public int read() throws IOException 28 | { 29 | int r = in.read(); 30 | if(r >= 0) 31 | { 32 | pos++; 33 | } 34 | return r; 35 | } 36 | 37 | 38 | public int read(byte b[]) throws IOException 39 | { 40 | int r = in.read(b); 41 | if(r > 0) 42 | { 43 | pos += r; 44 | } 45 | return r; 46 | } 47 | 48 | 49 | public int read(byte b[], int off, int len) throws IOException 50 | { 51 | int r = in.read(b, off, len); 52 | if(r > 0) 53 | { 54 | pos += r; 55 | } 56 | return r; 57 | } 58 | 59 | 60 | public long skip(long n) throws IOException 61 | { 62 | long r = in.skip(n); 63 | pos += r; 64 | return r; 65 | } 66 | 67 | 68 | public int available() throws IOException 69 | { 70 | return in.available(); 71 | } 72 | 73 | 74 | public void close() throws IOException 75 | { 76 | in.close(); 77 | } 78 | 79 | 80 | public synchronized void mark(int readlimit) 81 | { 82 | in.mark(readlimit); 83 | } 84 | 85 | 86 | public synchronized void reset() throws IOException 87 | { 88 | in.reset(); 89 | } 90 | 91 | 92 | public boolean markSupported() 93 | { 94 | return in.markSupported(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/goryachev/common/io/RandomAccessFileOutputStreamWrapper.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.io.RandomAccessFile; 6 | 7 | 8 | public class RandomAccessFileOutputStreamWrapper 9 | extends OutputStream 10 | { 11 | private final RandomAccessFile f; 12 | 13 | 14 | public RandomAccessFileOutputStreamWrapper(RandomAccessFile f) 15 | { 16 | this.f = f; 17 | } 18 | 19 | 20 | public void write(int b) throws IOException 21 | { 22 | f.write(b); 23 | } 24 | 25 | 26 | public void write(byte b[]) throws IOException 27 | { 28 | f.write(b, 0, b.length); 29 | } 30 | 31 | 32 | public void write(byte b[], int off, int len) throws IOException 33 | { 34 | f.write(b, off, len); 35 | } 36 | 37 | 38 | public void flush() throws IOException 39 | { 40 | } 41 | 42 | 43 | public void close() throws IOException 44 | { 45 | f.close(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/goryachev/common/io/StreamingInput.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | 4 | 5 | public interface StreamingInput 6 | { 7 | public T readFromStream(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/io/StreamingOutput.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.io; 3 | 4 | 5 | public interface StreamingOutput 6 | { 7 | public void writeToStream(T item); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/log/AppenderBase.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.log; 3 | import goryachev.common.log.internal.LogEventFormatter; 4 | import goryachev.common.util.CList; 5 | import java.util.List; 6 | 7 | 8 | /** 9 | * Log Appender base class. 10 | */ 11 | public abstract class AppenderBase 12 | implements IAppender 13 | { 14 | public abstract void emit(String s); 15 | 16 | // 17 | 18 | private int threshold; 19 | private ILogEventFormatter formatter = LogEventFormatter.simpleFormatter(); 20 | private final CList channels = new CList(); 21 | 22 | 23 | public AppenderBase(LogLevel threshold) 24 | { 25 | this.threshold = threshold.ordinal(); 26 | } 27 | 28 | 29 | public AppenderBase() 30 | { 31 | this(LogLevel.ALL); 32 | } 33 | 34 | 35 | public void setFormatter(ILogEventFormatter f) 36 | { 37 | formatter = f; 38 | } 39 | 40 | 41 | public int getThreshold() 42 | { 43 | return threshold; 44 | } 45 | 46 | 47 | public boolean needsCaller() 48 | { 49 | return formatter.needsCaller(); 50 | } 51 | 52 | 53 | public List getChannels() 54 | { 55 | return channels; 56 | } 57 | 58 | 59 | public void append(LogLevel level, long time, StackTraceElement caller, Throwable err, String msg) 60 | { 61 | if(level.ordinal() >= threshold) 62 | { 63 | String s = formatter.format(level, time, caller, err, msg); 64 | emit(s); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/goryachev/common/log/IAppender.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.common.log; 3 | 4 | 5 | /** 6 | * Log Appender Interface. 7 | */ 8 | public interface IAppender 9 | { 10 | public void append(LogLevel level, long time, StackTraceElement caller, Throwable err, String msg); 11 | 12 | 13 | public int getThreshold(); 14 | 15 | 16 | public boolean needsCaller(); 17 | } 18 | -------------------------------------------------------------------------------- /src/goryachev/common/log/ILogConfig.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.common.log; 3 | import java.util.List; 4 | 5 | 6 | /** 7 | * Log Config interface. 8 | */ 9 | public interface ILogConfig 10 | { 11 | public boolean isVerbose(); 12 | 13 | 14 | public LogLevel getLogLevel(String name); 15 | 16 | 17 | public LogLevel getDefaultLogLevel(); 18 | 19 | 20 | public List getAppenders() throws Exception; 21 | } 22 | -------------------------------------------------------------------------------- /src/goryachev/common/log/ILogEventFormatter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.common.log; 3 | 4 | 5 | /** 6 | * Log Event Formatter. 7 | */ 8 | public interface ILogEventFormatter 9 | { 10 | public String format(LogLevel level, long time, StackTraceElement caller, Throwable err, String msg); 11 | 12 | 13 | public boolean needsCaller(); 14 | } 15 | -------------------------------------------------------------------------------- /src/goryachev/common/log/LogLevel.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.log; 3 | import goryachev.common.util.Keep; 4 | 5 | 6 | /** 7 | * Log Level. 8 | * 9 | * Do not re-arrange, order is important. 10 | */ 11 | @Keep 12 | public enum LogLevel 13 | { 14 | ALL, 15 | TRACE, 16 | DEBUG, 17 | INFO, 18 | WARN, 19 | ERROR, 20 | FATAL, 21 | OFF 22 | } 23 | -------------------------------------------------------------------------------- /src/goryachev/common/log/internal/ConsoleAppender.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.common.log.internal; 3 | import goryachev.common.log.AppenderBase; 4 | import goryachev.common.log.LogLevel; 5 | import java.io.PrintStream; 6 | 7 | 8 | /** 9 | * Console Appender. 10 | */ 11 | public class ConsoleAppender 12 | extends AppenderBase 13 | { 14 | private final PrintStream out; 15 | 16 | 17 | public ConsoleAppender(LogLevel threshold, PrintStream out) 18 | { 19 | super(threshold); 20 | this.out = out; 21 | } 22 | 23 | 24 | public ConsoleAppender(PrintStream out) 25 | { 26 | this(LogLevel.ALL, out); 27 | } 28 | 29 | 30 | public void emit(String s) 31 | { 32 | out.println(s); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/goryachev/common/log/internal/LogEventFormatter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.common.log.internal; 3 | import goryachev.common.log.ILogEventFormatter; 4 | import goryachev.common.log.LogLevel; 5 | import goryachev.common.log.LogUtil; 6 | import goryachev.common.util.SB; 7 | 8 | 9 | /** 10 | * Log Event Formatter. 11 | */ 12 | public class LogEventFormatter 13 | implements ILogEventFormatter 14 | { 15 | private final FormatField[] fields; 16 | private final boolean needsCaller; 17 | 18 | 19 | public LogEventFormatter(FormatField[] fields) 20 | { 21 | this.fields = fields; 22 | this.needsCaller = LogUtil.needsCaller(fields); 23 | } 24 | 25 | 26 | public String format(LogLevel level, long time, StackTraceElement caller, Throwable err, String msg) 27 | { 28 | SB sb = new SB(128); 29 | for(int i=0; i 2 | package goryachev.common.test; 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.METHOD) 11 | public @interface After 12 | { 13 | } -------------------------------------------------------------------------------- /src/goryachev/common/test/AfterClass.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.test; 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.METHOD) 11 | public @interface AfterClass 12 | { 13 | } -------------------------------------------------------------------------------- /src/goryachev/common/test/Before.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.test; 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** marks code to be executed Before each @Test */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | public @interface Before 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/goryachev/common/test/BeforeClass.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.test; 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** marks static code to be executed once before each class containing @Test */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | public @interface BeforeClass 13 | { 14 | } -------------------------------------------------------------------------------- /src/goryachev/common/test/Test.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.test; 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD}) 11 | public @interface Test 12 | { 13 | static class NoThrowable 14 | extends Throwable 15 | { 16 | private NoThrowable() 17 | { 18 | } 19 | } 20 | 21 | 22 | // 23 | 24 | 25 | public Class expected() default NoThrowable.class; 26 | } -------------------------------------------------------------------------------- /src/goryachev/common/test/TestException.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2015-2024 Andy Goryachev 2 | package goryachev.common.test; 3 | 4 | 5 | /** 6 | * TestException is an exception with modified stack trace: 7 | * it starts with the method marked with @Test annotation. 8 | * 9 | * This class subclasses RuntimeException so as not to add throws keywords 10 | * to the test methods. 11 | */ 12 | public class TestException 13 | extends RuntimeException 14 | { 15 | public TestException(String message, Throwable cause) 16 | { 17 | super(message, cause); 18 | } 19 | 20 | 21 | public TestException(Throwable cause) 22 | { 23 | super(cause); 24 | } 25 | 26 | 27 | public TestException(String message) 28 | { 29 | super(message); 30 | } 31 | 32 | 33 | public TestException() 34 | { 35 | } 36 | 37 | 38 | public StackTraceElement[] getStackTrace() 39 | { 40 | StackTraceElement[] ss = super.getStackTrace(); 41 | 42 | String testPrefix = TestException.class.getPackage().getName(); 43 | 44 | for(int i=0; i 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * ASCII Codes. 7 | */ 8 | public class ASCII 9 | { 10 | public static final byte NUL = 0x00; 11 | public static final byte SOH = 0x01; 12 | public static final byte STX = 0x02; 13 | public static final byte ETX = 0x03; 14 | public static final byte EOT = 0x04; 15 | public static final byte ENQ = 0x05; 16 | public static final byte ACK = 0x06; 17 | public static final byte BEL = 0x07; 18 | public static final byte BS = 0x08; 19 | public static final byte HT = 0x09; 20 | public static final byte LF = 0x0A; 21 | public static final byte VT = 0x0B; 22 | public static final byte FF = 0x0C; 23 | public static final byte CR = 0x0D; 24 | public static final byte SO = 0x0E; 25 | public static final byte SI = 0x0F; 26 | public static final byte DLE = 0x10; 27 | public static final byte DC1 = 0x11; 28 | public static final byte DC2 = 0x12; 29 | public static final byte DC3 = 0x13; 30 | public static final byte DC4 = 0x14; 31 | public static final byte NAK = 0x15; 32 | public static final byte SYN = 0x16; 33 | public static final byte ETB = 0x17; 34 | public static final byte CAN = 0x18; 35 | public static final byte EM = 0x19; 36 | public static final byte SUB = 0x1A; 37 | public static final byte ESC = 0x1B; 38 | public static final byte FS = 0x1C; 39 | public static final byte GS = 0x1D; 40 | public static final byte RS = 0x1E; 41 | public static final byte US = 0x1F; 42 | public static final byte SPACE = 0x20; 43 | public static final byte DEL = 0x7F; 44 | } 45 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ASettingsStore.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Abstract Settings Store. 7 | */ 8 | public abstract class ASettingsStore 9 | { 10 | public abstract void setStream(String key, SStream stream); 11 | 12 | 13 | public abstract SStream getStream(String key); 14 | 15 | 16 | public abstract void setString(String key, String val); 17 | 18 | 19 | public abstract String getString(String key); 20 | 21 | 22 | /** triggers saving. this may not be necessary if saving is triggered automatically (after a short delay) */ 23 | public abstract void save(); 24 | 25 | 26 | // 27 | 28 | 29 | public ASettingsStore() 30 | { 31 | } 32 | 33 | 34 | public int getInt(String key, int defaultValue) 35 | { 36 | return Parsers.parseInt(getString(key), defaultValue); 37 | } 38 | 39 | 40 | public void setInt(String key, int val) 41 | { 42 | setString(key, String.valueOf(val)); 43 | } 44 | 45 | 46 | public void setBoolean(String key, boolean value) 47 | { 48 | setString(key, String.valueOf(value)); 49 | } 50 | 51 | 52 | public Boolean getBoolean(String key) 53 | { 54 | String v = getString(key); 55 | if(v != null) 56 | { 57 | if("true".equals(v)) 58 | { 59 | return Boolean.TRUE; 60 | } 61 | else if("false".equals(v)) 62 | { 63 | return Boolean.FALSE; 64 | } 65 | } 66 | return null; 67 | } 68 | 69 | 70 | public Boolean getBoolean(String key, boolean defaultValue) 71 | { 72 | Boolean b = getBoolean(key); 73 | if(b == null) 74 | { 75 | return defaultValue; 76 | } 77 | return b.booleanValue(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Activable.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | @Deprecated // bad idea 6 | public interface Activable 7 | { 8 | public void activate(); 9 | 10 | public void deactivate(); 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ByteArrayClassLoader.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * ClassLoader which loads classes from a byte[]. 7 | */ 8 | public class ByteArrayClassLoader 9 | extends ClassLoader 10 | { 11 | public Class load(String name, byte[] b) 12 | { 13 | return defineClass(name, b, 0, b.length); 14 | } 15 | } -------------------------------------------------------------------------------- /src/goryachev/common/util/CDateFormat.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.text.SimpleDateFormat; 4 | import java.time.format.DateTimeFormatter; 5 | import java.time.temporal.TemporalAccessor; 6 | import java.util.Date; 7 | 8 | 9 | /** 10 | * Common Date Format which operates with long, Dates, and new LocalDates types. 11 | */ 12 | public class CDateFormat 13 | implements IFormat 14 | { 15 | private final String pattern; 16 | private SimpleDateFormat simple; 17 | private DateTimeFormatter dt; 18 | 19 | 20 | public CDateFormat(String pattern) 21 | { 22 | this.pattern = pattern; 23 | } 24 | 25 | 26 | public String getPattern() 27 | { 28 | return pattern; 29 | } 30 | 31 | 32 | protected SimpleDateFormat simple() 33 | { 34 | if(simple == null) 35 | { 36 | simple = new SimpleDateFormat(pattern); 37 | } 38 | return simple; 39 | } 40 | 41 | 42 | protected DateTimeFormatter dt() 43 | { 44 | if(dt == null) 45 | { 46 | dt = DateTimeFormatter.ofPattern(pattern); 47 | } 48 | return dt; 49 | } 50 | 51 | 52 | public String format(Object x) 53 | { 54 | if(x != null) 55 | { 56 | if(x instanceof Long) 57 | { 58 | return simple().format(x); 59 | } 60 | else if(x instanceof Date) 61 | { 62 | return simple().format(x); 63 | } 64 | else if(x instanceof TemporalAccessor) 65 | { 66 | return dt().format((TemporalAccessor)x); 67 | } 68 | } 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CField.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.lang.reflect.Field; 4 | 5 | 6 | /** 7 | * A "typesafe" java.lang.reflect.Field wrapper that throws RuntimeExceptions. 8 | */ 9 | public final class CField 10 | { 11 | private final Field field; 12 | 13 | 14 | public CField(Field f) 15 | { 16 | this.field = f; 17 | } 18 | 19 | 20 | public T get(Object obj) 21 | { 22 | if(field != null) 23 | { 24 | try 25 | { 26 | return (T)field.get(obj); 27 | } 28 | catch(Throwable e) 29 | { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | 37 | public void set(Object obj, T value) 38 | { 39 | if(field != null) 40 | { 41 | try 42 | { 43 | field.set(obj, value); 44 | } 45 | catch(Throwable e) 46 | { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CFileLock.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.log.Log; 4 | import java.io.File; 5 | import java.io.RandomAccessFile; 6 | import java.nio.channels.FileChannel; 7 | import java.nio.channels.FileLock; 8 | 9 | 10 | public class CFileLock 11 | { 12 | protected static final Log log = Log.get("CFileLock"); 13 | private File file; 14 | private FileChannel channel; 15 | private FileLock lock; 16 | 17 | 18 | public CFileLock(File file) 19 | { 20 | this.file = file; 21 | } 22 | 23 | 24 | /** returns true if lock has been successfully acquired */ 25 | @SuppressWarnings("resource") 26 | public boolean lock() 27 | { 28 | try 29 | { 30 | if(!file.exists()) 31 | { 32 | file.getParentFile().mkdirs(); 33 | } 34 | channel = new RandomAccessFile(file, "rw").getChannel(); 35 | 36 | lock = channel.tryLock(); 37 | if(lock != null) 38 | { 39 | return true; 40 | } 41 | } 42 | catch(Exception e) 43 | { 44 | log.error(e); 45 | } 46 | 47 | unlock(); 48 | 49 | return false; 50 | } 51 | 52 | 53 | public void unlock() 54 | { 55 | if(lock != null) 56 | { 57 | try 58 | { 59 | lock.release(); 60 | } 61 | catch(Exception e) 62 | { 63 | log.error(e); 64 | } 65 | 66 | lock = null; 67 | } 68 | 69 | if(channel != null) 70 | { 71 | CKit.close(channel); 72 | channel = null; 73 | try 74 | { 75 | file.delete(); 76 | } 77 | catch(Exception e) 78 | { 79 | log.error(e); 80 | } 81 | } 82 | } 83 | 84 | 85 | public void checkLock() throws CFileLockedException 86 | { 87 | if(lock() == false) 88 | { 89 | throw new CFileLockedException(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CFileLockedException.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class CFileLockedException 6 | extends Exception 7 | { 8 | public CFileLockedException() 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CLookup.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class CLookup 6 | { 7 | private CMap values; 8 | 9 | 10 | public CLookup() 11 | { 12 | values = new CMap(); 13 | } 14 | 15 | 16 | public CLookup(Object ... pairs) 17 | { 18 | int sz = pairs.length; 19 | int cap = sz * 2; 20 | values = new CMap(cap); 21 | if(cap/2 != sz) 22 | { 23 | throw new RuntimeException("number of objects must be even: " + sz); 24 | } 25 | 26 | for(int i=0; i 2 | package goryachev.common.util; 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | 8 | public class CMap 9 | extends HashMap 10 | { 11 | public CMap() 12 | { 13 | } 14 | 15 | 16 | public CMap(int capacity) 17 | { 18 | super(capacity); 19 | } 20 | 21 | 22 | public CMap(Map m) 23 | { 24 | super(m); 25 | } 26 | 27 | 28 | public CList keys() 29 | { 30 | return new CList<>(keySet()); 31 | } 32 | 33 | 34 | /** removes values identified by the supplied keys. passing null has no effect */ 35 | public void removeAll(Collection keys) 36 | { 37 | if(keys != null) 38 | { 39 | for(K k: keys) 40 | { 41 | remove(k); 42 | } 43 | } 44 | } 45 | 46 | 47 | public V set(K k, V v) 48 | { 49 | if(v == null) 50 | { 51 | return remove(k); 52 | } 53 | else 54 | { 55 | return put(k, v); 56 | } 57 | } 58 | 59 | 60 | public Object clone() 61 | { 62 | return copyCMap(); 63 | } 64 | 65 | 66 | public CMap copyCMap() 67 | { 68 | return new CMap<>(this); 69 | } 70 | 71 | 72 | public void loadArray(Object ... xx) 73 | { 74 | if(xx != null) 75 | { 76 | int sz = xx.length; 77 | if((sz < 0) || CKit.isOdd(sz)) 78 | { 79 | throw new IllegalArgumentException("array must contain even number of elements"); 80 | } 81 | 82 | for(int i=0; i 2 | package goryachev.common.util; 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | 6 | 7 | /** 8 | * A "typesafe" Method (reflection) equivalent that throws RuntimeExceptions. 9 | */ 10 | public class CMethod 11 | { 12 | private final Method method; 13 | 14 | 15 | public CMethod(Method m) 16 | { 17 | this.method = m; 18 | } 19 | 20 | 21 | public T invoke(Object obj, Object ... args) 22 | { 23 | try 24 | { 25 | return (T)method.invoke(obj, args); 26 | } 27 | catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 28 | { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | 34 | public T invokeStatic(Object ... args) 35 | { 36 | try 37 | { 38 | return (T)method.invoke(null, args); 39 | } 40 | catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 41 | { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CSet.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2015-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | 6 | 7 | /** HashSet with additional methods */ 8 | public class CSet 9 | extends HashSet 10 | { 11 | private static final int DEFAULT_SIZE = 64; 12 | 13 | 14 | public CSet(int size) 15 | { 16 | super(size); 17 | } 18 | 19 | 20 | public CSet() 21 | { 22 | super(DEFAULT_SIZE); 23 | } 24 | 25 | 26 | public CSet(Collection items) 27 | { 28 | super(items); 29 | } 30 | 31 | 32 | public CSet(T[] items) 33 | { 34 | addAll(items); 35 | } 36 | 37 | 38 | public void addAll(T[] items) 39 | { 40 | if(items != null) 41 | { 42 | int sz = items.length; 43 | for(int i=0; i items) 65 | { 66 | if(items != null) 67 | { 68 | return super.addAll(items); 69 | } 70 | return false; 71 | } 72 | 73 | 74 | public CList asList() 75 | { 76 | return new CList<>(this); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CStringList.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | 6 | 7 | public class CStringList 8 | extends CList 9 | { 10 | public CStringList(int initialCapacity) 11 | { 12 | super(initialCapacity); 13 | } 14 | 15 | 16 | public CStringList() 17 | { 18 | super(); 19 | } 20 | 21 | 22 | public CStringList(Collection c) 23 | { 24 | super(c); 25 | } 26 | 27 | 28 | public CStringList(String[] a) 29 | { 30 | super(Arrays.asList(a)); 31 | } 32 | 33 | 34 | public String[] toStringArray() 35 | { 36 | return toArray(new String[size()]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CSystem.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2009-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.Properties; 6 | import java.util.Set; 7 | 8 | 9 | public class CSystem 10 | { 11 | public static String getUserHome() 12 | { 13 | return System.getProperty("user.home"); 14 | } 15 | 16 | 17 | public static String getUserName() 18 | { 19 | return System.getProperty("user.name"); 20 | } 21 | 22 | 23 | public static String getJavaVersion() 24 | { 25 | return System.getProperty("java.version"); 26 | } 27 | 28 | 29 | public static String listEnvironment() 30 | { 31 | return listEnvironment(0); 32 | } 33 | 34 | 35 | public static String listEnvironment(int indent) 36 | { 37 | StringBuilder sb = new StringBuilder(); 38 | Map env = System.getenv(); 39 | String[] keys = env.keySet().toArray(new String[env.size()]); 40 | Arrays.sort(keys); 41 | 42 | for(String key: keys) 43 | { 44 | for(int i=0; i pnames = p.stringPropertyNames(); 66 | String[] keys = pnames.toArray(new String[pnames.size()]); 67 | Arrays.sort(keys); 68 | 69 | for(String key: keys) 70 | { 71 | for(int i=0; i 2 | package goryachev.common.util; 3 | 4 | 5 | public class CancelledException 6 | extends RuntimeException 7 | { 8 | public CancelledException() 9 | { 10 | } 11 | 12 | 13 | public CancelledException(Throwable cause) 14 | { 15 | super(cause); 16 | } 17 | 18 | 19 | public CancelledException(String message) 20 | { 21 | super(message); 22 | } 23 | 24 | 25 | public CancelledException(String message, Throwable cause) 26 | { 27 | super(message, cause); 28 | } 29 | 30 | 31 | public static boolean isNot(Throwable e) 32 | { 33 | return !is(e); 34 | } 35 | 36 | 37 | public static boolean is(Throwable e) 38 | { 39 | return (e instanceof CancelledException); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Choices.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.List; 4 | 5 | 6 | /** 7 | * A bidirectional Choice - Text lookup facility. 8 | */ 9 | public class Choices 10 | { 11 | protected static class Choice 12 | { 13 | protected C choice; 14 | protected String text; 15 | 16 | public String toString() { return text; } 17 | } 18 | 19 | private final CList> choices; 20 | 21 | 22 | public Choices(Object ... choiceTextPairs) 23 | { 24 | choices = new CList>(choiceTextPairs.length / 2); 25 | for(int i=0; i ch = new Choice(); 28 | ch.choice = (T)choiceTextPairs[i++]; 29 | ch.text = (String)choiceTextPairs[i++]; 30 | choices.add(ch); 31 | } 32 | } 33 | 34 | 35 | public List> asList() 36 | { 37 | return new CList<>(choices); 38 | } 39 | 40 | 41 | public String lookupText(T choice) 42 | { 43 | for(Choice c: choices) 44 | { 45 | if(CKit.equals(c.choice, choice)) 46 | { 47 | return c.text; 48 | } 49 | } 50 | return null; 51 | } 52 | 53 | 54 | public String lookupText(T choice, String defaultValue) 55 | { 56 | String s = lookupText(choice); 57 | return s == null ? defaultValue : s; 58 | } 59 | 60 | 61 | public T lookupChoice(String text) 62 | { 63 | for(Choice c: choices) 64 | { 65 | if(CKit.equals(c.text, text)) 66 | { 67 | return c.choice; 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | 74 | public T lookupChoice(String text, T defaultValue) 75 | { 76 | T ch = lookupChoice(text); 77 | return ch == null ? defaultValue : ch; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Clearable.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2015-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface Clearable 6 | { 7 | public void clear(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ClipPlayer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.log.Log; 4 | import java.io.InputStream; 5 | import javax.sound.sampled.AudioFormat; 6 | import javax.sound.sampled.AudioInputStream; 7 | import javax.sound.sampled.AudioSystem; 8 | import javax.sound.sampled.Clip; 9 | import javax.sound.sampled.DataLine; 10 | import javax.sound.sampled.LineEvent; 11 | import javax.sound.sampled.LineListener; 12 | 13 | 14 | public class ClipPlayer 15 | { 16 | protected static final Log log = Log.get("ClipPlayer"); 17 | 18 | 19 | public static void play(InputStream in) 20 | { 21 | try 22 | { 23 | AudioInputStream stream = AudioSystem.getAudioInputStream(in); 24 | AudioFormat format = stream.getFormat(); 25 | DataLine.Info info = new DataLine.Info(Clip.class, format); 26 | 27 | final Clip clip = (Clip)AudioSystem.getLine(info); 28 | clip.addLineListener(new LineListener() 29 | { 30 | public void update(LineEvent ev) 31 | { 32 | LineEvent.Type t = ev.getType(); 33 | if(t == LineEvent.Type.STOP) 34 | { 35 | clip.close(); 36 | } 37 | } 38 | }); 39 | clip.open(stream); 40 | clip.start(); 41 | } 42 | catch(Exception e) 43 | { 44 | log.error(e); 45 | } 46 | } 47 | 48 | 49 | public static void playLocal(Object parent, String name) 50 | { 51 | InputStream in = parent.getClass().getResourceAsStream(name); 52 | play(in); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Committable.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface Committable 6 | { 7 | /** commit changes or throws an exception if anything fails */ 8 | public void commit() throws Exception; 9 | } 10 | -------------------------------------------------------------------------------- /src/goryachev/common/util/CompoundKey.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class CompoundKey 6 | { 7 | private final Object[] keys; 8 | private int hash; 9 | 10 | 11 | public CompoundKey(Object a, Object b) 12 | { 13 | this(new Object[] { a, b }); 14 | } 15 | 16 | 17 | public CompoundKey(Object a, Object b, Object c) 18 | { 19 | this(new Object[] { a, b, c }); 20 | } 21 | 22 | 23 | public CompoundKey(Object a, Object b, Object c, Object d) 24 | { 25 | this(new Object[] { a, b, c, d }); 26 | } 27 | 28 | 29 | public CompoundKey(Object a, Object b, Object c, Object d, Object e) 30 | { 31 | this(new Object[] { a, b, c, d, e }); 32 | } 33 | 34 | 35 | public CompoundKey(Object[] keys) 36 | { 37 | this.keys = keys; 38 | } 39 | 40 | 41 | public boolean equals(Object x) 42 | { 43 | if(x == this) 44 | { 45 | return true; 46 | } 47 | else if(x instanceof CompoundKey) 48 | { 49 | return CKit.equals(keys, ((CompoundKey)x).keys); 50 | } 51 | else 52 | { 53 | return false; 54 | } 55 | } 56 | 57 | 58 | public int hashCode() 59 | { 60 | if(hash == 0) 61 | { 62 | hash = FH.hash(keys); 63 | } 64 | return hash; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Copyright.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Copyright. 7 | */ 8 | public class Copyright 9 | { 10 | public static final String COPYRIGHT = "copyright © 2023 andy goryachev"; 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/DotSeparatedVersion.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** Logic for dot-separated version numbers */ 6 | public class DotSeparatedVersion 7 | { 8 | /** parses dot-separated version number into an array of integers, ignoring trailing non-numbers */ 9 | public static int[] parse(String text) 10 | { 11 | String[] ss = CKit.split(text, '.'); 12 | int sz = ss.length; 13 | int[] rv = new int[sz]; 14 | 15 | for(int i=0; i 0) 31 | { 32 | int d; 33 | for(int i=0; i 0) 41 | { 42 | return 1; 43 | } 44 | } 45 | 46 | d = ai.length - bi.length; 47 | return d; 48 | } 49 | 50 | throw new Exception("unable to compare " + a + " and " + b); 51 | } 52 | 53 | 54 | private static int parseNumber(String s) 55 | { 56 | // trim trailing non-numbers 57 | for(int i=0; i 2 | package goryachev.common.util; 3 | 4 | 5 | // A simple queue 6 | public class EQueue 7 | { 8 | protected CList queue; 9 | 10 | 11 | public EQueue(int size) 12 | { 13 | queue = new CList(size); 14 | } 15 | 16 | 17 | public EQueue() 18 | { 19 | this(16); 20 | } 21 | 22 | 23 | public synchronized void put(T d) 24 | { 25 | queue.add(d); 26 | notifyAll(); 27 | } 28 | 29 | 30 | public synchronized void putToFront(T d) 31 | { 32 | queue.add(0, d); 33 | notifyAll(); 34 | } 35 | 36 | 37 | public synchronized T get() 38 | { 39 | while(queue.isEmpty()) 40 | { 41 | try 42 | { 43 | wait(); 44 | } 45 | catch(InterruptedException e) 46 | { } 47 | } 48 | return queue.remove(0); 49 | } 50 | 51 | 52 | public synchronized boolean isEmpty() 53 | { 54 | return queue.isEmpty(); 55 | } 56 | 57 | 58 | public synchronized void delete(T d) 59 | { 60 | queue.remove(d); 61 | } 62 | 63 | 64 | public synchronized void clear() 65 | { 66 | queue.clear(); 67 | notify(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ElasticArray.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class ElasticArray 6 | { 7 | private CList list = new CList(); 8 | private int size; 9 | 10 | 11 | public ElasticArray() 12 | { 13 | } 14 | 15 | 16 | public void add(T item) 17 | { 18 | add(size, item); 19 | } 20 | 21 | 22 | public void add(int index, T item) 23 | { 24 | while(list.size() <= index) 25 | { 26 | list.add(null); 27 | } 28 | 29 | list.add(index, item); 30 | size++; 31 | } 32 | 33 | 34 | public void set(int index, T item) 35 | { 36 | while(list.size() <= index) 37 | { 38 | list.add(null); 39 | } 40 | 41 | list.set(index, item); 42 | if(size <= index) 43 | { 44 | size = index + 1; 45 | } 46 | } 47 | 48 | 49 | public T get(int index) 50 | { 51 | if(index >= 0) 52 | { 53 | if(index < list.size()) 54 | { 55 | return list.get(index); 56 | } 57 | } 58 | return null; 59 | } 60 | 61 | 62 | public int size() 63 | { 64 | return size; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Ex.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * An Exception with an Object identifier and an optional value, 7 | * useful for fast exception handling where "exception type" is determined by a fast comparison 8 | *
(e.getProblem() == CONSTANT)
9 | * and additional parameter can be obtained by 10 | *
Ex.getParameter(e)
11 | */ 12 | public class Ex 13 | extends Exception 14 | { 15 | private final Object id; 16 | private Object parameter; 17 | 18 | 19 | public Ex(Object id) 20 | { 21 | this.id = id; 22 | } 23 | 24 | 25 | public Ex(Object id, Throwable cause) 26 | { 27 | super(cause); 28 | this.id = id; 29 | } 30 | 31 | 32 | public Ex(Object id, Object parameter) 33 | { 34 | this.id = id; 35 | this.parameter = parameter; 36 | } 37 | 38 | 39 | public Ex(Object id, Object value, Throwable cause) 40 | { 41 | super(cause); 42 | this.id = id; 43 | this.parameter = value; 44 | } 45 | 46 | 47 | public String getMessage() 48 | { 49 | SB sb = new SB(); 50 | sb.a(id); 51 | if(parameter != null) 52 | { 53 | sb.sp(); 54 | sb.a(parameter); 55 | } 56 | if(getCause() != null) 57 | { 58 | sb.sp(); 59 | sb.a(getCause()); 60 | } 61 | return sb.toString(); 62 | } 63 | 64 | 65 | public Object getID() 66 | { 67 | return id; 68 | } 69 | 70 | 71 | public static Object getID(Throwable e) 72 | { 73 | if(e instanceof Ex) 74 | { 75 | return ((Ex)e).getID(); 76 | } 77 | return null; 78 | } 79 | 80 | 81 | public Object getParameter() 82 | { 83 | return parameter; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/goryachev/common/util/FileSettingsProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.log.Log; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | 7 | 8 | /** 9 | * File-based Settings Provider. 10 | */ 11 | public class FileSettingsProvider 12 | extends SettingsProviderBase 13 | { 14 | protected static final Log log = Log.get("FileSettingsProvider"); 15 | private File file; 16 | 17 | 18 | public FileSettingsProvider(File f) 19 | { 20 | setFile(f); 21 | } 22 | 23 | 24 | public void setFile(File f) 25 | { 26 | file = f; 27 | } 28 | 29 | 30 | protected void saveSettings() 31 | { 32 | try 33 | { 34 | String s = asString(); 35 | CKit.write(file, s); 36 | } 37 | catch(Exception e) 38 | { 39 | log.error(e); 40 | } 41 | } 42 | 43 | 44 | public void load() throws Exception 45 | { 46 | try 47 | { 48 | String s = CKit.readString(file); 49 | loadFromString(s); 50 | } 51 | catch(FileNotFoundException ignore) 52 | { 53 | } 54 | } 55 | 56 | 57 | public void loadQuiet() 58 | { 59 | try 60 | { 61 | load(); 62 | } 63 | catch(Exception e) 64 | { 65 | log.error(e); 66 | } 67 | } 68 | 69 | 70 | public void load(File f) throws Exception 71 | { 72 | setFile(f); 73 | load(); 74 | } 75 | 76 | 77 | public void loadQuiet(File f) 78 | { 79 | try 80 | { 81 | load(f); 82 | } 83 | catch(Exception e) 84 | { 85 | log.error(e); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/goryachev/common/util/FixedMemCache.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Random; 4 | 5 | 6 | /** simple object cache stores up to a maxItems elements */ 7 | public class FixedMemCache 8 | { 9 | private int maxItems; 10 | private CMap cache = new CMap(); 11 | private CList keys = new CList(); 12 | private Random random = new Random(); 13 | 14 | 15 | public FixedMemCache(int maxItems) 16 | { 17 | this.maxItems = maxItems; 18 | } 19 | 20 | 21 | protected void removeRandomObject() 22 | { 23 | int sz = keys.size(); 24 | if(sz > 0) 25 | { 26 | int ix = random.nextInt(sz); 27 | 28 | K key = keys.get(ix); 29 | keys.set(ix, keys.remove(sz-1)); 30 | 31 | cache.remove(key); 32 | } 33 | } 34 | 35 | 36 | public synchronized V get(K key) 37 | { 38 | return cache.get(key); 39 | } 40 | 41 | 42 | public synchronized void put(K key, V value) 43 | { 44 | while(keys.size() >= maxItems) 45 | { 46 | removeRandomObject(); 47 | } 48 | 49 | cache.put(key, value); 50 | keys.add(key); 51 | } 52 | 53 | 54 | public synchronized void clear() 55 | { 56 | cache.clear(); 57 | keys.clear(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/goryachev/common/util/GlobalSettingsProvider.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.List; 4 | 5 | 6 | /** 7 | * Global Settings Provider. 8 | */ 9 | public interface GlobalSettingsProvider 10 | { 11 | public String getString(String key); 12 | 13 | public void setString(String key, String val); 14 | 15 | public SStream getStream(String key); 16 | 17 | public void setStream(String key, SStream s); 18 | 19 | public List getKeys(); 20 | 21 | public void save(); 22 | } -------------------------------------------------------------------------------- /src/goryachev/common/util/HasDisplayName.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2010-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasDisplayName 6 | { 7 | public String getDisplayName(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasDump.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasDump 6 | { 7 | public String getDump(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasName.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasName 6 | { 7 | public String getName(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasObjectValue.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasObjectValue 6 | { 7 | public Object getObjectValue(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasPrompts.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasPrompts 6 | { 7 | public void updatePrompts(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2008-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasProperty 6 | { 7 | public String getProperty(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasStringValue.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public interface HasStringValue 6 | { 7 | public String getStringValue(); 8 | } 9 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HasText.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Has Text. 7 | */ 8 | public interface HasText 9 | { 10 | public String getText(); 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/HiddenData.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.io.DReader; 4 | import goryachev.common.io.DWriterBytes; 5 | import java.util.Random; 6 | 7 | 8 | /** Simple silly data encoder. Is it portable though (may have a different Random instance)? */ 9 | @SuppressWarnings("resource") 10 | public class HiddenData 11 | { 12 | public static byte[] encode(byte[] b) throws Exception 13 | { 14 | DReader in = new DReader(b); 15 | Random r = new Random(); 16 | long seed = r.nextLong(); 17 | r.setSeed(seed); 18 | 19 | DWriterBytes wr = new DWriterBytes(); 20 | wr.writeLong(seed); 21 | 22 | int c; 23 | while((c = in.readByteRaw()) >= 0) 24 | { 25 | wr.writeByte(c ^ r.nextInt()); 26 | } 27 | 28 | return wr.toByteArray(); 29 | } 30 | 31 | 32 | public static byte[] decode(byte[] b) throws Exception 33 | { 34 | DReader in = new DReader(b); 35 | long seed = in.readLong(); 36 | Random r = new Random(); 37 | r.setSeed(seed); 38 | 39 | DWriterBytes wr = new DWriterBytes(); 40 | int c; 41 | while((c = in.readByteRaw()) >= 0) 42 | { 43 | wr.writeByte(c ^ r.nextInt()); 44 | } 45 | 46 | return wr.toByteArray(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/goryachev/common/util/IDisconnectable.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Disconnectable. 7 | */ 8 | public interface IDisconnectable 9 | { 10 | public void disconnect(); 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/IFormat.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Format(ter) interface. 7 | */ 8 | public interface IFormat 9 | { 10 | public String format(Object x); 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Keep.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import static java.lang.annotation.ElementType.*; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | 10 | /** 11 | * Prevents obfuscation of an annotated field, method, or an entire class. 12 | */ 13 | @Documented 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(value = { CONSTRUCTOR, FIELD, METHOD, TYPE }) 16 | public @interface Keep 17 | { 18 | } -------------------------------------------------------------------------------- /src/goryachev/common/util/Lookup.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Service Registry. 7 | */ 8 | public class Lookup 9 | { 10 | @FunctionalInterface 11 | public interface Provider 12 | { 13 | public T createService(); 14 | } 15 | 16 | // 17 | 18 | private static final CMap providers = new CMap(); 19 | 20 | 21 | /** looks up a service */ 22 | public static T get(Class type) 23 | { 24 | Provider p = getProvider(type); 25 | if(p == null) 26 | { 27 | throw new Error("Must register a provider before first use: Lookup.register(" + type + ", provider);"); 28 | } 29 | 30 | return p.createService(); 31 | } 32 | 33 | 34 | private synchronized static Provider getProvider(Class type) 35 | { 36 | return providers.get(type); 37 | } 38 | 39 | 40 | /** register a service provider */ 41 | public synchronized static void register(Class type, Provider provider) 42 | { 43 | if(providers.containsKey(type)) 44 | { 45 | throw new Error("Provider already registered: " + type); 46 | } 47 | 48 | providers.put(type, provider); 49 | } 50 | 51 | 52 | /** register a service provider, unless another provider is already registered, in which case do nothing */ 53 | public synchronized static void registerIfEmpty(Class type, Provider provider) 54 | { 55 | if(!providers.containsKey(type)) 56 | { 57 | providers.put(type, provider); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/goryachev/common/util/LowMemoryException.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * RuntimeException thrown when CKit.isLowMemory() detects a low memory condition. 7 | */ 8 | public class LowMemoryException 9 | extends RuntimeException 10 | { 11 | public LowMemoryException() 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Mailbox.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | 5 | 6 | /** 7 | * Simple Synchronization Primitive. 8 | */ 9 | public class Mailbox 10 | { 11 | private final ArrayBlockingQueue queue; 12 | 13 | 14 | public Mailbox() 15 | { 16 | queue = new ArrayBlockingQueue(1); 17 | } 18 | 19 | 20 | public void put(T item) 21 | { 22 | queue.offer(item); 23 | } 24 | 25 | 26 | public T get() 27 | { 28 | try 29 | { 30 | return queue.take(); 31 | } 32 | catch(Exception e) 33 | { 34 | return null; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/goryachev/common/util/NamedEntry.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class NamedEntry 6 | implements HasName, HasStringValue 7 | { 8 | private String name; 9 | private String value; 10 | 11 | 12 | public NamedEntry(String name, String value) 13 | { 14 | this.name = name; 15 | this.value = value; 16 | } 17 | 18 | 19 | public String getName() 20 | { 21 | return name; 22 | } 23 | 24 | 25 | public String getStringValue() 26 | { 27 | return value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/goryachev/common/util/NamedObjects.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class NamedObjects 6 | extends CMap 7 | { 8 | public NamedObjects(int size) 9 | { 10 | super(size); 11 | } 12 | 13 | 14 | public NamedObjects(NamedObjects c) 15 | { 16 | super(c); 17 | } 18 | 19 | 20 | public NamedObjects() 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Obj.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class Obj 6 | { 7 | private final String name; 8 | 9 | 10 | public Obj(String name) 11 | { 12 | this.name = name; 13 | } 14 | 15 | 16 | public String toString() 17 | { 18 | return name; 19 | } 20 | 21 | 22 | public String getName() 23 | { 24 | return name; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ParallelExecutor.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.log.Log; 4 | import java.util.concurrent.SynchronousQueue; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | 11 | public class ParallelExecutor 12 | implements ThreadFactory 13 | { 14 | protected static final Log log = Log.get("ParallelExecutor"); 15 | private String name; 16 | private AtomicInteger number = new AtomicInteger(); 17 | private ThreadPoolExecutor exec; 18 | private boolean closed; 19 | 20 | 21 | public ParallelExecutor(String name) 22 | { 23 | this(name, 60); 24 | } 25 | 26 | 27 | public ParallelExecutor(String name, int keepAliveTimeSeconds) 28 | { 29 | this.name = name; 30 | 31 | exec = new ThreadPoolExecutor(0, Integer.MAX_VALUE, keepAliveTimeSeconds, TimeUnit.SECONDS, new SynchronousQueue(), this); 32 | exec.allowCoreThreadTimeOut(true); 33 | } 34 | 35 | 36 | public Thread newThread(Runnable r) 37 | { 38 | Thread t = new Thread(r, name + "." + number.getAndIncrement()); 39 | t.setDaemon(true); 40 | return t; 41 | } 42 | 43 | 44 | public void setKeepAliveTime(long time, TimeUnit unit) 45 | { 46 | exec.setKeepAliveTime(time, unit); 47 | } 48 | 49 | 50 | public synchronized void shutdown() 51 | { 52 | if(!closed) 53 | { 54 | exec.shutdown(); 55 | 56 | try 57 | { 58 | // why wait? 59 | exec.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); 60 | } 61 | catch(Exception e) 62 | { 63 | log.error(e); 64 | } 65 | closed = true; 66 | } 67 | } 68 | 69 | 70 | protected synchronized boolean isClosed() 71 | { 72 | // why is this needed? 73 | return closed; 74 | } 75 | 76 | 77 | public void submit(Runnable r) 78 | { 79 | exec.execute(r); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Progress.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class Progress 6 | { 7 | public static final Progress UNKNOWN = new Progress(0.0); 8 | public static final Progress DONE = new Progress(1.0); 9 | 10 | // 11 | 12 | private double progress; 13 | 14 | 15 | public Progress(double progress) 16 | { 17 | this.progress = progress; 18 | } 19 | 20 | 21 | public Progress(int count, int total) 22 | { 23 | this.progress = (total == 0 ? 0.0 : count/(double)total); 24 | } 25 | 26 | 27 | public Progress(long count, long total) 28 | { 29 | this.progress = (total == 0 ? 0.0 : count/(double)total); 30 | } 31 | 32 | 33 | public double getProgress() 34 | { 35 | return progress; 36 | } 37 | 38 | 39 | public boolean equals(Object x) 40 | { 41 | if(x == this) 42 | { 43 | return true; 44 | } 45 | else if(x instanceof Progress) 46 | { 47 | return progress == ((Progress)x).progress; 48 | } 49 | else 50 | { 51 | return false; 52 | } 53 | } 54 | 55 | 56 | public int hashCode() 57 | { 58 | int h = FH.hash(0, Progress.class); 59 | return FH.hash(h, progress); 60 | } 61 | 62 | 63 | public String getPercentString() 64 | { 65 | return getPercentString(2); 66 | } 67 | 68 | 69 | public String getPercentString(int significantDigits) 70 | { 71 | return CKit.getPercentString(progress, significantDigits); 72 | } 73 | 74 | 75 | public String toString() 76 | { 77 | return "Progress:" + progress; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/goryachev/common/util/RandomIndexGenerator.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Random; 4 | 5 | 6 | /** class that generates a random stream of unique indexes in the range 0...max-1 */ 7 | public class RandomIndexGenerator 8 | { 9 | public final Random random = new Random(); 10 | private Integer[] indexes; 11 | private int size; 12 | 13 | 14 | public RandomIndexGenerator(int max) 15 | { 16 | indexes = new Integer[max]; 17 | size = max; 18 | } 19 | 20 | 21 | protected int get(int ix) 22 | { 23 | Integer r = indexes[ix]; 24 | if(r == null) 25 | { 26 | return ix; 27 | } 28 | else 29 | { 30 | return r; 31 | } 32 | } 33 | 34 | 35 | /** returns randomly chosen index or -1 if no more indexes are available */ 36 | public int next() 37 | { 38 | if(size > 0) 39 | { 40 | int ix = random.nextInt(size); 41 | int r = get(ix); 42 | 43 | --size; 44 | indexes[ix] = get(size); 45 | return r; 46 | } 47 | return -1; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Randomizer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.Random; 4 | 5 | 6 | /** 7 | * Various random number utilities. 8 | */ 9 | public class Randomizer 10 | { 11 | private static final Random random = new Random(); 12 | 13 | 14 | protected static synchronized int nextInt(int n) 15 | { 16 | return random.nextInt(n); 17 | } 18 | 19 | 20 | /** returns a random number in the range [center-variance ... center+variance[ */ 21 | public static int getInt(int center, int variance) 22 | { 23 | return center - variance + nextInt(variance + variance); 24 | } 25 | 26 | 27 | /** returns a random number in the range [center*(1/2) ... center*(3/4)[ */ 28 | public static int getInt(int center) 29 | { 30 | return getInt(center, center / 2); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/goryachev/common/util/Reflector.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2015-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import goryachev.common.log.Log; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | 8 | /** Reflection helper */ 9 | public class Reflector 10 | { 11 | protected static final Log log = Log.get("Reflector"); 12 | 13 | 14 | /** returns an accessible method, wrapped in an exception-eating wrapper */ 15 | public static CMethod method(Class c, String name, Class ... args) 16 | { 17 | try 18 | { 19 | Method m = c.getDeclaredMethod(name, args); 20 | m.setAccessible(true); 21 | return new CMethod(m); 22 | } 23 | catch(Exception e) 24 | { 25 | throw new Error(e); 26 | } 27 | } 28 | 29 | 30 | /** returns the speicifed field, made accessible */ 31 | public static CField field(Class c, String name) 32 | { 33 | try 34 | { 35 | Field f = c.getDeclaredField(name); 36 | f.setAccessible(true); 37 | return new CField(f); 38 | } 39 | catch(Exception e) 40 | { 41 | throw new Error(e); 42 | } 43 | } 44 | 45 | 46 | /** invokes a specified method via reflection */ 47 | public static T invoke(Class returnType, String methodName, Object object, Object ... args) 48 | { 49 | if(object == null) 50 | { 51 | return null; 52 | } 53 | 54 | try 55 | { 56 | Class c = object.getClass(); 57 | 58 | // looking for exact match 59 | 60 | int sz = args.length; 61 | Class[] argTypes = new Class[sz]; 62 | for(int i=0; i 2 | package goryachev.common.util; 3 | 4 | 5 | /** String-based database identifier (key) */ 6 | public class SKey 7 | implements Cloneable, Comparable 8 | { 9 | public static interface Getter 10 | { 11 | public SKey getSKey(); 12 | } 13 | 14 | // 15 | 16 | private String key; 17 | 18 | 19 | public SKey(String key) 20 | { 21 | this.key = key; 22 | } 23 | 24 | 25 | public static SKey format(String format, Object ... args) 26 | { 27 | String s = String.format(format, args); 28 | return new SKey(s); 29 | } 30 | 31 | 32 | public String toString() 33 | { 34 | return key; 35 | } 36 | 37 | 38 | /** accepts either an SKey or a hex representation obtained via asString() */ 39 | public static SKey parse(Object x) throws Exception 40 | { 41 | if(x instanceof SKey) 42 | { 43 | return (SKey)x; 44 | } 45 | else if(x instanceof String) 46 | { 47 | return new SKey((String)x); 48 | } 49 | 50 | throw new Exception(); 51 | } 52 | 53 | 54 | public static SKey parseQuiet(Object x) 55 | { 56 | try 57 | { 58 | return parse(x); 59 | } 60 | catch(Exception e) 61 | { } 62 | 63 | return null; 64 | } 65 | 66 | 67 | public boolean equals(Object x) 68 | { 69 | if(x == this) 70 | { 71 | return true; 72 | } 73 | else if(x instanceof SKey) 74 | { 75 | return key.equals(((SKey)x).key); 76 | } 77 | else 78 | { 79 | return false; 80 | } 81 | } 82 | 83 | 84 | public int hashCode() 85 | { 86 | return FH.hash(SKey.class.hashCode(), key); 87 | } 88 | 89 | 90 | public boolean startsWith(String prefix) 91 | { 92 | return key.startsWith(prefix); 93 | } 94 | 95 | 96 | public boolean endsWith(String suffix) 97 | { 98 | return key.endsWith(suffix); 99 | } 100 | 101 | 102 | public int compareTo(SKey x) 103 | { 104 | return key.compareTo(x.key); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/goryachev/common/util/SW.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2005-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** SW measures elapsed time since instantioation or reset() */ 6 | public class SW 7 | { 8 | private long start; 9 | 10 | 11 | public SW() 12 | { 13 | reset(); 14 | } 15 | 16 | 17 | public void reset() 18 | { 19 | start = System.nanoTime(); 20 | } 21 | 22 | 23 | /** returns elapsed time in nanoseconds */ 24 | public long getElapsedTimeNano() 25 | { 26 | return System.nanoTime() - start; 27 | } 28 | 29 | 30 | /** returns elapsed time in milliseconds */ 31 | public long getElapsedTimeMilli() 32 | { 33 | return getElapsedTimeNano() / 1000000; 34 | } 35 | 36 | 37 | public String toString() 38 | { 39 | long elapsed = getElapsedTimeNano(); 40 | return CKit.formatTimePeriod(elapsed / 1000000L); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/goryachev/common/util/SequenceNumbers.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | 6 | /** Simple friendly sequence number generator */ 7 | public class SequenceNumbers 8 | { 9 | private static AtomicLong number = new AtomicLong(); 10 | 11 | 12 | public static long get() 13 | { 14 | return number.getAndIncrement(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/goryachev/common/util/TextSplitter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public abstract class TextSplitter 6 | { 7 | public abstract boolean isTextSeparator(char c); 8 | 9 | // 10 | 11 | public TextSplitter() 12 | { 13 | } 14 | 15 | 16 | public CList split(String text) 17 | { 18 | CList list = new CList(); 19 | int start = 0; 20 | int len = text.length(); 21 | boolean white = true; 22 | 23 | for(int i=0; i start) 31 | { 32 | add(list, text.substring(start, i)); 33 | } 34 | white = true; 35 | } 36 | } 37 | else 38 | { 39 | if(white) 40 | { 41 | start = i; 42 | white = false; 43 | } 44 | } 45 | } 46 | 47 | if(!white) 48 | { 49 | if(start < len) 50 | { 51 | add(list, text.substring(start, len)); 52 | } 53 | } 54 | 55 | return list; 56 | } 57 | 58 | 59 | protected void add(CList list, String s) 60 | { 61 | if(s.length() > 0) 62 | { 63 | list.add(s); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/goryachev/common/util/TriConsumer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Functional interface with three arguments, similar to Consumer and BiConsumer. 7 | */ 8 | @FunctionalInterface 9 | public interface TriConsumer 10 | { 11 | public void accept(A a, B b, C c); 12 | } 13 | -------------------------------------------------------------------------------- /src/goryachev/common/util/UrlStreamFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.net.URL; 4 | import java.net.URLStreamHandler; 5 | import java.net.URLStreamHandlerFactory; 6 | import java.util.Locale; 7 | 8 | 9 | /** 10 | * Global Url Stream Factory simplifies plugging in multiple custom URLStreamHandlers. 11 | */ 12 | public class UrlStreamFactory 13 | { 14 | private static CMap handlers; 15 | 16 | 17 | /** a centalized place to register a URLStreamHandler for a custom protocol */ 18 | public static synchronized void registerHandler(String protocol, URLStreamHandler h) throws Exception 19 | { 20 | if(handlers == null) 21 | { 22 | handlers = new CMap<>(); 23 | 24 | URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() 25 | { 26 | public URLStreamHandler createURLStreamHandler(String protocol) 27 | { 28 | URLStreamHandler h = getHandler(protocol); 29 | return h; 30 | } 31 | }); 32 | } 33 | 34 | handlers.put(protocol.toLowerCase(Locale.US), h); 35 | } 36 | 37 | 38 | protected static URLStreamHandler getHandler(String protocol) 39 | { 40 | return handlers.get(protocol.toLowerCase(Locale.US)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/goryachev/common/util/UserException.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2011-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | public class UserException 6 | extends RuntimeException 7 | { 8 | private String title; 9 | 10 | 11 | public UserException(String title, String message, Throwable cause) 12 | { 13 | super(message, cause); 14 | this.title = title; 15 | } 16 | 17 | 18 | public UserException(String title, String message) 19 | { 20 | super(message); 21 | this.title = title; 22 | } 23 | 24 | 25 | public UserException(String message, Throwable cause) 26 | { 27 | super(message, cause); 28 | } 29 | 30 | 31 | public UserException(String message) 32 | { 33 | super(message); 34 | } 35 | 36 | 37 | public String getTitle() 38 | { 39 | return title; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/goryachev/common/util/ValueGenerator.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | 4 | 5 | /** 6 | * Value Generator. 7 | */ 8 | @FunctionalInterface 9 | public interface ValueGenerator 10 | { 11 | public T generate() throws Throwable; 12 | } 13 | -------------------------------------------------------------------------------- /src/goryachev/common/util/WeakVector.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2024 Andy Goryachev 2 | package goryachev.common.util; 3 | import java.util.function.Consumer; 4 | 5 | 6 | /** 7 | * Synchronized List of WeakListeners. 8 | */ 9 | public class WeakVector 10 | extends WeakList 11 | { 12 | public WeakVector() 13 | { 14 | } 15 | 16 | 17 | public WeakVector(int size) 18 | { 19 | super(size); 20 | } 21 | 22 | 23 | public synchronized CList asList() 24 | { 25 | return super.asList(); 26 | } 27 | 28 | 29 | public synchronized T get(int ix) 30 | { 31 | return super.get(ix); 32 | } 33 | 34 | 35 | public synchronized void add(T item) 36 | { 37 | super.add(item); 38 | } 39 | 40 | 41 | public synchronized void add(int index, T item) 42 | { 43 | super.add(index, item); 44 | } 45 | 46 | 47 | public synchronized int size() 48 | { 49 | return super.size(); 50 | } 51 | 52 | 53 | public synchronized void remove(T item) 54 | { 55 | super.remove(item); 56 | } 57 | 58 | 59 | public synchronized T remove(int ix) 60 | { 61 | return super.remove(ix); 62 | } 63 | 64 | 65 | public synchronized int indexOf(T item) 66 | { 67 | return super.indexOf(item); 68 | } 69 | 70 | 71 | public synchronized void forEach(Consumer client) 72 | { 73 | super.forEach(client); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/goryachev/common/util/api/IMessageDigest.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.common.util.api; 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | 7 | /** 8 | * Message Digest Interface. 9 | */ 10 | public interface IMessageDigest 11 | { 12 | public void update(byte b); 13 | 14 | 15 | public void update(byte[] buf, int offset, int length); 16 | 17 | 18 | public void reset(); 19 | 20 | 21 | public byte[] digest(); 22 | 23 | 24 | // 25 | 26 | 27 | /** returns IMessageDigest based java.security implementation, may throw an Error */ 28 | public static IMessageDigest getInstance(String algorithm) 29 | { 30 | try 31 | { 32 | MessageDigest md = MessageDigest.getInstance(algorithm); 33 | 34 | return new IMessageDigest() 35 | { 36 | public void update(byte[] buf, int offset, int length) 37 | { 38 | md.update(buf, offset, length); 39 | } 40 | 41 | 42 | public void update(byte b) 43 | { 44 | md.update(b); 45 | } 46 | 47 | 48 | public void reset() 49 | { 50 | md.reset(); 51 | } 52 | 53 | 54 | public byte[] digest() 55 | { 56 | return md.digest(); 57 | } 58 | }; 59 | } 60 | catch(NoSuchAlgorithmException e) 61 | { 62 | throw new Error(e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/goryachev/common/util/api/IMessageDigestBlake2b.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.common.util.api; 3 | 4 | 5 | /** 6 | * IMessageDigest based on Blake2b. 7 | */ 8 | public interface IMessageDigestBlake2b 9 | extends IMessageDigest 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/platform/ApplicationSupport.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2014-2024 Andy Goryachev 2 | package goryachev.common.util.platform; 3 | 4 | 5 | public class ApplicationSupport 6 | { 7 | /** application needs to shutdown cjob executor on exit */ 8 | public static volatile boolean shutdownCJobExecutor; 9 | } 10 | -------------------------------------------------------------------------------- /src/goryachev/common/util/platform/CPlatformLinux.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util.platform; 3 | 4 | 5 | public class CPlatformLinux 6 | extends CPlatformUnix 7 | { 8 | public CPlatformLinux() 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/goryachev/common/util/platform/CPlatformMac.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2007-2024 Andy Goryachev 2 | package goryachev.common.util.platform; 3 | 4 | 5 | public class CPlatformMac 6 | extends CPlatformUnix 7 | { 8 | // Norwegian "place of interest" or apple command sign 9 | public static final String APPLE_COMMAND_SIGN = "\u2318"; 10 | 11 | 12 | public CPlatformMac() 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/goryachev/common/util/platform/CPlatformUnix.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2008-2024 Andy Goryachev 2 | package goryachev.common.util.platform; 3 | import goryachev.common.util.CPlatform; 4 | import java.io.File; 5 | 6 | 7 | public class CPlatformUnix 8 | extends CPlatform 9 | { 10 | protected File getSettingsFolderPrivate() 11 | { 12 | return new File(getUserHome(), "." + SETTINGS_FOLDER); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/goryachev/common/util/platform/CPlatformWindows.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2007-2024 Andy Goryachev 2 | package goryachev.common.util.platform; 3 | import goryachev.common.log.Log; 4 | import goryachev.common.util.CKit; 5 | import goryachev.common.util.CPlatform; 6 | import java.io.File; 7 | import java.nio.charset.Charset; 8 | 9 | 10 | public class CPlatformWindows 11 | extends CPlatform 12 | { 13 | private static final String REGQUERY_UTIL = "reg query "; 14 | private static final String REGSTR_TOKEN = "REG_SZ"; 15 | private static final String DESKTOP_FOLDER_CMD = REGQUERY_UTIL + "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\" /v DESKTOP"; 16 | protected static final Log log = Log.get("CPlatformWindows"); 17 | 18 | 19 | public CPlatformWindows() 20 | { 21 | } 22 | 23 | 24 | // http://stackoverflow.com/questions/1080634/how-to-get-the-desktop-path-in-java 25 | public static File getCurrentUserDesktop() 26 | { 27 | try 28 | { 29 | Process process = Runtime.getRuntime().exec(DESKTOP_FOLDER_CMD); 30 | String s = CKit.readString(process.getInputStream(), Charset.defaultCharset()); 31 | int ix = s.indexOf(REGSTR_TOKEN); 32 | if(ix >= 0) 33 | { 34 | return new File(s.substring(ix + REGSTR_TOKEN.length()).trim()); 35 | } 36 | } 37 | catch(Exception e) 38 | { 39 | log.error(e); 40 | } 41 | 42 | return null; 43 | } 44 | 45 | 46 | @Deprecated 47 | public File getDefaultSettingsFolder() 48 | { 49 | return new File(getUserHome(), SETTINGS_FOLDER); 50 | } 51 | 52 | 53 | protected File getSettingsFolderPrivate() 54 | { 55 | // TODO "Documents"? 56 | return new File(getUserHome(), SETTINGS_FOLDER); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/goryachev/common/util/text/CharCounter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2013-2024 Andy Goryachev 2 | package goryachev.common.util.text; 3 | 4 | 5 | public class CharCounter 6 | { 7 | public static int count(String s) 8 | { 9 | int count = 0; 10 | 11 | if(s != null) 12 | { 13 | for(int i=0; i 2 | package goryachev.common.util.text; 3 | import java.text.BreakIterator; 4 | 5 | 6 | /** 7 | * BreakIterator interface. 8 | * 9 | * Usage:
10 |  * bi.setText(text);
11 |  * int start = bi.first();
12 |  * for(int end=bi.next(); end!=BreakIterator.DONE; start=end, end=bi.next())
13 |  * {
14 |  *      String s = text.substring(start,end);
15 |  * }
16 |  */
17 | public interface IBreakIterator
18 | {
19 | 	public static final int DONE = -1;
20 | 	
21 | 	//
22 | 	
23 | 	public void setText(String text);
24 | 
25 | 	public int first();
26 | 
27 | 	public int next();
28 | 	
29 | 	public IBreakIterator copy();
30 | 	
31 | 	//
32 | 
33 | 	/** 
34 | 	 * wraps a standard java.util.BreakIterator instance.
35 | 	 * it is recommended to use com.ibm.icu.text.BreakIterator instead because
36 | 	 * the stock java one is not complete (emoji!)
37 | 	 */
38 | 	public static IBreakIterator wrap(BreakIterator br)
39 | 	{
40 | 		return new IBreakIterator()
41 | 		{
42 | 			public void setText(String text)
43 | 			{
44 | 				br.setText(text);
45 | 			}
46 | 
47 | 
48 | 			public int first()
49 | 			{
50 | 				return br.first();
51 | 			}
52 | 
53 | 
54 | 			public int next()
55 | 			{
56 | 				int rv = br.next();
57 | 				if(rv == BreakIterator.DONE)
58 | 				{
59 | 					return DONE;
60 | 				}
61 | 				return rv;
62 | 			}
63 | 			
64 | 			
65 | 			public IBreakIterator copy()
66 | 			{
67 | 				return (IBreakIterator)br.clone();
68 | 			}
69 | 		};
70 | 	}
71 | }
72 | 


--------------------------------------------------------------------------------
/src/goryachev/common/util/text/SimpleWordCounter.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2013-2024 Andy Goryachev 
 2 | package goryachev.common.util.text;
 3 | import goryachev.common.util.TextTools;
 4 | 
 5 | 
 6 | public class SimpleWordCounter
 7 | {
 8 | 	private enum Type 
 9 | 	{
10 | 		CJK,
11 | 		Space,
12 | 		Word
13 | 	};
14 | 	
15 | 	private final String text;
16 | 	private Type type;
17 | 	private int count;
18 | 	
19 | 	
20 | 	public SimpleWordCounter(String text)
21 | 	{
22 | 		this.text = text;
23 | 	}
24 | 	
25 | 	
26 | 	public int countWords()
27 | 	{
28 | 		count = 0;
29 | 		int len = text.length();
30 | 		type = Type.Space;
31 | 		
32 | 		for(int i=0; i
  2 | package goryachev.common.util.text;
  3 | import goryachev.common.util.CList;
  4 | 
  5 | 
  6 | public class ZQuery
  7 | {
  8 | 	private final String expression;
  9 | 	private CList includes;
 10 | 	private CList excludes;
 11 | 	
 12 | 	
 13 | 	public ZQuery(String expression)
 14 | 	{
 15 | 		this.expression = expression;
 16 | 		
 17 | 		ZQueryParser p = new ZQueryParser(expression);
 18 | 		p.parse();
 19 | 		this.includes = p.getIncludes();
 20 | 		this.excludes = p.getExcludes();
 21 | 	}
 22 | 	
 23 | 	
 24 | 	public int includedSegmentCount()
 25 | 	{
 26 | 		return includes == null ? 0 : includes.size();
 27 | 	}
 28 | 	
 29 | 	
 30 | 	public QuerySegment getIncludeSegment(int ix)
 31 | 	{
 32 | 		return includes.get(ix);
 33 | 	}
 34 | 	
 35 | 	
 36 | 	public String getExpression()
 37 | 	{
 38 | 		return expression;
 39 | 	}
 40 | 	
 41 | 	
 42 | 	public String toString()
 43 | 	{
 44 | 		return getExpression();
 45 | 	}
 46 | 	
 47 | 	
 48 | 	public boolean isIncluded(String s)
 49 | 	{
 50 | 		if(includes == null)
 51 | 		{
 52 | 			return true;
 53 | 		}
 54 | 		if(s == null)
 55 | 		{
 56 | 			return false;
 57 | 		}
 58 | 		
 59 | 		int sz = includes.size();
 60 | 		if(sz == 0)
 61 | 		{
 62 | 			return true;
 63 | 		}
 64 | 		
 65 | 		for(int i=0; i
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * Closing Window Operation.
 7 |  */
 8 | @FunctionalInterface
 9 | public interface ClosingWindowOperation
10 | {
11 | 	/**
12 | 	 * Valid inputs: DISCARD_ALL, SAVE_ALL, UNDEFINED.
13 | 	 * Valid outputs: CANCEL, CONTINUE, DISCARD_ALL, SAVE_ALL
14 | 	 */
15 | 	public ShutdownChoice confirmClosing(boolean exiting, boolean multiple, ShutdownChoice choice); 
16 | }
17 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/CssID.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import java.util.concurrent.atomic.AtomicLong;
 4 | 
 5 | 
 6 | /**
 7 |  * Css ID
 8 |  */
 9 | public class CssID
10 | {
11 | 	private final String id;
12 | 	private static final AtomicLong seq = new AtomicLong();
13 | 	
14 | 	
15 | 	public CssID(String id)
16 | 	{
17 | 		this.id = id + "_" + seq.incrementAndGet();
18 | 	}
19 | 	
20 | 	
21 | 	public CssID()
22 | 	{
23 | 		this("");
24 | 	}
25 | 	
26 | 	
27 | 	public String getID()
28 | 	{
29 | 		return id;
30 | 	}
31 | 	
32 | 	
33 | 	public String toString()
34 | 	{
35 | 		return getID();
36 | 	}
37 | }
38 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/CssPseudo.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * Css Pseudo class.
 7 |  */
 8 | public class CssPseudo
 9 | {
10 | 	private final String name;
11 | 	
12 | 	
13 | 	public CssPseudo(String name)
14 | 	{
15 | 		this.name = name;
16 | 	}
17 | 	
18 | 	
19 | 	public String getName()
20 | 	{
21 | 		return name;
22 | 	}
23 | 	
24 | 	
25 | 	public String toString()
26 | 	{
27 | 		return getName();
28 | 	}
29 | }
30 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/CssStyle.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import goryachev.common.util.FH;
 4 | import javafx.scene.Node;
 5 | 
 6 | 
 7 | /**
 8 |  * CSS Style.
 9 |  * 
10 |  * Usage example:
11 |  * 
12 |  * public static final CssStyle EXAMPLE = new CssStyle();
13 |  * ...
14 |  * {
15 |  *     Pane pane = new Pane();
16 |  *     EXAMPLE.set(pane);
17 |  * }
18 |  * 
19 |  */
20 | public class CssStyle
21 | {
22 | 	private String name;
23 | 	private static long seq;
24 | 
25 | 
26 | 	public CssStyle(String name)
27 | 	{
28 | 		this.name = generateName(name);
29 | 	}
30 | 	
31 | 	
32 | 	public CssStyle()
33 | 	{
34 | 		this.name = generateName(null);
35 | 	}
36 | 	
37 | 	
38 | 	private static synchronized String generateName(String name)
39 | 	{
40 | 		if(CssLoader.DUMP)
41 | 		{
42 | 			StackTraceElement s = new Throwable().getStackTrace()[2];
43 | 			String c = s.getClassName().replace('.', '_');
44 | 			return c + "-L" + s.getLineNumber() + (name == null ? "" : "-" + name);
45 | 		}
46 | 		else
47 | 		{
48 | 			return "S" + (seq++); 
49 | 		}
50 | 	}
51 | 	
52 | 	
53 | 	public boolean equals(Object x)
54 | 	{
55 | 		if(x == this)
56 | 		{
57 | 			return true;
58 | 		}
59 | 		else if(x instanceof CssStyle s)
60 | 		{
61 | 			return getName().equals(s.getName());
62 | 		}
63 | 		else
64 | 		{
65 | 			return false;
66 | 		}
67 | 	}
68 | 	
69 | 	
70 | 	public int hashCode()
71 | 	{
72 | 		int h = FH.hash(CssStyle.class);
73 | 		h = FH.hash(h, getName());
74 | 		return h;
75 | 	}
76 | 	
77 | 	
78 | 	public String getName()
79 | 	{
80 | 		return name;
81 | 	}
82 | 	
83 | 	
84 | 	public String toString()
85 | 	{
86 | 		return name;
87 | 	}
88 | 	
89 | 	
90 | 	public void set(Node n)
91 | 	{
92 | 		n.getStyleClass().add(getName());
93 | 	}
94 | }
95 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FlatButton.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.scene.Node;
 4 | 
 5 | 
 6 | /**
 7 |  * Flat Button.
 8 |  */
 9 | public class FlatButton
10 | 	extends FxButton
11 | {
12 | 	public static final CssStyle STYLE = new CssStyle("FlatButton_STYLE");
13 | 	
14 | 	
15 | 	public FlatButton(String text, FxAction a)
16 | 	{
17 | 		super(text, a);
18 | 		FX.style(this, STYLE);
19 | 	}
20 | 	
21 | 	
22 | 	public FlatButton(String text, Runnable action)
23 | 	{
24 | 		super(text, action);
25 | 		FX.style(this, STYLE);
26 | 	}
27 | 	
28 | 	
29 | 	public FlatButton(String text)
30 | 	{
31 | 		super(text);
32 | 		FX.style(this, STYLE);
33 | 	}
34 | 	
35 | 	
36 | 	public FlatButton(Node icon)
37 | 	{
38 | 		super(icon);
39 | 		FX.style(this, STYLE);
40 | 	}
41 | 	
42 | 	
43 | 	public FlatButton(Node icon, FxAction a)
44 | 	{
45 | 		super(icon, a);
46 | 		FX.style(this, STYLE);
47 | 	}
48 | 	
49 | 	
50 | 	public FlatButton(Node icon, Runnable action)
51 | 	{
52 | 		super(icon, action);
53 | 		FX.style(this, STYLE);
54 | 	}
55 | }
56 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FlatToggleButton.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2019-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import java.util.function.Function;
 4 | import javafx.beans.value.ChangeListener;
 5 | import javafx.beans.value.ObservableValue;
 6 | import javafx.scene.Node;
 7 | import javafx.scene.control.ToggleButton;
 8 | 
 9 | 
10 | /**
11 |  * Flat Toggle Button.
12 |  */
13 | public class FlatToggleButton
14 | 	extends ToggleButton
15 | {
16 | 	public static final CssStyle STYLE = new CssStyle("FlatToggleButton_STYLE");
17 | 	private static final Object ICONS = new Object();
18 | 	
19 | 	
20 | 	public FlatToggleButton(String text, Node graphic)
21 | 	{
22 | 		super(text, graphic);
23 | 		init();
24 | 	}
25 | 	
26 | 	
27 | 	public FlatToggleButton(Node graphic)
28 | 	{
29 | 		super(null, graphic);
30 | 		init();
31 | 	}
32 | 	
33 | 	
34 | 	public FlatToggleButton(String text)
35 | 	{
36 | 		super(text);
37 | 		init();
38 | 	}
39 | 	
40 | 	
41 | 	public FlatToggleButton()
42 | 	{
43 | 		init();
44 | 	}
45 | 	
46 | 	
47 | 	private void init()
48 | 	{
49 | 		FX.style(this, STYLE);
50 | 	}
51 | 	
52 | 	
53 | 	public void setIcons(Function generator)
54 | 	{
55 | 		Object prev = getProperties().get(ICONS);
56 | 		if(prev instanceof ChangeListener)
57 | 		{
58 | 			selectedProperty().removeListener((ChangeListener)prev);
59 | 		}
60 | 		
61 | 		ChangeListener li = new ChangeListener()
62 | 		{
63 | 			public void changed(ObservableValue src, Boolean prev, Boolean cur)
64 | 			{
65 | 				updateIcon(generator, cur);
66 | 			}
67 | 		};
68 | 		selectedProperty().addListener(li);
69 | 		getProperties().put(ICONS, li);
70 | 		
71 | 		updateIcon(generator, isSelected());
72 | 	}
73 | 	
74 | 	
75 | 	protected void updateIcon(Function generator, Boolean on)
76 | 	{
77 | 		Node icon = generator.apply(on);
78 | 		setGraphic(icon);
79 | 	}
80 | }
81 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/Formatters.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2017-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * Standard Formatters.
 7 |  */
 8 | public class Formatters
 9 | {
10 | 	// TODO should these be settable somehow?
11 | 	private static FxDecimalFormatter integerFormatter;
12 | 	
13 | 	
14 | 	public static FxDecimalFormatter integerFormatter()
15 | 	{
16 | 		if(integerFormatter == null)
17 | 		{
18 | 			integerFormatter = new FxDecimalFormatter("#,##0");
19 | 		}
20 | 		return integerFormatter;
21 | 	}
22 | }
23 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxApplication.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2021-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.application.Application;
 4 | 
 5 | 
 6 | /**
 7 |  * Base Class for FX Application.
 8 |  */
 9 | public abstract class FxApplication
10 | 	extends Application
11 | {
12 | 	private static FxApplication instance;
13 | 	
14 | 	
15 | 	public FxApplication()
16 | 	{
17 | 		if(instance != null)
18 | 		{
19 | 			throw new Error("there could be only one FxApplication");
20 | 		}
21 | 		instance = this;
22 | 	}
23 | 	
24 | 	
25 | 	public static FxApplication getInstance()
26 | 	{
27 | 		if(instance == null)
28 | 		{
29 | 			throw new Error("your application must extend FxApplication");
30 | 		}
31 | 		return instance;
32 | 	}
33 | }
34 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxBoolean.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2018-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.beans.property.ReadOnlyBooleanWrapper;
 4 | 
 5 | 
 6 | /**
 7 |  * Alias for SimpleBooleanProperty.
 8 |  */
 9 | public class FxBoolean
10 | 	extends ReadOnlyBooleanWrapper
11 | {
12 | 	public FxBoolean(boolean initialValue)
13 | 	{
14 | 		super(initialValue);
15 | 	}
16 | 	
17 | 	
18 | 	public FxBoolean()
19 | 	{
20 | 	}
21 | }
22 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxBooleanBinding.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2019-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.beans.Observable;
 4 | import javafx.beans.binding.BooleanBinding;
 5 | 
 6 | 
 7 | /**
 8 |  * FxBooleanBinding.
 9 |  */
10 | public abstract class FxBooleanBinding
11 | 	extends BooleanBinding
12 | {
13 | 	protected abstract boolean computeValue();
14 | 	
15 | 	
16 | 	public FxBooleanBinding(Observable ... dependencies)
17 | 	{
18 | 		bind(dependencies);
19 | 	}
20 | }
21 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxButtonPane.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * Fx Button Pane.
 7 |  * 
 8 |  * TODO default button
 9 |  * TODO fix HPane layout
10 |  * TODO set minimum button width
11 |  * TODO own layout, sets min width and alignment (to avoid fill())
12 |  */
13 | public class FxButtonPane
14 | 	extends HPane
15 | {
16 | 	public static final CssStyle PANE = new CssStyle("FxButtonPane_PANE");
17 | 	private static final int MIN_WIDTH = 70;
18 | 
19 | 	
20 | 	public FxButtonPane()
21 | 	{
22 | 		super(5);
23 | 		FX.style(this, PANE);
24 | 	}
25 | 	
26 | 	
27 | 	public FxButton addButton(String text, CssStyle style, FxAction a)
28 | 	{
29 | 		FxButton b = new FxButton(text, style, a);
30 | 		return addButton(b);
31 | 	}
32 | 	
33 | 	
34 | 	public FxButton addButton(String text, FxAction a)
35 | 	{
36 | 		FxButton b = new FxButton(text, a);
37 | 		return addButton(b);
38 | 	}
39 | 	
40 | 	
41 | 	public FxButton addButton(String text, CssStyle style, Runnable r)
42 | 	{
43 | 		FxButton b = new FxButton(text, style, new FxAction(r));  
44 | 		return addButton(b);
45 | 	}
46 | 	
47 | 	
48 | 	public FxButton addButton(String text, CssStyle style)
49 | 	{
50 | 		FxButton b = new FxButton(text, style, FxAction.DISABLED);  
51 | 		return addButton(b);
52 | 	}
53 | 	
54 | 	
55 | 	public FxButton addButton(String text, Runnable r)
56 | 	{
57 | 		FxButton b = new FxButton(text, new FxAction(r));  
58 | 		return addButton(b);
59 | 	}
60 | 	
61 | 	
62 | 	public FxButton addButton(String text)
63 | 	{
64 | 		FxButton b = new FxButton(text);
65 | 		b.setDisable(true);
66 | 		return addButton(b);
67 | 	}
68 | 	
69 | 	
70 | 	public FxButton addButton(FxButton b)
71 | 	{
72 | 		b.setMinWidth(MIN_WIDTH);
73 | 		add(b);
74 | 		return b;
75 | 	}
76 | }
77 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxChangeListener.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2020-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import goryachev.common.util.IDisconnectable;
 4 | import java.util.concurrent.CopyOnWriteArrayList;
 5 | import javafx.beans.value.ChangeListener;
 6 | import javafx.beans.value.ObservableValue;
 7 | 
 8 | 
 9 | /**
10 |  * A Change Listener that calls a callback when any of the registered properties change.
11 |  * This class allows for disconnecting the listeners from all the registered properties.
12 |  */
13 | public class FxChangeListener
14 | 	implements ChangeListener, IDisconnectable
15 | {
16 | 	private final Runnable callback;
17 | 	private final CopyOnWriteArrayList properties = new CopyOnWriteArrayList<>();
18 | 	private boolean enabled = true;
19 | 	
20 | 	
21 | 	public FxChangeListener(Runnable callback)
22 | 	{
23 | 		this.callback = callback;
24 | 	}
25 | 	
26 | 
27 | 	public void listen(ObservableValue p)
28 | 	{
29 | 		if(p != null)
30 | 		{
31 | 			properties.add(p);
32 | 			p.addListener(this);
33 | 		}
34 | 	}
35 | 	
36 | 	
37 | 	public void listen(ObservableValue ... props)
38 | 	{
39 | 		for(ObservableValue p: props)
40 | 		{
41 | 			listen(p);
42 | 		}
43 | 	}
44 | 	
45 | 	
46 | 	public void disconnect()
47 | 	{
48 | 		for(ObservableValue p: properties)
49 | 		{
50 | 			p.removeListener(this);
51 | 		}
52 | 	}
53 | 	
54 | 	
55 | 	public void enable()
56 | 	{
57 | 		setEnabled(true);
58 | 	}
59 | 	
60 | 	
61 | 	public void disable()
62 | 	{
63 | 		setEnabled(true);
64 | 	}
65 | 	
66 | 	
67 | 	public void setEnabled(boolean on)
68 | 	{
69 | 		enabled = on;
70 | 	}
71 | 	
72 | 	
73 | 	public boolean isEnabled()
74 | 	{
75 | 		return enabled;
76 | 	}
77 | 
78 | 
79 | 	public void changed(ObservableValue src, Object prev, Object curr)
80 | 	{
81 | 		fire();
82 | 	}
83 | 	
84 | 	
85 | 	public void fire()
86 | 	{
87 | 		if(enabled)
88 | 		{
89 | 			invokeCallback();
90 | 		}
91 | 	}
92 | 	
93 | 	
94 | 	protected void invokeCallback()
95 | 	{
96 | 		callback.run();
97 | 	}
98 | }
99 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxCheckBox.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.scene.control.CheckBox;
 4 | 
 5 | 
 6 | /**
 7 |  * CCheckBox.
 8 |  */
 9 | public class FxCheckBox
10 | 	extends CheckBox
11 | {
12 | 	public FxCheckBox(String text, boolean selected)
13 | 	{
14 | 		super(text);
15 | 		setSelected(selected);
16 | 	}
17 | 	
18 | 	
19 | 	public FxCheckBox(boolean selected)
20 | 	{
21 | 		setSelected(selected);
22 | 	}
23 | 
24 | 	
25 | 	public FxCheckBox(String text)
26 | 	{
27 | 		super(text);
28 | 	}
29 | 	
30 | 	
31 | 	public FxCheckBox()
32 | 	{
33 | 	}
34 | }
35 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxCheckMenuItem.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.beans.property.Property;
 4 | import javafx.scene.control.CheckMenuItem;
 5 | 
 6 | 
 7 | /**
 8 |  * CheckMenuItem that knows how to deal with FxAction or a Property.
 9 |  */
10 | public class FxCheckMenuItem
11 | 	extends CheckMenuItem
12 | {
13 | 	public FxCheckMenuItem(String text)
14 | 	{
15 | 		super(text);
16 | 	}
17 | 	
18 | 	
19 | 	public FxCheckMenuItem(String text, FxAction a)
20 | 	{
21 | 		super(text);
22 | 		a.attach(this);
23 | 	}
24 | 	
25 | 	
26 | 	public FxCheckMenuItem(String text, Property p)
27 | 	{
28 | 		super(text);
29 | 		selectedProperty().bindBidirectional(p);
30 | 	}
31 | 	
32 | 	
33 | 	public FxCheckMenuItem(String text, GlobalBooleanProperty op)
34 | 	{
35 | 		super(text);
36 | 		selectedProperty().bindBidirectional(op);
37 | 	}
38 | }
39 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxCtl.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * Simple tags to simplify creation of nodes, labels, and text controls.
 7 |  */
 8 | public enum FxCtl
 9 | {
10 | 	BOLD,
11 | 	EDITABLE,
12 | 	FOCUSABLE,
13 | 	FORCE_MAX_WIDTH,
14 | 	FORCE_MIN_HEIGHT,
15 | 	FORCE_MIN_WIDTH,
16 | 	NON_EDITABLE,
17 | 	NON_FOCUSABLE,
18 | 	WRAP_TEXT
19 | }
20 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxDateFormatter.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import java.text.SimpleDateFormat;
 4 | import java.util.Date;
 5 | 
 6 | 
 7 | /**
 8 |  * Fx DateFormatter.
 9 |  */
10 | public class FxDateFormatter
11 | 	extends FxFormatter
12 | {
13 | 	private final SimpleDateFormat format;
14 | 	
15 | 	
16 | 	public FxDateFormatter(String pattern)
17 | 	{
18 | 		format = new SimpleDateFormat(pattern);
19 | 	}
20 | 	
21 | 	
22 |     public String format(long t)
23 |     {
24 |     	return format.format(t);
25 |     }
26 | 
27 | 	
28 | 	public String toString(Object x)
29 | 	{
30 | 		if(x == null)
31 | 		{
32 | 			return null;
33 | 		}
34 | 		else if(x instanceof Date)
35 | 		{
36 | 			return format.format(x);
37 | 		}
38 | 		else if(x instanceof Long)
39 | 		{
40 | 			Long v = (Long)x;
41 | 			if(v.longValue() <= 0)
42 | 			{
43 | 				return null;
44 | 			}
45 | 			return format.format(x);
46 | 		}
47 | 		else
48 | 		{
49 | 			return null;
50 | 		}
51 | 	}
52 | }
53 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxDecimalFormatter.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2016-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import java.text.DecimalFormat;
 4 | 
 5 | 
 6 | /**
 7 |  * Fx Decimal Number Formatter.
 8 |  */
 9 | public class FxDecimalFormatter
10 | 	extends FxFormatter
11 | {
12 | 	private final DecimalFormat format;
13 | 	
14 | 	
15 | 	public FxDecimalFormatter(String pattern)
16 | 	{
17 | 		format = new DecimalFormat(pattern);
18 | 	}
19 | 
20 | 	
21 | 	public String toString(Object x)
22 | 	{
23 | 		if(x == null)
24 | 		{
25 | 			return null;
26 | 		}
27 | 		else if(x instanceof Number)
28 | 		{
29 | 			return format.format(x);
30 | 		}
31 | 		else
32 | 		{
33 | 			return null;
34 | 		}
35 | 	}
36 | }
37 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxDialogResponse.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * FxDialog response enum covers most common cases,
 7 |  * similar to {@link javafx.scene.control.ButtonBar.ButtonData}.
 8 |  */
 9 | public enum FxDialogResponse
10 | {
11 | 	CANCEL,
12 | 	CANCEL_ALL,
13 | 	DISCARD,
14 | 	DISCARD_ALL,
15 | 	SAVE,
16 | 	SAVE_ALL,
17 | }
18 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxDouble.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2019-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | import javafx.beans.property.SimpleDoubleProperty;
 4 | 
 5 | 
 6 | /**
 7 |  * Alias for SimpleLongProperty.
 8 |  */
 9 | public class FxDouble
10 | 	extends SimpleDoubleProperty
11 | {
12 | 	public FxDouble(double initialValue)
13 | 	{
14 | 		super(initialValue);
15 | 	}
16 | 	
17 | 	
18 | 	public FxDouble()
19 | 	{
20 | 	}
21 | }
22 | 


--------------------------------------------------------------------------------
/src/goryachev/fx/FxFlags.java:
--------------------------------------------------------------------------------
 1 | // Copyright © 2019-2024 Andy Goryachev 
 2 | package goryachev.fx;
 3 | 
 4 | 
 5 | /**
 6 |  * These application-wide flags control FX subsystem.
 7 |  */
 8 | public class FxFlags
 9 | {
10 | 	/**
11 | 	 * To enable polling of css style sheet for changes:
12 | 	 * 
13 | 	 * -Dcss.refresh=true
14 | 	 * 
15 | */ 16 | public static final String CSS_REFRESH = "css.refresh"; 17 | 18 | /** 19 | * Enables dumping of the stylesheet to stdout 20 | *
21 | 	 * -Dcss.dump=true
22 | 	 * 
23 | */ 24 | public static final String CSS_DUMP = "css.dump"; 25 | } 26 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxFormatter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.util.StringConverter; 4 | 5 | 6 | /** 7 | * A StringConverter extension. 8 | */ 9 | public abstract class FxFormatter 10 | extends StringConverter 11 | { 12 | public abstract String toString(Object x); 13 | 14 | // 15 | 16 | public FxFormatter() 17 | { 18 | } 19 | 20 | 21 | public Object fromString(String string) 22 | { 23 | throw new Error("FxFormatter: fromString not supported"); 24 | } 25 | 26 | 27 | public String format(Object x) 28 | { 29 | return toString(x); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxInt.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.SimpleIntegerProperty; 4 | import javafx.beans.value.ObservableValue; 5 | 6 | 7 | /** 8 | * Alias for SimpleIntegerProperty. 9 | */ 10 | public class FxInt 11 | extends SimpleIntegerProperty 12 | { 13 | public FxInt(int initialValue) 14 | { 15 | super(initialValue); 16 | } 17 | 18 | 19 | public FxInt() 20 | { 21 | } 22 | 23 | 24 | /** WARNING: potential loss of data */ 25 | public void set(Number n) 26 | { 27 | if(n != null) 28 | { 29 | set(n.intValue()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxLong.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.SimpleLongProperty; 4 | 5 | 6 | /** 7 | * Alias for SimpleLongProperty. 8 | */ 9 | public class FxLong 10 | extends SimpleLongProperty 11 | { 12 | public FxLong(long initialValue) 13 | { 14 | super(initialValue); 15 | } 16 | 17 | 18 | public FxLong() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxMenu.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.Property; 4 | import javafx.scene.control.Menu; 5 | import javafx.scene.control.MenuItem; 6 | import javafx.scene.control.SeparatorMenuItem; 7 | 8 | 9 | /** 10 | * FX Menu. 11 | */ 12 | public class FxMenu 13 | extends Menu 14 | { 15 | public FxMenu(String text) 16 | { 17 | super(text); 18 | } 19 | 20 | 21 | public FxMenu(String text, FxAction a) 22 | { 23 | super(text); 24 | a.attach(this); 25 | } 26 | 27 | 28 | public FxMenu(String text, Runnable r) 29 | { 30 | super(text); 31 | new FxAction(r).attach(this); 32 | } 33 | 34 | 35 | public SeparatorMenuItem separator() 36 | { 37 | SeparatorMenuItem m = new SeparatorMenuItem(); 38 | getItems().add(m); 39 | return m; 40 | } 41 | 42 | 43 | public FxMenuItem item(String text, FxAction a) 44 | { 45 | FxMenuItem m = new FxMenuItem(text, a); 46 | getItems().add(m); 47 | return m; 48 | } 49 | 50 | 51 | public FxMenuItem item(String text, Runnable r) 52 | { 53 | FxMenuItem m = new FxMenuItem(text, r); 54 | getItems().add(m); 55 | return m; 56 | } 57 | 58 | 59 | public FxMenu submenu(String text) 60 | { 61 | FxMenu m = new FxMenu(text); 62 | getItems().add(m); 63 | return m; 64 | } 65 | 66 | 67 | public FxCheckMenuItem item(String text, Property prop) 68 | { 69 | FxCheckMenuItem m = new FxCheckMenuItem(text, prop); 70 | getItems().add(m); 71 | return m; 72 | } 73 | 74 | 75 | public MenuItem add(MenuItem m) 76 | { 77 | getItems().add(m); 78 | return m; 79 | } 80 | 81 | 82 | /** adds a disabled menu item */ 83 | public MenuItem item(String text) 84 | { 85 | FxMenuItem m = new FxMenuItem(text); 86 | m.setDisable(true); 87 | return add(m); 88 | } 89 | 90 | 91 | /** remove all menu items */ 92 | public void clear() 93 | { 94 | getItems().clear(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxMenuItem.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.Node; 4 | import javafx.scene.control.MenuItem; 5 | 6 | 7 | /** 8 | * A more convenient MenuItem. 9 | */ 10 | public class FxMenuItem 11 | extends MenuItem 12 | { 13 | public FxMenuItem(String text, Node icon, FxAction a) 14 | { 15 | super(text); 16 | setGraphic(icon); 17 | 18 | if(a == null) 19 | { 20 | setDisable(true); 21 | } 22 | else 23 | { 24 | a.attach(this); 25 | } 26 | } 27 | 28 | 29 | public FxMenuItem(Node icon, FxAction a) 30 | { 31 | setGraphic(icon); 32 | 33 | if(a == null) 34 | { 35 | setDisable(true); 36 | } 37 | else 38 | { 39 | a.attach(this); 40 | } 41 | } 42 | 43 | 44 | public FxMenuItem(String text, FxAction a) 45 | { 46 | super(text); 47 | 48 | if(a == null) 49 | { 50 | setDisable(true); 51 | } 52 | else 53 | { 54 | a.attach(this); 55 | } 56 | } 57 | 58 | 59 | public FxMenuItem(String text, Runnable r) 60 | { 61 | super(text); 62 | new FxAction(r).attach(this); 63 | } 64 | 65 | 66 | public FxMenuItem(String text) 67 | { 68 | super(text); 69 | setDisable(true); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxObject.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.ReadOnlyObjectWrapper; 4 | 5 | 6 | /** 7 | * Alias for SimpleLongProperty. 8 | */ 9 | public class FxObject 10 | extends ReadOnlyObjectWrapper 11 | { 12 | public FxObject(T initialValue) 13 | { 14 | super(initialValue); 15 | } 16 | 17 | 18 | public FxObject() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxPath.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import java.util.Collection; 4 | import javafx.scene.shape.ClosePath; 5 | import javafx.scene.shape.LineTo; 6 | import javafx.scene.shape.MoveTo; 7 | import javafx.scene.shape.Path; 8 | import javafx.scene.shape.PathElement; 9 | 10 | 11 | /** 12 | * Path with convenience methods. 13 | */ 14 | public class FxPath 15 | extends Path 16 | { 17 | public FxPath() 18 | { 19 | } 20 | 21 | 22 | public FxPath(Collection elements) 23 | { 24 | super(elements); 25 | } 26 | 27 | 28 | public FxPath(PathElement ... elements) 29 | { 30 | super(elements); 31 | } 32 | 33 | 34 | public void moveto(double x, double y) 35 | { 36 | add(new MoveTo(x, y)); 37 | } 38 | 39 | 40 | public void lineto(double x, double y) 41 | { 42 | add(new LineTo(x, y)); 43 | } 44 | 45 | 46 | public void close() 47 | { 48 | add(new ClosePath()); 49 | } 50 | 51 | 52 | public void add(PathElement em) 53 | { 54 | getElements().add(em); 55 | } 56 | 57 | 58 | public void addAll(PathElement ... elements) 59 | { 60 | getElements().addAll(elements); 61 | } 62 | 63 | 64 | public void addAll(Collection elements) 65 | { 66 | getElements().addAll(elements); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxRadioToggleButton.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.Node; 4 | 5 | 6 | /** 7 | * FxToggleButton that behaves like RadioButton. 8 | */ 9 | public class FxRadioToggleButton 10 | extends FxToggleButton 11 | { 12 | public FxRadioToggleButton(String text, String tooltip) 13 | { 14 | super(text, tooltip); 15 | } 16 | 17 | 18 | public FxRadioToggleButton(String text, Node graphic) 19 | { 20 | super(text, graphic); 21 | } 22 | 23 | 24 | public void fire() 25 | { 26 | // behave like RadioButton 27 | if((getToggleGroup() == null) || !isSelected()) 28 | { 29 | super.fire(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxSize.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import goryachev.common.util.FH; 4 | 5 | 6 | /** 7 | * Width x Height 8 | */ 9 | public class FxSize 10 | { 11 | private double width; 12 | private double height; 13 | 14 | 15 | public FxSize(double width, double height) 16 | { 17 | this.width = width; 18 | this.height = height; 19 | } 20 | 21 | 22 | public FxSize() 23 | { 24 | } 25 | 26 | 27 | public void setWidth(double w) 28 | { 29 | width = w; 30 | } 31 | 32 | 33 | public void setHeight(double h) 34 | { 35 | height = h; 36 | } 37 | 38 | 39 | public double getWidth() 40 | { 41 | return width; 42 | } 43 | 44 | 45 | public double getHeight() 46 | { 47 | return height; 48 | } 49 | 50 | 51 | public boolean equals(Object x) 52 | { 53 | if(x == this) 54 | { 55 | return true; 56 | } 57 | else if(x instanceof FxSize) 58 | { 59 | FxSize s = (FxSize)x; 60 | return ((width == s.width) && (height == s.height)); 61 | } 62 | else 63 | { 64 | return false; 65 | } 66 | } 67 | 68 | 69 | public int hashCode() 70 | { 71 | int h = FH.hash(FxSize.class.hashCode(), width); 72 | return FH.hash(h, height); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxSplitMenuButton.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2023-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.control.MenuItem; 4 | import javafx.scene.control.SeparatorMenuItem; 5 | import javafx.scene.control.SplitMenuButton; 6 | 7 | 8 | /** 9 | * FxSplitMenuButton. 10 | */ 11 | public class FxSplitMenuButton extends SplitMenuButton 12 | { 13 | public FxSplitMenuButton(MenuItem ... items) 14 | { 15 | super(items); 16 | } 17 | 18 | 19 | public FxSplitMenuButton() 20 | { 21 | } 22 | 23 | 24 | public FxSplitMenuButton(String text) 25 | { 26 | setText(text); 27 | } 28 | 29 | 30 | public FxMenuItem item(String text) 31 | { 32 | FxMenuItem m = new FxMenuItem(text); 33 | getItems().add(m); 34 | return m; 35 | } 36 | 37 | 38 | public FxMenuItem item(String text, Runnable action) 39 | { 40 | FxMenuItem m = new FxMenuItem(text, action); 41 | getItems().add(m); 42 | return m; 43 | } 44 | 45 | 46 | public FxMenuItem item(String text, FxAction action) 47 | { 48 | FxMenuItem m = new FxMenuItem(text, action); 49 | getItems().add(m); 50 | return m; 51 | } 52 | 53 | 54 | public FxMenu menu(String text) 55 | { 56 | FxMenu m = new FxMenu(text); 57 | getItems().add(m); 58 | return m; 59 | } 60 | 61 | 62 | public SeparatorMenuItem separator() 63 | { 64 | SeparatorMenuItem m = new SeparatorMenuItem(); 65 | getItems().add(m); 66 | return m; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxSplitPane.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.geometry.Orientation; 4 | import javafx.scene.Node; 5 | import javafx.scene.control.SplitPane; 6 | 7 | 8 | /** 9 | * A slightly more conventient SplitPane. 10 | */ 11 | public class FxSplitPane 12 | extends SplitPane 13 | { 14 | public FxSplitPane(Orientation ori, Node ... items) 15 | { 16 | super(items); 17 | setOrientation(ori); 18 | } 19 | 20 | 21 | public FxSplitPane(Node ... items) 22 | { 23 | super(items); 24 | } 25 | 26 | 27 | public FxSplitPane() 28 | { 29 | } 30 | 31 | 32 | public void setNoResize(Node n) 33 | { 34 | FX.preventSplitPaneResizing(n); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxString.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.SimpleStringProperty; 4 | 5 | 6 | /** 7 | * Alias for SimpleStringProperty. 8 | */ 9 | public class FxString 10 | extends SimpleStringProperty 11 | { 12 | public FxString(String initialValue) 13 | { 14 | super(initialValue); 15 | } 16 | 17 | 18 | public FxString() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxTask.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import goryachev.common.log.Log; 4 | import goryachev.common.util.CTask; 5 | import javafx.application.Platform; 6 | 7 | 8 | /** 9 | * FX CTask. 10 | */ 11 | public class FxTask 12 | extends CTask 13 | { 14 | public FxTask() 15 | { 16 | } 17 | 18 | 19 | protected void handleSuccess(T result) 20 | { 21 | if(onSuccess != null) 22 | { 23 | Platform.runLater(() -> super.handleSuccess(result)); 24 | } 25 | } 26 | 27 | 28 | protected void handleError(Throwable e) 29 | { 30 | if(onError == null) 31 | { 32 | log.error(e); 33 | } 34 | else 35 | { 36 | Platform.runLater(() -> super.handleError(e)); 37 | } 38 | } 39 | 40 | 41 | protected void handleFinish() 42 | { 43 | if(onFinish != null) 44 | { 45 | Platform.runLater(() -> super.handleFinish()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxTimer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import goryachev.common.log.Log; 4 | import javafx.animation.KeyFrame; 5 | import javafx.animation.Timeline; 6 | import javafx.util.Duration; 7 | 8 | 9 | /** 10 | * FxTimer provides API similar to java.swing.Timer. 11 | */ 12 | public class FxTimer 13 | { 14 | protected static final Log log = Log.get("FxTimer"); 15 | private final Runnable action; 16 | private final Timeline timeline; 17 | 18 | 19 | /** single shot timer */ 20 | public FxTimer(Duration delay, Runnable action) 21 | { 22 | this.action = action; 23 | 24 | timeline = new Timeline(new KeyFrame(delay, (ev) -> fire())); 25 | timeline.setCycleCount(1); 26 | } 27 | 28 | 29 | /** periodic timer */ 30 | public FxTimer(Duration initialDelay, Duration delay, Runnable action) 31 | { 32 | this.action = action; 33 | 34 | timeline = new Timeline(new KeyFrame(delay, (ev) -> fire())); 35 | timeline.setDelay(initialDelay); 36 | timeline.setCycleCount(Timeline.INDEFINITE); 37 | } 38 | 39 | 40 | /** single shot timer */ 41 | public FxTimer(int delayMillis, Runnable action) 42 | { 43 | this(Duration.millis(delayMillis), action); 44 | } 45 | 46 | 47 | /** periodic timer */ 48 | public FxTimer(int initialDelay, int delay, Runnable action) 49 | { 50 | this(Duration.millis(initialDelay), Duration.millis(delay), action); 51 | } 52 | 53 | 54 | public void start() 55 | { 56 | timeline.play(); 57 | } 58 | 59 | 60 | public void stop() 61 | { 62 | timeline.stop(); 63 | } 64 | 65 | 66 | public void restart() 67 | { 68 | timeline.stop(); 69 | timeline.play(); 70 | } 71 | 72 | 73 | protected void fire() 74 | { 75 | try 76 | { 77 | action.run(); 78 | } 79 | catch(Throwable e) 80 | { 81 | log.error(e); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxToggleButton.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.Property; 4 | import javafx.scene.Node; 5 | import javafx.scene.control.ToggleButton; 6 | 7 | 8 | /** 9 | * Slightly more convenient ToggleButton. 10 | * 11 | * When you need a toggle button that behave like RadioButtons in a group 12 | * (i.e. to keep one always selected), use FxRadioToggleButton. 13 | */ 14 | public class FxToggleButton 15 | extends ToggleButton 16 | { 17 | public FxToggleButton(String text) 18 | { 19 | super(text); 20 | } 21 | 22 | 23 | public FxToggleButton(String text, Property prop) 24 | { 25 | super(text); 26 | selectedProperty().bindBidirectional(prop); 27 | } 28 | 29 | 30 | public FxToggleButton(String text, FxAction a) 31 | { 32 | super(text); 33 | a.attach(this); 34 | } 35 | 36 | 37 | public FxToggleButton(String text, String tooltip, Property prop) 38 | { 39 | super(text); 40 | selectedProperty().bindBidirectional(prop); 41 | FX.setTooltip(this, tooltip); 42 | } 43 | 44 | 45 | public FxToggleButton(String text, String tooltip) 46 | { 47 | super(text); 48 | FX.setTooltip(this, tooltip); 49 | } 50 | 51 | 52 | public FxToggleButton(String text, String tooltip, FxAction a) 53 | { 54 | super(text); 55 | a.attach(this); 56 | FX.setTooltip(this, tooltip); 57 | } 58 | 59 | 60 | public FxToggleButton(String text, Node graphic) 61 | { 62 | super(text, graphic); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/goryachev/fx/FxToggleGroup.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.control.ToggleButton; 4 | import javafx.scene.control.ToggleGroup; 5 | 6 | 7 | /** 8 | * Slightly more convenient ToggleGroup, 9 | * ensures that one button is always selected. 10 | * 11 | * WARNING: selectedToggleProperty() change event sometimes comes BEFORE 12 | * a linked toggle changes it selected property! 13 | * It is a goo idea to invokeLater() when listening to selectedToggleProperty. 14 | */ 15 | public class FxToggleGroup 16 | extends ToggleGroup 17 | { 18 | public FxToggleGroup(ToggleButton ... buttons) 19 | { 20 | for(ToggleButton b: buttons) 21 | { 22 | b.setToggleGroup(this); 23 | } 24 | 25 | selectedToggleProperty().addListener((s, p, c) -> 26 | { 27 | if(c == null) 28 | { 29 | p.setSelected(true); 30 | } 31 | }); 32 | 33 | ensureSelected(buttons); 34 | } 35 | 36 | 37 | protected static void ensureSelected(ToggleButton[] buttons) 38 | { 39 | if(buttons.length > 0) 40 | { 41 | for(ToggleButton b: buttons) 42 | { 43 | if(b.isSelected()) 44 | { 45 | return; 46 | } 47 | } 48 | 49 | buttons[0].setSelected(true); 50 | } 51 | } 52 | 53 | 54 | public FxToggleGroup() 55 | { 56 | } 57 | 58 | 59 | public void add(ToggleButton b) 60 | { 61 | b.setToggleGroup(this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalBooleanProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.BooleanPropertyBase; 4 | import javafx.util.StringConverter; 5 | 6 | 7 | /** 8 | * Global Boolean Property. 9 | */ 10 | public class GlobalBooleanProperty 11 | extends BooleanPropertyBase 12 | implements GlobalProperty 13 | { 14 | private final String key; 15 | 16 | 17 | public GlobalBooleanProperty(String key, boolean defaultValue) 18 | { 19 | super(defaultValue); 20 | this.key = key; 21 | 22 | GlobalProperties.add(this); 23 | } 24 | 25 | 26 | public GlobalBooleanProperty(String key) 27 | { 28 | this(key, false); 29 | } 30 | 31 | 32 | /** who knows what this is for */ 33 | public Object getBean() 34 | { 35 | return null; 36 | } 37 | 38 | 39 | public String getName() 40 | { 41 | return key; 42 | } 43 | 44 | 45 | public StringConverter getConverter() 46 | { 47 | return Converters.BOOLEAN(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalDoubleProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.DoublePropertyBase; 4 | import javafx.util.StringConverter; 5 | 6 | 7 | /** 8 | * Global Boolean Property. 9 | */ 10 | public class GlobalDoubleProperty 11 | extends DoublePropertyBase 12 | implements GlobalProperty 13 | { 14 | private final String key; 15 | 16 | 17 | public GlobalDoubleProperty(String key, double defaultValue) 18 | { 19 | super(defaultValue); 20 | this.key = key; 21 | 22 | GlobalProperties.add(this); 23 | } 24 | 25 | 26 | public GlobalDoubleProperty(String key) 27 | { 28 | this(key, 0); 29 | } 30 | 31 | 32 | /** who knows what this is for */ 33 | public Object getBean() 34 | { 35 | return null; 36 | } 37 | 38 | 39 | public String getName() 40 | { 41 | return key; 42 | } 43 | 44 | 45 | public StringConverter getConverter() 46 | { 47 | return Converters.NUMBER_DOUBLE(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalIntProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.IntegerPropertyBase; 4 | import javafx.util.StringConverter; 5 | 6 | 7 | /** 8 | * Global Boolean Property. 9 | */ 10 | public class GlobalIntProperty 11 | extends IntegerPropertyBase 12 | implements GlobalProperty 13 | { 14 | private final String key; 15 | 16 | 17 | public GlobalIntProperty(String key, int defaultValue) 18 | { 19 | super(defaultValue); 20 | this.key = key; 21 | 22 | GlobalProperties.add(this); 23 | } 24 | 25 | 26 | public GlobalIntProperty(String key) 27 | { 28 | this(key, 0); 29 | } 30 | 31 | 32 | /** who knows what this is for */ 33 | public Object getBean() 34 | { 35 | return null; 36 | } 37 | 38 | 39 | public String getName() 40 | { 41 | return key; 42 | } 43 | 44 | 45 | public StringConverter getConverter() 46 | { 47 | return Converters.NUMBER_INT(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalProperties.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import goryachev.common.log.Log; 4 | import goryachev.common.util.GlobalSettings; 5 | import goryachev.common.util.WeakList; 6 | import javafx.util.StringConverter; 7 | 8 | 9 | /** 10 | * Global Properties. 11 | */ 12 | public class GlobalProperties 13 | { 14 | protected static final Log log = Log.get("GlobalProperties"); 15 | private static final WeakList> properties = new WeakList(); 16 | 17 | 18 | /** adds a property to the weak list of global properties. no deduplication is performed though. */ 19 | public static void add(GlobalProperty p) 20 | { 21 | properties.add(p); 22 | 23 | p.addListener((src,old,cur) -> store(p)); 24 | 25 | load(p); 26 | } 27 | 28 | 29 | protected static void store(GlobalProperty p) 30 | { 31 | try 32 | { 33 | String k = p.getName(); 34 | T v = p.getValue(); 35 | 36 | String s; 37 | if(v == null) 38 | { 39 | s = null; 40 | } 41 | else 42 | { 43 | StringConverter c = p.getConverter(); 44 | s = c.toString(v); 45 | } 46 | GlobalSettings.setString(k, s); 47 | } 48 | catch(Exception e) 49 | { 50 | log.error(e); 51 | } 52 | } 53 | 54 | 55 | protected static void load(GlobalProperty p) 56 | { 57 | try 58 | { 59 | String k = p.getName(); 60 | String s = GlobalSettings.getString(k); 61 | if(s != null) 62 | { 63 | T v = p.getConverter().fromString(s); 64 | p.setValue(v); 65 | } 66 | } 67 | catch(Exception e) 68 | { 69 | log.error(e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.Property; 4 | import javafx.util.StringConverter; 5 | 6 | 7 | /** 8 | * GlobalProperty. 9 | */ 10 | public interface GlobalProperty 11 | extends Property 12 | { 13 | /** name will be used as key to store the value in the GlobalSettings */ 14 | public String getName(); 15 | 16 | 17 | public StringConverter getConverter(); 18 | } 19 | -------------------------------------------------------------------------------- /src/goryachev/fx/GlobalStringProperty.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.property.StringPropertyBase; 4 | import javafx.util.StringConverter; 5 | 6 | 7 | /** 8 | * Global String Property. 9 | */ 10 | public class GlobalStringProperty 11 | extends StringPropertyBase 12 | implements GlobalProperty 13 | { 14 | private final String key; 15 | 16 | 17 | public GlobalStringProperty(String key, String defaultValue) 18 | { 19 | super(defaultValue); 20 | this.key = key; 21 | 22 | GlobalProperties.add(this); 23 | } 24 | 25 | 26 | public GlobalStringProperty(String key) 27 | { 28 | this(key, null); 29 | } 30 | 31 | 32 | /** who knows what this is for */ 33 | public Object getBean() 34 | { 35 | return null; 36 | } 37 | 38 | 39 | public String getName() 40 | { 41 | return key; 42 | } 43 | 44 | 45 | public StringConverter getConverter() 46 | { 47 | return Converters.STRING(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/goryachev/fx/HasSettings.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | 4 | 5 | /** 6 | * This interface indicates the component has [multiple] FX settings. 7 | */ 8 | public interface HasSettings 9 | { 10 | /** stores in GlobalSettings under the specified prefix */ 11 | public void storeSettings(String prefix); 12 | 13 | 14 | /** restores from GlobalSettings under the specified prefix */ 15 | public void restoreSettings(String prefix); 16 | } 17 | -------------------------------------------------------------------------------- /src/goryachev/fx/HotKey.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.beans.value.ChangeListener; 4 | import javafx.scene.Node; 5 | import javafx.scene.Scene; 6 | import javafx.scene.control.MenuItem; 7 | import javafx.scene.input.KeyCombination; 8 | import javafx.scene.input.Mnemonic; 9 | import javafx.stage.Window; 10 | 11 | 12 | /** 13 | * Hot Key. 14 | * 15 | * TODO: 16 | * - register with multiple windows 17 | * - update scene.getAccelerators() when key combination changes 18 | * - new Mnemonic(node, keyCombination), scene.addMnemonic(m) 19 | */ 20 | public class HotKey 21 | { 22 | private final String id; 23 | private KeyCombination key; 24 | private Runnable action; 25 | 26 | 27 | public HotKey(String id, KeyCombination key) 28 | { 29 | this.id = id; 30 | this.key = key; 31 | } 32 | 33 | 34 | public void setKeyCombination(KeyCombination k) 35 | { 36 | // TODO scan windows for accelerators and mnemonics 37 | } 38 | 39 | 40 | public boolean isSet() 41 | { 42 | return (key != null) && (action != null); 43 | } 44 | 45 | 46 | public void attach(Node n) 47 | { 48 | if(isSet()) 49 | { 50 | Mnemonic m = new Mnemonic(n, key); 51 | Scene sc = n.getScene(); 52 | if(sc == null) 53 | { 54 | n.sceneProperty().addListener((s,p,c) -> 55 | { 56 | if(c != null) 57 | { 58 | c.addMnemonic(m); 59 | // do it once 60 | n.sceneProperty().removeListener((ChangeListener)this); 61 | } 62 | }); 63 | } 64 | else 65 | { 66 | sc.addMnemonic(m); 67 | } 68 | } 69 | } 70 | 71 | 72 | public void attach(Window w) 73 | { 74 | if(isSet()) 75 | { 76 | Scene sc = w.getScene(); 77 | if(sc != null) 78 | { 79 | sc.getAccelerators().put(key, action); 80 | } 81 | } 82 | } 83 | 84 | 85 | public void attach(MenuItem m) 86 | { 87 | if(key != null) 88 | { 89 | m.setAccelerator(key); 90 | } 91 | else 92 | { 93 | // TODO 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/goryachev/fx/IStyledText.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.paint.Color; 4 | 5 | 6 | /** 7 | * Defines an interface for monospaced rich text with limited styling, 8 | * as provided by TextCellStyle. 9 | * 10 | * This class only works in situations where there is 1:1 correspondence 11 | * between characters and glyphs, and therefore does not support combining characters 12 | * or Unicode symbols beyond the Basic Multilingual Plane. 13 | */ 14 | public interface IStyledText 15 | { 16 | /** returns the plain text, or null */ 17 | public String getPlainText(); 18 | 19 | 20 | /** length of the plain text, or 0 if unknown */ 21 | public int getTextLength(); 22 | 23 | 24 | /** 25 | * returns cell styles at the given char index, or null if no styling exists. 26 | * The styles should not include view-specific styles such as current line or cursor. 27 | */ 28 | public TextCellStyle getCellStyle(int charOffset); 29 | 30 | 31 | /** returns a line color or null */ 32 | public Color getLineColor(); 33 | 34 | 35 | /** returns a character in the specific cell */ 36 | public char charAt(int charOffset); 37 | } 38 | -------------------------------------------------------------------------------- /src/goryachev/fx/IconBase.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import java.util.Collection; 4 | import javafx.scene.Node; 5 | import javafx.scene.layout.Region; 6 | 7 | 8 | /** 9 | * Icon Base Region. 10 | */ 11 | public class IconBase 12 | extends Region 13 | { 14 | public IconBase(double size) 15 | { 16 | this(size, size); 17 | } 18 | 19 | 20 | public IconBase(double width, double height) 21 | { 22 | setMinSize(width, height); 23 | setPrefSize(width, height); 24 | setMaxSize(width, height); 25 | } 26 | 27 | 28 | public void add(Node n) 29 | { 30 | getChildren().add(n); 31 | } 32 | 33 | 34 | public void addAll(Node ... ns) 35 | { 36 | getChildren().addAll(ns); 37 | } 38 | 39 | 40 | public void addAll(Collection ns) 41 | { 42 | getChildren().addAll(ns); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/goryachev/fx/SSConverter.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import goryachev.common.util.SStream; 4 | 5 | 6 | /** 7 | * String Stream Converter. 8 | */ 9 | public interface SSConverter 10 | { 11 | public abstract SStream toStream(T object); 12 | 13 | 14 | public abstract T fromStream(SStream s); 15 | } 16 | -------------------------------------------------------------------------------- /src/goryachev/fx/ShutdownChoice.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2024 Andy Goryachev 2 | package goryachev.fx; 3 | 4 | 5 | /** 6 | * User choice for saving/discarding modified content in multiple windows 7 | * when exiting the application. 8 | */ 9 | public enum ShutdownChoice 10 | { 11 | CANCEL, 12 | CONTINUE, 13 | DISCARD_ALL, 14 | SAVE_ALL, 15 | UNDEFINED; 16 | } -------------------------------------------------------------------------------- /src/goryachev/fx/SimpleStyledText.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.paint.Color; 4 | 5 | 6 | /** 7 | * Single-style Styled Text. 8 | */ 9 | public class SimpleStyledText 10 | implements IStyledText 11 | { 12 | private final String text; 13 | private final TextCellStyle style; 14 | 15 | 16 | public SimpleStyledText(String text, TextCellStyle style) 17 | { 18 | this.text = text; 19 | this.style = style; 20 | } 21 | 22 | 23 | public static SimpleStyledText of(String text, Color textColor) 24 | { 25 | if(text == null) 26 | { 27 | return null; 28 | } 29 | 30 | TextCellStyle s = new TextCellStyle(textColor); 31 | 32 | return new SimpleStyledText(text, s); 33 | } 34 | 35 | 36 | public static SimpleStyledText of(String text, Color textColor, Color lineColor) 37 | { 38 | if(text == null) 39 | { 40 | text = ""; 41 | } 42 | 43 | TextCellStyle s = new TextCellStyle(textColor); 44 | 45 | return new SimpleStyledText(text, s) 46 | { 47 | public Color getLineColor() 48 | { 49 | return lineColor; 50 | } 51 | }; 52 | } 53 | 54 | 55 | public String getPlainText() 56 | { 57 | return text; 58 | } 59 | 60 | 61 | public int getTextLength() 62 | { 63 | return text.length(); 64 | } 65 | 66 | 67 | public TextCellStyle getCellStyle(int charOffset) 68 | { 69 | return style; 70 | } 71 | 72 | 73 | public Color getLineColor() 74 | { 75 | return null; 76 | } 77 | 78 | 79 | public char charAt(int charOffset) 80 | { 81 | return text.charAt(charOffset); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/goryachev/fx/StyledTextFlow.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.text.Text; 4 | import javafx.scene.text.TextFlow; 5 | 6 | 7 | /** 8 | * Styled Text Flow. 9 | */ 10 | public class StyledTextFlow 11 | extends TextFlow 12 | { 13 | public StyledTextFlow() 14 | { 15 | } 16 | 17 | 18 | public void append(CharSequence text) 19 | { 20 | Text t = new Text(text.toString()); 21 | getChildren().add(t); 22 | } 23 | 24 | 25 | public void append(CssStyle style, CharSequence text) 26 | { 27 | Text t = new Text(text.toString()); 28 | FX.style(t, style); 29 | getChildren().add(t); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/goryachev/fx/TextCellMetrics.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2018-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import javafx.scene.text.Font; 4 | 5 | 6 | /** 7 | * Monospaced Text Cell Metrics. 8 | */ 9 | public class TextCellMetrics 10 | { 11 | public final Font font; 12 | public final double baseline; 13 | public final int cellWidth; 14 | public final int cellHeight; 15 | 16 | 17 | public TextCellMetrics(Font f, double baseline, int cellWidth, int cellHeight) 18 | { 19 | this.font = f; 20 | this.cellHeight = cellHeight; 21 | this.baseline = baseline; 22 | this.cellWidth = cellWidth; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/goryachev/fx/XScrollBar.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx; 3 | import java.util.function.Consumer; 4 | import javafx.geometry.HPos; 5 | import javafx.geometry.VPos; 6 | import javafx.scene.canvas.Canvas; 7 | import javafx.scene.control.ScrollBar; 8 | 9 | 10 | /** 11 | * A ScrollBar with underlying Canvas. 12 | */ 13 | public class XScrollBar 14 | extends ScrollBar 15 | { 16 | private Canvas canvas; 17 | private Consumer painter; 18 | 19 | 20 | public XScrollBar() 21 | { 22 | setStyle("-fx-background-color:transparent; -fx-background-insets:0px;"); 23 | } 24 | 25 | 26 | public void setPainter(Consumer p) 27 | { 28 | if(canvas != null) 29 | { 30 | getChildren().remove(canvas); 31 | canvas = null; 32 | } 33 | 34 | this.painter = p; 35 | requestLayout(); 36 | } 37 | 38 | 39 | protected void layoutChildren() 40 | { 41 | super.layoutChildren(); 42 | 43 | if(painter == null) 44 | { 45 | return; 46 | } 47 | 48 | double w = getWidth(); 49 | double h = getHeight(); 50 | 51 | if 52 | ( 53 | (canvas == null) || 54 | (canvas.getWidth() != w) || 55 | (canvas.getHeight() != h) 56 | ) 57 | { 58 | if(canvas != null) 59 | { 60 | getChildren().remove(canvas); 61 | } 62 | 63 | canvas = new Canvas(w, h); 64 | canvas.setManaged(false); 65 | getChildren().add(0, canvas); 66 | layoutInArea(canvas, 0, 0, w, h, 0, null, true, true, HPos.CENTER, VPos.CENTER); 67 | 68 | painter.accept(canvas); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/ClearIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FxPath; 4 | import goryachev.fx.IconBase; 5 | import javafx.scene.Group; 6 | import javafx.scene.paint.Color; 7 | import javafx.scene.shape.Circle; 8 | import javafx.scene.shape.StrokeLineCap; 9 | 10 | 11 | /** 12 | * Clear Field Icon. 13 | */ 14 | public class ClearIcon 15 | extends IconBase 16 | { 17 | public ClearIcon(double size) 18 | { 19 | super(size); 20 | 21 | double r = 0.4 * size; 22 | double w = 0.075 * size; 23 | double d = 0.14 * size; 24 | 25 | Circle c = new Circle(0, 0, r); 26 | c.setFill(null); 27 | c.setStrokeWidth(0); 28 | c.setStroke(null); 29 | c.setFill(Color.LIGHTGRAY); 30 | 31 | FxPath p = new FxPath(); 32 | p.setStrokeLineCap(StrokeLineCap.SQUARE); 33 | p.setStroke(Color.WHITE); 34 | p.setStrokeWidth(w); 35 | p.moveto(-d, -d); 36 | p.lineto(d, d); 37 | p.moveto(d, -d); 38 | p.lineto(-d, d); 39 | 40 | Group g = new Group(c, p); 41 | g.setTranslateX(size * 0.5); 42 | g.setTranslateY(size * 0.5); 43 | 44 | add(g); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/CloseIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FxPath; 4 | import goryachev.fx.IconBase; 5 | import javafx.scene.Group; 6 | import javafx.scene.paint.Color; 7 | import javafx.scene.shape.Circle; 8 | import javafx.scene.shape.StrokeLineCap; 9 | 10 | 11 | /** 12 | * Close Icon. 13 | */ 14 | public class CloseIcon 15 | extends IconBase 16 | { 17 | public CloseIcon(double size) 18 | { 19 | super(size); 20 | 21 | double r = 0.4 * size; 22 | double w = 0.075 * size; 23 | double d = 0.3 * size; 24 | 25 | FxPath p = new FxPath(); 26 | p.setStrokeLineCap(StrokeLineCap.ROUND); 27 | p.setStroke(Color.BLACK); 28 | p.setStrokeWidth(w); 29 | p.moveto(-d, -d); 30 | p.lineto(d, d); 31 | p.moveto(d, -d); 32 | p.lineto(-d, d); 33 | 34 | Group g = new Group(p); 35 | g.setTranslateX(size * 0.5); 36 | g.setTranslateY(size * 0.5); 37 | 38 | add(g); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/EmptyIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2020-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import javafx.scene.layout.Region; 4 | 5 | 6 | /** 7 | * Empty Icon. 8 | */ 9 | public class EmptyIcon 10 | extends Region 11 | { 12 | public EmptyIcon(double size) 13 | { 14 | setPrefWidth(size); 15 | setPrefHeight(size); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/FindIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FxPath; 4 | import goryachev.fx.IconBase; 5 | import javafx.scene.Group; 6 | import javafx.scene.paint.Color; 7 | import javafx.scene.shape.Circle; 8 | import javafx.scene.shape.StrokeLineCap; 9 | 10 | 11 | /** 12 | * Find Icon. 13 | */ 14 | public class FindIcon 15 | extends IconBase 16 | { 17 | public FindIcon(double size) 18 | { 19 | super(size); 20 | 21 | double r = 0.3 * size; 22 | double w = 0.075 * size; 23 | double gap = 0.12 * size; 24 | double handle = 0.15 * size; 25 | 26 | Circle c = new Circle(0, 0, r); 27 | c.setFill(null); 28 | c.setStrokeWidth(w); 29 | c.setStroke(Color.BLACK); 30 | c.setFill(null); 31 | 32 | FxPath p = new FxPath(); 33 | p.setStrokeLineCap(StrokeLineCap.SQUARE); 34 | p.setStroke(Color.BLACK); 35 | p.setStrokeWidth(w); 36 | p.moveto(r, 0); 37 | p.lineto(r + gap, 0); 38 | 39 | FxPath h = new FxPath(); 40 | h.setStrokeLineCap(StrokeLineCap.ROUND); 41 | h.setStroke(Color.BLACK); 42 | h.setStrokeWidth(w * 2); 43 | h.moveto(r + gap, 0); 44 | h.lineto(r + gap + handle, 0); 45 | 46 | Group g = new Group(c, p, h); 47 | g.setRotate(135); 48 | g.setTranslateX(size * 0.30); 49 | g.setTranslateY(size * 0.54); 50 | 51 | add(g); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/GalleryIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FX; 4 | import goryachev.fx.FxPath; 5 | import goryachev.fx.IconBase; 6 | import javafx.scene.paint.Color; 7 | import javafx.scene.shape.StrokeLineCap; 8 | 9 | 10 | /** 11 | * Gallery Icon. 12 | */ 13 | public class GalleryIcon 14 | extends IconBase 15 | { 16 | public GalleryIcon(double size) 17 | { 18 | this(size, 3, 3); 19 | } 20 | 21 | 22 | public GalleryIcon(double size, int cols, int rows) 23 | { 24 | super(size); 25 | 26 | double N = 3; 27 | double dx = size / (N * cols + Math.max(0, cols - 1)); 28 | double dy = size / (N * rows + Math.max(0, rows - 1)); 29 | double th = size * 0.05 / Math.min(cols, rows); 30 | double w = N * dx; 31 | double h = N * dy; 32 | 33 | FxPath p = new FxPath(); 34 | p.setStroke(Color.BLACK); 35 | p.setStrokeWidth(th); 36 | p.setStrokeLineCap(StrokeLineCap.ROUND); 37 | p.setFill(FX.alpha(Color.BLACK, 0.5)); 38 | 39 | for(int r=0; r 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FxPath; 4 | import goryachev.fx.IconBase; 5 | import javafx.scene.paint.Color; 6 | import javafx.scene.shape.StrokeLineCap; 7 | 8 | 9 | /** 10 | * Hamburger (Settings) Icon. 11 | */ 12 | public class HamburgerIcon 13 | extends IconBase 14 | { 15 | public HamburgerIcon(double sz) 16 | { 17 | super(sz); 18 | 19 | double gapx = sz * 0.2; 20 | double gapy = sz * 0.25; 21 | double th = sz * 0.075; 22 | 23 | double x0 = gapx; 24 | double xm = sz - gapx; 25 | double y0 = gapy; 26 | double y1 = sz / 2; 27 | double ym = sz - gapy; 28 | 29 | FxPath p = new FxPath(); 30 | p.setStroke(Color.BLACK); 31 | p.setStrokeWidth(th); 32 | p.setStrokeLineCap(StrokeLineCap.ROUND); 33 | 34 | p.moveto(x0, y0); 35 | p.lineto(xm, y0); 36 | 37 | p.moveto(x0, y1); 38 | p.lineto(xm, y1); 39 | 40 | p.moveto(x0, ym); 41 | p.lineto(xm, ym); 42 | 43 | add(p); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/ProcessingIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.fx.FxIconBuilder; 4 | import goryachev.fx.IconBase; 5 | import javafx.animation.Animation; 6 | import javafx.animation.Interpolator; 7 | import javafx.animation.RotateTransition; 8 | import javafx.scene.paint.Color; 9 | import javafx.scene.shape.StrokeLineCap; 10 | import javafx.util.Duration; 11 | 12 | 13 | /** 14 | * Processing Icon. 15 | */ 16 | public class ProcessingIcon 17 | { 18 | private ProcessingIcon() 19 | { 20 | } 21 | 22 | 23 | public static IconBase create(double size) 24 | { 25 | double sz2 = size / 2.0; 26 | FxIconBuilder b = new FxIconBuilder(size, sz2, sz2); 27 | 28 | double a = Math.PI / 4; 29 | double r = 0.8 * sz2; 30 | double w = 0.15 * sz2; 31 | 32 | b.setFill(null); 33 | b.setStrokeWidth(w); 34 | b.setStrokeLineCap(StrokeLineCap.ROUND); 35 | b.setStrokeColor(Color.BLACK); 36 | 37 | // beware of clipping 38 | // https://bugs.openjdk.java.net/browse/JDK-8088365 39 | // b.setEffect(new Bloom(0.5)); 40 | 41 | b.newPath(); 42 | b.moveTo(r * Math.cos(a), -r * Math.sin(a)); 43 | b.arcRel(0, 0, r, -(Math.PI - a - a)); 44 | b.moveTo(r * Math.cos(a), r * Math.sin(a)); 45 | b.arcRel(0, 0, r, (Math.PI - a - a)); 46 | 47 | IconBase ic = b.getIcon(); 48 | 49 | // FIX BUG click on a title bar and rotation pauses (and might stop completely) 50 | RotateTransition t = new RotateTransition(Duration.millis(750), ic); 51 | t.setByAngle(360); 52 | t.setCycleCount(RotateTransition.INDEFINITE); 53 | t.setInterpolator(Interpolator.LINEAR); 54 | t.play(); 55 | 56 | return ic; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/goryachev/fx/icon/StarIcon.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fx.icon; 3 | import goryachev.common.util.CKit; 4 | import goryachev.fx.FxPath; 5 | import goryachev.fx.IconBase; 6 | import javafx.scene.Group; 7 | import javafx.scene.paint.Color; 8 | import javafx.scene.shape.StrokeLineCap; 9 | import javafx.scene.shape.StrokeLineJoin; 10 | 11 | 12 | /** 13 | * Star Icon. 14 | */ 15 | public class StarIcon 16 | extends IconBase 17 | { 18 | public StarIcon(double size, Color fill) 19 | { 20 | super(size); 21 | 22 | double rm = size * 0.4; 23 | double r0 = size * 0.15; 24 | double w = size * 0.0325; 25 | 26 | FxPath p = new FxPath(); 27 | p.setStrokeLineCap(StrokeLineCap.ROUND); 28 | p.setStrokeLineJoin(StrokeLineJoin.ROUND); 29 | p.setStroke(Color.BLACK); 30 | p.setStrokeWidth(w); 31 | p.setFill(fill); 32 | 33 | for(int i=0; i<11; i++) 34 | { 35 | double a = Math.PI * i / 5; 36 | double r = CKit.isEven(i) ? rm : r0; 37 | double x = r * Math.cos(a); 38 | double y = r * Math.sin(a); 39 | 40 | switch(i) 41 | { 42 | case 0: 43 | p.moveto(x, y); 44 | break; 45 | case 10: 46 | p.close(); 47 | break; 48 | default: 49 | p.lineto(x, y); 50 | break; 51 | } 52 | } 53 | 54 | Group g = new Group(p); 55 | g.setRotate(-90); 56 | g.setTranslateX(size * 0.5); 57 | g.setTranslateY(size * 0.5); 58 | 59 | add(g); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/ColorMixer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import goryachev.fx.FX; 4 | import javafx.scene.paint.Color; 5 | 6 | 7 | /** 8 | * Color Mixer performs gamma-correct runtime mixing of colors. 9 | */ 10 | public class ColorMixer 11 | { 12 | private Object value; // Color or Color[] 13 | 14 | 15 | public ColorMixer() 16 | { 17 | } 18 | 19 | 20 | public ColorMixer(Color base) 21 | { 22 | this.value = base; 23 | } 24 | 25 | 26 | public ColorMixer(ColorMixer x) 27 | { 28 | if(x != null) 29 | { 30 | value = x.value; 31 | } 32 | } 33 | 34 | 35 | public void add(Color c) 36 | { 37 | if(value instanceof Color) 38 | { 39 | value = new Color[] { (Color)value, c }; 40 | } 41 | else if(value instanceof Color[]) 42 | { 43 | Color[] old = (Color[])value; 44 | Color[] cs = new Color[old.length + 1]; 45 | System.arraycopy(old, 0, cs, 0, old.length); 46 | cs[old.length] = c; 47 | value = cs; 48 | } 49 | else 50 | { 51 | value = c; 52 | } 53 | } 54 | 55 | 56 | public Color getColor() 57 | { 58 | if(value instanceof Color) 59 | { 60 | return (Color)value; 61 | } 62 | else if(value instanceof Color[]) 63 | { 64 | Color c = mix((Color[])value); 65 | value = c; 66 | return c; 67 | } 68 | return null; 69 | } 70 | 71 | 72 | public static Color mix(Color[] colors) 73 | { 74 | return FX.mix(colors); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/DisconnectableIntegerListener.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import goryachev.common.util.IDisconnectable; 4 | import java.util.function.IntConsumer; 5 | import javafx.beans.property.ReadOnlyIntegerProperty; 6 | import javafx.beans.value.ChangeListener; 7 | import javafx.beans.value.ObservableValue; 8 | 9 | 10 | /** 11 | * Disconnectable Integer Listener. 12 | */ 13 | public class DisconnectableIntegerListener 14 | implements ChangeListener, IDisconnectable 15 | { 16 | private final ReadOnlyIntegerProperty prop; 17 | private final IntConsumer onChange; 18 | 19 | 20 | public DisconnectableIntegerListener(ReadOnlyIntegerProperty prop, IntConsumer onChange) 21 | { 22 | this.prop = prop; 23 | this.onChange = onChange; 24 | 25 | prop.addListener(this); 26 | } 27 | 28 | 29 | public void disconnect() 30 | { 31 | prop.removeListener(this); 32 | } 33 | 34 | 35 | public void changed(ObservableValue observable, Number oldValue, Number newValue) 36 | { 37 | // newValue cannot be null because the source is a ReadOnlyIntegerProperty. 38 | int v = newValue.intValue(); 39 | onChange.accept(v); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/FxCssProp.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import goryachev.common.util.SB; 4 | 5 | 6 | /** 7 | * Fx Css Property. 8 | */ 9 | public class FxCssProp 10 | { 11 | protected final String name; 12 | protected final Object value; 13 | 14 | 15 | public FxCssProp(String name, Object value) 16 | { 17 | this.name = name; 18 | this.value = value; 19 | } 20 | 21 | 22 | public void write(SB sb) 23 | { 24 | sb.a(name); 25 | sb.a(": "); 26 | sb.a(CssTools.toValue(value)); 27 | sb.a("; "); 28 | } 29 | 30 | 31 | public String toString() 32 | { 33 | SB sb = new SB(); 34 | write(sb); 35 | return sb.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/FxStyleHandler.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import goryachev.common.util.CMap; 4 | import goryachev.common.util.SB; 5 | import java.util.Map; 6 | import java.util.StringTokenizer; 7 | 8 | 9 | /** 10 | * A utility for adding/removing CSS styles. 11 | */ 12 | public class FxStyleHandler 13 | { 14 | private final CMap styles = new CMap(); 15 | 16 | 17 | public FxStyleHandler(String style) 18 | { 19 | if(style != null) 20 | { 21 | StringTokenizer tok = new StringTokenizer(style, ";"); 22 | while(tok.hasMoreElements()) 23 | { 24 | String s = tok.nextToken(); 25 | int ix = s.indexOf(':'); 26 | if(ix > 0) 27 | { 28 | String prop = s.substring(0, ix); 29 | styles.put(prop, s); 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | public void put(String property, Object value) 37 | { 38 | String v = property + ":" + value; 39 | styles.put(property, v); 40 | } 41 | 42 | 43 | public void remove(String property) 44 | { 45 | styles.remove(property); 46 | } 47 | 48 | 49 | public String toStyleString() 50 | { 51 | SB sb = new SB(256); 52 | for(Map.Entry en: styles.entrySet()) 53 | { 54 | sb.append(en.getValue()).append(';'); 55 | } 56 | return sb.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/GlyphCache.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | 4 | 5 | /** 6 | * Glyph Cache quickly converts characters to String for a limited set of alphabets: 7 | * Latin, Greek, Cyrillic. 8 | */ 9 | public class GlyphCache 10 | { 11 | private static final int CAPACITY = 0x04ff + 1; 12 | private static final String[] cache = new String[CAPACITY]; 13 | 14 | 15 | /** converts character to a String, caching Latin, Greek, and Cyrillic */ 16 | public static String get(char ch) 17 | { 18 | if(ch < CAPACITY) 19 | { 20 | String s = cache[ch]; 21 | if(s == null) 22 | { 23 | s = String.valueOf(ch); 24 | cache[ch] = s; 25 | } 26 | return s; 27 | } 28 | else 29 | { 30 | return String.valueOf(ch); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/StandardThemes.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import goryachev.common.util.CMap; 4 | import goryachev.fx.FX; 5 | import goryachev.fx.Theme; 6 | import javafx.scene.paint.Color; 7 | 8 | 9 | /** 10 | * Standard Themes. 11 | */ 12 | public class StandardThemes 13 | { 14 | /** standard light theme */ 15 | public static Theme createLightTheme() 16 | { 17 | Color base = FX.rgb(0xececec); 18 | 19 | Theme t = new Theme(); 20 | t.affirm = FX.mix(base, Color.LIGHTGREEN, 0.8); 21 | t.base = base; 22 | t.control = FX.rgb(0x666666); 23 | t.destruct = FX.mix(base, Color.MAGENTA, 0.7); 24 | t.focus = FX.rgb(0x48dd48); //FX.rgb(0xff6d00), 25 | t.outline = FX.rgb(0xdddddd); 26 | t.selectedTextBG = FX.rgb(255, 255, 148, 0.7); //Color.rgb(193, 245, 176), //FX.rgb(0xffff00), 27 | t.selectedTextFG = Color.BLACK; 28 | t.textBG = Color.WHITE; 29 | t.textFG = Color.BLACK; 30 | return t; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/TextStyleFlags.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | 4 | 5 | /** 6 | * Simple Text Style Flags 7 | */ 8 | public class TextStyleFlags 9 | { 10 | private static final int BOLD = 1; 11 | private static final int ITALIC = 1 << 2; 12 | private static final int UNDERSCORE = 1 << 3; 13 | private static final int STRIKETHROUGH = 1 << 4; 14 | 15 | 16 | public static boolean isBold(int x) 17 | { 18 | return (x & BOLD) != 0; 19 | } 20 | 21 | 22 | public static void setBold(byte[] flags, int ix, boolean on) 23 | { 24 | if(on) 25 | { 26 | flags[ix] |= BOLD; 27 | } 28 | else 29 | { 30 | flags[ix] &= (~BOLD); 31 | } 32 | } 33 | 34 | 35 | public static boolean isItalic(int x) 36 | { 37 | return (x & ITALIC) != 0; 38 | } 39 | 40 | 41 | public static void setItalic(byte[] flags, int ix, boolean on) 42 | { 43 | if(on) 44 | { 45 | flags[ix] |= ITALIC; 46 | } 47 | else 48 | { 49 | flags[ix] &= (~ITALIC); 50 | } 51 | } 52 | 53 | 54 | public static boolean isStrikeThrough(int x) 55 | { 56 | return (x & STRIKETHROUGH) != 0; 57 | } 58 | 59 | 60 | public static void setStrikeThrough(byte[] flags, int ix, boolean on) 61 | { 62 | if(on) 63 | { 64 | flags[ix] |= STRIKETHROUGH; 65 | } 66 | else 67 | { 68 | flags[ix] &= (~STRIKETHROUGH); 69 | } 70 | } 71 | 72 | 73 | public static boolean isUnderscore(int x) 74 | { 75 | return (x & UNDERSCORE) != 0; 76 | } 77 | 78 | 79 | public static void setUnderscore(byte[] flags, int ix, boolean on) 80 | { 81 | if(on) 82 | { 83 | flags[ix] |= UNDERSCORE; 84 | } 85 | else 86 | { 87 | flags[ix] &= (~UNDERSCORE); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/goryachev/fx/internal/WeakAnimation.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.internal; 3 | import java.lang.ref.WeakReference; 4 | import javafx.animation.KeyFrame; 5 | import javafx.animation.Timeline; 6 | import javafx.util.Duration; 7 | 8 | 9 | /** 10 | * Simple animation helper designed to prevent memory leaks. 11 | */ 12 | public abstract class WeakAnimation 13 | { 14 | protected abstract void handleFrame(T parent); 15 | 16 | // 17 | 18 | private final WeakReference ref; 19 | private final Timeline timeline; 20 | 21 | 22 | public WeakAnimation(T parent, Duration period) 23 | { 24 | ref = new WeakReference<>(parent); 25 | 26 | timeline = new Timeline(new KeyFrame(period, (ev) -> handleFramePrivate())); 27 | timeline.setCycleCount(Timeline.INDEFINITE); 28 | timeline.play(); 29 | } 30 | 31 | 32 | protected void handleFramePrivate() 33 | { 34 | T parent = ref.get(); 35 | if(parent == null) 36 | { 37 | stop(); 38 | } 39 | else 40 | { 41 | handleFrame(parent); 42 | } 43 | } 44 | 45 | 46 | public void stop() 47 | { 48 | timeline.stop(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/goryachev/fx/table/FxTableCellRenderer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.table; 3 | import javafx.scene.Node; 4 | 5 | 6 | /** 7 | * FxTableCellRenderer. 8 | */ 9 | public abstract class FxTableCellRenderer 10 | { 11 | public abstract Node getCellValue(T item); 12 | } 13 | -------------------------------------------------------------------------------- /src/goryachev/fx/table/FxTreeTableCellFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.table; 3 | import javafx.scene.control.TreeTableCell; 4 | import javafx.scene.control.TreeTableColumn; 5 | import javafx.util.Callback; 6 | 7 | 8 | /** 9 | * FxTreeTableCellFactory. 10 | */ 11 | public abstract class FxTreeTableCellFactory 12 | implements Callback, TreeTableCell> 13 | { 14 | public abstract TreeTableCell call(TreeTableColumn col); 15 | } 16 | -------------------------------------------------------------------------------- /src/goryachev/fx/table/FxTreeTableCellValueFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fx.table; 3 | import goryachev.fx.FxObject; 4 | import javafx.beans.value.ObservableValue; 5 | import javafx.scene.control.TreeItem; 6 | import javafx.scene.control.TreeTableColumn; 7 | import javafx.scene.control.TreeTableColumn.CellDataFeatures; 8 | import javafx.scene.control.TreeTableView; 9 | import javafx.util.Callback; 10 | 11 | 12 | /** 13 | * FxTreeTableCellValueFactory. 14 | */ 15 | public abstract class FxTreeTableCellValueFactory 16 | implements Callback, ObservableValue> 17 | { 18 | public abstract T value(TreeItem value, TreeTableColumn col, TreeTableView t); 19 | 20 | // 21 | 22 | public ObservableValue call(CellDataFeatures f) 23 | { 24 | T v = value(f.getValue(), f.getTreeTableColumn(), f.getTreeTableView()); 25 | return new FxObject(v); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/goryachev/fx/table/ICellRenderer.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2021-2024 Andy Goryachev 2 | package goryachev.fx.table; 3 | import javafx.scene.control.TableCell; 4 | 5 | 6 | /** 7 | * Cell Renderer. 8 | */ 9 | public interface ICellRenderer 10 | { 11 | /** 12 | * Renders the table cell value. 13 | * 14 | * @return String, Node, or null. When null is returned, it is expected that the 15 | * renderer configured the TableCell (setText, setGraphic, setAlignment etc. 16 | */ 17 | public Object renderCell(TableCell tableCell, CELL value); 18 | } 19 | -------------------------------------------------------------------------------- /src/goryachev/fx/util/FxPathBuilder.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fx.util; 3 | import goryachev.common.util.CList; 4 | import java.util.List; 5 | import javafx.scene.shape.LineTo; 6 | import javafx.scene.shape.MoveTo; 7 | import javafx.scene.shape.PathElement; 8 | 9 | 10 | /** 11 | * Utility to simplify code for building complex paths. 12 | */ 13 | public class FxPathBuilder 14 | { 15 | private final CList path = new CList<>(); 16 | 17 | 18 | public FxPathBuilder() 19 | { 20 | } 21 | 22 | 23 | public void moveto(double x, double y) 24 | { 25 | add(new MoveTo(x, y)); 26 | } 27 | 28 | 29 | public void lineto(double x, double y) 30 | { 31 | add(new LineTo(x, y)); 32 | } 33 | 34 | 35 | public List getPath() 36 | { 37 | return path; 38 | } 39 | 40 | 41 | public void add(PathElement em) 42 | { 43 | path.add(em); 44 | } 45 | 46 | 47 | public void addAll(PathElement[] elements) 48 | { 49 | path.addAll(elements); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/goryachev/fx/util/FxTools.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2022-2024 Andy Goryachev 2 | package goryachev.fx.util; 3 | import goryachev.common.util.CKit; 4 | import goryachev.fx.FX; 5 | import java.util.List; 6 | import javafx.stage.Stage; 7 | import javafx.stage.Window; 8 | 9 | 10 | /** 11 | * FxTools. 12 | */ 13 | public class FxTools 14 | { 15 | /** finds the maximum value in the list of Integers */ 16 | public static int getMaximumValue(List items) 17 | { 18 | int rv = Integer.MIN_VALUE; 19 | 20 | for(int v: items) 21 | { 22 | if(rv < v) 23 | { 24 | rv = v; 25 | } 26 | } 27 | return rv; 28 | } 29 | 30 | 31 | public static String describe(Window w) 32 | { 33 | if(w == null) 34 | { 35 | return ""; 36 | } 37 | 38 | if(w instanceof Stage s) 39 | { 40 | String title = s.getTitle(); 41 | if(CKit.isNotBlank(title)) 42 | { 43 | return title; 44 | } 45 | } 46 | return w.getClass().getSimpleName() + "(" + FX.getName(w) + ")"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/AbstractPlainTextEditorModel.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import javafx.scene.text.Text; 4 | 5 | 6 | /** 7 | * Plain Text FxEditor Model Base Class. 8 | */ 9 | public abstract class AbstractPlainTextEditorModel 10 | extends FxEditorModel 11 | { 12 | public AbstractPlainTextEditorModel() 13 | { 14 | } 15 | 16 | 17 | public LineBox getLineBox(int line) 18 | { 19 | LineBox b = new LineBox(); 20 | String s = getPlainText(line); 21 | if(s != null) 22 | { 23 | Text tx = new Text(s); 24 | b.addText(tx); 25 | } 26 | return b; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/ClipboardHandlerBase.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import javafx.scene.input.DataFormat; 4 | 5 | 6 | /** 7 | * Clipboard Handler Base. 8 | */ 9 | public abstract class ClipboardHandlerBase 10 | { 11 | public abstract Object copy(FxEditorModel model, EditorSelection sel) throws Exception; 12 | 13 | // 14 | 15 | private final DataFormat format; 16 | 17 | 18 | public ClipboardHandlerBase(DataFormat format) 19 | { 20 | this.format = format; 21 | } 22 | 23 | 24 | public DataFormat getFormat() 25 | { 26 | return format; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/Edit.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | 4 | 5 | /** 6 | * An Edit. 7 | */ 8 | public class Edit 9 | { 10 | private final EditorSelection selection; 11 | private final CharSequence replaceText; 12 | 13 | 14 | public Edit(EditorSelection sel, CharSequence replaceText) 15 | { 16 | this.selection = sel; 17 | this.replaceText = replaceText; 18 | } 19 | 20 | 21 | public EditorSelection getSelection() 22 | { 23 | return selection; 24 | } 25 | 26 | 27 | public CharSequence getReplaceText() 28 | { 29 | return replaceText; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/EditablePlainTextEditorModel.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import goryachev.common.util.CList; 4 | import javafx.scene.text.Text; 5 | 6 | 7 | /** 8 | * Editable plain text FxEditor model. 9 | */ 10 | public class EditablePlainTextEditorModel 11 | extends FxEditorModel 12 | { 13 | protected final CList lines = new CList(""); 14 | 15 | 16 | public EditablePlainTextEditorModel() 17 | { 18 | } 19 | 20 | 21 | public void setText(String text) 22 | { 23 | // TODO 24 | } 25 | 26 | 27 | public void addText(String text) 28 | { 29 | // TODO 30 | } 31 | 32 | 33 | public void insertText(int line, int pos, String text) 34 | { 35 | // TODO 36 | } 37 | 38 | 39 | public LineBox getLineBox(int line) 40 | { 41 | LineBox box = new LineBox(); 42 | String s = getPlainText(line); 43 | if(s != null) 44 | { 45 | Text t = new Text(s); 46 | box.addText(t); 47 | } 48 | return box; 49 | } 50 | 51 | 52 | public String getPlainText(int line) 53 | { 54 | return lines.get(line); 55 | } 56 | 57 | 58 | public LoadInfo getLoadInfo() 59 | { 60 | return null; 61 | } 62 | 63 | 64 | public int getLineCount() 65 | { 66 | return lines.size(); 67 | } 68 | 69 | 70 | public Edit edit(Edit ed) throws Exception 71 | { 72 | // TODO 73 | throw new Exception(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/FindPane.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import goryachev.fx.FxButton; 4 | import goryachev.fx.FxComboBox; 5 | import goryachev.fx.CPane; 6 | import goryachev.fx.FX; 7 | 8 | 9 | /** 10 | * Find Pane. 11 | */ 12 | public class FindPane 13 | extends CPane 14 | { 15 | public final FxComboBox searchField; 16 | public final FxButton ignoreCaseButton; 17 | public final FxButton wholeWordButton; 18 | public final FxButton prevButton; 19 | public final FxButton nextButton; 20 | 21 | 22 | public FindPane() 23 | { 24 | setPadding(2); 25 | 26 | searchField = new FxComboBox(); 27 | searchField.setEditable(true); 28 | 29 | ignoreCaseButton = new FxButton("Aa"); 30 | 31 | wholeWordButton = new FxButton("[]"); 32 | 33 | prevButton = new FxButton("<"); 34 | 35 | nextButton = new FxButton(">"); 36 | 37 | setHGap(5); 38 | addColumns 39 | ( 40 | CPane.PREF, 41 | CPane.FILL, 42 | CPane.PREF, 43 | CPane.PREF, 44 | CPane.PREF, 45 | CPane.PREF 46 | ); 47 | 48 | add(0, 0, FX.label("Find:")); 49 | add(1, 0, searchField); 50 | add(2, 0, ignoreCaseButton); 51 | add(3, 0, wholeWordButton); 52 | add(4, 0, prevButton); 53 | add(5, 0, nextButton); 54 | } 55 | 56 | 57 | public void focusSearch() 58 | { 59 | searchField.getEditor().requestFocus(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/FxEditorModelListener.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | 4 | 5 | /** 6 | * FxEditor Model Listener. 7 | * 8 | * The idea is that after each event, the model indexes change. 9 | * The clients should query the model for new information, using new text row indexes. 10 | */ 11 | public interface FxEditorModelListener 12 | { 13 | /** 14 | * The text between two positions has changed: either deleted, replaced, or inserted. 15 | *
16 | 	 * Before:
17 | 	 *   startLine ->  TTTTTTT|DDDDD                      | startPos=7
18 | 	 *                 DDDDDDDDDD                         |
19 | 	 *     endLine ->  DDDD|TTTTTTTTTTTT                  | endPos=4
20 | 	 * 
21 | 	 * After:
22 | 	 *   startLine ->  TTTTTTT|II                         | startCharsInserted=2
23 | 	 *                 IIII                               | linesInserted=1
24 | 	 *     endLine ->  I|TTTTTTTTTTTT                     | endCharsInserted=1
25 | 	 * 
26 | * 27 | * @param startLine - first marker line 28 | * @param startPos - first marker position (0 ... length) 29 | * @param startCharsInserted - number of characters inserted after startPos on the startLine 30 | * @param linesInserted - number of lines inserted between (and not counting) startLine and endLine 31 | * @param endLine - second marker line 32 | * @param endPos - second marker position 33 | * @param endCharsInserted - number of characters inserted before endPos on the endLine 34 | */ 35 | public void eventTextUpdated(int startLine, int startPos, int startCharsInserted, int linesInserted, int endPos, int endCharIndex, int endCharsInserted); 36 | 37 | /** 38 | * All lines in the editor have changed. 39 | * The clients should re-query the model and rebuild everything 40 | */ 41 | public void eventAllLinesChanged(); 42 | } 43 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/FxEditorStyles.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import goryachev.fx.FxStyleSheet; 4 | import goryachev.fx.Theme; 5 | 6 | 7 | /** 8 | * FxEditorStyles. 9 | */ 10 | public class FxEditorStyles 11 | extends FxStyleSheet 12 | { 13 | public Object fxEditor(Theme theme) 14 | { 15 | return selector(FxEditor.PANE).defines 16 | ( 17 | backgroundColor(commas(theme.textBG, theme.textBG)), 18 | backgroundInsets(commas(0, 1)), 19 | backgroundRadius(0), 20 | 21 | // FIX does not work 22 | selector(FOCUSED).defines 23 | ( 24 | backgroundColor(commas(theme.focus, theme.textBG)), 25 | backgroundInsets(commas(0, 1)) 26 | ) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/LoadStatus.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2019-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | 4 | 5 | /** 6 | * Load Status. 7 | */ 8 | public class LoadStatus 9 | { 10 | public static final LoadStatus UNKNOWN = new LoadStatus(0.0, true, false); 11 | 12 | private final double progress; 13 | private final boolean loading; 14 | private final boolean valid; 15 | 16 | 17 | public LoadStatus(double progress, boolean loading, boolean valid) 18 | { 19 | this.progress = progress; 20 | this.loading = loading; 21 | this.valid = valid; 22 | } 23 | 24 | 25 | public double getProgress() 26 | { 27 | return progress; 28 | } 29 | 30 | 31 | public boolean isLoading() 32 | { 33 | return loading; 34 | } 35 | 36 | 37 | public boolean isValid() 38 | { 39 | return valid; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/PlainTextClipboardHandler.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import java.io.StringWriter; 4 | import javafx.scene.input.DataFormat; 5 | 6 | 7 | /** 8 | * Plain Text Clipboard Handler. 9 | */ 10 | public class PlainTextClipboardHandler 11 | extends ClipboardHandlerBase 12 | { 13 | public PlainTextClipboardHandler() 14 | { 15 | super(DataFormat.PLAIN_TEXT); 16 | } 17 | 18 | 19 | public Object copy(FxEditorModel model, EditorSelection sel) throws Exception 20 | { 21 | StringWriter wr = new StringWriter(); 22 | model.getPlainText(sel, wr); 23 | String rv = wr.toString(); 24 | return rv; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/StyledTextPaneMouseController.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import javafx.scene.input.KeyEvent; 4 | import javafx.scene.input.MouseEvent; 5 | import javafx.scene.text.TextFlow; 6 | 7 | 8 | /** 9 | * Mouse Controller. 10 | */ 11 | public class StyledTextPaneMouseController 12 | { 13 | protected final StyledTextPane editor; 14 | 15 | 16 | public StyledTextPaneMouseController(StyledTextPane p) 17 | { 18 | this.editor = p; 19 | 20 | TextFlow ed = p.textFlow; 21 | ed.addEventFilter(KeyEvent.KEY_PRESSED, (ev) -> handleKeyPressed(ev)); 22 | ed.addEventFilter(KeyEvent.KEY_RELEASED, (ev) -> handleKeyReleased(ev)); 23 | ed.addEventFilter(KeyEvent.KEY_TYPED, (ev) -> handleKeyTyped(ev)); 24 | ed.addEventFilter(MouseEvent.MOUSE_PRESSED, (ev) -> handleMousePressed(ev)); 25 | ed.addEventFilter(MouseEvent.MOUSE_RELEASED, (ev) -> handleMouseReleased(ev)); 26 | ed.addEventFilter(MouseEvent.MOUSE_DRAGGED, (ev) -> handleMouseDragged(ev)); 27 | } 28 | 29 | 30 | protected void handleKeyPressed(KeyEvent ev) 31 | { 32 | // TODO 33 | // switch(ev.getCode()) 34 | // { 35 | // case PAGE_DOWN: 36 | // } 37 | } 38 | 39 | 40 | protected void handleKeyReleased(KeyEvent ev) 41 | { 42 | // TODO 43 | } 44 | 45 | 46 | protected void handleKeyTyped(KeyEvent ev) 47 | { 48 | // TODO 49 | } 50 | 51 | 52 | protected void handleMousePressed(MouseEvent ev) 53 | { 54 | // FIX problem: misses mouse clicks on the empty margin 55 | // when a non-zero padding is set on styled pane 56 | int ix = editor.getInsertionIndex(ev.getScreenX(), ev.getScreenY()); 57 | editor.setSelection(ix); 58 | } 59 | 60 | 61 | protected void handleMouseDragged(MouseEvent ev) 62 | { 63 | } 64 | 65 | 66 | protected void handleMouseReleased(MouseEvent ev) 67 | { 68 | } 69 | } -------------------------------------------------------------------------------- /src/goryachev/fxeditor/TextFlowWithHighlights.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor; 3 | import goryachev.common.util.CList; 4 | import goryachev.fx.CssStyle; 5 | import javafx.scene.shape.Path; 6 | import javafx.scene.shape.PathElement; 7 | 8 | 9 | /** 10 | * TextFlow With Highlights. 11 | * 12 | * Since we can't style Text background directly, this has to be done in TextFlow. 13 | */ 14 | public class TextFlowWithHighlights 15 | extends CTextFlow 16 | { 17 | private final CList highlights = new CList(); 18 | 19 | 20 | public TextFlowWithHighlights() 21 | { 22 | } 23 | 24 | 25 | public void clearHighlights() 26 | { 27 | removeHighlights(); 28 | highlights.clear(); 29 | } 30 | 31 | 32 | /** 33 | * adds a highlight. the node that implements a highlight is a Path (Shape) 34 | */ 35 | public void addHighlight(CssStyle style, int start, int end) 36 | { 37 | HL h = new HL(); 38 | h.style = style; 39 | h.start = start; 40 | h.end = end; 41 | highlights.add(h); 42 | requestLayout(); 43 | } 44 | 45 | 46 | protected void layoutChildren() 47 | { 48 | super.layoutChildren(); 49 | 50 | updateHighlights(); 51 | } 52 | 53 | 54 | protected void updateHighlights() 55 | { 56 | removeHighlights(); 57 | 58 | int ix = 0; 59 | for(HL h: highlights) 60 | { 61 | PathElement[] es = getRange(h.start, h.end); 62 | 63 | Path p = new Path(es); 64 | p.setManaged(false); 65 | p.getStyleClass().add(h.style.getName()); 66 | getChildren().add(ix, p); 67 | 68 | h.path = p; 69 | ix++; 70 | } 71 | } 72 | 73 | 74 | protected void removeHighlights() 75 | { 76 | for(HL h: highlights) 77 | { 78 | if(h.path != null) 79 | { 80 | getChildren().remove(h.path); 81 | } 82 | } 83 | } 84 | 85 | 86 | // 87 | 88 | 89 | protected static class HL 90 | { 91 | public CssStyle style; 92 | public int start; 93 | public int end; 94 | public Path path; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/internal/CaretLocation.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2024 Andy Goryachev 2 | package goryachev.fxeditor.internal; 3 | 4 | 5 | /** 6 | * Enapsulates local caret coordinates. 7 | * A caret is expected to be a single vertical line. 8 | */ 9 | public class CaretLocation 10 | { 11 | public final double x; 12 | public final double y0; 13 | public final double y1; 14 | 15 | 16 | public CaretLocation(double x, double y0, double y1) 17 | { 18 | this.x = x; 19 | this.y0 = y0; 20 | this.y1 = y1; 21 | } 22 | 23 | 24 | public String toString() 25 | { 26 | return "(" + x + "," + y0 + ".." + y1 + ")"; 27 | } 28 | 29 | 30 | public boolean containsY(double y) 31 | { 32 | return (y >= y0) && (y < y1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/goryachev/fxeditor/internal/Markers.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package goryachev.fxeditor.internal; 3 | import goryachev.common.util.WeakList; 4 | import goryachev.fxeditor.Marker; 5 | 6 | 7 | /** 8 | * Maintains weak list of Markers. 9 | * This editor-specific class is needed to allow for marker adjustment after an editing operation. 10 | */ 11 | public class Markers 12 | { 13 | private final WeakList markers; 14 | 15 | 16 | public Markers(int size) 17 | { 18 | markers = new WeakList(); 19 | } 20 | 21 | 22 | public Marker newMarker(int lineNumber, int charIndex, boolean leading) 23 | { 24 | Marker m = new Marker(this, lineNumber, charIndex, leading); 25 | markers.add(m); 26 | 27 | if(markers.size() > 1_000_000) 28 | { 29 | markers.gc(); 30 | 31 | if(markers.size() > 1_000_000) 32 | { 33 | throw new Error("too many markers"); 34 | } 35 | } 36 | 37 | return m; 38 | } 39 | 40 | 41 | public void clear() 42 | { 43 | markers.clear(); 44 | } 45 | 46 | 47 | public void update(int startLine, int startPos, int startCharsInserted, int linesInserted, int endLine, int endPos, int endCharsInserted) 48 | { 49 | for(int i=markers.size()-1; i>=0; --i) 50 | { 51 | Marker m = markers.get(i); 52 | if(m == null) 53 | { 54 | markers.remove(i); 55 | } 56 | else 57 | { 58 | if(m.isBefore(startLine, startPos)) 59 | { 60 | // unchanged 61 | } 62 | else if(m.isAfter(endLine, endPos)) 63 | { 64 | // shift 65 | if(endLine == m.getLine()) 66 | { 67 | // marker on the end line 68 | int charDelta = endCharsInserted - (endPos - startPos); 69 | m.moveCharIndex(charDelta); 70 | } 71 | 72 | int lineDelta = linesInserted - (endLine - startLine); 73 | m.moveLine(lineDelta); 74 | } 75 | else 76 | { 77 | // reset to start 78 | m.reset(startLine, startPos, true); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/research/md/Test.md: -------------------------------------------------------------------------------- 1 | # Test 2 | 3 | ## Table 4 | 5 | | Name | Description | Right-Aligned Column | 6 | | :--- | --- | ---: | 7 | | one | Cells separated by a **pipe** (\|) character | 8 | | two | Headers separated by at least three hyphens | 9 | | three | 10 | | |four | 11 | 12 | Paragraph text. 13 | -------------------------------------------------------------------------------- /test/research/unicode/TestUnicode.java: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2024 Andy Goryachev 2 | package research.unicode; 3 | import goryachev.common.test.TF; 4 | import goryachev.common.test.Test; 5 | 6 | 7 | /** 8 | * Test Unicode. 9 | */ 10 | public class TestUnicode 11 | { 12 | public static void main(String[] args) 13 | { 14 | TF.run(); 15 | } 16 | 17 | 18 | @Test 19 | public void print() 20 | { 21 | // https://www.w3.org/People/danield/unic/unichar.htm 22 | t("Etruscan", 0x00010200, 0x00010227); 23 | t("Gothic", 0x00010230, 0x0001024B); 24 | t("Klingon", 0x000123D0, 0x000123F9); 25 | t("Western Musical Symbols", 0x0001D103, 0x0001D1D7); 26 | } 27 | 28 | 29 | protected void t(String name, int min, int max) 30 | { 31 | System.out.println(name); 32 | 33 | for(int i=min; i<=max; i++) 34 | { 35 | System.out.print(new String(Character.toChars(i))); 36 | } 37 | System.out.println("\n"); 38 | } 39 | } 40 | --------------------------------------------------------------------------------