diff --git a/.gitignore b/.gitignore index 585203ce5..db78f1b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ bin/ xtend-gen/ target/ -localM2/ -klighd-snapshots/ -klighd/ +/localM2/ +/klighd-snapshots/ +/klighd/ .svn/ .DS_Store *.class diff --git a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/EObjectFallbackSynthesis.xtend b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/EObjectFallbackSynthesis.xtend index 7692d8c14..175c9321f 100644 --- a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/EObjectFallbackSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/EObjectFallbackSynthesis.xtend @@ -21,10 +21,11 @@ import com.google.common.collect.Table import com.google.inject.Inject import de.cau.cs.kieler.klighd.KlighdConstants import de.cau.cs.kieler.klighd.SynthesisOption +import de.cau.cs.kieler.klighd.ide.syntheses.action.EcoreModelExpandDetailsAction import de.cau.cs.kieler.klighd.kgraph.KNode import de.cau.cs.kieler.klighd.kgraph.KPort import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil -import de.cau.cs.kieler.klighd.krendering.Colors +import de.cau.cs.kieler.klighd.krendering.KColor import de.cau.cs.kieler.klighd.krendering.LineStyle import de.cau.cs.kieler.klighd.krendering.extensions.KColorExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions @@ -36,8 +37,8 @@ import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions import de.cau.cs.kieler.klighd.microlayout.PlacementUtil import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis -import de.cau.cs.kieler.klighd.ide.syntheses.action.EcoreModelExpandDetailsAction import de.cau.cs.kieler.klighd.util.KlighdProperties +import java.awt.Color import java.util.List import java.util.Map import org.eclipse.elk.alg.layered.options.EdgeStraighteningStrategy @@ -55,6 +56,7 @@ import org.eclipse.emf.ecore.EStructuralFeature import org.eclipse.emf.ecore.util.EContentsEList import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* +import static extension org.eclipse.emf.ecore.util.EcoreUtil.copy /** * Diagram synthesis of a {@link EObject}. @@ -116,11 +118,20 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { val Map nodeCache = newHashMap val Table portCache = HashBasedTable.create + // Colors + var KColor foregroundColor + var KColor backgroundColor + var KColor foregroundNodeColor + var KColor backgroundNodeColor + override KNode transform(EObject model) { // Init cache nodeCache.clear portCache.clear + // establish color theme + configureColors() + val rootNode = createNode(); rootNode.addLayoutParam(CoreOptions::ALGORITHM, LayeredOptions.ALGORITHM_ID); @@ -128,6 +139,7 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { rootNode.setLayoutOption(LayeredOptions::NODE_PLACEMENT_BK_EDGE_STRAIGHTENING, EdgeStraighteningStrategy.IMPROVE_STRAIGHTNESS); rootNode.setLayoutOption(LayeredOptions::SPACING_EDGE_NODE, LayeredOptions.SPACING_NODE_NODE.^default * 1.1f); rootNode.setLayoutOption(LayeredOptions::SPACING_EDGE_NODE_BETWEEN_LAYERS, LayeredOptions.SPACING_NODE_NODE.^default * 1.1f); + rootNode.setProperty(KlighdProperties.DIAGRAM_BACKGROUND, backgroundColor) // transform root object rootNode.children += model.transformToNode @@ -149,8 +161,12 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { } it.target = nodeCache.get(eObject); it.addPolyline => [ - addHeadArrowDecorator - addJunctionPointDecorator + it.foreground = foregroundNodeColor.copy; + it.addHeadArrowDecorator + it.addJunctionPointDecorator => [ + it.foreground = foregroundNodeColor.copy + it.background = foregroundNodeColor.copy + ] ] ] } @@ -169,9 +185,13 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { } it.target = nodeCache.get(targetEObject); it.addPolyline => [ - addHeadArrowDecorator - addJunctionPointDecorator - lineStyle = LineStyle.DASH + it.foreground = foregroundNodeColor.copy; + it.addHeadArrowDecorator + it.addJunctionPointDecorator => [ + it.foreground = foregroundNodeColor.copy + it.background = foregroundNodeColor.copy + ] + it.lineStyle = LineStyle.DASH ]; ] } @@ -197,14 +217,15 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { // Expanded Rectangle node.createFigure() => [ it.setProperty(KlighdProperties::EXPANDED_RENDERING, true); + it.addDoubleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.setGridPlacement(1); // add name of object as text it.addText(object.eClass.name).associateWith(object) => [ it.fontSize = 11; - it.setFontBold(true); + it.foreground = foregroundColor.copy; it.setGridPlacementData().from(LEFT, 9, 0, TOP, 8f, 0).to(RIGHT, 8, 0, BOTTOM, 4, 0); - it.suppressSelectability; + it.selectionFontBold = false; ]; // collapse button @@ -214,14 +235,16 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { it.addSingleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.addDoubleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.setGridPlacementData().from(LEFT, 8, 0, TOP, 0, 0).to(RIGHT, 8, 0, BOTTOM, 0, 0); + it.selectionFontBold = false; + it.suppressSelectability; ]; if (hasAttributes || hasSuperTypes) { // Add Details it.addRectangle => [ it.setGridPlacementData.from(LEFT, 8, 0, TOP, 0, 0).to(RIGHT, 8, 0, BOTTOM, 8, 0); - it.setBackground = "white".color; - it.foreground = "gray".color + it.background = backgroundColor.copy; + it.foreground = foregroundNodeColor.copy; it.lineWidth = 1; it.setGridPlacement(1); @@ -230,16 +253,19 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { // add all super types as string representation if (hasSuperTypes) { it.addText("SuperTypes") => [ - it.foreground = "gray".color; + it.foreground = foregroundNodeColor.copy; it.fontSize = 8; it.setGridPlacementData().from(LEFT, 5, 0, TOP, 2, 0).to(RIGHT, 5, 0, BOTTOM, 2, 0); + it.selectionFontBold = false; it.suppressSelectability; ]; eclass.EAllSuperTypes.filterNull.forEach [ // add a text with name and value of the attribute container.addText(it.name) => [ it.fontSize = 9; + it.foreground = foregroundColor.copy; it.setGridPlacementData().from(LEFT, 5, 0, TOP, 2, 0).to(RIGHT, 5, 0, BOTTOM, 2, 0); + it.selectionFontBold = false; it.suppressSelectability; ]; ] @@ -247,19 +273,22 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { // add all attributes as string representation if (hasAttributes) { if (hasSuperTypes) { - it.addHorizontalSeperatorLine(1, 1).foreground = "gray".color; + it.addHorizontalSeperatorLine(1, 1).foreground = foregroundNodeColor.copy; } it.addText("Attributes") => [ - it.foreground = "gray".color; + it.foreground = foregroundNodeColor.copy; it.fontSize = 8; it.setGridPlacementData().from(LEFT, 5, 0, TOP, 2, 0).to(RIGHT, 5, 0, BOTTOM, 2, 0); + it.selectionFontBold = false; it.suppressSelectability; ]; eclass.EAllAttributes.filterNull.forEach [ // add a text with name and value of the attribute container.addText(it.name + ": " + String::valueOf(object.eGet(it))) => [ it.fontSize = 9; + it.foreground = foregroundColor.copy; it.setGridPlacementData().from(LEFT, 5, 0, TOP, 2, 0).to(RIGHT, 5, 0, BOTTOM, 2, 0); + it.selectionFontBold = false; it.suppressSelectability; ]; ] @@ -269,27 +298,30 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { ]; // Collapse Rectangle - node.createFigure() => - [ + node.createFigure() => [ it.setProperty(KlighdProperties::COLLAPSED_RENDERING, true); + it.addDoubleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.setGridPlacement(1); + // add name of object as text - it.addText(object.eClass.name).associateWith(object) => - [ + it.addText(object.eClass.name).associateWith(object) => [ it.fontSize = 11; - it.setFontBold(true); + it.foreground = foregroundColor.copy; it.setGridPlacementData().from(LEFT, 8, 0, TOP, 8, 0).to(RIGHT, 8, 0, BOTTOM, if(hasAttributes || hasSuperTypes) 4 else 8, 0); - it.suppressSelectability; + it.selectionFontBold = false; ]; + if (hasAttributes || hasSuperTypes) { // Add details button it.addText("[Details]") => [ - it.foreground = "blue".color - it.fontSize = 9 + it.foreground = "blue".color; + it.fontSize = 9; it.addSingleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.addDoubleClickAction(KlighdConstants::ACTION_COLLAPSE_EXPAND); it.setGridPlacementData().from(LEFT, 8, 0, TOP, 0, 0).to(RIGHT, 8, 0, BOTTOM, 8, 0); + it.selectionFontBold = false; + it.suppressSelectability; ]; } ]; @@ -306,14 +338,16 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { private def transformToPort(EStructuralFeature containerFeature, KNode node) { val port = KGraphUtil::createInitializedPort node.ports += port; - port.setPortSize(portEdgeLength, portEdgeLength); + port.setPortSize(0, 0); port.addLayoutParam(CoreOptions::PORT_SIDE, PortSide::EAST); port.setPortPos(node.width-1, node.nextEPortYPosition); port.createLabel => [ - text = containerFeature.name + it.text = containerFeature.name val size = PlacementUtil.estimateSize(it) it.width = size.width it.height = size.height + it.configureOutsidePortLabel(containerFeature.name) + it.getKRendering.foreground = foregroundColor.copy ] // Add to cache @@ -328,12 +362,41 @@ class EObjectFallbackSynthesis extends AbstractDiagramSynthesis { private def createFigure(KNode node) { val figure = node.addRoundedRectangle(8, 8, 1); figure.lineWidth = 1; - figure.foreground = Colors.GRAY; - figure.background = Colors.GRAY_95; + figure.foreground = foregroundNodeColor.copy; + figure.background = backgroundNodeColor.copy; // minimal node size is necessary if no text will be added node.setMinimalNodeSize(2 * figure.cornerWidth, 2 * figure.cornerHeight); return figure; } + + /** + * Configure the colors used in the diagram + */ + def configureColors() { + // default colors + foregroundColor = "black".color + backgroundColor = "white".color + foregroundNodeColor = "#bebebe".color + backgroundNodeColor = "#f2f2f2".color + + val colorPrefereces = usedContext.getProperty(KlighdProperties.COLOR_PREFERENCES); + if (colorPrefereces !== null) { + foregroundColor = colorPrefereces.foreground; + backgroundColor = colorPrefereces.background; + + val fgHSB = Color.RGBtoHSB(foregroundColor.getRed(), foregroundColor.getGreen(), foregroundColor.getBlue(), null); + val bgHSB = Color.RGBtoHSB(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), null); + val adjustment = bgHSB.get(2) < 0.6f ? -0.1f : 0.04f; // reduce brightness if color is dark dark + + fgHSB.set(2, Math.max(0, Math.min(1, fgHSB.get(2) + adjustment))); + val newFg = Color.getHSBColor(fgHSB.get(0), fgHSB.get(1), fgHSB.get(2)); + foregroundNodeColor.setColor(newFg.red, newFg.green, newFg.blue); + + bgHSB.set(2, Math.max(0, Math.min(1, bgHSB.get(2) - adjustment))); + val newBg = Color.getHSBColor(bgHSB.get(0), bgHSB.get(1), bgHSB.get(2)); + backgroundNodeColor.setColor(newBg.red, newBg.green, newBg.blue); + } + } } diff --git a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/ErrorModelSynthesis.xtend b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/ErrorModelSynthesis.xtend index 2ee5813eb..dd5fb7a13 100644 --- a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/ErrorModelSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/ErrorModelSynthesis.xtend @@ -43,7 +43,7 @@ class ErrorModelSynthesis extends AbstractDiagramSynthesis { extension KContainerRenderingExtensions @Inject - extension MessageModelSynthesis + MessageModelSynthesis delegate // ------------------------------------------------------------------------- // Constants @@ -53,7 +53,8 @@ class ErrorModelSynthesis extends AbstractDiagramSynthesis { // Synthesis override KNode transform(ErrorModel model) { // create basic representation with super synthesis - val rootNode = (model as MessageModel).transform; + delegate.use(usedContext); + val rootNode = delegate.transform(model as MessageModel); // Adjust diagram if (rootNode !== null && !rootNode.children.empty) { val messageNode = rootNode.children.head; diff --git a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/MessageModelSynthesis.xtend b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/MessageModelSynthesis.xtend index dbb44990b..ec614bf7b 100644 --- a/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/MessageModelSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.klighd.ide/src/de/cau/cs/kieler/klighd/ide/syntheses/MessageModelSynthesis.xtend @@ -19,12 +19,15 @@ package de.cau.cs.kieler.klighd.ide.syntheses import com.google.inject.Inject import de.cau.cs.kieler.klighd.ide.model.MessageModel import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.krendering.extensions.KColorExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis +import de.cau.cs.kieler.klighd.util.KlighdProperties import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* +import static extension org.eclipse.emf.ecore.util.EcoreUtil.copy /** * Diagram synthesis for a {@link MessageModel}. @@ -42,6 +45,9 @@ class MessageModelSynthesis extends AbstractDiagramSynthesis { @Inject extension KContainerRenderingExtensions + @Inject + extension KColorExtensions + // ------------------------------------------------------------------------- // Constants public static val String ID = "de.cau.cs.kieler.klighd.ui.view.syntheses.MessageModelSynthesis"; @@ -50,6 +56,15 @@ class MessageModelSynthesis extends AbstractDiagramSynthesis { // Synthesis override KNode transform(MessageModel model) { val rootNode = createNode(); + + // color theme + val colorPrefereces = usedContext.getProperty(KlighdProperties.COLOR_PREFERENCES); + val fg = if (colorPrefereces !== null) { + colorPrefereces.foreground; + } else { + "#000000".color; + } + rootNode.children += createNode(model) => [ it.addRectangle() => [ it.invisible = true; @@ -68,12 +83,15 @@ class MessageModelSynthesis extends AbstractDiagramSynthesis { it.addRoundedRectangle(7, 7) => [ it.setGridPlacement(1); it.lineWidth = 2; + it.foreground = fg.copy; //title if (model.getTitle !== null) { it.addText(model.getTitle) => [ it.fontSize = 12; it.setFontBold = true; + it.foreground = fg.copy; it.setGridPlacementData().from(LEFT, 8, 0, TOP, 8, 0).to(RIGHT, 8, 0, BOTTOM, 4, 0); + it.selectionFontBold = false; // No selection style it.suppressSelectability; ] } @@ -81,11 +99,13 @@ class MessageModelSynthesis extends AbstractDiagramSynthesis { if (model.getMessage !== null) { it.addText(model.getMessage) => [ it.fontSize = 12; + it.foreground = fg.copy; if (model.getTitle !== null) { it.setGridPlacementData().from(LEFT, 8, 0, TOP, 0, 0).to(RIGHT, 8, 0, BOTTOM, 4, 0); } else { it.setGridPlacementData().from(LEFT, 8, 0, TOP, 8, 0).to(RIGHT, 8, 0, BOTTOM, 8, 0); } + it.selectionFontBold = false; // No selection style it.suppressSelectability; ] } diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend index aa2ecb3c6..b62109f36 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend @@ -152,6 +152,12 @@ class KGraphDiagramGenerator implements IDiagramGenerator { id = uri children = new ArrayList ] + + // Special property for the diagram background, to be set on the diagram root. + val background = parentNode.getProperty(KlighdProperties.DIAGRAM_BACKGROUND) + if (background !== null) { + (diagramRoot as SKGraph).properties.put(KlighdProperties.DIAGRAM_BACKGROUND.id, background) + } diagramRoot.children.addAll(createNodesAndPrepareEdges(#[parentNode], diagramRoot)) // Do post processing. diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend index bd09ac095..531c7dea8 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend @@ -28,6 +28,7 @@ import de.cau.cs.kieler.klighd.ViewContext import de.cau.cs.kieler.klighd.kgraph.KNode import de.cau.cs.kieler.klighd.lsp.model.CheckImagesAction import de.cau.cs.kieler.klighd.lsp.model.CheckedImagesAction +import de.cau.cs.kieler.klighd.lsp.model.ClientColorPreferencesAction import de.cau.cs.kieler.klighd.lsp.model.DisplayedActionUIData import de.cau.cs.kieler.klighd.lsp.model.LayoutOptionUIData import de.cau.cs.kieler.klighd.lsp.model.PerformActionAction @@ -41,6 +42,7 @@ import de.cau.cs.kieler.klighd.lsp.model.StoreImagesAction import de.cau.cs.kieler.klighd.lsp.model.UpdateDiagramOptionsAction import de.cau.cs.kieler.klighd.lsp.model.ValuedSynthesisOption import de.cau.cs.kieler.klighd.lsp.utils.KRenderingIdGenerator +import de.cau.cs.kieler.klighd.util.ColorPreferences import java.io.FileNotFoundException import java.io.InputStream import java.util.ArrayList @@ -225,6 +227,8 @@ class KGraphDiagramServer extends LanguageAwareDiagramServer { handle(action as SetSynthesisAction) } else if (action.getKind === CheckedImagesAction.KIND) { handle(action as CheckedImagesAction) + } else if (action.getKind === ClientColorPreferencesAction.KIND) { + handle(action as ClientColorPreferencesAction) } else if (action.getKind === RefreshDiagramAction.KIND) { handle(action as RefreshDiagramAction) } else if (action.getKind === RefreshLayoutAction.KIND) { @@ -347,6 +351,23 @@ class KGraphDiagramServer extends LanguageAwareDiagramServer { setOrUpdateModel } } + + /** + * Called when a {@link ClientColorPreferencesAction} is received. + * Tells the server that the client has new color preferences available that should be considered. + */ + protected def handle(ClientColorPreferencesAction action) { + val kind = action.clientColorPreferences.kind + val foregroundColor = LSPUtil.parseColor(action.clientColorPreferences.foreground) + val backgroundColor = LSPUtil.parseColor(action.clientColorPreferences.background) + val highlightColor = LSPUtil.parseColor(action.clientColorPreferences.highlight) + + val colorPreferences = new ColorPreferences(kind, foregroundColor, backgroundColor, highlightColor) + synchronized (diagramState) { + diagramState.colorPreferences = colorPreferences + } + updateDiagram() + } /** * Called when a {@link RefreshDiagramAction} is received. diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramState.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramState.xtend index 586d85d0b..ab9506532 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramState.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramState.xtend @@ -26,6 +26,7 @@ import de.cau.cs.kieler.klighd.internal.ISynthesis import de.cau.cs.kieler.klighd.kgraph.KGraphElement import de.cau.cs.kieler.klighd.krendering.KImage import de.cau.cs.kieler.klighd.lsp.model.ImageData +import de.cau.cs.kieler.klighd.util.ColorPreferences import java.net.URLDecoder import java.util.HashMap import java.util.HashSet @@ -106,6 +107,11 @@ class KGraphDiagramState { */ JsonElement clientOptions + /** + * The color preferences as configured by the client. + */ + public ColorPreferences colorPreferences + /** * A map to map the Sprotty client id to the URI leading to the resource. */ diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramUpdater.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramUpdater.xtend index cf9501e9c..2182feac4 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramUpdater.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramUpdater.xtend @@ -31,6 +31,7 @@ import de.cau.cs.kieler.klighd.lsp.model.RequestDiagramPieceAction import de.cau.cs.kieler.klighd.lsp.model.SKGraph import de.cau.cs.kieler.klighd.lsp.utils.KGraphMappingUtil import de.cau.cs.kieler.klighd.lsp.utils.RenderingPreparer +import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.klighd.util.KlighdSynthesisProperties import java.util.HashSet import java.util.List @@ -269,6 +270,12 @@ class KGraphDiagramUpdater extends DiagramUpdater { } } + synchronized (diagramState) { + if (diagramState.colorPreferences !== null) { + viewContext.setProperty(KlighdProperties.COLOR_PREFERENCES, diagramState.colorPreferences) + } + } + val vc = viewContext // Update the model and with that call the diagram synthesis. AbstractLanguageServer.addToMainThreadQueue([ diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphLanguageServerExtension.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphLanguageServerExtension.xtend index 4d707ad41..f5fe619c7 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphLanguageServerExtension.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphLanguageServerExtension.xtend @@ -110,6 +110,8 @@ class KGraphLanguageServerExtension extends SyncDiagramLanguageServer */ public static String CLIENT_DIAGRAM_OPTIONS_PROPERTY = "clientDiagramOptions" + public static String CLIENT_COLOR_PREFERNENCES = "clientColorPreferences" + override initialize(InitializeParams params) { // Close all diagram servers still open from a previous session. val oldClientIds = diagramServerManager.diagramServers.map[ clientId ].toList // toList to avoid lazy evaluation @@ -118,6 +120,7 @@ class KGraphLanguageServerExtension extends SyncDiagramLanguageServer if (initializationOptions instanceof JsonObject) { synchronized (diagramState) { diagramState.clientOptions = initializationOptions.get(CLIENT_DIAGRAM_OPTIONS_PROPERTY) + diagramState.colorPreferences = LSPUtil.parseColorPreferences(initializationOptions.get(CLIENT_COLOR_PREFERNENCES)) } } return super.initialize(params) diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/LSPUtil.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/LSPUtil.xtend index b69faaf60..1c69a1649 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/LSPUtil.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/LSPUtil.xtend @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2020 by + * Copyright 2020-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -17,8 +17,14 @@ package de.cau.cs.kieler.klighd.lsp import com.google.common.html.HtmlEscapers +import com.google.gson.JsonElement import de.cau.cs.kieler.klighd.ViewContext import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.krendering.KColor +import de.cau.cs.kieler.klighd.krendering.KRenderingFactory +import de.cau.cs.kieler.klighd.util.ColorPreferences +import de.cau.cs.kieler.klighd.util.ColorThemeKind +import java.awt.Color /** * Utility methods for graphs in a language server context. @@ -80,4 +86,46 @@ class LSPUtil { // Replace tabs with four spaces. .replace("\t", "    ") } + + + + /** + * Parses a jsonElement for color preferences from the client. unreadable colors are defaulted to black and white. + */ + static def ColorPreferences parseColorPreferences(JsonElement jsonColors) { + if (jsonColors === null || !jsonColors.isJsonObject) return null + val kind = jsonColors.asJsonObject.get("kind") + val foreground = jsonColors.asJsonObject.get("foreground") + val background = jsonColors.asJsonObject.get("background") + val highlight = jsonColors.asJsonObject.get("highlight") + val ColorThemeKind colorKind = if (kind === null) ColorThemeKind.LIGHT else ColorThemeKind.values.get(kind.asInt) + val foregroundColor = parseColor(foreground) + val backgroundColor = parseColor(background) + val highlightColor = parseColor(highlight) + + return new ColorPreferences(colorKind, foregroundColor, backgroundColor, highlightColor) + } + + /** + * Parses a single color string in the form #RRGGBB into a KColor, or null if the string is unparsable. + */ + static def KColor parseColor(String stringColor) { + var KColor color = null + try { + val awtColor = Color.decode(stringColor) + color = KRenderingFactory.eINSTANCE.createKColor + color.red = awtColor.red + color.green = awtColor.green + color.blue = awtColor.blue + } catch (NumberFormatException e) {} + return color + } + + /** + * Parses a single color string in the form #RRGGBB into a KColor, or null if the string is unparsable. + */ + static def KColor parseColor(JsonElement jsonColor) { + if (jsonColor === null || !jsonColor.isJsonPrimitive) return null + return parseColor(jsonColor.asString) + } } diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend index b0bdcc778..9c6d84ef8 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend @@ -22,6 +22,7 @@ import de.cau.cs.kieler.klighd.KlighdDataManager import de.cau.cs.kieler.klighd.SynthesisOption import de.cau.cs.kieler.klighd.lsp.ISprottyActionHandler import de.cau.cs.kieler.klighd.lsp.model.CheckedImagesAction +import de.cau.cs.kieler.klighd.lsp.model.ClientColorPreferencesAction import de.cau.cs.kieler.klighd.lsp.model.PerformActionAction import de.cau.cs.kieler.klighd.lsp.model.RefreshDiagramAction import de.cau.cs.kieler.klighd.lsp.model.RefreshLayoutAction @@ -48,6 +49,7 @@ class KGraphTypeAdapterUtil { addActionKind(SetSynthesisAction.KIND, SetSynthesisAction) addActionKind(RefreshDiagramAction.KIND, RefreshDiagramAction) addActionKind(RefreshLayoutAction.KIND, RefreshLayoutAction) + addActionKind(ClientColorPreferencesAction.KIND, ClientColorPreferencesAction) // Load all registered action handlers and add their actions. ServiceLoader.load(ISprottyActionHandler, KlighdDataManager.getClassLoader()).forEach[handler | diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/model/Actions.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/model/Actions.xtend index 14e874f37..6a045734b 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/model/Actions.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/model/Actions.xtend @@ -17,6 +17,7 @@ package de.cau.cs.kieler.klighd.lsp.model import de.cau.cs.kieler.klighd.krendering.KImage +import de.cau.cs.kieler.klighd.util.ColorThemeKind import java.util.List import java.util.Map import java.util.Set @@ -159,6 +160,37 @@ class CheckedImagesAction implements ResponseAction { } } +/** + * Sent from the client to the server to notify it about new color preferences. + * + * @author nre + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls = true) +class ClientColorPreferencesAction implements Action { + public static val KIND = 'changeClientColorPreferences' + String kind = KIND + + ClientColorPreferences clientColorPreferences + + new() {} + new(Consumer initializer) { + initializer.accept(this) + } +} + +/** + * The client color preferences for individual styling for syntheses for the ClientColorPreferencesAction + */ +@Accessors +class ClientColorPreferences { + ColorThemeKind kind + String foreground + String background + String highlight +} + /** * Sent from the client to the server to request a new diagram with the given synthesis. * diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/viewer/PiccoloViewer.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/viewer/PiccoloViewer.java index 969bf8b56..2535f2f4a 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/viewer/PiccoloViewer.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/viewer/PiccoloViewer.java @@ -16,6 +16,7 @@ */ package de.cau.cs.kieler.klighd.piccolo.viewer; +import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; @@ -27,6 +28,8 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.elk.core.math.KVector; import org.eclipse.elk.core.options.CoreOptions; import org.eclipse.swt.SWT; @@ -46,6 +49,7 @@ import de.cau.cs.kieler.klighd.kgraph.KGraphElement; import de.cau.cs.kieler.klighd.kgraph.KNode; import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil; +import de.cau.cs.kieler.klighd.krendering.KRenderingUtil; import de.cau.cs.kieler.klighd.piccolo.KlighdPiccolo; import de.cau.cs.kieler.klighd.piccolo.internal.KlighdCanvas; import de.cau.cs.kieler.klighd.piccolo.internal.controller.DiagramController; @@ -58,6 +62,8 @@ import de.cau.cs.kieler.klighd.piccolo.internal.events.KlighdSelectiveZoomEventHandler; import de.cau.cs.kieler.klighd.piccolo.internal.nodes.KlighdMainCamera; import de.cau.cs.kieler.klighd.piccolo.internal.util.NodeUtil; +import de.cau.cs.kieler.klighd.util.ColorPreferences; +import de.cau.cs.kieler.klighd.util.ColorThemeKind; import de.cau.cs.kieler.klighd.util.KlighdProperties; import de.cau.cs.kieler.klighd.viewers.AbstractViewer; import de.cau.cs.kieler.klighd.viewers.ContextViewer; @@ -140,8 +146,29 @@ public PiccoloViewer(final ContextViewer theParentViewer, final Composite parent throw new IllegalArgumentException(msg); } this.parentViewer = theParentViewer; - this.canvas = new KlighdCanvas(parent, style, - theParentViewer.getViewContext().getProperty(KlighdProperties.CANVAS_COLOR)); + + ViewContext viewContext = theParentViewer.getViewContext(); + Color background = viewContext.getProperty(KlighdProperties.CANVAS_COLOR); + if (!viewContext.hasProperty(KlighdProperties.CANVAS_COLOR)) { + // Check and activate dark theme + IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode("org.eclipse.e4.ui.css.swt.theme"); + if (prefs != null) { + if ("org.eclipse.e4.ui.css.theme.e4_dark".equals(prefs.get("themeid", ""))) { + // Simulate dark mode + background = Color.decode("#2f2f2f"); + ColorPreferences colors = new ColorPreferences( + ColorThemeKind.DARK, + KRenderingUtil.getColor("#cccccc"), + KRenderingUtil.getColor("#2f2f2f"), + KRenderingUtil.getColor("#cccccc") + ); + viewContext.setProperty(KlighdProperties.COLOR_PREFERENCES, colors); + } else { + viewContext.setProperty(KlighdProperties.COLOR_PREFERENCES, KlighdProperties.COLOR_PREFERENCES.getDefault()); + } + } + } + this.canvas = new KlighdCanvas(parent, style, background); final KlighdMainCamera camera = canvas.getCamera(); this.magnificationLensVisibleSupplier = installEventHanders(camera); diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/ViewContext.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/ViewContext.java index 68ed9f8e9..b8b9caacf 100644 --- a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/ViewContext.java +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/ViewContext.java @@ -141,6 +141,11 @@ public ViewContext(final ViewContext otherContext, final Object inputModel) { this.businessModel = inputModel; if (otherContext != null) { this.synthesisOptionConfig.putAll(otherContext.synthesisOptionConfig); + // Color preferences are kind of synthesis option and should be copied + if (otherContext.hasProperty(KlighdProperties.COLOR_PREFERENCES)) { + this.setProperty(KlighdProperties.COLOR_PREFERENCES, + otherContext.getProperty(KlighdProperties.COLOR_PREFERENCES)); + } } } diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorPreferences.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorPreferences.java new file mode 100644 index 000000000..edaca03c0 --- /dev/null +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorPreferences.java @@ -0,0 +1,144 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023-2024 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package de.cau.cs.kieler.klighd.util; + +import de.cau.cs.kieler.klighd.krendering.KColor; +import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; + +/** + * Color preferences for KLighD syntheses. May provide information about the foreground, background, and a highlighting + * color, that may be set by a frontend. Alternatively, this preference sets a general color theme kind instead of + * pre-defined colors. + * + * @author nre + */ +public class ColorPreferences { + +// The kind of color theme to let the synthesis pick its own color palette. + private ColorThemeKind kind; + + /** + * The foreground color, to be used e.g. for texts, lines, etc., or as a reference. + */ + private KColor foreground; + + /** + * The background color to be used as the background, fill color, etc., or as a reference. + */ + private KColor background; + + /** + * A special highlighting color to be used for special renderings, or as a reference. + */ + private KColor highlight; + + /** + * Default constructor for color preferences, setting foreground and highlight to black, background to white. + */ + public ColorPreferences() { + this(null, null, null, null); + } + + /** + * Creates a new ColorPreferences with the given colors, or the default colors if some parameters are {@code null}. + * + * @param kind + * @param foreground + * @param background + * @param highlight + */ + public ColorPreferences(ColorThemeKind kind, KColor foreground, KColor background, KColor highlight) { + this.kind = kind; + if (this.kind == null) { + this.kind = ColorThemeKind.LIGHT; + } + if (foreground == null) { + this.foreground = KRenderingFactory.eINSTANCE.createKColor(); + if (this.kind == ColorThemeKind.LIGHT || this.kind == ColorThemeKind.HIGH_CONTRAST_LIGHT) { + this.foreground.setRed(0); + this.foreground.setGreen(0); + this.foreground.setBlue(0); + } else { // dark theme + // D4D4D4 + this.foreground.setRed(212); + this.foreground.setGreen(212); + this.foreground.setBlue(212); + } + } else { + this.foreground = foreground; + } + if (background == null) { + this.background = KRenderingFactory.eINSTANCE.createKColor(); + if (this.kind == ColorThemeKind.LIGHT || this.kind == ColorThemeKind.HIGH_CONTRAST_LIGHT) { + this.background.setRed(255); + this.background.setGreen(255); + this.background.setBlue(255); + } else { // dark theme + // 1E1E1E + this.background.setRed(30); + this.background.setGreen(30); + this.background.setBlue(30); + } + } else { + this.background = background; + } + if (highlight == null) { + this.highlight = KRenderingFactory.eINSTANCE.createKColor(); + if (this.kind == ColorThemeKind.LIGHT || this.kind == ColorThemeKind.HIGH_CONTRAST_LIGHT) { + // 005FB8 + this.highlight.setRed(0); + this.highlight.setGreen(95); + this.highlight.setBlue(184); + } else { // dark theme + // 0078D4 + this.highlight.setRed(0); + this.highlight.setGreen(120); + this.highlight.setBlue(212); + } + } else { + this.highlight = highlight; + } + } + + /** + * @return the theme kind + */ + public ColorThemeKind getKind() { + return kind; + } + + /** + * @return the foreground color + */ + public KColor getForeground() { + return foreground; + } + + /** + * @return the background color + */ + public KColor getBackground() { + return background; + } + + /** + * @return the highlight color + */ + public KColor getHighlight() { + return highlight; + } +} diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorThemeKind.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorThemeKind.java new file mode 100644 index 000000000..cac59a21c --- /dev/null +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/ColorThemeKind.java @@ -0,0 +1,41 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package de.cau.cs.kieler.klighd.util; + +/** + * Kinds of color themes, as an enum similar to VS Code's ColorThemeKind. + * + * @author nre + */ +public enum ColorThemeKind { + /** + * Light color theme with light backgrounds and darker writing + */ + LIGHT, + /** + * Dark color theme with dark backgrounds and lighter writing + */ + DARK, + /** + * Light color theme with a higher contrast. + */ + HIGH_CONTRAST_LIGHT, + /** + * Dark color theme with a higher contrast. + */ + HIGH_CONTRAST_DARK +} diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java index fb3d6d5c6..9eb5d795e 100644 --- a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java @@ -33,6 +33,7 @@ import de.cau.cs.kieler.klighd.kgraph.KGraphData; import de.cau.cs.kieler.klighd.kgraph.KGraphElement; import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.krendering.KColor; import de.cau.cs.kieler.klighd.krendering.KText; import de.cau.cs.kieler.klighd.microlayout.Bounds; @@ -381,6 +382,19 @@ public static boolean isSelectable(final EObject viewElement) { public static final IProperty IS_NODE_TITLE = new Property("klighd.isNodeTitle", false); + /** + * Property determining the custom color theme to be used by syntheses. + * Null means no preferences could be detected and the diagram background will most likely be white. + */ + public static final IProperty COLOR_PREFERENCES = + new Property("klighd.colorPreferences", null); + /** + * Property to be set on the root node of the graph that defines a custom background color that the diagram + * canvas should use. Null means no custom background, a default white one will be used. + */ + public static final IProperty DIAGRAM_BACKGROUND = + new Property("klighd.diagramBackground", null); + /** * Property determining whether this node should be rendered as a proxy. */