From c2c0f5fdb5fbb49d29aff2a21573898ae4451d7a Mon Sep 17 00:00:00 2001 From: Ludovic Apvrille <ludovic.apvrille@telecom-paristech.fr> Date: Mon, 28 Mar 2011 14:01:46 +0000 Subject: [PATCH] AVATAR : Update on simulator: new transaction information tab, saving of the SD diagram in PNG format and id of transactions on the SD panel --- src/ui/IconManager.java | 2 + src/ui/JMenuBarTurtle.java | 9 +- src/ui/MainGUI.java | 2 +- .../AvatarInteractiveSimulationActions.java | 4 +- .../AvatarSaveCommandsToolBar.java | 4 +- .../AvatarSpecificationSimulationSDPanel.java | 77 ++++++++++++++-- .../JFrameAvatarInteractiveSimulation.java | 84 +++++++++++++++++- src/ui/images/savepng24.gif | Bin 0 -> 270 bytes 8 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 src/ui/images/savepng24.gif diff --git a/src/ui/IconManager.java b/src/ui/IconManager.java index af4eae7903..f2ed7b9e09 100755 --- a/src/ui/IconManager.java +++ b/src/ui/IconManager.java @@ -522,6 +522,7 @@ public class IconManager { private static String icon5100 = "images/avatarhead16.gif"; private static String icon5102 = "images/avatarhead32.gif"; + private static String icon5104 = "images/savepng24.gif"; public IconManager() { @@ -872,6 +873,7 @@ public class IconManager { imgic5100 = getIcon(icon5100); imgic5102 = getIcon(icon5102); + imgic5104 = getIcon(icon5104); if (imgic5100 != null) { img5100 = imgic5100.getImage(); } diff --git a/src/ui/JMenuBarTurtle.java b/src/ui/JMenuBarTurtle.java index 747a0facd8..5585dda49f 100755 --- a/src/ui/JMenuBarTurtle.java +++ b/src/ui/JMenuBarTurtle.java @@ -462,7 +462,14 @@ public class JMenuBarTurtle extends JMenuBar { menuItem.addMouseListener(mgui.mouseHandler); menuItem = vAndV.add(mgui.actions[TGUIAction.ACT_VIEW_PM_SAVED_AUT]); menuItem.addMouseListener(mgui.mouseHandler); - + + vAndV.addSeparator(); + menuItem = vAndV.add(mgui.actions[TGUIAction.ACT_AVATAR_SIM]); + menuItem.addMouseListener(mgui.mouseHandler); + menuItem = vAndV.add(mgui.actions[TGUIAction.ACT_AVATAR_FV_UPPAAL]); + menuItem.addMouseListener(mgui.mouseHandler); + menuItem = vAndV.add(mgui.actions[TGUIAction.ACT_AVATAR_FV_PROVERIF]); + menuItem.addMouseListener(mgui.mouseHandler); /*vAndV.addSeparator(); menuItem = vAndV.add(mgui.actions[TGUIAction.ACT_DEADLOCK_SEEKER_AUT]); diff --git a/src/ui/MainGUI.java b/src/ui/MainGUI.java index 386f017e46..06be432045 100755 --- a/src/ui/MainGUI.java +++ b/src/ui/MainGUI.java @@ -4108,7 +4108,7 @@ public class MainGUI implements ActionListener, WindowListener, KeyListener { } if (info) { JOptionPane.showMessageDialog(frame, - "The capture was correctly performed", + "The capture was correctly performed and saved in " + file.getAbsolutePath(), "Screen capture ok", JOptionPane.INFORMATION_MESSAGE); } diff --git a/src/ui/avatarinteractivesimulation/AvatarInteractiveSimulationActions.java b/src/ui/avatarinteractivesimulation/AvatarInteractiveSimulationActions.java index 3a7c7202ba..32700b5157 100755 --- a/src/ui/avatarinteractivesimulation/AvatarInteractiveSimulationActions.java +++ b/src/ui/avatarinteractivesimulation/AvatarInteractiveSimulationActions.java @@ -72,7 +72,7 @@ public class AvatarInteractiveSimulationActions extends AbstractAction { public static final int ACT_RUN_UNTIL_MEMORY_ACCESS = 24; public static final int ACT_RUN_UNTIL_CHANNEL_ACCESS = 25; - public static final int ACT_SAVE_VCD = 10; + public static final int ACT_SAVE_SD_PNG = 10; public static final int ACT_SAVE_HTML = 11; public static final int ACT_SAVE_TXT = 12; @@ -152,7 +152,7 @@ public class AvatarInteractiveSimulationActions extends AbstractAction { actions[ACT_RUN_UNTIL_MEMORY_ACCESS] = new TAction("run-to-memory-accessd", "Run until a memory access is performed", IconManager.imgic1322, IconManager.imgic1322, "Run until a memory access is performed", "Run simulation until a memory access is performed on selected memory. Works only if the simulator is \"ready\"", 'R'); actions[ACT_RUN_UNTIL_CHANNEL_ACCESS] = new TAction("run--to-channel-access", "Run until a channel is accessed", IconManager.imgic1324, IconManager.imgic1324, "Run until a channel is accessed", "Run until a channel is accessed. Works only if the simulator is \"ready\"", 'R'); - actions[ACT_SAVE_VCD] = new TAction("save-vcd", "Save trace in VCD format", IconManager.imgic1310, IconManager.imgic1310, "Save trace in VCD format", "Save trace in VCD format", 'R'); + actions[ACT_SAVE_SD_PNG] = new TAction("save-sd-png", "Save SD trace in PNG format", IconManager.imgic5104, IconManager.imgic5104, "Save SD trace in PNG format", "Save SD trace in PNG format", '0'); actions[ACT_SAVE_HTML] = new TAction("save-html", "Save trace in HTML format", IconManager.imgic1312, IconManager.imgic1312, "Save trace in HTML format", "Save trace in HTML format", 'R'); actions[ACT_SAVE_TXT] = new TAction("save-txt", "Save trace in TXT format", IconManager.imgic1314, IconManager.imgic1314, "Save trace in TXT format", "Save trace in TXT format", 'R'); diff --git a/src/ui/avatarinteractivesimulation/AvatarSaveCommandsToolBar.java b/src/ui/avatarinteractivesimulation/AvatarSaveCommandsToolBar.java index 1144512f56..d97d3a6508 100755 --- a/src/ui/avatarinteractivesimulation/AvatarSaveCommandsToolBar.java +++ b/src/ui/avatarinteractivesimulation/AvatarSaveCommandsToolBar.java @@ -58,7 +58,7 @@ public class AvatarSaveCommandsToolBar extends AvatarInteractiveSimulationBar { } protected void setActive(boolean b) { - jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_VCD].setEnabled(b); + jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_SD_PNG].setEnabled(b); jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_HTML].setEnabled(b); jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_TXT].setEnabled(b); } @@ -66,7 +66,7 @@ public class AvatarSaveCommandsToolBar extends AvatarInteractiveSimulationBar { protected void setButtons() { JButton button; - button = this.add(jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_VCD]); + button = this.add(jfais.actions[AvatarInteractiveSimulationActions.ACT_SAVE_SD_PNG]); button.addMouseListener(jfais.mouseHandler); this.addSeparator(); diff --git a/src/ui/avatarinteractivesimulation/AvatarSpecificationSimulationSDPanel.java b/src/ui/avatarinteractivesimulation/AvatarSpecificationSimulationSDPanel.java index de63d9a2c1..c6a7d3178c 100644 --- a/src/ui/avatarinteractivesimulation/AvatarSpecificationSimulationSDPanel.java +++ b/src/ui/avatarinteractivesimulation/AvatarSpecificationSimulationSDPanel.java @@ -50,6 +50,7 @@ package ui.avatarinteractivesimulation; import javax.swing.*; import java.awt.*; +import java.awt.image.*; import java.awt.event.*; import java.util.*; @@ -99,10 +100,15 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous private int xMouse, yMouse; private boolean drawInfo = false; private long clockValueMouse; + private Vector<Point> points; + private Vector<AvatarSimulationTransaction> transactionsOfPoints; + public AvatarSpecificationSimulationSDPanel(AvatarSpecificationSimulation _ass) { ass = _ass; + points = new Vector<Point>(); + transactionsOfPoints = new Vector<AvatarSimulationTransaction>(); setBackground(Color.WHITE); @@ -156,7 +162,7 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous } } - // returns the currentY position + // Returns the currentY position protected int paintTopElements(Graphics g, int currentX, int currentY) { String name; int w; @@ -217,18 +223,27 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous index = blocks.indexOf(ast.asb); xOfBlock = currentX + (index * spaceBetweenLifeLines) + spaceBetweenLifeLines/2; + points.clear(); + transactionsOfPoints.clear(); + if (ast.executedElement instanceof AvatarState) { - newCurrentY = drawState(g, (AvatarState)(ast.executedElement), xOfBlock, currentY); + newCurrentY = drawState(g, ast, (AvatarState)(ast.executedElement), xOfBlock, currentY); } else if (ast.executedElement instanceof AvatarTransition) { newCurrentY = drawTransition(g, (AvatarTransition)(ast.executedElement), ast, xOfBlock, currentY); } else if (ast.executedElement instanceof AvatarActionOnSignal) { newCurrentY = drawAvatarActionOnSignal(g, (AvatarActionOnSignal)(ast.executedElement), ast, xOfBlock, currentY, currentX); } else if (ast.executedElement instanceof AvatarStopState) { - newCurrentY = drawAvatarStopState(g, xOfBlock, currentY, currentX); + newCurrentY = drawAvatarStopState(g, ast, xOfBlock, currentY, currentX); } else if (ast.executedElement instanceof AvatarRandom) { newCurrentY = drawRandom(g, (AvatarRandom)(ast.executedElement), ast, xOfBlock, currentY); } + if ((yMouse>= currentY) && (yMouse <= newCurrentY)) { + for(int cpt = 0; cpt<points.size(); cpt++) { + drawIDInfo(g, points.get(cpt).x, points.get(cpt).y, transactionsOfPoints.get(cpt).id); + } + } + // Draw the line of other blocks if (currentY != newCurrentY) { @@ -256,7 +271,7 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous return currentY; } - private int drawState(Graphics g, AvatarState as, int currentX, int currentY) { + private int drawState(Graphics g, AvatarSimulationTransaction _ast, AvatarState as, int currentX, int currentY) { int w; int x, y, width, height; @@ -274,6 +289,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous height = g.getFontMetrics().getHeight() + spaceVerticalText * 2; g.fillRoundRect(x, y, width, height, 5, 5); + points.add(new Point(x+width, y)); + transactionsOfPoints.add(_ast); g.setColor(c); g.drawRoundRect(x, y, width, height, 5, 5); @@ -295,6 +312,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous g.drawLine(currentX, currentY, currentX, currentY+verticalLink); currentY += verticalLink; g.drawRect(currentX-5, currentY, 10, 30); + points.add(new Point(currentX+10, currentY)); + transactionsOfPoints.add(ast); g.drawString(""+ ast.duration, currentX+6, currentY+17); currentY += 30; g.setColor(ColorManager.AVATAR_TIME); @@ -326,6 +345,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous g.fillRoundRect(x, y, width, height, 5, 5); g.setColor(c); g.drawRoundRect(x, y, width, height, 5, 5); + points.add(new Point(x+width, y)); + transactionsOfPoints.add(ast); cpt = 1; @@ -340,6 +361,7 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous currentY += height; g.drawLine(currentX, currentY, currentX, currentY+verticalLink); + return currentY + verticalLink; } @@ -364,6 +386,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous height = g.getFontMetrics().getHeight() + spaceVerticalText * 2; g.setColor(Color.WHITE); g.fillRoundRect(x, y, width, height, 5, 5); + points.add(new Point(x+width, y)); + transactionsOfPoints.add(ast); g.setColor(c); g.drawRoundRect(x, y, width, height, 5, 5); @@ -382,6 +406,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous avatartranslator.AvatarRelation rel = ass.getAvatarSpecification().getAvatarRelationWithSignal(sig); if (sig.isIn()) { if (!(rel.isAsynchronous())) { + + //Synchronous if (ast.linkedTransaction != null) { // Computing message name AvatarActionOnSignal otherAaos = (AvatarActionOnSignal)(ast.linkedTransaction.executedElement); @@ -407,6 +433,10 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous g.drawLine(xOf2ndBlock, currentY-1, currentX, currentY-1); g.setColor(c); GraphicLib.arrowWithLine(g, 1, 0, 10, xOf2ndBlock, currentY, currentX, currentY, true); + points.add(new Point(xOf2ndBlock, currentY)); + transactionsOfPoints.add(ast); + points.add(new Point(currentX, currentY)); + transactionsOfPoints.add(ast.linkedTransaction); // Putting the message name w = g.getFontMetrics().stringWidth(messageName); @@ -433,6 +463,8 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous g.drawLine(currentX-lengthAsync, currentY-1, currentX, currentY-1); g.setColor(c); GraphicLib.arrowWithLine(g, 1, 1, 10, currentX-lengthAsync, currentY, currentX, currentY, false); + points.add(new Point(currentX, currentY)); + transactionsOfPoints.add(ast); // Putting the message name w = g.getFontMetrics().stringWidth(messageName); @@ -495,7 +527,9 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous g.drawLine(currentX+lengthAsync, currentY-1, currentX, currentY-1); g.setColor(c); GraphicLib.arrowWithLine(g, 1, 1, 10, currentX, currentY, currentX+lengthAsync, currentY, false); - + points.add(new Point(currentX, currentY)); + transactionsOfPoints.add(ast); + // Putting the message name w = g.getFontMetrics().stringWidth(messageName); g.drawString(messageName, currentX+lengthAsync-w/2, currentY-2); @@ -524,11 +558,14 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous return currentY; } - private int drawAvatarStopState(Graphics g, int currentX, int currentY, int startX) { + private int drawAvatarStopState(Graphics g, AvatarSimulationTransaction _ast, int currentX, int currentY, int startX) { g.drawLine(currentX, currentY, currentX, currentY+spaceStop+3); currentX -= (spaceStop/2); g.drawLine(currentX, currentY, currentX+spaceStop, currentY+spaceStop); g.drawLine(currentX, currentY+spaceStop, currentX+spaceStop, currentY); + points.add(new Point(currentX+spaceStop, currentY)); + transactionsOfPoints.add(_ast); + currentY += spaceStop + 3; return currentY; } @@ -562,7 +599,12 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous private void drawInfo(Graphics g) { GraphicLib.dashedLine(g, spaceAtEnd, yMouse, maxX-spaceAtEnd, yMouse); - g.drawString("@" + clockValueMouse, 10, yMouse+g.getFontMetrics().getHeight()/2); + g.drawString("@" + clockValueMouse, 10, yMouse+g.getFontMetrics().getHeight()/2); + /*if (minIdValueMouse == maxIdValueMouse) { + g.drawString("ID: " + minIdValueMouse, 10, yMouse+(g.getFontMetrics().getHeight()/2)+12); + } else { + g.drawString("ID: " + minIdValueMouse + " to " + maxIdValueMouse, 10, yMouse+(g.getFontMetrics().getHeight()/2)+12); + }*/ String name; int w; @@ -577,4 +619,25 @@ public class AvatarSpecificationSimulationSDPanel extends JPanel implements Mous } } + private void drawIDInfo(Graphics g, int _x, int _y, long _id) { + Color c = g.getColor(); + g.setColor(ColorManager.AVATAR_EXPIRE_TIMER); + g.fillOval(_x-3, _y-3, 6, 6); + g.drawLine(_x, _y, _x+6, _y-6); + g.drawLine(_x+6, _y-6, _x+12, _y-6); + g.drawString(""+_id, _x+13, _y-6); + g.setColor(c); + } + + public BufferedImage performCapture() { + int w = this.getWidth(); + int h = this.getHeight(); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + g.setColor(Color.black); + paintComponent(g); + g.dispose(); + return image; + } + } \ No newline at end of file diff --git a/src/ui/avatarinteractivesimulation/JFrameAvatarInteractiveSimulation.java b/src/ui/avatarinteractivesimulation/JFrameAvatarInteractiveSimulation.java index 537af06e7d..7d66e2e60c 100755 --- a/src/ui/avatarinteractivesimulation/JFrameAvatarInteractiveSimulation.java +++ b/src/ui/avatarinteractivesimulation/JFrameAvatarInteractiveSimulation.java @@ -51,6 +51,7 @@ import javax.swing.event.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; +import java.awt.image.*; import java.io.*; import java.util.*; @@ -138,6 +139,11 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS private VariableTableModel variabletm; private JScrollPane jspVariableInfo; + // Transactions + private JPanel transactionPanel; + private TransactionTableModel transactiontm; + private JScrollPane jspTransactionInfo; + // Sequence Diagram private AvatarSpecificationSimulationSDPanel sdpanel; @@ -618,6 +624,45 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS //updateTaskInformationButton = new JButton(actions[InteractiveSimulationActions.ACT_UPDATE_TASKS]); //taskPanel.add(updateTaskInformationButton, BorderLayout.SOUTH); + // Transactions + transactionPanel = new JPanel(); + transactionPanel.setLayout(new BorderLayout()); + infoTab.addTab("Transactions", IconManager.imgic1202, transactionPanel, "Transactions"); + transactiontm = new TransactionTableModel(ass); + + /*sorterPI = new TableSorter(transactiontm); + jtablePI = new JTable(sorterPI); + sorterPI.setTableHeader(jtablePI.getTableHeader()); + ((jtablePI.getColumnModel()).getColumn(0)).setPreferredWidth(50); + ((jtablePI.getColumnModel()).getColumn(1)).setPreferredWidth(75); + ((jtablePI.getColumnModel()).getColumn(2)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(3)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(4)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(5)).setPreferredWidth(75); + ((jtablePI.getColumnModel()).getColumn(6)).setPreferredWidth(100); + jtablePI.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + jspTransactionInfo = new JScrollPane(jtablePI); + jspTransactionInfo.setWheelScrollingEnabled(true); + jspTransactionInfo.getVerticalScrollBar().setUnitIncrement(10); + jspTransactionInfo.setPreferredSize(new Dimension(250, 300)); + transactionPanel.add(jspTransactionInfo, BorderLayout.CENTER);*/ + + jtablePI = new JTable(transactiontm); + ((jtablePI.getColumnModel()).getColumn(0)).setPreferredWidth(50); + ((jtablePI.getColumnModel()).getColumn(1)).setPreferredWidth(75); + ((jtablePI.getColumnModel()).getColumn(2)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(3)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(4)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(5)).setPreferredWidth(100); + ((jtablePI.getColumnModel()).getColumn(6)).setPreferredWidth(75); + ((jtablePI.getColumnModel()).getColumn(7)).setPreferredWidth(100); + jtablePI.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + jspTransactionInfo = new JScrollPane(jtablePI); + jspTransactionInfo.setWheelScrollingEnabled(true); + jspTransactionInfo.getVerticalScrollBar().setUnitIncrement(10); + jspTransactionInfo.setPreferredSize(new Dimension(250, 300)); + transactionPanel.add(jspTransactionInfo, BorderLayout.CENTER); + // Variables /*variablePanel = new JPanel(); variablePanel.setLayout(new BorderLayout()); @@ -794,6 +839,7 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS // Diagram animation? if (!(busyMode == AvatarSpecificationSimulation.GATHER) && !(busyMode == AvatarSpecificationSimulation.EXECUTE)) { updateMetElements(); + updateTransactionsTable(); animateDiagrams(); } @@ -850,7 +896,7 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS break; } - actions[AvatarInteractiveSimulationActions.ACT_SAVE_VCD].setEnabled(b); + actions[AvatarInteractiveSimulationActions.ACT_SAVE_SD_PNG].setEnabled(b); actions[AvatarInteractiveSimulationActions.ACT_SAVE_HTML].setEnabled(b); actions[AvatarInteractiveSimulationActions.ACT_SAVE_TXT].setEnabled(b); actions[AvatarInteractiveSimulationActions.ACT_PRINT_BENCHMARK].setEnabled(b); @@ -977,7 +1023,11 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS nbOfAllExecutedElements = allExecutedElements.size(); } - + public void updateTransactionsTable() { + if (transactiontm != null) { + transactiontm.fireTableStructureChanged(); + } + } public void animateDiagrams() { if ((animate != null) && (ass != null) && (ass.getSimulationBlocks() != null)) { @@ -1025,6 +1075,32 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS ass.printExecutedTransactions(); } + public void actSaveSDPNG() { + //Saving PNG file; + BufferedImage bi; + File file; + + bi = sdpanel.performCapture(); + + String filePath=""; + if (ConfigurationTTool.IMGPath != null) { + filePath += ConfigurationTTool.IMGPath; + if (!filePath.endsWith(File.separator)) { + filePath += File.separator; + } + } + + if ((saveFileName.getText() != null) && (saveFileName.getText().length() > 0)) { + filePath += saveFileName.getText(); + } else { + filePath += "foo.png"; + } + + file = new File(filePath); + + mgui.writeImageCapture(bi, file, true); + } + // Mouse management public void mouseReleased(MouseEvent e) {} @@ -1090,6 +1166,10 @@ public class JFrameAvatarInteractiveSimulation extends JFrame implements AvatarS actSaveTxt(); return; //TraceManager.addDev("Start simulation!"); + } else if (command.equals(actions[AvatarInteractiveSimulationActions.ACT_SAVE_SD_PNG].getActionCommand())) { + actSaveSDPNG(); + return; + //TraceManager.addDev("Start simulation!"); } } diff --git a/src/ui/images/savepng24.gif b/src/ui/images/savepng24.gif new file mode 100644 index 0000000000000000000000000000000000000000..1cffb99578077e10a9455be2a7c0fcd9d62da3fb GIT binary patch literal 270 zcmZ?wbhEHblwgoxc+AMaz`*eT|NjFA4j33D+S=xQ`t<4i`SWMaoH=yp(Ek1V=gpfp zapJ_hyu8H3L|$Ir|Ns9h{$ycfVBlxa0qF#p!N9Wn!%5H8Yx$n6{jE1Wn8n+`jAzxX zMXW3xO%fib&XgwZnU<kr(yVo(VXM}9iF4bod^E6n;FGGY)Hh{G;t|uzbpI}Xah1m# zCND|an78@<ZU=p-4>v63JAYXzeEDD3SnF@6%Fxu&!0cYF6c;<8w1~fT5-0PF>9b}q zv-TJmDX}vzUOZzd=Om%2`qO95nZ-U&fq$MH>xyMd=ByHmUCYeLzV6`B&5Apg9NN8W buSfrd2^qo_<z<saC*JB8zk82gkii-N(l~T# literal 0 HcmV?d00001 -- GitLab