├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── META-INF └── MANIFEST.MF ├── README.md ├── build.gradle ├── durian-swt.cocoa.macosx.aarch64 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt.cocoa.macosx.x86_64 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt.gtk.linux.x86 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt.gtk.linux.x86_64 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt.os └── src │ ├── main │ └── java │ │ └── com │ │ └── diffplug │ │ └── common │ │ └── swt │ │ └── os │ │ ├── Arch.java │ │ ├── OS.java │ │ ├── SwtPlatform.java │ │ ├── WS.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── os │ └── SwtPlatformTest.java ├── durian-swt.png ├── durian-swt.win32.win32.x86 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt.win32.win32.x86_64 └── src │ └── main │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ └── widgets │ └── SmoothTable.java ├── durian-swt ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── diffplug │ │ └── common │ │ └── swt │ │ ├── Coat.kt │ │ ├── CoatMux.java │ │ ├── ColorPool.java │ │ ├── ColumnFormat.java │ │ ├── ControlWrapper.kt │ │ ├── Corner.java │ │ ├── Fonts.java │ │ ├── InteractiveTest.java │ │ ├── LayoutWrapper.java │ │ ├── Layouts.java │ │ ├── LayoutsFillLayout.java │ │ ├── LayoutsGridData.java │ │ ├── LayoutsGridLayout.java │ │ ├── LayoutsRowData.java │ │ ├── LayoutsRowLayout.java │ │ ├── MouseClick.java │ │ ├── OnePerWidget.java │ │ ├── Shells.kt │ │ ├── SiliconFix.java │ │ ├── SwtDebug.java │ │ ├── SwtExec.java │ │ ├── SwtMisc.java │ │ ├── SwtRx.java │ │ ├── SwtThread.java │ │ ├── TypedDataField.java │ │ ├── VScrollBubble.java │ │ ├── dnd │ │ ├── CustomDragImageListener.java │ │ ├── CustomLocalTransfer.java │ │ ├── DndOp.java │ │ ├── DragSourceRouter.java │ │ ├── Draggable.java │ │ ├── DropTargetRouter.java │ │ ├── Droppable.java │ │ ├── StructuredDrag.java │ │ ├── StructuredDrop.java │ │ ├── ToolBarDrop.java │ │ ├── TypedTransfer.java │ │ └── package-info.java │ │ ├── jface │ │ ├── Actions.java │ │ ├── ColumnViewerFormat.java │ │ ├── ImageDescriptors.java │ │ ├── JFaceRx.java │ │ ├── LabelProviders.java │ │ ├── ViewerMisc.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── widgets │ │ ├── AbstractSmoothTable.java │ │ ├── ButtonPanel.java │ │ ├── FlatBtn.java │ │ ├── LayoutableLayout.java │ │ ├── LinkBtn.java │ │ ├── NoBorderBtn.java │ │ ├── RadioGroup.java │ │ ├── ScaleCtl.java │ │ └── VScrollCtl.java │ └── test │ └── java │ └── com │ └── diffplug │ └── common │ └── swt │ ├── CoatMuxTest.java │ ├── InteractiveTestTest.java │ ├── ShellsTest.java │ ├── SwtExecProfile.java │ ├── SwtExecSchedulingTest.java │ ├── SwtMiscTest.java │ ├── SwtRxTest.java │ ├── dnd │ ├── StructuredDndMappingTest.java │ └── ToolBarDropTest.java │ ├── jface │ ├── ActionsTest.java │ ├── ColumnViewerFormatTest.java │ ├── JFaceRxTest.java │ └── ViewerMiscTest.java │ └── widgets │ └── RadioGroupTest.java ├── durian.svg ├── durian.svg.license ├── gradle.properties ├── gradle ├── javadoc-eclipse │ └── package-list ├── spotless.eclipseformat.xml ├── spotless.importorder ├── spotless.license.java └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── interactive-test.png └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.MF text eol=crlf 3 | *.jar binary 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: [main] 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | jre: [17] 14 | os: [ubuntu-latest, windows-latest] 15 | include: 16 | - jre: 21 17 | os: ubuntu-latest 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Install JDK ${{ matrix.jre }} 23 | uses: actions/setup-java@v4 24 | with: 25 | distribution: "temurin" 26 | java-version: ${{ matrix.jre }} 27 | - name: gradle caching 28 | uses: gradle/actions/setup-gradle@v4 29 | - name: git fetch origin main 30 | run: git fetch origin main 31 | - name: gradlew build 32 | run: | 33 | if [ "$RUNNER_OS" == "Linux" ]; then 34 | sudo apt-get install xvfb 35 | Xvfb :99 & 36 | export DISPLAY=:99 37 | fi 38 | ./gradlew build --no-configuration-cache 39 | shell: bash 40 | - name: junit result 41 | uses: mikepenz/action-junit-report@v4 42 | if: always() # always run even if the previous step fails 43 | with: 44 | check_name: JUnit ${{ matrix.jre }} ${{ matrix.os }} 45 | report_paths: '*/build/test-results/*/TEST-*.xml' 46 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # NEXUS_USER 2 | # NEXUS_PASS64 (base64 NOTE: `base64` and `openssl base64` failed, had to use Java 3 | # byte[] data = "{{password}}".getBytes(StandardCharsets.UTF_8); 4 | # String encoded = new String(Base64.getEncoder().encode(data), StandardCharsets.UTF_8); 5 | # System.out.println(encoded); 6 | # GPG_PASSPHRASE 7 | # GPG_KEY64 (base64) 8 | # gpg --export-secret-keys --armor KEY_ID | openssl base64 | pbcopy 9 | 10 | name: deploy 11 | on: 12 | workflow_dispatch: 13 | inputs: 14 | to_publish: 15 | description: 'What to publish' 16 | required: true 17 | default: 'all' 18 | type: choice 19 | options: 20 | - all 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | name: deploy 26 | env: 27 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | ORG_GRADLE_PROJECT_nexus_user: ${{ secrets.NEXUS_USER }} 29 | ORG_GRADLE_PROJECT_nexus_pass64: ${{ secrets.NEXUS_PASS64 }} 30 | ORG_GRADLE_PROJECT_gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} 31 | ORG_GRADLE_PROJECT_gpg_key64: ${{ secrets.GPG_KEY64 }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: jdk 17 35 | uses: actions/setup-java@v4 36 | with: 37 | java-version: 17 38 | distribution: 'temurin' 39 | - name: gradle caching 40 | uses: gradle/actions/setup-gradle@v4 41 | - name: git fetch origin main 42 | run: git fetch origin main 43 | - name: publish all 44 | if: "${{ github.event.inputs.to_publish == 'all' }}" 45 | run: | 46 | ./gradlew :changelogPush -Prelease=true -Penable_publishing=true --stacktrace --warning-mode all 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # mac stuff 3 | *.DS_Store 4 | 5 | # gradle stuff 6 | .gradle/ 7 | build/ 8 | .kotlin/ 9 | 10 | # IntelliJ 11 | .idea 12 | 13 | # Eclipse stuff 14 | .project 15 | .classpath 16 | .settings/ 17 | bin/ 18 | 19 | # manifests 20 | MANIFEST.MF 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Durian 2 | 3 | Pull requests are welcome, preferably against `master`. 4 | 5 | ## Build instructions 6 | 7 | It's a bog-standard gradle build. 8 | 9 | `gradlew eclipse` 10 | * creates an Eclipse project file for you. 11 | 12 | `gradlew build` 13 | * builds the jar 14 | * runs FindBugs 15 | * checks the formatting 16 | * runs the tests 17 | 18 | If you're getting style warnings, `gradlew spotlessApply` will apply anything necessary to fix formatting. For more info on the formatter, check out [spotless](https://github.com/diffplug/spotless). 19 | 20 | ## Known problems 21 | 22 | On OS X, SWT must be started with the `-XstartOnFirstThread` flag. [The gradle test runner doesn't support this](https://discuss.gradle.org/t/gradle-test-task-and-xstartonfirstthread/6844). This only affects testing from the command line, testing from the IDE is still just fine. There is a [workaround](https://github.com/ReadyTalk/swt-bling/issues/4) by calling out to ant, but it's not friendly to our requirement to include `@Category(InteractiveTest.class)` and exclude `@Category(FailsWithoutUser.class)`. 23 | 24 | ## License 25 | 26 | By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/diffplug/durian/blob/master/LICENSE 27 | 28 | All files are released with the Apache 2.0 license as such: 29 | 30 | ``` 31 | Copyright 2020 DiffPlug 32 | 33 | Licensed under the Apache License, Version 2.0 (the "License"); 34 | you may not use this file except in compliance with the License. 35 | You may obtain a copy of the License at 36 | 37 | https://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. 44 | ``` 45 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-DocURL: https://github.com/diffplug/durian-swt 3 | Bundle-License: https://github.com/diffplug/durian-swt/blob/v3.0.0-SNA 4 | PSHOT/LICENSE 5 | Bundle-ManifestVersion: 2 6 | Bundle-RequiredExecutionEnvironment: JavaSE-1.8 7 | Bundle-SymbolicName: com.diffplug.durian.swt 8 | Bundle-Vendor: DiffPlug 9 | Bundle-Version: 3.0.0.I201807211107 10 | Export-Package: com.diffplug.common.swt;uses:="com.diffplug.common.bas 11 | e,com.diffplug.common.collect,com.diffplug.common.rx,com.diffplug.com 12 | mon.tree,io.reactivex,io.reactivex.disposables,javax.annotation,org.e 13 | clipse.jface.viewers,org.eclipse.swt.graphics,org.eclipse.swt.layout, 14 | org.eclipse.swt.widgets";version="3.0.0",com.diffplug.common.swt.jfac 15 | e;uses:="com.diffplug.common.base,com.diffplug.common.collect,com.dif 16 | fplug.common.rx,com.diffplug.common.swt,com.diffplug.common.tree,java 17 | x.annotation,org.eclipse.jface.action,org.eclipse.jface.resource,org. 18 | eclipse.jface.viewers,org.eclipse.swt.graphics,org.eclipse.swt.widget 19 | s";version="3.0.0",com.diffplug.common.swt.os;version="3.0.0" 20 | Import-Package: com.diffplug.common.base;version="[1.2,2)",com.diffplu 21 | g.common.collect;version="[1.2,2)",com.diffplug.common.primitives;ver 22 | sion="[1.2,2)",com.diffplug.common.rx;version="[3.0,4)",com.diffplug. 23 | common.swt,com.diffplug.common.swt.os,com.diffplug.common.tree;versio 24 | n="[1.2,2)",com.diffplug.common.util.concurrent;version="[1.2,2)",io. 25 | reactivex;version="[2.0,3)",io.reactivex.disposables;version="[2.0,3) 26 | ",io.reactivex.functions;version="[2.0,3)",io.reactivex.schedulers;ve 27 | rsion="[2.0,3)",io.reactivex.subjects;version="[2.0,3)",javax.annotat 28 | ion,org.eclipse.jface.action,org.eclipse.jface.layout,org.eclipse.jfa 29 | ce.resource,org.eclipse.jface.util,org.eclipse.jface.viewers,org.ecli 30 | pse.swt,org.eclipse.swt.custom,org.eclipse.swt.graphics,org.eclipse.s 31 | wt.layout,org.eclipse.swt.widgets 32 | Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" 33 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.diffplug.blowdryer' 3 | id 'com.diffplug.spotless-changelog' 4 | } 5 | group = 'com.diffplug.durian' 6 | spotlessChangelog { 7 | changelogFile 'CHANGES.md' 8 | } 9 | repositories { mavenCentral() } 10 | allprojects { 11 | apply from: 干.file('base/changelog.gradle') 12 | } 13 | apply from: 干.file('spotless/freshmark.gradle') 14 | apply from: 干.file('base/sonatype.gradle') 15 | 16 | subprojects { subProject -> 17 | apply plugin: 'java-library' 18 | apply from: 干.file('base/java.gradle') 19 | apply from: 干.file('spotless/java.gradle') 20 | 21 | ext.maven_name = subProject.name 22 | ext.javadoc_links = [ 23 | "https://javadoc.io/doc/com.diffplug.durian/durian-core/${VER_DURIAN}", 24 | "https://javadoc.io/doc/com.diffplug.durian/durian-collect/${VER_DURIAN}", 25 | "https://javadoc.io/doc/com.diffplug.durian/durian-concurrent/${VER_DURIAN}", 26 | "https://javadoc.io/doc/com.diffplug.durian/durian-debug/${VER_DURIAN_DEBUG}", 27 | "https://javadoc.io/doc/com.diffplug.durian/durian-rx/${VER_DURIAN_RX}", 28 | 'https://docs.oracle.com/javase/8/docs/api/' 29 | ].join(' ') 30 | 31 | apply from: 干.file('base/maven.gradle') 32 | apply from: 干.file('base/sonatype.gradle') 33 | javadoc { 34 | options.linksOffline 'https://help.eclipse.org/2019-12/topic/org.eclipse.platform.doc.isv/reference/api/', rootProject.file('gradle/javadoc-eclipse').absolutePath 35 | } 36 | 37 | if (subProject.name == 'durian-swt') { 38 | // configured there 39 | } else if (subProject.name == 'durian-swt.os') { 40 | dependencies { 41 | compileOnly 'com.google.code.findbugs:jsr305:3.0.2' 42 | testImplementation "junit:junit:$VER_JUNIT" 43 | } 44 | tasks.register('osMain', JavaExec) { 45 | classpath = sourceSets.main.runtimeClasspath 46 | main = 'com.diffplug.common.swt.os.OS' 47 | } 48 | } else { 49 | String platformCode = project.name.substring('durian-swt.'.length()) 50 | String SWT_TO_USE = platformCode.endsWith("x86") ? SWT_VERSION_X86 : SWT_VERSION 51 | apply plugin: 'dev.equo.p2deps' 52 | p2deps { 53 | into 'api', { 54 | p2repo "https://download.eclipse.org/eclipse/updates/$SWT_TO_USE/" 55 | install "org.eclipse.swt.$platformCode" 56 | addFilter 'no-platform-filter', { 57 | it.platform(com.diffplug.common.swt.os.SwtPlatform.parseWsOsArch(platformCode)) 58 | } 59 | } 60 | } 61 | dependencies { 62 | api project(':durian-swt') 63 | } 64 | configurations.all { 65 | resolutionStrategy.eachDependency { DependencyResolveDetails details -> 66 | if (details.requested.name.contains('${osgi.platform}')) { 67 | details.useTarget('org.eclipse.platform:org.eclipse.swt:' + details.requested.version) 68 | } 69 | } 70 | } 71 | // the manifest should be a fragment 72 | def swtPlatform = com.diffplug.common.swt.os.SwtPlatform.parseWsOsArch(platformCode) 73 | jar.manifest.attributes ( 74 | 'Fragment-Host': 'durian-swt', 75 | 'Eclipse-PlatformFilter': swtPlatform.platformFilter(), 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /durian-swt.cocoa.macosx.aarch64/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.internal.cocoa.NSPoint; 20 | import org.eclipse.swt.widgets.Composite; 21 | 22 | public final class SmoothTable extends AbstractSmoothTable.Scrollable { 23 | public SmoothTable(Composite parent, int tableStyle) { 24 | super(parent, tableStyle); 25 | } 26 | 27 | @Override 28 | protected void setTopPixelWithinTable(int topPixel) { 29 | NSPoint pt = new NSPoint(); 30 | pt.x = 0; 31 | pt.y = topPixel; 32 | table.view.scrollPoint(pt); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /durian-swt.cocoa.macosx.x86_64/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.internal.cocoa.NSPoint; 20 | import org.eclipse.swt.widgets.Composite; 21 | 22 | public final class SmoothTable extends AbstractSmoothTable.Scrollable { 23 | public SmoothTable(Composite parent, int tableStyle) { 24 | super(parent, tableStyle); 25 | } 26 | 27 | @Override 28 | protected void setTopPixelWithinTable(int topPixel) { 29 | NSPoint pt = new NSPoint(); 30 | pt.x = 0; 31 | pt.y = topPixel; 32 | table.view.scrollPoint(pt); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /durian-swt.gtk.linux.x86/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.widgets.Composite; 20 | 21 | public final class SmoothTable extends AbstractSmoothTable.Scrollable { 22 | public SmoothTable(Composite parent, int tableStyle) { 23 | super(parent, tableStyle); 24 | } 25 | 26 | @Override 27 | protected void setTopPixelWithinTable(int value) { 28 | table.getVerticalBar().setSelection(value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /durian-swt.gtk.linux.x86_64/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.widgets.Composite; 20 | 21 | public final class SmoothTable extends AbstractSmoothTable.Scrollable { 22 | public SmoothTable(Composite parent, int tableStyle) { 23 | super(parent, tableStyle); 24 | } 25 | 26 | @Override 27 | protected void setTopPixelWithinTable(int value) { 28 | table.getVerticalBar().setSelection(value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /durian-swt.os/src/main/java/com/diffplug/common/swt/os/Arch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2023 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.os; 17 | 18 | /** Enum for handling different processor architectures supported by SWT. */ 19 | public enum Arch { 20 | x86, x64, arm64, unknown; 21 | 22 | /** Returns the appropriate value depending on the arch. */ 23 | public T x86x64(T val86, T val64) { 24 | switch (this) { 25 | case x86: 26 | return val86; 27 | case x64: 28 | return val64; 29 | default: 30 | throw unsupportedException(this); 31 | } 32 | } 33 | 34 | /** Returns the appropriate value depending on the arch. */ 35 | public T x64arm64(T val64, T arm64) { 36 | switch (this) { 37 | case x64: 38 | return val64; 39 | case arm64: 40 | return arm64; 41 | default: 42 | throw unsupportedException(this); 43 | } 44 | } 45 | 46 | /** Returns the appropriate value depending on the arch. */ 47 | public T x86x64arm64(T val86, T val64, T arm64) { 48 | switch (this) { 49 | case x86: 50 | return val86; 51 | case x64: 52 | return val64; 53 | case arm64: 54 | return arm64; 55 | default: 56 | throw unsupportedException(this); 57 | } 58 | } 59 | 60 | /** Returns the appropriate value depending on the arch. */ 61 | public T x86x64arm64unknown(T val86, T val64, T arm64, T unknown) { 62 | switch (this) { 63 | case x86: 64 | return val86; 65 | case x64: 66 | return val64; 67 | case arm64: 68 | return arm64; 69 | case unknown: 70 | return unknown; 71 | default: 72 | throw unsupportedException(this); 73 | } 74 | } 75 | 76 | /** Returns the Arch for the native platform: 32-bit JVM on 64-bit Windows returns Arch.x64. */ 77 | public static Arch getNative() { 78 | return OS.getNative().getArch(); 79 | } 80 | 81 | /** Returns the Arch for the native platform: 32-bit JVM on 64-bit Windows returns Arch.x86. */ 82 | public static Arch getRunning() { 83 | return OS.getRunning().getArch(); 84 | } 85 | 86 | /** Returns an UnsupportedOperationException for the given arch. */ 87 | public static UnsupportedOperationException unsupportedException(Arch arch) { 88 | return new UnsupportedOperationException("Arch '" + arch + "' is unsupported."); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /durian-swt.os/src/main/java/com/diffplug/common/swt/os/SwtPlatform.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2023 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.os; 17 | 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.LinkedHashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.stream.Collectors; 25 | 26 | /** Models the platforms that SWT binaries are built for, useful for build tools that interact with SWT jars. */ 27 | public class SwtPlatform { 28 | /** Windowing system. */ 29 | private final String ws; 30 | /** Operating system. */ 31 | private final String os; 32 | /** CPU architecture. */ 33 | private final String arch; 34 | 35 | /** Arguments go from most-specific (windowing-system) to least-specific (CPU architecture). */ 36 | private SwtPlatform(String ws, String os, String arch) { 37 | this.ws = ws; 38 | this.os = os; 39 | this.arch = arch; 40 | } 41 | 42 | /** Returns the windowing system. */ 43 | public String getWs() { 44 | return ws; 45 | } 46 | 47 | /** Returns the operating system. */ 48 | public String getOs() { 49 | return os; 50 | } 51 | 52 | /** Returns the CPU architecture. */ 53 | public String getArch() { 54 | return arch; 55 | } 56 | 57 | @Override 58 | public boolean equals(Object otherRaw) { 59 | if (otherRaw instanceof SwtPlatform) { 60 | SwtPlatform other = (SwtPlatform) otherRaw; 61 | return ws.equals(other.ws) && os.equals(other.os) && arch.equals(other.arch); 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(ws, os, arch); 70 | } 71 | 72 | /** Returns "ws.os.arch", which is how SWT bundles are named. */ 73 | @Override 74 | public String toString() { 75 | return ws + "." + os + "." + arch; 76 | } 77 | 78 | /** Returns a string appropriate as an Eclipse-PlatformFilter in a MANIFEST.MF */ 79 | public String platformFilter() { 80 | return "(& " + "(osgi.ws=" + ws + ") " + "(osgi.os=" + os + ") " + "(osgi.arch=" + arch + ")" + " )"; 81 | } 82 | 83 | /** Returns a map containing the platform properties. */ 84 | public Map platformProperties() { 85 | Map map = new LinkedHashMap<>(); 86 | map.put("osgi.ws", ws); 87 | map.put("osgi.os", os); 88 | map.put("osgi.arch", arch); 89 | return map; 90 | } 91 | 92 | /** Returns the folder name that wuff uses for this SwtPlatform. */ 93 | public String getWuffString() { 94 | // @formatter:off 95 | Map wuffMap = new HashMap<>(); 96 | wuffMap.put(parseWsOsArch("cocoa.macosx.x86_64"), "macosx-x86_64"); 97 | wuffMap.put(parseWsOsArch("gtk.linux.x86"), "linux-x86_32"); 98 | wuffMap.put(parseWsOsArch("gtk.linux.x86_64"), "linux-x86_64"); 99 | wuffMap.put(parseWsOsArch("win32.win32.x86"), "windows-x86_32"); 100 | wuffMap.put(parseWsOsArch("win32.win32.x86_64"), "windows-x86_64"); 101 | // @formatter:on 102 | return Objects.requireNonNull(wuffMap.get(this)); 103 | } 104 | 105 | /** Parses ws.os.arch strings (which is how SWT bundles are specified). */ 106 | public static SwtPlatform parseWsOsArch(String unparsed) { 107 | String[] pieces = unparsed.split("\\.", -1); 108 | if (pieces.length != 3) { 109 | throw new IllegalArgumentException(unparsed + " should have the form 'ws.os.arch'."); 110 | } 111 | String ws = pieces[0]; 112 | String os = pieces[1]; 113 | String arch = pieces[2]; 114 | return new SwtPlatform(ws, os, arch); 115 | } 116 | 117 | /** Returns the SwtPlatform for the native platform: 32-bit JVM on 64-bit Windows returns x86_64. */ 118 | public static SwtPlatform getNative() { 119 | return fromOS(OS.getNative()); 120 | } 121 | 122 | /** Returns the SwtPlatform for the running platform: 32-bit JVM on 64-bit Windows returns x86. */ 123 | public static SwtPlatform getRunning() { 124 | return fromOS(OS.getRunning()); 125 | } 126 | 127 | /** Converts an OS to an SwtPlatform. */ 128 | public static SwtPlatform fromOS(OS raw) { 129 | String ws = raw.winMacLinux("win32", "cocoa", "gtk"); 130 | String os = raw.winMacLinux("win32", "macosx", "linux"); 131 | String arch = raw.getArch().x86x64arm64unknown("x86", "x86_64", "aarch64", "unknown"); 132 | return new SwtPlatform(ws, os, arch); 133 | } 134 | 135 | /** Converts an SwtPlatform back to an OS. */ 136 | public OS toOS() { 137 | for (OS os : OS.values()) { 138 | if (fromOS(os).equals(this)) { 139 | return os; 140 | } 141 | } 142 | throw new IllegalArgumentException("No known OS matches this platform: " + this); 143 | } 144 | 145 | /** Returns all of the platforms. */ 146 | public static List getAll() { 147 | return Arrays.asList(OS.values()).stream() 148 | .map(SwtPlatform::fromOS) 149 | .collect(Collectors.toList()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /durian-swt.os/src/main/java/com/diffplug/common/swt/os/WS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.os; 17 | 18 | /** Models the windowing systems that we currently support. */ 19 | public enum WS { 20 | WIN, COCOA, GTK; 21 | 22 | public boolean isWin() { 23 | return this == WIN; 24 | } 25 | 26 | public boolean isCocoa() { 27 | return this == COCOA; 28 | } 29 | 30 | public boolean isGTK() { 31 | return this == GTK; 32 | } 33 | 34 | public T winCocoaGtk(T win, T cocoa, T gtk) { 35 | // @formatter:off 36 | switch (this) { 37 | case WIN: return win; 38 | case COCOA: return cocoa; 39 | case GTK: return gtk; 40 | default: throw unsupportedException(this); 41 | } 42 | // @formatter:on 43 | } 44 | 45 | private static final WS RUNNING_WS = OS.getRunning().winMacLinux(WIN, COCOA, GTK); 46 | 47 | public static WS getRunning() { 48 | return RUNNING_WS; 49 | } 50 | 51 | /** Returns an UnsupportedOperationException for the given arch. */ 52 | public static UnsupportedOperationException unsupportedException(WS ws) { 53 | return new UnsupportedOperationException("Window system '" + ws + "' is unsupported."); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /durian-swt.os/src/main/java/com/diffplug/common/swt/os/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.diffplug.common.swt.os; 3 | 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | -------------------------------------------------------------------------------- /durian-swt.os/src/test/java/com/diffplug/common/swt/os/SwtPlatformTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.os; 17 | 18 | 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | 22 | public class SwtPlatformTest { 23 | @Test 24 | public void test() { 25 | SwtPlatform platform = SwtPlatform.parseWsOsArch("win32.win32.x86"); 26 | Assert.assertEquals("win32", platform.getWs()); 27 | Assert.assertEquals("win32", platform.getOs()); 28 | Assert.assertEquals("x86", platform.getArch()); 29 | 30 | Assert.assertEquals("(& (osgi.ws=win32) (osgi.os=win32) (osgi.arch=x86) )", platform.platformFilter()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /durian-swt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diffplug/durian-swt/b6f99cff6318c8b7111abd8dc09a29cb9f636496/durian-swt.png -------------------------------------------------------------------------------- /durian-swt.win32.win32.x86/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.widgets.Composite; 20 | 21 | public final class SmoothTable extends AbstractSmoothTable.Unscrollable { 22 | public SmoothTable(Composite parent, int tableStyle) { 23 | super(parent, tableStyle); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /durian-swt.win32.win32.x86_64/src/main/java/com/diffplug/common/swt/widgets/SmoothTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import org.eclipse.swt.widgets.Composite; 20 | 21 | public final class SmoothTable extends AbstractSmoothTable.Unscrollable { 22 | public SmoothTable(Composite parent, int tableStyle) { 23 | super(parent, tableStyle); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /durian-swt/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 干.file('base/kotlin.gradle') 2 | 3 | apply plugin: 'dev.equo.p2deps' 4 | p2deps { 5 | into 'api', { 6 | p2repo "https://download.eclipse.org/eclipse/updates/$SWT_VERSION/" 7 | install 'org.eclipse.swt' 8 | install 'org.eclipse.jface' 9 | addFilter 'no-platform', { 10 | it.platformNone() 11 | } 12 | } 13 | into 'compileOnly', { 14 | p2repo "https://download.eclipse.org/eclipse/updates/$SWT_VERSION/" 15 | install 'org.eclipse.swt' 16 | } 17 | into 'testImplementation', { 18 | p2repo "https://download.eclipse.org/eclipse/updates/$SWT_VERSION/" 19 | install 'org.eclipse.swt' 20 | } 21 | } 22 | dependencies { 23 | api project(':durian-swt.os') 24 | api "com.diffplug.durian:durian-rx:$VER_DURIAN_RX" 25 | implementation "com.diffplug.durian:durian-core:$VER_DURIAN" 26 | implementation "com.diffplug.durian:durian-collect:$VER_DURIAN" 27 | implementation "com.diffplug.durian:durian-concurrent:$VER_DURIAN" 28 | compileOnly 'com.google.code.findbugs:jsr305:3.0.2' 29 | compileOnly 'com.google.code.findbugs:annotations:3.0.1' 30 | compileOnly "org.jetbrains:annotations:23.0.0" 31 | 32 | testImplementation "junit:junit:$VER_JUNIT" 33 | testImplementation "org.assertj:assertj-core:$VER_ASSERTJ" 34 | testImplementation "com.diffplug.durian:durian-debug:$VER_DURIAN_DEBUG" 35 | } 36 | // in java-library mode, durian-swt.os remains as a folder of classes for gradle internally, which the osgi plugin does not like 37 | tasks.named('jar').configure { 38 | dependsOn(':durian-swt.os:jar') 39 | } 40 | 41 | ///////////////////////// 42 | // INTERACTIVE TESTING // 43 | ///////////////////////// 44 | // standard `gradlew test` will autoclose after 500ms 45 | boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac") 46 | test { 47 | systemProperty 'com.diffplug.test.autoclose.milliseconds', '500' 48 | useJUnit { 49 | // there are some tests that can't pass without a user, so we'll exclude them 50 | excludeCategories 'com.diffplug.common.swt.InteractiveTest$FailsWithoutUser' 51 | // SWT tests don't work in gradle on OS X (https://github.com/ReadyTalk/swt-bling/issues/4) 52 | if (isMac) { 53 | jvmArgs = ['-XstartOnFirstThread'] 54 | } 55 | } 56 | // log all test events 57 | testLogging { 58 | events "failed", "passed", "skipped", "standard_error", "standard_out", "started" 59 | } 60 | } 61 | 62 | // only run the interactive tests 63 | tasks.register('interactiveTest', Test) { 64 | systemProperty 'com.diffplug.test.autoclose.milliseconds', null 65 | useJUnit { 66 | includeCategories 'com.diffplug.common.swt.InteractiveTest' 67 | if (isMac) { 68 | jvmArgs = ['-XstartOnFirstThread'] 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/Coat.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt 17 | 18 | import com.diffplug.common.swt.Coat.Returning 19 | import org.eclipse.swt.widgets.Composite 20 | 21 | /** 22 | * A function that can be "put on" a blank [Composite]. 23 | * 24 | * 25 | * An SWT Composite is a blank canvas. As such, it's common to write functions 26 | * that look like `void initializeCmp(Composite cmp)`. In order to make higher-order 27 | * functionality, such as a utility for stacking `Composite`s, you need a way to pass 28 | * these kinds of functions as arguments. That's what `Coat` does. 29 | */ 30 | fun interface Coat { 31 | /** 32 | * Populates the given composite. 33 | * Caller promises that the composite has no layout and contains no children. 34 | */ 35 | fun putOn(cmp: Composite) 36 | 37 | /** A Coat which returns a handle to the content it created. */ 38 | fun interface Returning { 39 | /** 40 | * Populates the given composite, and returns a handle for communicating with the created GUI. 41 | * Caller promises that the composite has no layout, and contains no children. 42 | */ 43 | fun putOn(cmp: Composite): T 44 | 45 | companion object { 46 | /** Converts a non-returning Coat to a Coat.Returning. */ 47 | fun fromNonReturning(coat: Coat, returnValue: T): Returning { 48 | return Returning { cmp: Composite -> 49 | coat.putOn(cmp) 50 | returnValue 51 | } 52 | } 53 | } 54 | } 55 | 56 | companion object { 57 | /** A Coat which does nothing. */ 58 | @JvmStatic 59 | fun empty(): Coat { 60 | return EMPTY 61 | } 62 | 63 | val EMPTY = Coat { cmp: Composite -> } 64 | } 65 | } -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/CoatMux.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import com.diffplug.common.base.Preconditions; 20 | import com.diffplug.common.rx.Chit; 21 | import com.diffplug.common.rx.Rx; 22 | import com.diffplug.common.rx.RxBox; 23 | import com.diffplug.common.rx.RxGetter; 24 | import java.util.Optional; 25 | import java.util.function.Function; 26 | import javax.annotation.Nullable; 27 | import org.eclipse.swt.SWT; 28 | import org.eclipse.swt.custom.StackLayout; 29 | import org.eclipse.swt.widgets.Composite; 30 | import org.eclipse.swt.widgets.Control; 31 | 32 | /** A widget that switches between multiple `Coat`s. */ 33 | @SwtThread 34 | public class CoatMux extends ControlWrapper.AroundControl { 35 | /** The StackLayout for switching between layers. */ 36 | private StackLayout stack = new StackLayout(); 37 | /** The currently displayed layer (if any). */ 38 | private RxBox>> currentLayer = RxBox.of(Optional.empty()); 39 | 40 | public CoatMux(Composite parent) { 41 | this(parent, SWT.NONE); 42 | } 43 | 44 | public CoatMux(Composite parent, int style) { 45 | super(new Composite(parent, style)); 46 | wrapped.setLayout(stack); 47 | // when the current layer changes, set the topControl appropriately 48 | Rx.subscribe(currentLayer, opt -> { 49 | stack.topControl = opt.map(layer -> layer.control).orElse(null); 50 | wrapped.layout(); 51 | wrapped.requestLayout(); 52 | }); 53 | } 54 | 55 | /** The current layer (if any). */ 56 | public RxGetter>> rxCurrent() { 57 | return currentLayer; 58 | } 59 | 60 | /** Sets the mux to be empty. */ 61 | public void setEmpty() { 62 | currentLayer.set(Optional.empty()); 63 | } 64 | 65 | /** The Control at the top of the stack (possibly null). */ 66 | public Control getTopControl() { 67 | return stack.topControl; 68 | } 69 | 70 | /** Represents a persistent layer within this `CoatMux`. It can be shown, hidden, and disposed. */ 71 | public class Layer { 72 | final Control control; 73 | final T handle; 74 | 75 | private Layer(Control control, T handle) { 76 | this.control = control; 77 | this.handle = handle; 78 | } 79 | 80 | /** {@link RxGetter} which keeps track of whether this `Layer` is currently on top. */ 81 | public RxGetter rxOnTop() { 82 | return currentLayer.map(opt -> opt.isPresent() && opt.get() == this); 83 | } 84 | 85 | /** The control at the root of this layer. */ 86 | public Control getControl() { 87 | return control; 88 | } 89 | 90 | /** The handle which was returned by the {@link Coat.Returning}. */ 91 | public T getHandle() { 92 | return handle; 93 | } 94 | 95 | /** Brings this layer to the top. */ 96 | public void bringToTop() { 97 | currentLayer.set(Optional.of(this)); 98 | } 99 | 100 | /** Disposes the contents of this layer. */ 101 | public void dispose() { 102 | if (rxOnTop().get()) { 103 | currentLayer.set(Optional.empty()); 104 | } 105 | SwtExec.immediate().execute(control::dispose); 106 | } 107 | 108 | public Chit chit() { 109 | return SwtRx.chit(control); 110 | } 111 | } 112 | 113 | /** Adds a persistent {@link Layer} which will be populated immediately by the given `Coat`, using `value` as the key. */ 114 | public Layer addCoat(Coat coat, @Nullable T value) { 115 | return addCoat(Coat.Returning.Companion.fromNonReturning(coat, value)); 116 | } 117 | 118 | /** Adds a persistent {@link Layer} which will be populated immediately by the given `Coat.Returning`, using the return value as the key. */ 119 | public Layer addCoat(Coat.Returning coat) { 120 | Composite composite = new Composite(wrapped, SWT.NONE); 121 | return new Layer(composite, coat.putOn(composite)); 122 | } 123 | 124 | private static final String RAW = "The function must create exactly one child of the composite which gets passed in"; 125 | 126 | /** 127 | * Adds a persistent {@link Layer} which will be populated immediately by the given `Function`, with the returned control as the key. 128 | *

129 | * The function must create exactly one child of the composite, and must return that child. 130 | */ 131 | public Layer addControl(Function creator) { 132 | int before = wrapped.getChildren().length; 133 | T control = creator.apply(wrapped); 134 | int after = wrapped.getChildren().length; 135 | Preconditions.checkArgument(control.getParent() == wrapped, RAW); 136 | Preconditions.checkArgument(before + 1 == after, RAW); 137 | return new Layer(control, control); 138 | } 139 | 140 | /** 141 | * Adds a persistent {@link Layer} which will be populated immediately by the given `Function`, with the returned control as the key. 142 | *

143 | * The function must create exactly one child of the composite, and it must return that child. 144 | */ 145 | public Layer addWrapper(Function creator) { 146 | int before = wrapped.getChildren().length; 147 | T wrapper = creator.apply(wrapped); 148 | int after = wrapped.getChildren().length; 149 | Preconditions.checkArgument(wrapper.getParent() == wrapped, RAW); 150 | Preconditions.checkArgument(before + 1 == after, RAW); 151 | return new Layer(wrapper.getRootControl(), wrapper); 152 | } 153 | 154 | private T makeTemporary(Layer layer) { 155 | // bring it to the top 156 | layer.bringToTop(); 157 | // dispose the layer when it's no longer current 158 | SwtExec.immediate().guardOn(layer.control).subscribe(layer.rxOnTop(), isCurrent -> { 159 | if (!isCurrent) { 160 | layer.dispose(); 161 | } 162 | }); 163 | // return the handle that the coat created 164 | return layer.handle; 165 | } 166 | 167 | /** Sets the current content of this `CoatMux`, gets disposed as soon as anything else becomes the top layer. */ 168 | public void setCoat(Coat coat) { 169 | setCoatReturning(Coat.Returning.Companion.fromNonReturning(coat, null)); 170 | } 171 | 172 | /** Sets the current content of this `CoatMux`, gets disposed as soon as anything else becomes the top layer. */ 173 | public T setCoatReturning(Coat.Returning coat) { 174 | return makeTemporary(addCoat(coat)); 175 | } 176 | 177 | /** Sets the current content of this `CoatMux`, gets disposed as soon as anything else becomes the top layer. */ 178 | public T setControl(Function creator) { 179 | return makeTemporary(addControl(creator)); 180 | } 181 | 182 | /** Sets the current content of this `CoatMux`, gets disposed as soon as anything else becomes the top layer. */ 183 | public T setWrapper(Function creator) { 184 | return makeTemporary(addWrapper(creator)); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/ColorPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import java.util.HashMap; 20 | import org.eclipse.swt.graphics.Color; 21 | import org.eclipse.swt.graphics.RGB; 22 | import org.eclipse.swt.widgets.Widget; 23 | 24 | /** Caches {@link Color}s, and automatically manages their disposal. */ 25 | public class ColorPool { 26 | private final HashMap colorTable = new HashMap<>(); 27 | 28 | private ColorPool() {} 29 | 30 | /** Returns a Color for the given RGB value. */ 31 | public Color getColor(RGB rgb) { 32 | return colorTable.computeIfAbsent(rgb, raw -> new Color(raw)); 33 | } 34 | 35 | /** Returns a ColorPool for the given Widget, creating one if necessary. */ 36 | public static ColorPool forWidget(Widget widget) { 37 | return onePerWidget.forWidget(widget); 38 | } 39 | 40 | /** Returns a ColorPool for the given ControlWrapper, creating one if necessary. */ 41 | public static ColorPool forWidget(ControlWrapper wrapper) { 42 | return onePerWidget.forWidget(wrapper.getRootControl()); 43 | } 44 | 45 | private static final OnePerWidget onePerWidget = OnePerWidget.from(unused -> new ColorPool()); 46 | } 47 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/ControlWrapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt 17 | 18 | import org.eclipse.swt.widgets.Composite 19 | import org.eclipse.swt.widgets.Control 20 | import org.eclipse.swt.widgets.Shell 21 | 22 | 23 | /** 24 | * Wraps an SWT [Control] to encapsulate its API. 25 | * 26 | * 27 | * The traditional way to make a custom class is this: `class CustomControl extends [Composite]` 28 | * 29 | * 30 | * This has three main problems: 31 | * 32 | * 1. Users can add random widgets to your "Control" because it exposes the [Composite] interface. 33 | * 1. Users can set the layout to your "Control" because it exposes the [Composite] interface. 34 | * 1. Users can add random listeners to your "Control", and overridding [Widget.addListener][org.eclipse.swt.widgets.Widget.addListener] to intercept them is a **very dangerous plan**. 35 | * 36 | * 37 | * 38 | * ControlWrapper fixes this by providing an low-overhead skeleton which encapsulates the 39 | * SWT Control that you're using as the base of your custom control, which allows you to only 40 | * expose the APIs that are appropriate. 41 | */ 42 | interface ControlWrapper { 43 | var layoutData: Any? 44 | /** Returns the LayoutData for this control. */ 45 | get() = rootControl.layoutData 46 | /** Sets the LayoutData for this control. */ 47 | set(layoutData) { 48 | rootControl.layoutData = layoutData 49 | } 50 | 51 | val parent: Composite 52 | /** Returns the parent of this Control. */ 53 | get() = rootControl.parent 54 | 55 | val shell: Shell 56 | /** Returns the parent Shell of this Control. */ 57 | get() = rootControl.shell 58 | 59 | /** Disposes the underlying control. */ 60 | fun dispose() { 61 | rootControl.dispose() 62 | } 63 | 64 | val isDisposed: Boolean 65 | /** Returns true iff the underlying control is disposed. */ 66 | get() = rootControl.isDisposed 67 | 68 | /** 69 | * Changes the parent of the widget to be the one provided. 70 | * Returns `true` if the parent is successfully changed 71 | */ 72 | fun setParent(parent: Composite): Boolean { 73 | return rootControl.setParent(parent) 74 | } 75 | 76 | /** 77 | * Returns the wrapped [Control] (only appropriate for limited purposes!). 78 | * 79 | * 80 | * The implementor of this ControlWrapper is free to change the wrapped Control 81 | * as she sees fit, and she doesn't have to tell you about it! You shouldn't rely 82 | * on this control being anything in particular. 83 | * 84 | * 85 | * You *can* rely on this Control for: 86 | * 87 | * 1. Managing lifetimes: `wrapped.getRootControl().addListener(SWT.Dispose, ...` 88 | * 89 | * 90 | * 91 | * But that's all. If you use it for something else, it's on you when it breaks. 92 | */ 93 | val rootControl: Control 94 | 95 | /** Default implementation of a [ControlWrapper] which wraps a [Control]. */ 96 | open class AroundControl 97 | /** Creates a ControlWrapper which wraps the given control. */( 98 | /** The wrapped control. */ 99 | @JvmField protected val wrapped: T 100 | ) : ControlWrapper { 101 | override val rootControl: T 102 | get() = wrapped 103 | } 104 | 105 | /** Default implementation of a [ControlWrapper] which wraps some other form of `ControlWrapper` with a new interface. */ 106 | open class AroundWrapper( 107 | @JvmField 108 | protected val wrapped: T 109 | ) : ControlWrapper { 110 | override val rootControl: Control 111 | get() = wrapped.rootControl 112 | } 113 | 114 | /** Creates a ControlWrapper which wraps the given control. */ 115 | class Transparent( 116 | override val rootControl: T 117 | ) : ControlWrapper 118 | 119 | companion object { 120 | /** Most-efficient way to transparently pass a Control to a ControlWrapper API. */ 121 | @JvmStatic 122 | fun transparent(control: T): Transparent { 123 | return Transparent(control) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/Corner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.graphics.Point; 20 | import org.eclipse.swt.graphics.Rectangle; 21 | import org.eclipse.swt.widgets.Control; 22 | import org.eclipse.swt.widgets.Shell; 23 | import org.eclipse.swt.widgets.ToolBar; 24 | import org.eclipse.swt.widgets.ToolItem; 25 | 26 | /** Positions within a rectangle (the corners, the center of the lines, and the center). */ 27 | public enum Corner { 28 | // @formatter:off 29 | TOP_LEFT(0, 0), TOP_RIGHT(1, 0), BOTTOM_LEFT(0, 1), BOTTOM_RIGHT(1, 1), // true corners 30 | TOP(0.5f, 0), LEFT(0, 0.5f), BOTTOM(0.5f, 1), RIGHT(1, 0.5f), CENTER(0.5f, 0.5f); // edges and center 31 | // @formatter:on 32 | 33 | private final float x, y; 34 | 35 | private Corner(float x, float y) { 36 | this.x = x; 37 | this.y = y; 38 | } 39 | 40 | /** Returns this corner's position within the given rectangle. */ 41 | public Point getPosition(Rectangle rectangle) { 42 | return new Point( 43 | rectangle.x + (int) (x * rectangle.width), 44 | rectangle.y + (int) (y * rectangle.height)); 45 | } 46 | 47 | /** Returns this corner's position on the given control in display coordinates. */ 48 | public Point getPosition(Control control) { 49 | if (control instanceof Shell) { 50 | return getPosition(control.getBounds()); 51 | } else { 52 | return control.getDisplay().map(control.getParent(), null, getPosition(control.getBounds())); 53 | } 54 | } 55 | 56 | /** Returns this corner's position on the given control in display coordinates. */ 57 | public Point getPosition(ControlWrapper wrapper) { 58 | return getPosition(wrapper.getRootControl()); 59 | } 60 | 61 | /** Returns this corner's position on the given ToolItem in display coordinates. */ 62 | public Point getPosition(ToolItem item) { 63 | ToolBar toolbar = item.getParent(); 64 | return toolbar.getDisplay().map(toolbar, null, getPosition(item.getBounds())); 65 | } 66 | 67 | /** 68 | * If you move the topLeft of `rectangle` to the returned point, 69 | * then this corner will be at `position`. 70 | */ 71 | public Point topLeftRequiredFor(Rectangle rectangle, Point position) { 72 | Point current = getPosition(rectangle); 73 | return new Point( 74 | rectangle.x + position.x - current.x, 75 | rectangle.y + position.y - current.y); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/Fonts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import com.diffplug.common.base.Errors; 20 | import com.diffplug.common.base.Preconditions; 21 | import com.diffplug.common.swt.os.OS; 22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import org.eclipse.swt.SWT; 26 | import org.eclipse.swt.graphics.Font; 27 | import org.eclipse.swt.graphics.FontData; 28 | import org.eclipse.swt.widgets.Display; 29 | 30 | /** Registry of fonts, especially system fonts. */ 31 | public class Fonts { 32 | private static Fonts instance; 33 | 34 | @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "SwtMisc.assertUI() ensures it is only called from one thread.") 35 | private static Fonts getInstance() { 36 | Display display = SwtMisc.assertUI(); 37 | if (instance == null) { 38 | instance = new Fonts(display); 39 | } 40 | return instance; 41 | } 42 | 43 | private final Display display; 44 | private final Map map = new HashMap<>(); 45 | 46 | private Fonts(Display display) { 47 | this.display = display; 48 | } 49 | 50 | private Font getFont(String name, int size, int style) { 51 | String key = getFontKey(name, size, style); 52 | return map.computeIfAbsent(key, unused -> { 53 | return new Font(display, name, size, style); 54 | }); 55 | } 56 | 57 | private String getFontKey(String name, int size, int style) { 58 | return name + "_" + Integer.toString(size) + "_" + Integer.toString(style); 59 | } 60 | 61 | /** Returns the given font. */ 62 | public static Font get(String name, int size, int style) { 63 | Preconditions.checkArgument(size > 0, "Size must be greater than 0. Did you switch the size and the style? size=%s style=%s", size, style); 64 | return getInstance().getFont(name, size, style); 65 | } 66 | 67 | /** Returns the default font for this system. */ 68 | public static Font system() { 69 | FontData font = SwtMisc.assertUI().getSystemFont().getFontData()[0]; 70 | return get(font.getName(), font.getHeight(), SWT.NONE); 71 | } 72 | 73 | /** Returns the default bold font for this system. */ 74 | public static Font systemBold() { 75 | FontData font = SwtMisc.assertUI().getSystemFont().getFontData()[0]; 76 | return get(font.getName(), font.getHeight(), SWT.BOLD); 77 | } 78 | 79 | /** Returns a largish system font appropriate for dialog headers. */ 80 | public static Font systemLarge() { 81 | FontData font = SwtMisc.assertUI().getSystemFont().getFontData()[0]; 82 | return get(font.getName(), font.getHeight() * 4 / 3, SWT.NORMAL); 83 | } 84 | 85 | /** Returns a monospace font for this system. */ 86 | public static Font systemMonospace() { 87 | FontData monospace = systemMonospaceFontData(); 88 | return get(monospace.getName(), monospace.getHeight(), SWT.NONE); 89 | } 90 | 91 | /** The cached value of the best monospace font on this system. */ 92 | private static FontData bestSystemMonospaceFontData; 93 | 94 | /** Calculates the best monospaced font available on this system, can be called from any thread. */ 95 | @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "SwtMisc.assertUI() ensures it is only called from one thread.") 96 | public static FontData systemMonospaceFontData() { 97 | Display display = SwtMisc.assertUI(); 98 | if (bestSystemMonospaceFontData != null) { 99 | return bestSystemMonospaceFontData; 100 | } 101 | 102 | // get the default fonts for each OS 103 | String defaultFonts = OS.getNative().winMacLinux( 104 | "Consolas:10;Courier New:10", 105 | "Monaco:11;Courier:12;Courier New:12", 106 | "Monospace:10"); 107 | 108 | String[] fonts = defaultFonts.split(";", -1); 109 | for (String font : fonts) { 110 | // parse out each of the suggested fonts 111 | String[] pieces = font.split(":", -1); 112 | String fontName = pieces[0]; 113 | int fontSize = Integer.parseInt(pieces[1]); 114 | 115 | // get the available fonts 116 | FontData[] available = display.getFontList(fontName, true); 117 | if (available != null && available.length > 0) { 118 | // it was available, so we'll return it 119 | bestSystemMonospaceFontData = new FontData(fontName, fontSize, SWT.NONE); 120 | return bestSystemMonospaceFontData; 121 | } 122 | } 123 | 124 | // our lists didn't work, so we'll fall back to the system font 125 | Errors.log().accept(new IllegalArgumentException("Couldn't find a good monospaced font.")); 126 | bestSystemMonospaceFontData = display.getSystemFont().getFontData()[0]; 127 | return bestSystemMonospaceFontData; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.widgets.Layout; 20 | 21 | /** Base class to Layouts{X}Layout. */ 22 | public abstract class LayoutWrapper { 23 | protected final T wrapped; 24 | 25 | protected LayoutWrapper(T wrapped) { 26 | this.wrapped = wrapped; 27 | } 28 | 29 | public T getRaw() { 30 | return wrapped; 31 | } 32 | 33 | /** Sets all margins to the given value. */ 34 | public abstract LayoutWrapper margin(int margin); 35 | 36 | /** Sets all spacing to the given value. */ 37 | public abstract LayoutWrapper spacing(int spacing); 38 | 39 | /** Sets the margin and spacing to {@link Layouts#defaultMargin()}. */ 40 | public void setMarginAndSpacingToDefault() { 41 | margin(Layouts.defaultMargin()); 42 | spacing(Layouts.defaultMargin()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutsFillLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.SWT; 20 | import org.eclipse.swt.layout.FillLayout; 21 | 22 | /** 23 | * A fluent api for setting and modifying a {@link FillLayout}, created by {@link Layouts}. 24 | * 25 | * Inspired by Moritz Post's blog post.. 26 | */ 27 | public class LayoutsFillLayout extends LayoutWrapper { 28 | LayoutsFillLayout(FillLayout fillLayout) { 29 | super(fillLayout); 30 | } 31 | 32 | /** Sets marginWidth and marginHeight to the given value. */ 33 | @Override 34 | public LayoutsFillLayout margin(int margin) { 35 | wrapped.marginWidth = margin; 36 | wrapped.marginHeight = margin; 37 | return this; 38 | } 39 | 40 | @Override 41 | public LayoutsFillLayout spacing(int spacing) { 42 | wrapped.spacing = spacing; 43 | return this; 44 | } 45 | 46 | public LayoutsFillLayout vertical() { 47 | wrapped.type = SWT.VERTICAL; 48 | return this; 49 | } 50 | 51 | public LayoutsFillLayout horizontal() { 52 | wrapped.type = SWT.HORIZONTAL; 53 | return this; 54 | } 55 | 56 | public LayoutsFillLayout marginWidth(int marginWidth) { 57 | wrapped.marginWidth = marginWidth; 58 | return this; 59 | } 60 | 61 | public LayoutsFillLayout marginHeight(int marginHeight) { 62 | wrapped.marginHeight = marginHeight; 63 | return this; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutsGridData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.SWT; 20 | import org.eclipse.swt.graphics.Point; 21 | import org.eclipse.swt.layout.GridData; 22 | 23 | /** 24 | * A fluent api for setting and modifying a {@link GridData}, created by {@link Layouts}. 25 | * 26 | * Inspired by Moritz Post's blog post.. 27 | */ 28 | public class LayoutsGridData { 29 | private final GridData gridData; 30 | 31 | LayoutsGridData(GridData gridData) { 32 | this.gridData = gridData; 33 | } 34 | 35 | /** Returns the raw GridData. */ 36 | public GridData getRaw() { 37 | return gridData; 38 | } 39 | 40 | /** The GridData will grab space in all directions. */ 41 | public LayoutsGridData grabAll() { 42 | grabHorizontal(); 43 | grabVertical(); 44 | return this; 45 | } 46 | 47 | /** The GridData will grab space horizontally. */ 48 | public LayoutsGridData grabHorizontal() { 49 | gridData.horizontalAlignment = SWT.FILL; 50 | gridData.grabExcessHorizontalSpace = true; 51 | return this; 52 | } 53 | 54 | /** The GridData will grab space vertically. */ 55 | public LayoutsGridData grabVertical() { 56 | gridData.verticalAlignment = SWT.FILL; 57 | gridData.grabExcessVerticalSpace = true; 58 | return this; 59 | } 60 | 61 | //////////////////////////// 62 | // Setters for all fields // 63 | //////////////////////////// 64 | public LayoutsGridData grabExcessHorizontalSpace(boolean grabExcessHorizontalSpace) { 65 | gridData.grabExcessHorizontalSpace = grabExcessHorizontalSpace; 66 | return this; 67 | } 68 | 69 | public LayoutsGridData grabExcessVerticalSpace(boolean grabExcessVerticalSpace) { 70 | gridData.grabExcessVerticalSpace = grabExcessVerticalSpace; 71 | return this; 72 | } 73 | 74 | public LayoutsGridData horizontalSpan(int horizontalSpan) { 75 | gridData.horizontalSpan = horizontalSpan; 76 | return this; 77 | } 78 | 79 | public LayoutsGridData verticalSpan(int verticalSpan) { 80 | gridData.verticalSpan = verticalSpan; 81 | return this; 82 | } 83 | 84 | public LayoutsGridData minimumHeight(int minimumHeight) { 85 | gridData.minimumHeight = minimumHeight; 86 | return this; 87 | } 88 | 89 | public LayoutsGridData minimumWidth(int minimumWidth) { 90 | gridData.minimumWidth = minimumWidth; 91 | return this; 92 | } 93 | 94 | public LayoutsGridData minimumSize(Point size) { 95 | gridData.minimumWidth = size.x; 96 | gridData.minimumHeight = size.y; 97 | return this; 98 | } 99 | 100 | public LayoutsGridData verticalIndent(int verticalIndent) { 101 | gridData.verticalIndent = verticalIndent; 102 | return this; 103 | } 104 | 105 | public LayoutsGridData horizontalIndent(int horizontalIndent) { 106 | gridData.horizontalIndent = horizontalIndent; 107 | return this; 108 | } 109 | 110 | public LayoutsGridData heightHint(int heightHint) { 111 | gridData.heightHint = heightHint; 112 | return this; 113 | } 114 | 115 | public LayoutsGridData widthHint(int widthHint) { 116 | gridData.widthHint = widthHint; 117 | return this; 118 | } 119 | 120 | public LayoutsGridData sizeHint(Point sizeHint) { 121 | gridData.widthHint = sizeHint.x; 122 | gridData.heightHint = sizeHint.y; 123 | return this; 124 | } 125 | 126 | public LayoutsGridData verticalAlignment(int verticalAlignment) { 127 | gridData.verticalAlignment = verticalAlignment; 128 | return this; 129 | } 130 | 131 | public LayoutsGridData horizontalAlignment(int horizontalAlignment) { 132 | gridData.horizontalAlignment = horizontalAlignment; 133 | return this; 134 | } 135 | 136 | public LayoutsGridData exclude(boolean exclude) { 137 | gridData.exclude = exclude; 138 | return this; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutsGridLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.layout.GridLayout; 20 | 21 | /** 22 | * A fluent api for setting and modifying a {@link GridLayout}, created by {@link Layouts}. 23 | * 24 | * Inspired by Moritz Post's blog post.. 25 | */ 26 | public class LayoutsGridLayout extends LayoutWrapper { 27 | LayoutsGridLayout(GridLayout gridLayout) { 28 | super(gridLayout); 29 | } 30 | 31 | /** Sets marginWidth and marginHeight to the given value, and left/right/top/bottom to 0. */ 32 | @Override 33 | public LayoutsGridLayout margin(int margin) { 34 | wrapped.marginWidth = margin; 35 | wrapped.marginHeight = margin; 36 | wrapped.marginLeft = 0; 37 | wrapped.marginRight = 0; 38 | wrapped.marginTop = 0; 39 | wrapped.marginBottom = 0; 40 | return this; 41 | } 42 | 43 | /** Sets marginHeight to 0, and top / bottom to the given values. */ 44 | public LayoutsGridLayout marginTopBottom(int top, int bottom) { 45 | wrapped.marginHeight = 0; 46 | wrapped.marginTop = top; 47 | wrapped.marginBottom = bottom; 48 | return this; 49 | } 50 | 51 | /** Sets marginWidth to 0, and left / right to the given values. */ 52 | public LayoutsGridLayout marginLeftRight(int left, int right) { 53 | wrapped.marginWidth = 0; 54 | wrapped.marginLeft = left; 55 | wrapped.marginRight = right; 56 | return this; 57 | } 58 | 59 | /** Sets all margins to the given value. */ 60 | @Override 61 | public LayoutsGridLayout spacing(int spacing) { 62 | wrapped.verticalSpacing = spacing; 63 | wrapped.horizontalSpacing = spacing; 64 | return this; 65 | } 66 | 67 | public LayoutsGridLayout numColumns(int numColumns) { 68 | wrapped.numColumns = numColumns; 69 | return this; 70 | } 71 | 72 | public LayoutsGridLayout columnsEqualWidth(boolean columnsEqualWidth) { 73 | wrapped.makeColumnsEqualWidth = columnsEqualWidth; 74 | return this; 75 | } 76 | 77 | public LayoutsGridLayout marginWidth(int marginWidth) { 78 | wrapped.marginWidth = marginWidth; 79 | return this; 80 | } 81 | 82 | public LayoutsGridLayout marginHeight(int marginHeight) { 83 | wrapped.marginHeight = marginHeight; 84 | return this; 85 | } 86 | 87 | public LayoutsGridLayout marginLeft(int marginLeft) { 88 | wrapped.marginLeft = marginLeft; 89 | return this; 90 | } 91 | 92 | public LayoutsGridLayout marginTop(int marginTop) { 93 | wrapped.marginTop = marginTop; 94 | return this; 95 | } 96 | 97 | public LayoutsGridLayout marginRight(int marginRight) { 98 | wrapped.marginRight = marginRight; 99 | return this; 100 | } 101 | 102 | public LayoutsGridLayout marginBottom(int marginBottom) { 103 | wrapped.marginBottom = marginBottom; 104 | return this; 105 | } 106 | 107 | public LayoutsGridLayout horizontalSpacing(int horizontalSpacing) { 108 | wrapped.horizontalSpacing = horizontalSpacing; 109 | return this; 110 | } 111 | 112 | public LayoutsGridLayout verticalSpacing(int verticalSpacing) { 113 | wrapped.verticalSpacing = verticalSpacing; 114 | return this; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutsRowData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.layout.RowData; 20 | 21 | /** 22 | * A fluent api for setting and modifying a {@link RowData}, created by {@link Layouts}. 23 | * 24 | * Inspired by Moritz Post's blog post.. 25 | */ 26 | public class LayoutsRowData { 27 | private final RowData rowData; 28 | 29 | LayoutsRowData(RowData rowData) { 30 | this.rowData = rowData; 31 | } 32 | 33 | /** Returns the raw GridData. */ 34 | public RowData getRaw() { 35 | return rowData; 36 | } 37 | 38 | public LayoutsRowData width(int width) { 39 | rowData.width = width; 40 | return this; 41 | } 42 | 43 | public LayoutsRowData height(int height) { 44 | rowData.height = height; 45 | return this; 46 | } 47 | 48 | public LayoutsRowData exclude(boolean exclude) { 49 | rowData.exclude = exclude; 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/LayoutsRowLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import org.eclipse.swt.SWT; 20 | import org.eclipse.swt.layout.RowLayout; 21 | 22 | /** 23 | * A fluent api for setting and modifying a {@link RowLayout}, created by {@link Layouts}. 24 | * 25 | * Inspired by Moritz Post's blog post.. 26 | */ 27 | public class LayoutsRowLayout extends LayoutWrapper { 28 | LayoutsRowLayout(RowLayout rowLayout) { 29 | super(rowLayout); 30 | } 31 | 32 | /** Sets marginWidth and marginHeight to the given value, and left/right/top/bottom to 0. */ 33 | @Override 34 | public LayoutsRowLayout margin(int margin) { 35 | wrapped.marginWidth = margin; 36 | wrapped.marginHeight = margin; 37 | wrapped.marginLeft = 0; 38 | wrapped.marginRight = 0; 39 | wrapped.marginTop = 0; 40 | wrapped.marginBottom = 0; 41 | return this; 42 | } 43 | 44 | /** Sets marginHeight to 0, and top / bottom to the given values. */ 45 | public LayoutsRowLayout marginTopBottom(int top, int bottom) { 46 | wrapped.marginHeight = 0; 47 | wrapped.marginTop = top; 48 | wrapped.marginBottom = bottom; 49 | return this; 50 | } 51 | 52 | /** Sets marginWidth to 0, and left / right to the given values. */ 53 | public LayoutsRowLayout marginLeftRight(int left, int right) { 54 | wrapped.marginWidth = 0; 55 | wrapped.marginLeft = left; 56 | wrapped.marginRight = right; 57 | return this; 58 | } 59 | 60 | /** Sets the spacing to zero. */ 61 | @Override 62 | public LayoutsRowLayout spacing(int spacing) { 63 | wrapped.spacing = spacing; 64 | return this; 65 | } 66 | 67 | /** Makes this a vertical layout. */ 68 | public LayoutsRowLayout vertical() { 69 | wrapped.type = SWT.VERTICAL; 70 | return this; 71 | } 72 | 73 | /** Makes this a horizontal layout. */ 74 | public LayoutsRowLayout horizontal() { 75 | wrapped.type = SWT.HORIZONTAL; 76 | return this; 77 | } 78 | 79 | public LayoutsRowLayout marginWidth(int marginWidth) { 80 | wrapped.marginWidth = marginWidth; 81 | return this; 82 | } 83 | 84 | public LayoutsRowLayout marginHeight(int marginHeight) { 85 | wrapped.marginHeight = marginHeight; 86 | return this; 87 | } 88 | 89 | public LayoutsRowLayout marginLeft(int marginLeft) { 90 | wrapped.marginLeft = marginLeft; 91 | return this; 92 | } 93 | 94 | public LayoutsRowLayout marginTop(int marginTop) { 95 | wrapped.marginTop = marginTop; 96 | return this; 97 | } 98 | 99 | public LayoutsRowLayout marginRight(int marginRight) { 100 | wrapped.marginRight = marginRight; 101 | return this; 102 | } 103 | 104 | public LayoutsRowLayout marginBottom(int marginBottom) { 105 | wrapped.marginBottom = marginBottom; 106 | return this; 107 | } 108 | 109 | /** 110 | * wrap specifies whether a control will be wrapped to the next 111 | * row if there is insufficient space on the current row. 112 | *

113 | * The default value is true. 114 | */ 115 | public LayoutsRowLayout wrap(boolean wrap) { 116 | wrapped.wrap = wrap; 117 | return this; 118 | } 119 | 120 | /** 121 | * pack specifies whether all controls in the layout take 122 | * their preferred size. If pack is false, all controls will 123 | * have the same size which is the size required to accommodate the 124 | * largest preferred height and the largest preferred width of all 125 | * the controls in the layout. 126 | *

127 | * The default value is true. 128 | */ 129 | public LayoutsRowLayout pack(boolean pack) { 130 | wrapped.pack = pack; 131 | return this; 132 | } 133 | 134 | /** 135 | * fill specifies whether the controls in a row should be 136 | * all the same height for horizontal layouts, or the same 137 | * width for vertical layouts. 138 | *

139 | * The default value is false. 140 | */ 141 | public LayoutsRowLayout fill(boolean fill) { 142 | wrapped.fill = fill; 143 | return this; 144 | } 145 | 146 | /** 147 | * center specifies whether the controls in a row should be 148 | * centered vertically in each cell for horizontal layouts, 149 | * or centered horizontally in each cell for vertical layouts. 150 | *

151 | * The default value is false. 152 | */ 153 | public LayoutsRowLayout center(boolean center) { 154 | wrapped.center = center; 155 | return this; 156 | } 157 | 158 | /** 159 | * justify specifies whether the controls in a row should be 160 | * fully justified, with any extra space placed between the controls. 161 | *

162 | * The default value is false. 163 | */ 164 | public LayoutsRowLayout justify(boolean justify) { 165 | wrapped.justify = justify; 166 | return this; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/MouseClick.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import java.util.function.Predicate; 20 | import org.eclipse.swt.SWT; 21 | import org.eclipse.swt.widgets.Event; 22 | 23 | /** Enum to help model different mouse clicks. */ 24 | public enum MouseClick implements Predicate { 25 | LEFT, MIDDLE, RIGHT; 26 | 27 | public int code() { 28 | return ordinal() + 1; 29 | } 30 | 31 | @Override 32 | public boolean test(Event e) { 33 | return e.button == code(); 34 | } 35 | 36 | /** Should we use MouseDown or MouseUp for right-clicks? This (down) is the answer. */ 37 | public static final int RIGHT_CLICK_EVENT = SWT.MouseDown; 38 | } 39 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/OnePerWidget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.function.Function; 22 | import org.eclipse.swt.SWT; 23 | import org.eclipse.swt.widgets.Widget; 24 | 25 | /** 26 | * Maintains a cache of values which are mapped to SWT widgets. The 27 | * cache is automatically updated as these widgets are disposed. 28 | * 29 | * Useful for implementing resource managers, such as {@link ColorPool}. 30 | */ 31 | public abstract class OnePerWidget { 32 | /** Creates a OnePerWidget instance where objects are created using the given function. */ 33 | public static OnePerWidget from(Function creator) { 34 | return new OnePerWidget() { 35 | @Override 36 | protected T create(WidgetType widget) { 37 | return creator.apply(widget); 38 | } 39 | }; 40 | } 41 | 42 | private Map map = new HashMap<>(); 43 | 44 | /** Returns the object for the given control. */ 45 | public T forWidget(WidgetType ctl) { 46 | T value = map.get(ctl); 47 | if (value == null) { 48 | value = create(ctl); 49 | map.put(ctl, value); 50 | ctl.addListener(SWT.Dispose, e -> { 51 | map.remove(ctl); 52 | }); 53 | } 54 | return value; 55 | } 56 | 57 | /** Creates a new object for the control. */ 58 | protected abstract T create(WidgetType ctl); 59 | } 60 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/SiliconFix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import com.diffplug.common.swt.os.Arch; 20 | import com.diffplug.common.swt.os.OS; 21 | import org.eclipse.swt.widgets.List; 22 | import org.eclipse.swt.widgets.Table; 23 | import org.eclipse.swt.widgets.Tree; 24 | 25 | /** 26 | * Call these methods whenever a `Table`, `Tree`, or `List` is instantiated and it will do nothing, 27 | * excpet on Mac Apple Silicon where it will call `setFont(null)` as a workaround for 28 | * https://bugs.eclipse.org/bugs/show_bug.cgi?id=575696 29 | */ 30 | public class SiliconFix { 31 | public static boolean APPLY_FIX = OS.getRunning().isMac() && Arch.getRunning() == Arch.arm64; 32 | 33 | public static void fix(Table table) { 34 | if (APPLY_FIX) { 35 | table.setFont(null); 36 | } 37 | } 38 | 39 | public static void fix(Tree tree) { 40 | if (APPLY_FIX) { 41 | tree.setFont(null); 42 | } 43 | } 44 | 45 | public static void fix(List list) { 46 | if (APPLY_FIX) { 47 | list.setFont(null); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/SwtThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | import com.diffplug.common.rx.IFlowable; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * Marks that the given code should only be called 26 | * from an SWT thread. 27 | * 28 | * When annotated on a method or constructor, this 29 | * indicates that all of its methods should only be 30 | * called from the SWT thread. 31 | * 32 | * When annotated on a class, indicates that all 33 | * of its methods should only be called from the 34 | * SWT thread 35 | * 36 | * When annotated on a parameter or field, it 37 | * must be either an {@link rx.Observable}, 38 | * {@link IFlowable}, or a 39 | * {@link java.util.concurrent.CompletionStage}, and 40 | * it indicates that the given code should only be 41 | * set and listened to from SWT. 42 | * 43 | * You can use `@SwtThread(SwtThread.Kind.THREADSAFE)` 44 | * to mark exceptions (e.g. mark a class as SwtThread, 45 | * but some methods as threadsafe). 46 | */ 47 | @Retention(RetentionPolicy.RUNTIME) 48 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD}) 49 | public @interface SwtThread { 50 | static public enum Kind { 51 | REQUIRED, THREADSAFE 52 | } 53 | 54 | Kind value() default Kind.REQUIRED; 55 | } 56 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/TypedDataField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import java.util.Objects; 20 | import javax.annotation.Nullable; 21 | import org.eclipse.swt.widgets.Widget; 22 | 23 | /** 24 | * Typed utility for getting and setting data using SWT's Widget getData() / setData() API. 25 | * 26 | * ``` 27 | * TypedDataField field = TypedDataField.create("model"); 28 | * 29 | * Model model = field.get(widget); 30 | * field.set(widget, model); 31 | * ``` 32 | */ 33 | public class TypedDataField { 34 | /** Creates a TypedDataField for `setData(String key, Object value)`. */ 35 | public static TypedDataField create(String key) { 36 | return new TypedDataField<>(key); 37 | } 38 | 39 | /** Creates a TypedDataField for `setData(Object value)`. */ 40 | public static TypedDataField create() { 41 | return new TypedDataField<>(null); 42 | } 43 | 44 | @Nullable 45 | final String key; 46 | 47 | TypedDataField(@Nullable String key) { 48 | this.key = key; 49 | } 50 | 51 | public T get(W widget) { 52 | return Objects.requireNonNull(getNullable(widget)); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | @Nullable 57 | public T getNullable(W widget) { 58 | if (key == null) { 59 | return (T) widget.getData(); 60 | } else { 61 | return (T) widget.getData(key); 62 | } 63 | } 64 | 65 | public void set(W widget, T value) { 66 | setNullable(widget, Objects.requireNonNull(value)); 67 | } 68 | 69 | public void setNullable(W widget, @Nullable T value) { 70 | if (key == null) { 71 | widget.setData(value); 72 | } else { 73 | widget.setData(key, value); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/VScrollBubble.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2022 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt; 17 | 18 | 19 | import com.diffplug.common.tree.TreeStream; 20 | import java.util.Optional; 21 | import org.eclipse.swt.SWT; 22 | import org.eclipse.swt.custom.ScrolledComposite; 23 | import org.eclipse.swt.graphics.Point; 24 | import org.eclipse.swt.graphics.Rectangle; 25 | import org.eclipse.swt.widgets.Composite; 26 | import org.eclipse.swt.widgets.Display; 27 | import org.eclipse.swt.widgets.Event; 28 | import org.eclipse.swt.widgets.ScrollBar; 29 | import org.eclipse.swt.widgets.Scrollable; 30 | import org.eclipse.swt.widgets.Tree; 31 | import org.eclipse.swt.widgets.TreeItem; 32 | 33 | /** 34 | * Bubbles vertical scroll events up to a parent container, so that you don't get stuck. 35 | * Doesn't work great on mac due to rubber-banding. 36 | */ 37 | public class VScrollBubble { 38 | private static Display appliedTo; 39 | 40 | /** Returns true iff the given scrollable is maxed out for scrolling in the direction of the given event. */ 41 | private static boolean isMaxed(Event e, Scrollable scrollable) { 42 | ScrollBar scrollBar = scrollable.getVerticalBar(); 43 | if (scrollBar == null) { 44 | return true; 45 | } else { 46 | boolean isMaxed; 47 | // System.out.println("[" + scrollBar.getMinimum() + " " + scrollBar.getMaximum() + "] " + scrollBar.getSelection() + " thumb=" + scrollBar.getThumb()); 48 | if (e.count > 0) { 49 | // scrolling up 50 | isMaxed = scrollBar.getSelection() <= 0; 51 | } else { 52 | // scrolling down 53 | isMaxed = scrollBar.getSelection() >= scrollBar.getMaximum() - scrollBar.getThumb(); 54 | if (!isMaxed) { 55 | // special case for trees & tables which have all their items visible, where scrolling down gives 56 | // scrollBar.getMinimum()/getMaximum=[0 100] scrollBar.getSelection()=0 scrollBar.getThumb()=10 57 | if (scrollable instanceof Tree) { 58 | Tree tree = (Tree) scrollable; 59 | int itemCount = tree.getItemCount(); 60 | if (itemCount == 0) { 61 | return true; 62 | } 63 | TreeItem lastItem = tree.getItem(itemCount - 1); 64 | while (lastItem.getItemCount() > 0) { 65 | lastItem = lastItem.getItem(lastItem.getItemCount() - 1); 66 | } 67 | Rectangle itemBounds = lastItem.getBounds(); 68 | Rectangle clientArea = tree.getClientArea(); 69 | return itemBounds.y + itemBounds.height < clientArea.height; 70 | } 71 | } 72 | } 73 | return isMaxed; 74 | } 75 | } 76 | 77 | /** Applies the necessary display filter, don't worry about calling it more than once. */ 78 | public static void applyTo(Display display) { 79 | // prevent double-apply 80 | if (display == appliedTo) { 81 | return; 82 | } 83 | appliedTo = display; 84 | display.addFilter(SWT.MouseVerticalWheel, e -> { 85 | if (!(e.widget instanceof Scrollable)) { 86 | return; 87 | } 88 | // find the parent two levels up 89 | Composite parentOfScrollable = ((Scrollable) e.widget).getParent(); 90 | if (parentOfScrollable == null) { 91 | return; 92 | } 93 | parentOfScrollable = parentOfScrollable.getParent(); 94 | if (parentOfScrollable == null) { 95 | return; 96 | } 97 | 98 | boolean isMaxed = isMaxed(e, (Scrollable) e.widget); 99 | if (!isMaxed) { 100 | return; 101 | } 102 | 103 | Optional firstNotMaxed = TreeStream.toParent(SwtMisc.treeDefComposite(), parentOfScrollable) 104 | .filter(c -> c instanceof ScrolledComposite && SwtMisc.flagIsSet(SWT.V_SCROLL, c)) 105 | .map(ScrolledComposite.class::cast) 106 | .filter(sc -> !isMaxed(e, sc)) 107 | .findFirst(); 108 | if (!firstNotMaxed.isPresent()) { 109 | // there isn't a parent which we can bubble the scroll to 110 | return; 111 | } 112 | ScrolledComposite toScroll = firstNotMaxed.get(); 113 | Point origin = toScroll.getOrigin(); 114 | origin.y -= e.count * toScroll.getVerticalBar().getIncrement(); 115 | toScroll.setOrigin(origin); 116 | 117 | // copy the event 118 | Event copy = SwtMisc.copyEvent(e); 119 | // cancel the old one 120 | e.doit = false; 121 | // and send the copy to the parent 122 | copy.widget = toScroll; 123 | Point d = ((Scrollable) e.widget).toDisplay(e.x, e.y); 124 | Point mapped = toScroll.toControl(d); 125 | copy.x = mapped.x; 126 | copy.y = mapped.y; 127 | copy.widget.notifyListeners(SWT.MouseVerticalWheel, copy); 128 | }); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/CustomDragImageListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import org.eclipse.swt.dnd.DragSourceEvent; 20 | import org.eclipse.swt.dnd.DragSourceListener; 21 | import org.eclipse.swt.graphics.Image; 22 | 23 | /** Custom implementation of a local transfer class. The "data" object will be this Transfer itself. */ 24 | public abstract class CustomDragImageListener implements DragSourceListener { 25 | private Image dragImage; 26 | private final DragSourceListener delegate; 27 | 28 | protected CustomDragImageListener(DragSourceListener delegate) { 29 | this.delegate = delegate; 30 | } 31 | 32 | /** 33 | * Creates the image to put under the cursor during drag. It will be disposed automatically. 34 | * 35 | * {@link CustomDrawer} might useful here. 36 | */ 37 | protected abstract Image createImage(DragSourceEvent e); 38 | 39 | /** Disposes the image which was formerly under the cursor during drag. */ 40 | private void disposeImage() { 41 | if (dragImage != null) { 42 | dragImage.dispose(); 43 | dragImage = null; 44 | } 45 | } 46 | 47 | @Override 48 | public void dragStart(DragSourceEvent e) { 49 | disposeImage(); 50 | 51 | // start the drag 52 | delegate.dragStart(e); 53 | 54 | if (e.doit) { 55 | dragImage = createImage(e); 56 | } 57 | } 58 | 59 | @Override 60 | public void dragSetData(DragSourceEvent e) { 61 | delegate.dragSetData(e); 62 | } 63 | 64 | @Override 65 | public void dragFinished(DragSourceEvent e) { 66 | disposeImage(); 67 | delegate.dragFinished(e); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/CustomLocalTransfer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import com.diffplug.common.base.Box; 20 | import com.diffplug.common.base.Preconditions; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.Objects; 23 | import org.eclipse.swt.dnd.DragSourceEvent; 24 | import org.eclipse.swt.dnd.DropTargetEvent; 25 | import org.eclipse.swt.dnd.TransferData; 26 | 27 | /** Custom implementation of a local transfer class. The "data" object will be this Transfer itself. */ 28 | public abstract class CustomLocalTransfer extends TypedTransfer implements Box { 29 | // create a custom type name 30 | private final String TYPE_NAME = getClass().getCanonicalName() + System.currentTimeMillis(); 31 | private final int TYPE_ID = registerType(TYPE_NAME); 32 | 33 | protected CustomLocalTransfer() {} 34 | 35 | @Override 36 | protected int[] getTypeIds() { 37 | return new int[]{TYPE_ID}; 38 | } 39 | 40 | @Override 41 | protected String[] getTypeNames() { 42 | return new String[]{TYPE_NAME}; 43 | } 44 | 45 | @SuppressWarnings("unchecked") 46 | @Override 47 | public void javaToNative(Object object, TransferData transferData) { 48 | if (object != null) { 49 | set((T) object); 50 | } 51 | byte[] check = TYPE_NAME.getBytes(StandardCharsets.UTF_8); 52 | super.javaToNative(check, transferData); 53 | } 54 | 55 | @Override 56 | public Object nativeToJava(TransferData transferData) { 57 | if (obj == null) { 58 | return null; 59 | } else { 60 | // check result 61 | Object result = super.nativeToJava(transferData); 62 | Preconditions.checkArgument(result instanceof byte[], "%s should have been byte[]", result.getClass()); 63 | String resultStr = new String((byte[]) result, StandardCharsets.UTF_8); 64 | Preconditions.checkArgument(TYPE_NAME.equals(resultStr), "%s should have been %s", resultStr, TYPE_NAME); 65 | 66 | // return this transfer object itself 67 | return get(); 68 | } 69 | } 70 | 71 | @Override 72 | protected final boolean canSetValue(@javax.annotation.Nullable T value) { 73 | if (value != null && canSetValueNonnull(value)) { 74 | set(value); 75 | return true; 76 | } else { 77 | obj = null; 78 | return false; 79 | } 80 | } 81 | 82 | /** Returns the underlying value. */ 83 | @Override 84 | public T getValue(DropTargetEvent e) { 85 | return get(); 86 | } 87 | 88 | /** Sets the underlying value. */ 89 | @Override 90 | public void setValue(DragSourceEvent e, T value) { 91 | set(value); 92 | } 93 | 94 | private T obj; 95 | 96 | @Override 97 | public final void set(T value) { 98 | this.obj = Objects.requireNonNull(value); 99 | } 100 | 101 | @Override 102 | public final T get() { 103 | return Objects.requireNonNull(obj); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/DndOp.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import com.diffplug.common.base.Unhandled; 20 | import com.diffplug.common.swt.SwtMisc; 21 | import org.eclipse.swt.dnd.DND; 22 | import org.eclipse.swt.dnd.DropTargetEvent; 23 | 24 | public enum DndOp { 25 | COPY, MOVE; 26 | 27 | /** Returns DND.DROP_DEFAULT | DND.DROP_COPY | DND.DROP_MOVE; */ 28 | public static int dropAll() { 29 | return DND.DROP_DEFAULT | DND.DROP_COPY | DND.DROP_MOVE; 30 | } 31 | 32 | /** Returns DND.DROP_COPY | DND.DROP_MOVE; */ 33 | public static int dragAll() { 34 | return DND.DROP_COPY | DND.DROP_MOVE; 35 | } 36 | 37 | /** Return DND.DROP_COPY or DND.DROP_MOVE, as appropriate. */ 38 | public int flag() { 39 | // @formatter:off 40 | switch (this) { 41 | case COPY: return DND.DROP_COPY; 42 | case MOVE: return DND.DROP_MOVE; 43 | default: throw Unhandled.enumException(this); 44 | } 45 | // @formatter:on 46 | } 47 | 48 | /** Attempts to set the detail of this event, if possible. Returns true if it was possible. */ 49 | public boolean trySetDetail(DropTargetEvent event) { 50 | if (SwtMisc.flagIsSet(flag(), event.operations)) { 51 | event.detail = flag(); 52 | return true; 53 | } else { 54 | event.detail = DND.DROP_NONE; 55 | return false; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/DragSourceRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import com.diffplug.common.swt.OnePerWidget; 20 | import org.eclipse.swt.dnd.DragSource; 21 | import org.eclipse.swt.dnd.DragSourceListener; 22 | import org.eclipse.swt.dnd.Transfer; 23 | import org.eclipse.swt.widgets.Control; 24 | 25 | /** 26 | * Provides a mechanism for a single control to route its DragSourceEvents to various listeners. 27 | * 28 | * There is only one active listener at a time. 29 | */ 30 | public final class DragSourceRouter { 31 | private final DragSource source; 32 | private DragSourceListener currentListener; 33 | 34 | /** Private constructor to force people to use the map. */ 35 | private DragSourceRouter(Control ctl) { 36 | source = new DragSource(ctl, DndOp.dragAll()); 37 | currentListener = null; 38 | } 39 | 40 | /** Sets the DragSourceListener which will get called for this DragSource. */ 41 | public void setActiveListener(DragSourceListener newListener, Transfer[] transfers) { 42 | if (currentListener != null) { 43 | source.removeDragListener(currentListener); 44 | } 45 | currentListener = newListener; 46 | source.setTransfer(transfers); 47 | if (currentListener != null) { 48 | source.addDragListener(currentListener); 49 | } 50 | } 51 | 52 | /** Returns the MultipleDragSource for the given Control. */ 53 | public static DragSourceRouter forControl(final Control ctl) { 54 | return onePerControl.forWidget(ctl); 55 | } 56 | 57 | private static OnePerWidget onePerControl = OnePerWidget.from(DragSourceRouter::new); 58 | } 59 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/Draggable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import org.eclipse.swt.dnd.DragSourceListener; 20 | import org.eclipse.swt.dnd.Transfer; 21 | 22 | /** Generic interface for a custom widget which supports dragging. */ 23 | public interface Draggable { 24 | void addDragSupport(DragSourceListener dragListener, Transfer[] transfers); 25 | 26 | default void addDragSupport(StructuredDrag drag) { 27 | addDragSupport(drag.getListener(), drag.getListener().transferArray()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/DropTargetRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import com.diffplug.common.swt.OnePerWidget; 20 | import java.util.Arrays; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | import org.eclipse.swt.dnd.DropTarget; 24 | import org.eclipse.swt.dnd.DropTargetEvent; 25 | import org.eclipse.swt.dnd.DropTargetListener; 26 | import org.eclipse.swt.dnd.Transfer; 27 | import org.eclipse.swt.widgets.Control; 28 | 29 | public class DropTargetRouter { 30 | private DropTarget target; 31 | 32 | /** All of the Transfers currently supported by this DropTargetRouter. */ 33 | private Set transferSet = new HashSet<>(); 34 | 35 | /** Private constructor to force people to use the map. */ 36 | private DropTargetRouter(Control ctl) { 37 | target = new DropTarget(ctl, DndOp.dropAll()); 38 | target.addDropListener(new DelegatingListener()); 39 | target.setDropTargetEffect(null); 40 | } 41 | 42 | private void addTransfers(Transfer[] transfers) { 43 | transferSet.addAll(Arrays.asList(transfers)); 44 | Transfer[] allTransfers = transferSet.toArray(new Transfer[transferSet.size()]); 45 | target.setTransfer(allTransfers); 46 | } 47 | 48 | private Disposable subscription = null; 49 | 50 | /** Sets the DropTargetListener which will get called for this DropTarget. */ 51 | public Disposable subscribe(DropTargetListener listener, DropTargetEvent e) { 52 | if (subscription != null) { 53 | subscription.unsubscribe(e); 54 | } 55 | subscription = new Disposable(listener, e); 56 | return subscription; 57 | } 58 | 59 | public class Disposable { 60 | private DropTargetListener listener; 61 | private boolean unsubscribed = false; 62 | 63 | /** Call dragEnter on subscription. */ 64 | private Disposable(DropTargetListener listener, DropTargetEvent e) { 65 | this.listener = listener; 66 | listener.dragEnter(e); 67 | } 68 | 69 | /** Call dragLeave on unsubscribe. Don't complete the unsubscription until drop or dropAccept is called. */ 70 | public void unsubscribe(DropTargetEvent e) { 71 | if (unsubscribed) { 72 | if (subscription == this) { 73 | subscription = null; 74 | } 75 | } else { 76 | unsubscribed = true; 77 | listener.dragLeave(e); 78 | } 79 | } 80 | 81 | public void dragOperationChanged(DropTargetEvent e) { 82 | if (unsubscribed) { 83 | subscription = null; 84 | } else { 85 | listener.dragOperationChanged(e); 86 | } 87 | } 88 | 89 | public void dragOver(DropTargetEvent e) { 90 | if (unsubscribed) { 91 | subscription = null; 92 | } else { 93 | listener.dragOver(e); 94 | } 95 | } 96 | 97 | public void drop(DropTargetEvent e) { 98 | listener.drop(e); 99 | } 100 | 101 | public void dropAccept(DropTargetEvent e) { 102 | listener.dropAccept(e); 103 | } 104 | } 105 | 106 | /** Adds a listener which bypasses the routing mechanism. */ 107 | public void addBypassListener(DropTargetListener listener) { 108 | target.addDropListener(listener); 109 | } 110 | 111 | /** Adds a listener which bypasses the routing mechanism. */ 112 | public void removeBypassListener(DropTargetListener listener) { 113 | target.removeDropListener(listener); 114 | } 115 | 116 | /** Listener which delegates its calls to the currentListener. */ 117 | private class DelegatingListener implements DropTargetListener { 118 | // enter / leave is handled by the Disposable object 119 | @Override 120 | public void dragEnter(DropTargetEvent e) {} 121 | 122 | @Override 123 | public void dragLeave(DropTargetEvent e) {} 124 | 125 | @Override 126 | public void dragOperationChanged(DropTargetEvent e) { 127 | if (subscription != null) { 128 | subscription.dragOperationChanged(e); 129 | } 130 | } 131 | 132 | @Override 133 | public void dragOver(DropTargetEvent e) { 134 | if (subscription != null) { 135 | subscription.dragOver(e); 136 | } 137 | } 138 | 139 | @Override 140 | public void drop(DropTargetEvent e) { 141 | if (subscription != null) { 142 | subscription.drop(e); 143 | } 144 | } 145 | 146 | @Override 147 | public void dropAccept(DropTargetEvent e) { 148 | if (subscription != null) { 149 | subscription.dropAccept(e); 150 | } 151 | } 152 | } 153 | 154 | /** Returns the MultipleDragSource for the given Control. */ 155 | public static DropTargetRouter forControl(Control ctl, Transfer[] transfers) { 156 | DropTargetRouter router = onePerControl.forWidget(ctl); 157 | router.addTransfers(transfers); 158 | return router; 159 | } 160 | 161 | private static final OnePerWidget onePerControl = OnePerWidget.from(DropTargetRouter::new); 162 | } 163 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/Droppable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import org.eclipse.swt.dnd.DropTargetListener; 20 | import org.eclipse.swt.dnd.Transfer; 21 | 22 | /** Generic interface for a custom widget which supports dropping. */ 23 | public interface Droppable { 24 | void addDropSupport(DropTargetListener dropListener, Transfer[] transfers); 25 | 26 | default void addDropSupport(StructuredDrop drop) { 27 | addDropSupport(drop.getListener(), drop.getListener().transferArray()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/ToolBarDrop.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import com.diffplug.common.base.Unhandled; 20 | import com.diffplug.common.swt.OnePerWidget; 21 | import com.diffplug.common.swt.SwtThread; 22 | import com.diffplug.common.swt.dnd.StructuredDrop.DropMethod; 23 | import java.util.Arrays; 24 | import java.util.HashMap; 25 | import java.util.HashSet; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import org.eclipse.jface.action.ActionContributionItem; 30 | import org.eclipse.jface.action.IAction; 31 | import org.eclipse.jface.action.ToolBarManager; 32 | import org.eclipse.swt.dnd.DND; 33 | import org.eclipse.swt.dnd.DropTarget; 34 | import org.eclipse.swt.dnd.DropTargetEvent; 35 | import org.eclipse.swt.dnd.DropTargetListener; 36 | import org.eclipse.swt.dnd.Transfer; 37 | import org.eclipse.swt.graphics.Point; 38 | import org.eclipse.swt.widgets.ToolBar; 39 | import org.eclipse.swt.widgets.ToolItem; 40 | 41 | /** Mechanism for adding drop to toolbars. */ 42 | @SwtThread 43 | public class ToolBarDrop { 44 | private static final OnePerWidget pool = new OnePerWidget() { 45 | @Override 46 | protected ToolBarDrop create(ToolBar ctl) { 47 | return new ToolBarDrop(ctl); 48 | } 49 | }; 50 | 51 | final ToolBar toolbar; 52 | final Set transferSet = new HashSet<>(); 53 | final Map actionToListener = new HashMap<>(); 54 | final Map toolItemToListener = new HashMap<>(); 55 | final DropTarget target; 56 | Transfer[] transferArray; 57 | 58 | private ToolBarDrop(ToolBar toolbar) { 59 | this.toolbar = toolbar; 60 | Object obj = toolbar.getData(DND.DROP_TARGET_KEY); 61 | if (obj != null) { 62 | // CoolBars might add a drop listener, but we don't want their behavior. 63 | // Clobber the existing DropTarget. 64 | ((DropTarget) obj).dispose(); 65 | } 66 | target = new DropTarget(toolbar, DndOp.dropAll()); 67 | target.addDropListener(delegateListener); 68 | } 69 | 70 | final DropTargetListener delegateListener = new DropTargetListener() { 71 | // @formatter:off 72 | @Override public void dragEnter(DropTargetEvent event) { handle(event, DropMethod.dragEnter); } 73 | @Override public void dragLeave(DropTargetEvent event) { handle(event, DropMethod.dragLeave); } 74 | @Override public void dragOperationChanged(DropTargetEvent event) { handle(event, DropMethod.dragOperationChanged); } 75 | @Override public void dragOver(DropTargetEvent event) { handle(event, DropMethod.dragOver); } 76 | @Override public void drop(DropTargetEvent event) { handle(event, DropMethod.drop); } 77 | @Override public void dropAccept(DropTargetEvent event) { handle(event, DropMethod.dropAccept); } 78 | // @formatter:on 79 | 80 | private void handle(DropTargetEvent event, DropMethod method) { 81 | // by default we'll set the detail to NONE, to tell the user that there's no deal. 82 | // if there is a listener, it can override this 83 | event.detail = DND.DROP_DEFAULT; 84 | 85 | // map the point from global coordinates to the toolbar 86 | Point point = new Point(event.x, event.y); 87 | point = toolbar.getDisplay().map(null, toolbar, point); 88 | // get the ToolItem that it landed on 89 | ToolItem item = toolbar.getItem(point); 90 | 91 | // find the item directly or through actionToListener 92 | DropTargetListener listener = toolItemToListener.get(item); 93 | if (listener == null && item != null && item.getData() instanceof ActionContributionItem) { 94 | // if it's an ActionContributionItem, get it 95 | ActionContributionItem contributionItem = (ActionContributionItem) item.getData(); 96 | // find the associated action 97 | IAction action = contributionItem.getAction(); 98 | // find the DropTargetListener for the item 99 | listener = actionToListener.get(action); 100 | } 101 | 102 | if (listener != null) { 103 | // if there is a listener, delegate the method call to it 104 | // @formatter:off 105 | switch (method) { 106 | case dragEnter: listener.dragEnter(event); break; 107 | case dragLeave: listener.dragLeave(event); break; 108 | case dragOperationChanged: listener.dragOperationChanged(event); break; 109 | case dragOver: listener.dragOver(event); break; 110 | case drop: listener.drop(event); break; 111 | case dropAccept: listener.dropAccept(event); break; 112 | default: throw Unhandled.enumException(method); 113 | } 114 | // @formatter:on 115 | } 116 | } 117 | }; 118 | 119 | private void enforceTransfers(List transfers) { 120 | if (transferSet.containsAll(transfers)) { 121 | return; 122 | } 123 | transferSet.addAll(transfers); 124 | transferArray = transferSet.toArray(new Transfer[transferSet.size()]); 125 | target.setTransfer(transferArray); 126 | } 127 | 128 | private void add(IAction action, Transfer[] transfers, DropTargetListener listener) { 129 | enforceTransfers(Arrays.asList(transfers)); 130 | Object previous = actionToListener.put(action, listener); 131 | if (previous != null) { 132 | throw new IllegalArgumentException("Duplicate drop listener for " + action); 133 | } 134 | } 135 | 136 | private void add(ToolItem item, Transfer[] transfers, DropTargetListener listener) { 137 | enforceTransfers(Arrays.asList(transfers)); 138 | Object previous = toolItemToListener.put(item, listener); 139 | if (previous != null) { 140 | throw new IllegalArgumentException("Duplicate drop listener for " + item); 141 | } 142 | } 143 | 144 | public static void addDropSupport(ToolBarManager toolbarManager, IAction action, Transfer[] transfers, DropTargetListener listener) { 145 | pool.forWidget(toolbarManager.getControl()).add(action, transfers, listener); 146 | } 147 | 148 | public static void addDropSupport(ToolBarManager toolbarManager, IAction action, StructuredDrop.Listener listener) { 149 | addDropSupport(toolbarManager, action, listener.transferArray(), listener); 150 | } 151 | 152 | public static void addDropSupport(ToolItem item, Transfer[] transfers, DropTargetListener listener) { 153 | pool.forWidget(item.getParent()).add(item, transfers, listener); 154 | } 155 | 156 | public static void addDropSupport(ToolItem toolItem, StructuredDrop.Listener listener) { 157 | addDropSupport(toolItem, listener.transferArray(), listener); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/TypedTransfer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.dnd; 17 | 18 | 19 | import javax.annotation.Nullable; 20 | import org.eclipse.swt.dnd.ByteArrayTransfer; 21 | import org.eclipse.swt.dnd.DragSourceEvent; 22 | import org.eclipse.swt.dnd.DropTargetEvent; 23 | 24 | /** A strongly-typed custom transfer type. */ 25 | public abstract class TypedTransfer extends ByteArrayTransfer { 26 | public abstract T getValue(DropTargetEvent e); 27 | 28 | public abstract void setValue(DragSourceEvent e, T value); 29 | 30 | protected boolean canSetValue(@Nullable T value) { 31 | return value == null ? false : canSetValueNonnull(value); 32 | } 33 | 34 | protected abstract boolean canSetValueNonnull(T value); 35 | 36 | /** 37 | * Called after every call to {@link StructuredDrag#add(TypedTransfer, com.diffplug.common.swt.dnd.StructuredDrag.TypedDragHandler)} 38 | * and {@link StructuredDrag.TypeMapper#mapTo(TypedTransfer, com.diffplug.common.base.Converter)}. 39 | */ 40 | protected void mapDrag(StructuredDrag.TypeMapper typeMapper) {} 41 | 42 | /** 43 | * Called after every call to {@link StructuredDrop#add(TypedTransfer, com.diffplug.common.swt.dnd.StructuredDrop.TypedDropHandler)} 44 | * and {@link StructuredDrop.TypeMapper#mapFrom(TypedTransfer, java.util.function.Function)} 45 | */ 46 | protected void mapDrop(StructuredDrop.TypeMapper typeMapper) {} 47 | } 48 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/dnd/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.diffplug.common.swt.dnd; 3 | 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/jface/ImageDescriptors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.jface; 17 | 18 | 19 | import com.diffplug.common.base.Box; 20 | import com.diffplug.common.base.Errors; 21 | import com.diffplug.common.base.Unhandled; 22 | import com.diffplug.common.swt.OnePerWidget; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Objects; 26 | import java.util.function.Consumer; 27 | import java.util.function.Supplier; 28 | import org.eclipse.jface.resource.ImageDescriptor; 29 | import org.eclipse.swt.SWT; 30 | import org.eclipse.swt.graphics.Image; 31 | import org.eclipse.swt.graphics.ImageData; 32 | import org.eclipse.swt.widgets.Button; 33 | import org.eclipse.swt.widgets.Item; 34 | import org.eclipse.swt.widgets.Label; 35 | import org.eclipse.swt.widgets.Widget; 36 | 37 | /** Utilities for using {@link ImageDescriptor}s correctly. */ 38 | public class ImageDescriptors { 39 | /** Creates an image from the given data, and disposes it when the lifecycle widget is disposed. */ 40 | public static Image createManagedImage(ImageData data, Widget lifecycle) { 41 | Image image = new Image(lifecycle.getDisplay(), data); 42 | lifecycle.addListener(SWT.Dispose, e -> { 43 | image.dispose(); 44 | }); 45 | return image; 46 | } 47 | 48 | /** 49 | * {@link ImageDescriptor} allows an {@link Image} to be shared in a pool using reference counting. In order to not screw-up the reference 50 | * counting, you need to be pretty careful with how you use them. 51 | *

52 | * This creates a {@link com.diffplug.common.base.Box.Nullable Box.Nullable<ImageDescriptor>} which sets and gets images in a way that will keep the reference counting happy. 53 | *

54 | * NO ONE MUST SET THE IMAGE EXCEPT THIS SETTER. 55 | * 56 | * @param lifecycle Any outstanding images will be destroyed with the lifecycle of this Widget. 57 | * @param imageGetter Function which returns the image currently on the Widget (used to ensure that nobody messed with it). 58 | * @param imageSetter Function which sets the image on the Widget. 59 | * @return A `Box.Nullable` for setting the {@link Image} using an {@link ImageDescriptor}. 60 | */ 61 | public static Box.Nullable createSetter(Widget lifecycle, Supplier imageGetter, Consumer imageSetter) { 62 | return new Box.Nullable() { 63 | private ImageDescriptor lastDesc; 64 | private Image lastImg; 65 | 66 | { 67 | // when the control is disposed, we'll clear the image 68 | lifecycle.addListener(SWT.Dispose, e -> { 69 | if (lastDesc != null) { 70 | lastDesc.destroyResource(lastImg); 71 | } 72 | }); 73 | } 74 | 75 | @Override 76 | public ImageDescriptor get() { 77 | return lastDesc; 78 | } 79 | 80 | @Override 81 | public void set(ImageDescriptor newDesc) { 82 | // make sure nobody else messed with the image 83 | if (!Objects.equals(imageGetter.get(), lastImg)) { 84 | // if someone else did mess with it, we can probably survive, so best to just 85 | // log the failure and continue with setting the image 86 | Errors.log().accept(new IllegalStateException("Setter must have exclusive control over the image field.")); 87 | } 88 | 89 | // set the new image 90 | Image newImg; 91 | if (newDesc != null) { 92 | newImg = (Image) newDesc.createResource(lifecycle.getDisplay()); 93 | } else { 94 | newImg = null; 95 | } 96 | imageSetter.accept(newImg); 97 | 98 | // if an image was already set, destroy it 99 | if (lastDesc != null) { 100 | lastDesc.destroyResource(lastImg); 101 | } 102 | 103 | // save the fields for the next go-round 104 | lastDesc = newDesc; 105 | lastImg = newImg; 106 | } 107 | }; 108 | } 109 | 110 | /** Global cache of widget -> image descriptor setters. */ 111 | private static final OnePerWidget> globalSetter = OnePerWidget.from((Widget widget) -> { 112 | if (widget instanceof Item) { 113 | Item cast = (Item) widget; 114 | return createSetter(cast, cast::getImage, cast::setImage); 115 | } else if (widget instanceof Button) { 116 | Button cast = (Button) widget; 117 | return createSetter(cast, cast::getImage, cast::setImage); 118 | } else if (widget instanceof Label) { 119 | Label cast = (Label) widget; 120 | return createSetter(cast, cast::getImage, cast::setImage); 121 | } else { 122 | throw Unhandled.classException(widget); 123 | } 124 | }); 125 | 126 | /** Sets the given {@link Item} to have the image described by the given descriptor, maintaining proper reference counting. */ 127 | public static void set(Item widget, ImageDescriptor image) { 128 | globalSetter.forWidget(widget).set(image); 129 | } 130 | 131 | /** Sets the given {@link Button} to have the image described by the given descriptor, maintaining proper reference counting. */ 132 | public static void set(Button widget, ImageDescriptor image) { 133 | globalSetter.forWidget(widget).set(image); 134 | } 135 | 136 | /** Sets the given {@link Label} to have the image described by the given descriptor, maintaining proper reference counting. */ 137 | public static void set(Label widget, ImageDescriptor image) { 138 | globalSetter.forWidget(widget).set(image); 139 | } 140 | 141 | private static final OnePerWidget> globalPool = OnePerWidget.from(widget -> { 142 | Map map = new HashMap<>(); 143 | widget.addListener(SWT.Dispose, e -> map.values().forEach(Image::dispose)); 144 | return map; 145 | }); 146 | 147 | /** Returns an image which will be bound to the lifecycle of the owner widget. */ 148 | public static Image getFromPool(Widget owner, ImageDescriptor descriptor) { 149 | Map map = globalPool.forWidget(owner); 150 | Image image = map.get(descriptor); 151 | if (image == null) { 152 | image = descriptor.createImage(true, owner.getDisplay()); 153 | map.put(descriptor, image); 154 | } 155 | return image; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/jface/JFaceRx.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.jface; 17 | 18 | 19 | import com.diffplug.common.base.Preconditions; 20 | import com.diffplug.common.rx.Rx; 21 | import com.diffplug.common.rx.RxBox; 22 | import com.diffplug.common.swt.SwtMisc; 23 | import com.diffplug.common.swt.SwtThread; 24 | import org.eclipse.jface.action.IAction; 25 | 26 | /** Utilities that convert JFace events into Rx-friendly Observables. */ 27 | public class JFaceRx { 28 | /** 29 | * Returns an `RxBox` for the toggle state of the given action as an RxBox. 30 | *

31 | * Applicable to IAction.AS_CHECK_BOX and AS_RADIO_BUTTON. 32 | */ 33 | public static @SwtThread RxBox toggle(IAction action) { 34 | Preconditions.checkArgument(SwtMisc.flagIsSet(IAction.AS_CHECK_BOX, action.getStyle()) || 35 | SwtMisc.flagIsSet(IAction.AS_RADIO_BUTTON, action.getStyle())); 36 | RxBox box = RxBox.of(action.isChecked()); 37 | action.addPropertyChangeListener(e -> { 38 | if ("checked".equals(e.getProperty())) { 39 | box.set((Boolean) e.getNewValue()); 40 | } 41 | }); 42 | Rx.subscribe(box, isChecked -> { 43 | action.setChecked(isChecked); 44 | }); 45 | return box; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/jface/LabelProviders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.jface; 17 | 18 | 19 | import com.diffplug.common.base.Functions; 20 | import java.util.function.Function; 21 | import org.eclipse.jface.viewers.CellLabelProvider; 22 | import org.eclipse.jface.viewers.ColumnLabelProvider; 23 | import org.eclipse.jface.viewers.ILabelProvider; 24 | import org.eclipse.swt.graphics.Color; 25 | import org.eclipse.swt.graphics.Font; 26 | import org.eclipse.swt.graphics.Image; 27 | 28 | /** Utilities for creating JFace {@link CellLabelProvider} (which is also appropriate for plain-jane {@link ILabelProvider}). */ 29 | public class LabelProviders { 30 | /** Returns a fluent builder for creating a {@link ColumnLabelProvider}. */ 31 | public static Builder builder() { 32 | return new Builder(); 33 | } 34 | 35 | /** Creates a {@link ColumnLabelProvider} for text. */ 36 | public static ColumnLabelProvider createWithText(Function text) { 37 | Builder builder = builder(); 38 | builder.setText(text); 39 | return builder.build(); 40 | } 41 | 42 | /** Creates a {@link ColumnLabelProvider} for images. */ 43 | public static ColumnLabelProvider createWithImage(Function image) { 44 | Builder builder = builder(); 45 | builder.setImage(image); 46 | return builder.build(); 47 | } 48 | 49 | /** Creates a {@link ColumnLabelProvider} for text and images. */ 50 | public static ColumnLabelProvider createWithTextAndImage(Function text, Function image) { 51 | Builder builder = builder(); 52 | builder.setText(text); 53 | builder.setImage(image); 54 | return builder.build(); 55 | } 56 | 57 | /** An override of {@link ColumnLabelProvider}. */ 58 | @SuppressWarnings("unchecked") 59 | private static class Imp extends ColumnLabelProvider { 60 | private final Function text; 61 | private final Function image; 62 | private final Function background; 63 | private final Function foreground; 64 | private final Function font; 65 | 66 | private Imp(Function text, Function image, Function background, Function foreground, Function font) { 67 | this.text = text; 68 | this.image = image; 69 | this.background = background; 70 | this.foreground = foreground; 71 | this.font = font; 72 | } 73 | 74 | @Override 75 | public String getText(Object element) { 76 | return text.apply((T) element); 77 | } 78 | 79 | @Override 80 | public Image getImage(Object element) { 81 | return image.apply((T) element); 82 | } 83 | 84 | @Override 85 | public Color getBackground(Object element) { 86 | return background.apply((T) element); 87 | } 88 | 89 | @Override 90 | public Color getForeground(Object element) { 91 | return foreground.apply((T) element); 92 | } 93 | 94 | @Override 95 | public Font getFont(Object element) { 96 | return font.apply((T) element); 97 | } 98 | } 99 | 100 | /** A fluent API for creating a {@link ColumnLabelProvider}. */ 101 | public static class Builder extends ColumnLabelProvider { 102 | private Function text = Functions.constant(null); 103 | private Function image = Functions.constant(null); 104 | private Function background = Functions.constant(null); 105 | private Function foreground = Functions.constant(null); 106 | private Function font = Functions.constant(null); 107 | 108 | private Builder() {} 109 | 110 | /** Sets the function used to determine the text. */ 111 | public Builder setText(Function text) { 112 | this.text = text; 113 | return this; 114 | } 115 | 116 | /** Sets the function used to determine the image. */ 117 | public Builder setImage(Function image) { 118 | this.image = image; 119 | return this; 120 | } 121 | 122 | /** Sets the function used to determine the background color. */ 123 | public Builder setBackground(Function background) { 124 | this.background = background; 125 | return this; 126 | } 127 | 128 | /** Sets the function used to determine the foreground color. */ 129 | public Builder setForeground(Function foreground) { 130 | this.foreground = foreground; 131 | return this; 132 | } 133 | 134 | /** Sets the function used to determine the font. */ 135 | public Builder setFont(Function font) { 136 | this.font = font; 137 | return this; 138 | } 139 | 140 | /** Returns a new ColumnLabelProvider. */ 141 | public ColumnLabelProvider build() { 142 | return new Imp(text, image, background, foreground, font); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/jface/ViewerMisc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.jface; 17 | 18 | import com.diffplug.common.base.Converter; 19 | import com.diffplug.common.base.Preconditions; 20 | import com.diffplug.common.collect.ImmutableList; 21 | import com.diffplug.common.collect.ImmutableSet; 22 | import com.diffplug.common.rx.RxBox; 23 | import com.diffplug.common.swt.SwtExec; 24 | import com.diffplug.common.swt.SwtMisc; 25 | import com.diffplug.common.swt.SwtThread; 26 | import com.diffplug.common.tree.TreeDef; 27 | import java.util.Optional; 28 | import org.eclipse.jface.viewers.ILazyTreeContentProvider; 29 | import org.eclipse.jface.viewers.IStructuredSelection; 30 | import org.eclipse.jface.viewers.ITreeContentProvider; 31 | import org.eclipse.jface.viewers.StructuredSelection; 32 | import org.eclipse.jface.viewers.StructuredViewer; 33 | import org.eclipse.jface.viewers.TreeViewer; 34 | import org.eclipse.jface.viewers.Viewer; 35 | import org.eclipse.swt.SWT; 36 | 37 | /** Utilities for manipulating and creating JFace viewers. */ 38 | public class ViewerMisc { 39 | /** Returns a thread-safe `RxBox` for manipulating the selection of a {@link StructuredViewer} created with {@link SWT#SINGLE}. */ 40 | public static @SwtThread RxBox> singleSelection(StructuredViewer viewer) { 41 | RxBox> box = RxBox.of(Optional.empty()); 42 | singleSelection(viewer, box); 43 | return box; 44 | } 45 | 46 | /** Returns a thread-safe `RxBox` for manipulating the selection of a {@link StructuredViewer} created with {@link SWT#SINGLE}. */ 47 | public static void singleSelection(StructuredViewer viewer, RxBox> box) { 48 | Preconditions.checkArgument(SwtMisc.flagIsSet(SWT.SINGLE, viewer.getControl()), "Control style does not have SWT.SINGLE set."); 49 | // set the box when the selection changes 50 | viewer.addSelectionChangedListener(event -> { 51 | IStructuredSelection selection = (IStructuredSelection) event.getSelection(); 52 | @SuppressWarnings("unchecked") 53 | T selected = (T) selection.getFirstElement(); 54 | box.set(Optional.ofNullable(selected)); 55 | }); 56 | // set the selection when the box changes 57 | SwtExec.immediate().guardOn(viewer.getControl()).subscribe(box, optional -> { 58 | if (optional.isPresent()) { 59 | viewer.setSelection(new StructuredSelection(optional.get())); 60 | } else { 61 | viewer.setSelection(StructuredSelection.EMPTY); 62 | } 63 | }); 64 | } 65 | 66 | /** Returns a thread-safe `RxBox` for manipulating the selection of a {@link StructuredViewer} created with {@link SWT#MULTI}. */ 67 | public static @SwtThread RxBox> multiSelectionSet(StructuredViewer viewer) { 68 | RxBox> box = RxBox.of(ImmutableSet.of()); 69 | multiSelectionSet(viewer, box); 70 | return box; 71 | } 72 | 73 | /** Manipulates the selection of the given viewer with the given `RxBox`. */ 74 | public static void multiSelectionSet(StructuredViewer viewer, RxBox> box) { 75 | Converter, ImmutableList> converter = Converter.from( 76 | ImmutableSet::asList, ImmutableSet::copyOf, "setToList"); 77 | multiSelectionList(viewer, box.map(converter)); 78 | } 79 | 80 | /** Returns a thread-safe `RxBox` for manipulating the selection of a {@link StructuredViewer} created with {@link SWT#MULTI}. */ 81 | public static @SwtThread RxBox> multiSelectionList(StructuredViewer viewer) { 82 | RxBox> box = RxBox.of(ImmutableList.of()); 83 | multiSelectionList(viewer, box); 84 | return box; 85 | } 86 | 87 | @SuppressWarnings("unchecked") 88 | public static void multiSelectionList(StructuredViewer viewer, RxBox> box) { 89 | Preconditions.checkArgument(SwtMisc.flagIsSet(SWT.MULTI, viewer.getControl()), "Control style does not have SWT.MULTI set."); 90 | // set the box when the selection changes 91 | viewer.addSelectionChangedListener(event -> { 92 | IStructuredSelection selection = (IStructuredSelection) event.getSelection(); 93 | box.set(ImmutableList.copyOf(selection.toList())); 94 | }); 95 | // set the selection when the box changes 96 | SwtExec.immediate().guardOn(viewer.getControl()).subscribe(box, list -> { 97 | viewer.setSelection(new StructuredSelection(list)); 98 | }); 99 | } 100 | 101 | /** Sets an {@link ITreeContentProvider} implemented by the given {@link TreeDef.Parented}. */ 102 | @SuppressWarnings("unchecked") 103 | public static void setTreeContentProvider(TreeViewer viewer, TreeDef.Parented treeDef) { 104 | viewer.setContentProvider(new ITreeContentProvider() { 105 | @Override 106 | public Object[] getElements(Object inputElement) { 107 | return getChildren(inputElement); 108 | } 109 | 110 | @Override 111 | public Object[] getChildren(Object parentElement) { 112 | return treeDef.childrenOf((T) parentElement).toArray(); 113 | } 114 | 115 | @Override 116 | public Object getParent(Object element) { 117 | return treeDef.parentOf((T) element); 118 | } 119 | 120 | @Override 121 | public boolean hasChildren(Object element) { 122 | return !treeDef.childrenOf((T) element).isEmpty(); 123 | } 124 | 125 | @Override 126 | public void dispose() {} 127 | 128 | @Override 129 | public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {} 130 | }); 131 | } 132 | 133 | /** Sets an {@link ILazyTreeContentProvider} implemented by the given {@link TreeDef.Parented}. */ 134 | @SuppressWarnings("unchecked") 135 | public static void setLazyTreeContentProvider(TreeViewer viewer, TreeDef.Parented treeDef) { 136 | Preconditions.checkArgument(SwtMisc.flagIsSet(SWT.VIRTUAL, viewer.getControl()), "The tree must have SWT.VIRTUAL set."); 137 | viewer.setUseHashlookup(true); 138 | viewer.setContentProvider(new ILazyTreeContentProvider() { 139 | @Override 140 | public void dispose() {} 141 | 142 | @Override 143 | public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {} 144 | 145 | @Override 146 | public void updateElement(Object parent, int index) { 147 | T child = treeDef.childrenOf((T) parent).get(index); 148 | viewer.replace(parent, index, child); 149 | updateChildCount(child, 0); 150 | } 151 | 152 | @Override 153 | public void updateChildCount(Object element, int currentChildCount) { 154 | viewer.setChildCount(element, treeDef.childrenOf((T) element).size()); 155 | } 156 | 157 | @Override 158 | public Object getParent(Object element) { 159 | return treeDef.parentOf((T) element); 160 | } 161 | }); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/jface/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.diffplug.common.swt.jface; 3 | 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.diffplug.common.swt; 3 | 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | -------------------------------------------------------------------------------- /durian-swt/src/main/java/com/diffplug/common/swt/widgets/ButtonPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 DiffPlug 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.diffplug.common.swt.widgets; 17 | 18 | 19 | import com.diffplug.common.base.Preconditions; 20 | import com.diffplug.common.collect.ImmutableList; 21 | import com.diffplug.common.collect.Lists; 22 | import com.diffplug.common.collect.MoreCollectors; 23 | import com.diffplug.common.swt.Coat; 24 | import com.diffplug.common.swt.ControlWrapper; 25 | import com.diffplug.common.swt.Layouts; 26 | import com.diffplug.common.swt.LayoutsGridData; 27 | import com.diffplug.common.swt.SwtMisc; 28 | import java.util.List; 29 | import java.util.Optional; 30 | import java.util.function.Consumer; 31 | import org.eclipse.swt.SWT; 32 | import org.eclipse.swt.widgets.Button; 33 | import org.eclipse.swt.widgets.Composite; 34 | 35 | /** A quick way to make right-justified buttons. */ 36 | public class ButtonPanel extends ControlWrapper.AroundControl { 37 | public static class Builder { 38 | private Optional leftCoat = Optional.empty(); 39 | private List texts = Lists.newArrayList(); 40 | private List sizes = Lists.newArrayList(); 41 | private List actions = Lists.newArrayList(); 42 | 43 | /** Adds an OK button. */ 44 | public Builder leftSide(Coat coat) { 45 | Preconditions.checkArgument(!leftCoat.isPresent()); 46 | leftCoat = Optional.of(coat); 47 | return this; 48 | } 49 | 50 | /** Adds an OK button. */ 51 | public Builder ok(Runnable ok) { 52 | return add("OK", ok); 53 | } 54 | 55 | /** Adds an OK and Cancel button. */ 56 | public Builder okCancel(Runnable ok, Runnable cancel) { 57 | add("OK", ok); 58 | add("Cancel", cancel); 59 | return this; 60 | } 61 | 62 | /** Adds an OK and Cancel button. */ 63 | public Builder okCancel(Consumer okClicked) { 64 | return okCancel(() -> okClicked.accept(true), () -> okClicked.accept(false)); 65 | } 66 | 67 | /** Adds a button with the given text and action, as well as the default width. */ 68 | public Builder add(String text, Runnable action) { 69 | return add(text, SwtMisc.defaultButtonWidth(), action); 70 | } 71 | 72 | /** Adds a button with the given text, width, and action. */ 73 | public Builder add(String text, int size, Runnable action) { 74 | texts.add(text); 75 | sizes.add(size); 76 | actions.add(action); 77 | return this; 78 | } 79 | 80 | /** Creates a button panel on the given Composite. */ 81 | public ButtonPanel build(Composite parent) { 82 | return new ButtonPanel(parent, this); 83 | } 84 | 85 | /** Creates a button panel on the given Composite, and returns a LayoutsGridData for manipulating it. */ 86 | public LayoutsGridData buildOnGrid(Composite parent) { 87 | return Layouts.setGridData(build(parent)); 88 | } 89 | } 90 | 91 | /** Creates a ButtonPanel builder. */ 92 | public static Builder builder() { 93 | return new Builder(); 94 | } 95 | 96 | final ImmutableList