-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathRenderableBlock.java
More file actions
2751 lines (2467 loc) · 90.9 KB
/
Copy pathRenderableBlock.java
File metadata and controls
2751 lines (2467 loc) · 90.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt
package openblocks.renderable;
import openblocks.codeblocks.Block;
import openblocks.codeblocks.BlockConnector;
import openblocks.codeblocks.BlockConnectorShape;
import openblocks.codeblocks.BlockGenus;
import openblocks.codeblocks.BlockLink;
import openblocks.codeblocks.BlockLinkChecker;
import openblocks.codeblocks.BlockShape;
import openblocks.codeblocks.BlockStub;
import openblocks.codeblocks.ComplaintDepartment;
import openblocks.codeblocks.InfixBlockShape;
import openblocks.codeblocks.JComponentDragHandler;
import openblocks.codeblocks.rendering.BlockShapeUtil;
import openblocks.codeblockutil.CToolTip;
import openblocks.codeblockutil.GraphicsManager;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import openblocks.workspace.FactoryCanvas;
import openblocks.workspace.FactoryManager;
import openblocks.workspace.ISupportMemento;
import openblocks.workspace.MiniMap;
import openblocks.workspace.RBParent;
import openblocks.workspace.SearchableElement;
import openblocks.workspace.TrashCan;
import openblocks.workspace.Workspace;
import openblocks.workspace.WorkspaceEvent;
import openblocks.workspace.WorkspaceWidget;
import openblocks.yacodeblocks.BlockParser;
import openblocks.yacodeblocks.FeedbackReporter;
import openblocks.yacodeblocks.WorkspaceController;
import openblocks.yacodeblocks.WorkspaceControllerHolder;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JToolTip;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
/**
* RenderableBlock is responsible for all graphical rendering of a code Block. This class is also
* responsible for consuming all mouse and key events on itself. Each RenderableBlock object is
* coupled with its associated Block object, and uses information maintained in Block to
* render the graphical block accordingly.
*/
public class RenderableBlock extends JComponent implements SearchableElement,
MouseListener, MouseMotionListener, ISupportMemento {
private static final long serialVersionUID = 1L;
private static final boolean DEBUG = false;
private Color color;
private Color activatedColor;
private static final Color deactivatedColor = Color.WHITE;
private class BlockImage {
// RENDERING RELATED FIELDS
// Shape components used to draw this block's geometrical shape.
private BlockShape shape; // the shape which is an abstract outline of the block
private Area abstractArea; // a filled in abstract shape
private Area area = new Area(); // area which is a filled in pixel shape
private BufferedImage buffImg = null; // static drawing area for unstable blocks
private List<ConnectorTag> socketTags = new ArrayList<ConnectorTag>(); // the block's sockets
/**
* Redraws the entire buffer on a Graphics2D, called by paintCompnent() only
* if the buffer has been cleared.
*/
void updateBuffImg() {
synchronizeLabelsAndSockets();
reformBlockShape();
// create image
// note: need to add twice the highlight stroke width so that the highlight
// does not get cut off
GraphicsManager.recycleGCCompatibleImage(buffImg);
buffImg = GraphicsManager.getGCCompatibleImage(
area.getBounds().width,
area.getBounds().height);
Graphics2D buffImgG2 = (Graphics2D) buffImg.getGraphics();
// update bounds of this renderableBlock as bounds of the shape
Dimension updatedDimensionRect = new Dimension(area.getBounds().getSize());
// get size of block to determine size needed for bevel image
Image bevelImage = BlockShapeUtil.getBevelImage(
updatedDimensionRect.width, updatedDimensionRect.height, area);
// need anti-aliasing to remove color fill artifacts outside the bevel
buffImgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// ADD BLOCK COLOR
buffImgG2.setColor(color);
buffImgG2.fill(area);
// draw the bevel on the shape -- comment out this line to not apply beveling
buffImgG2.drawImage(bevelImage, 0, 0, null);
}
/**
* Clears the BufferedImage of this
*/
public void clearBufferedImage() {
if (buffImg != null) {
GraphicsManager.recycleGCCompatibleImage(buffImg);
buffImg = null;
}
}
}
final BlockImage normalImage;
final BlockImage collapsedImage;
BlockImage blockImage;
/*
*
*The following may be null: parent, lastdragwidget, comment
*/
// STATIC FIELDS
/** The maximum distance between blocks that are still considered nearby enough to link */
// private static final double NEARBY_RADIUS = 20.0;
/** The alpha level while dragging - lower means more transparent */
private static final float DRAGGING_ALPHA = 0.66F;
/** Mapping from blockID to the corresponding RenderableBlock instance */
private static final Map<Long, RenderableBlock> ALL_RENDERABLE_BLOCKS =
new HashMap<Long, RenderableBlock>();
// COMPONENT FIELDS
/** BlockID of this. MAY BE Block.NULL */
private final Long blockID;
/** Parent workspace widget. May be null */
private WorkspaceWidget parent;
/** The previous known WorkspaceWidget this block was dragged over. May be null */
private WorkspaceWidget lastDragWidget = null;
/**
* An internal JComponent whose functionality is independent of any other
* functionality. If the block widget is the largest component in the
* block, then the renderableblock's Shape is determined from the dimensions
* of this widget. They should not be related to starlogo or codeblocks. MAY BE NULL*/
private JComponent blockWidget = null;
private final BlockMenu blockMenu;
// Internal Managers
/** HighlightManager that manages drawing of highlights around this block */
private RBHighlightHandler highlighter;
/** dragHandler keeps the block within the workspace area. It manages relocating the block. */
private JComponentDragHandler dragHandler;
//ATTRIBUTE FIELDS
/** Binary attributes of this RenderableBlocks:
* (1) popupIconVisible is true if the pop-up icon is visible,
* (2) isSearchResult is true if this block is being queried by search
* (3) isPickedUp is true if mousePressed was performed on this block,
* (4) dragging is true if mouseDragged was performed on this block at least
* once,
* (5) linkedDefArgsBefore is any default arguments were never attached
* (6) isLoading is true if RenderableBlock is still loading- Though its data
* may have loaded completely, it still may need other connected
* RenderableBlocks to finish loading as well. In this case, isLoading would
* still be false */
private boolean isSearchResult = false;
private boolean pickedUp = false;
private boolean dragging = false;
private boolean linkedDefArgsBefore = false;
boolean isLoading = false;
private boolean loadedAsCollapsed = false;
private Integer pendingReplMessages = 0;
///////////////////////////
//Sockets and Labels
/** TODO(user): Documentation does not exist for these components. Consult author*/
private final NameLabel blockLabel;
private final PageLabel pageLabel;
private final DecoratorLabel decoratorLabel;
private ArrayList<BlockNote> blockNotes = new ArrayList<BlockNote>();
private boolean blockNotesChanged = false;
private final CollapseLabel collapseLabel;
private final ConnectorTag plugTag;
private final ConnectorTag afterTag;
private final ConnectorTag beforeTag;
// the values of the x and y coordinates of block when zoom = 1.0
private double unzoomedX;
private double unzoomedY;
/**
* Constructs a new RenderableBlock instance with the specified parent WorkspaceWidget and
* Long blockID of its associated Block
* @param parent the WorkspaceWidget containing this
* @param blockID Long Block id of associated with this
*/
public RenderableBlock(WorkspaceWidget parent, Long blockID){
this(parent, blockID, false);
}
/**
* Constructs a new RenderableBlock instance with the specified parent WorkspaceWidget and
* Long blockID of its associated Block
* @param parent the WorkspaceWidget containing this
* @param blockID Long Block id of associated with this
* @param isLoading indicates if this block is still waiting for all information
* needed to properly construct it
*/
private RenderableBlock(WorkspaceWidget parent, Long blockID, boolean isLoading){
super();
this.isLoading = isLoading;
/*
* Sets whether focus traversal keys are enabled
* for this Component. Components for which focus
* traversal keys are disabled receive key events
* for focus traversal keys.
*/
setFocusTraversalKeysEnabled(false);
this.parent = parent;
this.blockID = blockID;
ALL_RENDERABLE_BLOCKS.put(blockID, this);
boolean collapseable = isCollapseable(getBlock().getGenusName());
normalImage = new BlockImage(); //form basic shape
blockImage = normalImage;
if (getBlock().isInfix()) {
normalImage.shape = new InfixBlockShape(this);
} else {
normalImage.shape = new BlockShape(this, getBlock());
}
if (collapseable) {
collapsedImage = new BlockImage();
Block temp = new Block("collapsed", getBlock().getBlockLabel());
collapsedImage.shape = new BlockShape(this, temp);
temp.forget();
} else {
collapsedImage = null;
}
//set null layout so as to add blockLabels where ever we want
setLayout(null);
activatedColor = BlockGenus.getGenusWithName(getGenus()).getColor();
color = activatedColor;
boolean headless = WorkspaceControllerHolder.isHeadless();
if (!headless) {
dragHandler = new JComponentDragHandler(this); // set up drag handler delegate
addMouseListener(this);
addMouseMotionListener(this);
}
//initialize tags, labels, and sockets:
plugTag = new ConnectorTag(getBlock().getPlug());
afterTag = new ConnectorTag(getBlock().getAfterConnector());
beforeTag = new ConnectorTag(getBlock().getBeforeConnector());
blockLabel = new NameLabel(getBlock().getBlockLabel(),
BlockLabel.Type.NAME_LABEL, getBlock().isLabelEditable(), blockID);
decoratorLabel = new DecoratorLabel(getBlock().getDecoratorLabel(), getBlockID());
pageLabel =
new PageLabel(getBlock().getPageLabel(), BlockLabel.Type.PAGE_LABEL, false, blockID);
add(pageLabel.getJComponent());
add(blockLabel.getJComponent(), 0);
add(decoratorLabel.getJComponent());
synchronizeSockets();
if (!isLoading) {
reformBlockShape(); //to update socket points to position labels and setBounds of this rb
//to cache image upon instantiation, update buffered image here:
}
if (collapseable) {
collapseLabel = new CollapseLabel(blockID);
add(collapseLabel);
} else {
collapseLabel = null;
}
// FactoryRenderableBlocks should not show menus. They don't today because
// they override the mouseReleased method/
blockMenu = headless ? null : new BlockMenu(this);
if (!headless) {
highlighter = new RBHighlightHandler(this);
}
String blockDescription = getBlock().getBlockDescription();
if (blockDescription != null) {
setBlockToolTip(getBlock().getBlockDescription().trim());
}
if (!headless) {
setCursor(dragHandler.getDragHintCursor());
}
}
private boolean isCollapseable(String genusName) {
return genusName.startsWith("define")
|| genusName.equals("def")
|| BlockGenus.getGenusWithName(genusName).getDecorator().equals("when");
}
/**
* Removes the target block from its container and widget. Notifies
* all WorkspaceListeners with the BLOCK_REMOVED event.
*
* Requires that renderable != null && renderable.blockID != null &&
* renderable.blockID != Block.NULL.
*
* @param renderable The target block to remove.
* @param widget The display widget for renderable.
* @param container The Swing container that this block is attached to.
* on the after block of the target block.
*
*/
public static void removeBlock(RenderableBlock renderable, WorkspaceWidget widget,
Container container){
for (BlockNote bn : renderable.blockNotes) {
bn.delete();
}
if (widget != null) {
widget.removeBlock(renderable);
}
if (container != null) {
container.remove(renderable);
container.validate();
container.repaint();
}
renderable.setParentWidget(null);
// TODO(sharon): figure out how to make the tooltip disappear if it was showing
Workspace.getInstance().notifyListeners(new WorkspaceEvent(widget, renderable.getBlockID(),
WorkspaceEvent.BLOCK_REMOVED));
}
/**
* Returns the Long id of this
* @return the Long id of this
*/
public Long getBlockID(){
return blockID;
}
public ArrayList<BlockNote> getBlockNotes() {
return blockNotes;
}
/**
* Returns the height of the block shape of this
* @return the height of the block shape of this
*/
public int getBlockHeight(){
return blockImage.area.getBounds().height;
}
/**
* Returns the dimensions of the block shape of this
* @return the dimensions of the block shape of this
*/
private Dimension getBlockSize(){
return blockImage.area.getBounds().getSize();
}
/**
* Returns the width of the block shape of this
* @return the width of the block shape of this
*/
public int getBlockWidth(){
return blockImage.area.getBounds().width;
}
/**
* Returns the BlockShape instance representing this
* @return the BlockShape instance representing this
*/
public BlockShape getBlockShape(){
return blockImage.shape;
}
/**
* @return the abstract BlockArea
*/
Area getAbstractBlockArea() {
return blockImage.abstractArea;
}
/**
* Moves this component to a new location. The top-left corner of
* the new location is specified by the <code>x</code> and <code>y</code>
* parameters in the coordinate space of this component's parent.
* @param x the <i>x</i>-coordinate of the new location's
* top-left corner in the parent's coordinate space
* @param y the <i>y</i>-coordinate of the new location's
* top-left corner in the parent's coordinate space
*/
@Override
public void setLocation(int x, int y) {
int dx, dy;
dx = x - getX();
dy = y - getY();
if (dx == 0 && dy == 0) {
return;
}
super.setLocation(x, y);
// The test (dx == x && dy == y) is a way of asking if
// getX() and getY() were 0 before the super.setLocation, which is a way
// of asking whether this call is setting the location away from (0,0)
// which is true during loading.
// But what if the block just happens to be at (0,0)? As far as I can tell
// it's not possible to drag a block to (0,0). (2/4/10)
// Bogus!
if (dx == x && dy == y) {
return;
}
for (BlockNote bn : blockNotes) {
if (bn.getParent() != getParent()) {
bn.setParent(getParent());
}
bn.translatePosition(dx, dy);
}
}
/**
* Moves this component to a new location. The top-left corner of
* the new location is specified by point <code>p</code>. Point
* <code>p</code> is given in the parent's coordinate space.
* @param p the point defining the top-left corner
* of the new location, given in the coordinate space of this
* component's parent
*/
@Override
public void setLocation(Point p) {
setLocation(p.x, p.y);
}
/**
* Returns the width of the stroke used to draw the highlight.
* Note that the highlight will only appear half this width,
* so the overall width of the block + highlight will be
* blockWidth + highlightStrokeWidth.
* @return the width of the stroke used to draw the highlight.
*/
public int getHighlightStrokeWidth() {
return RBHighlightHandler.HIGHLIGHT_STROKE_WIDTH;
}
/**
* Returns the bounds of the block stack of this, where this block
* is at the top of its stack (in other words, it does not take the
* bounds of the blocks above it into account).
* @return the bounds of the block stack of this, where this block
* is at the top of its stack.
*/
public Rectangle getStackBounds(){
return new Rectangle(getLocation(), calcStackDimensions(this));
}
/**
* Helper method to calculate the bounds of a stack. For now this method naively traverses
* through the entire stack of the specified RenderableBlock rb and calculates the bounds.
* @param rb the RenderableBlock to calculate the stack bounds of
* @return Dimensions of the stack of the specified rb
*/
private Dimension calcStackDimensions(RenderableBlock rb){
if (rb.getBlock().getAfterBlockID() != Block.NULL){
Dimension dim = calcStackDimensions(RenderableBlock.getRenderableBlock(rb.getBlock().
getAfterBlockID()));
return new Dimension(Math.max(rb.getBlockWidth() + rb.getMaxWidthOfSockets(rb.getBlockID()),
dim.width),
rb.getBlockHeight() + dim.height);
} else {
return new Dimension(rb.getBlockWidth() + rb.getMaxWidthOfSockets(rb.blockID),
rb.getBlockHeight());
}
}
/**
* sets the label to belonging to this renderable block to
* editing state == true (editing mode)
*/
public void switchToLabelEditingMode(boolean highlighted){
if (getBlock().isLabelEditable()){
if (highlighted){
blockLabel.setEditingState(true);
blockLabel.highlightText();
} else {
blockLabel.setEditingState(true);
}
}
}
/**
* returns the blockWidget for this RenderableBlock
* @return the blockWidget for this RenderableBlock
*/
JComponent getBlockWidget() {
return blockWidget;
}
/**
* @return the dimension of the sole block widget in this block.
* May NOT return null.
*/
public Dimension getBlockWidgetDimension(){
if (blockWidget == null) {
return new Dimension(0, 0);
} else {
return blockWidget.getSize();
}
}
/**
* @param blockWidget
*
* @requires none
* @modifies blockWidget
* @effects sets block widget to the input argument "blockWidget"
* and revalidates the JComponent representation of RenderableBlock
*/
public void setBlockWidget(JComponent blockWidget){
if (blockWidget != null){
remove(blockWidget);
}
this.blockWidget = blockWidget;
if (blockWidget != null){
add(blockWidget);
}
revalidate();
}
/**
* Clears all renderable block instances and all
* block instances
*/
public static void reset(){
if (DEBUG) {
System.out.println("reseting all renderable blocks");
}
for (RenderableBlock block : ALL_RENDERABLE_BLOCKS.values()) {
block.setVisible(false);
}
ALL_RENDERABLE_BLOCKS.clear();
BlockUtilities.reset();
Block.reset();
BlockStub.reset();
System.gc();
}
public JComponentDragHandler getDragHandler() {
return dragHandler;
}
/**
* Returns the BlockImageIcon instance at the specified location; null if
* no BlockImageIcon exists at that location
* @param location the ImageLocation of the desired BlockImageIcon
* @return the BlockImageIcon instance at the specified location; null if
* no BlockImageIcon exists at that location
*/
// private BlockImageIcon getImageIconAt(ImageLocation location){
// return blockImage.map.get(location);
// }
///////////////////
// LABEL METHODS //
///////////////////
/**
* Synchronizes this RenderableBlock's socket components (including tags, labels)
* with the associated Block's list of sockets.
* @effects for every socket in Block:
* (1) check/add corresponding tag structure,
* (2) check/add block label
* for every tag in Renderable:
* (3) delete any sockets not in Block
* @complexity Running time for n Block sockets
* and m Renderable tags: O(m+nm)=O(nm)
*/
private boolean synchronizeSockets(){
if (isCollapsed()) {
return false;
}
boolean changed = false;
List<ConnectorTag> newSocketTags = new ArrayList<ConnectorTag>();
for (ConnectorTag tag : blockImage.socketTags){
if (tag.getLabel() != null){
remove(tag.getLabel().getJComponent());
}
}
for (int i = 0; i < getBlock().getNumSockets(); i++) {
BlockConnector socket = getBlock().getSocketAt(i);
ConnectorTag tag = getConnectorTag(socket);
if (tag == null){
tag = new ConnectorTag(socket);
if (SocketLabel.ignoreSocket(socket)) {
tag.setLabel(null); //ignored sockets have no labels
} else {
SocketLabel label = new SocketLabel(socket, socket.getLabel(),
BlockLabel.Type.PORT_LABEL, socket.isLabelEditable(), blockID);
String argumentToolTip = getBlock().getArgumentDescription(i);
if (argumentToolTip != null){
label.setToolTipText(getBlock().getArgumentDescription(i).trim());
}
tag.setLabel(label);
label.setZoomLevel(getZoom());
label.setText(socket.getLabel());
add(label.getJComponent());
changed = true;
}
} else {
SocketLabel label = tag.getLabel();
if (!SocketLabel.ignoreSocket(socket)) {
//ignored bottom sockets or sockets with label == ""
if (label == null){
label = new SocketLabel(socket, socket.getLabel(),
BlockLabel.Type.PORT_LABEL, socket.isLabelEditable(), blockID);
String argumentToolTip = getBlock().getArgumentDescription(i);
if (argumentToolTip != null){
label.setToolTipText(getBlock().getArgumentDescription(i).trim());
}
tag.setLabel(label);
label.setText(socket.getLabel());
add(label.getJComponent());
changed = true;
} else {
label.setText(socket.getLabel());
add(label.getJComponent());
changed = true;
}
label.setZoomLevel(getZoom());
}
}
newSocketTags.add(tag);
}
blockImage.socketTags.clear();
blockImage.socketTags = newSocketTags;
return changed;
}
/**
* Change this block's genus
* @param newGenus the new genus
* @param preserveLabel if the label should be preserved, or reset to the genus's default
*/
public void changeGenus(String newGenus, boolean preserveLabel) {
blockLabel.genusChanged(newGenus, preserveLabel);
}
/**
* Updates all the labels and sockets within this block.
*/
private void synchronizeLabelsAndSockets(){
boolean blockLabelChanged = getBlock().getBlockLabel() != null &&
!blockLabel.getText().equals(getBlock().getBlockLabel());
boolean pageLabelChanged = getBlock().getPageLabel() != null &&
!pageLabel.getText().equals(getBlock().getPageLabel());
boolean decoratorLabelChanged = getBlock().getDecoratorLabel() != null &&
!decoratorLabel.getText().equals(getBlock().getDecoratorLabel());
boolean socketLabelsChanged = false;
if (getBlock().getNumSockets() == 0) {
// in case we just deleted the last socket...
socketLabelsChanged = synchronizeSockets();
}
// If tag label isn't the same as socket label, synchronize.
// If the block doesn't have an editable socket label, synchronize.
//
// Needed to not synchronize the socket if it is label editable so it
// doesn't synchronize when it gains focus.
//
// May possibly be done better if synchronizeSockets is rewritten. It has to
// be written such that it doesn't remove the sockets' JComponents/remake
// them. Currently relies on the synchronizeSockets() call in
// getSocketPixelPoint(BlockConnector) to make sure the dimensions
// and number of sockets are consistent.
for (int i = 0; i < getBlock().getNumSockets(); i++) {
BlockConnector socket = getBlock().getSocketAt(i);
ConnectorTag tag = getConnectorTag(socket);
if (tag != null) {
if (tag.getLabel() != null){
if (!tag.getLabel().getText().equals(socket.getLabel())){
socketLabelsChanged = synchronizeSockets();
break;
}
}
}
if (!socket.isLabelEditable()){
socketLabelsChanged = synchronizeSockets();
break;
}
}
if (blockLabelChanged){
blockLabel.setText(getBlock().getBlockLabel());
}
if (pageLabelChanged){
pageLabel.setText(getBlock().getPageLabel());
}
if (decoratorLabelChanged) {
decoratorLabel.setText(getBlock().getDecoratorLabel());
}
if (blockLabelChanged || pageLabelChanged || socketLabelsChanged || blockNotesChanged ||
decoratorLabelChanged) {
reformBlockShape();
blockNotesChanged = false;
}
if (BlockLinkChecker.hasPlugEquivalent(getBlock())){
BlockConnector plug = BlockLinkChecker.getPlugEquivalent(getBlock());
Block plugBlock = Block.getBlock(plug.getBlockID());
if (plugBlock != null) {
if (plugBlock.getConnectorTo(blockID) == null) {
throw new RuntimeException("one-sided connection from " + getBlock().getBlockLabel() +
" to " + Block.getBlock(blockID).getBlockLabel());
}
RenderableBlock receiver = RenderableBlock.getRenderableBlock(plug.getBlockID());
if (!receiver.isCollapsed()) {
receiver.updateSocketSpace(plugBlock.getConnectorTo(blockID), blockID, true);
}
}
}
}
/**
* Determine the width necessary to accommodate for placed labels. Used to
* determine the minimum width of a block.
* @return int pixel width needed for the labels
*/
public int accommodateLabelsWidth() {
int maxSocketWidth = 0;
int width = 0;
for (ConnectorTag tag : blockImage.socketTags){
SocketLabel label = tag.getLabel();
if (label != null) maxSocketWidth = Math.max(maxSocketWidth, label.getAbstractWidth());
}
if (getBlock().hasPageLabel()) {
width += Math.max(blockLabel.getAbstractWidth(), pageLabel.getAbstractWidth()) +
maxSocketWidth;
width += getControlLabelsWidth() + decoratorLabel.getAbstractWidth();
} else {
width += blockLabel.getAbstractWidth() + maxSocketWidth;
width += Math.max(getControlLabelsWidth(), decoratorLabel.getAbstractWidth()
+ getBlockNoteLabelsWidth()) + 4;
}
return width;
}
/**
* @return pixel width needed for the comment label
*/
public int getBlockNoteLabelsWidth() {
return blockNotes.size() * BlockNoteLabel.BLOCK_NOTE_LABEL_SIZE;
}
public int accommodateSocketTagsWidth() {
int maxSocketWidth = 0;
for (ConnectorTag tag : blockImage.socketTags){
SocketLabel label = tag.getLabel();
if (label != null) {
maxSocketWidth = Math.max(maxSocketWidth, label.getAbstractWidth());
}
}
return maxSocketWidth + 4;
}
/**
* Returns the minimum block height required to accommodate the page label,
* decorator label, and block label for this block. This version assumes
* that the decorator label and the block label overlap vertically.
* @return the minimum height
*/
public int accommodateBlockLabelsHeight() {
return accommodatePageLabelHeight() +
Math.max(blockLabel.getAbstractHeight(), getDecoratorLabelHeight()) +
// add a little padding
4;
}
/**
* Returns the width of the page label on this block; if page label
* is not enabled and does not exist, returns 0.
* @return the width of the page label on this block iff page label
* is enabled and exists; returns 0 otherwise.
*/
public int accommodatePageLabelHeight(){
if (getBlock().hasPageLabel()) {
return pageLabel.getAbstractHeight();
} else {
return 0;
}
}
/**
* Sets all the labels of this block as uneditable block labels.
* Useful for Factory blocks.
*/
public void setBlockLabelUneditable(){
blockLabel.setEditable(false);
}
////////////////////////////////////////
// BLOCK IMAGE MANAGEMENT AND METHODS //
////////////////////////////////////////
/**
* Returns the total height of all the images to draw on this block
* @return the total height of all the images to draw on this block
*/
public int accommodateImagesHeight(){
int maxImgHt = 0;
for (BlockImageIcon img : getBlock().getInitBlockImageMap().values()){
maxImgHt += img.getImageIcon().getIconHeight();
}
return maxImgHt;
}
/**
* @return the total width of all the images to draw on this block
*/
public int accommodateImagesWidth(){
int maxImgWt = 0;
for (BlockImageIcon img : getBlock().getInitBlockImageMap().values()){
maxImgWt += img.getImageIcon().getIconWidth();
}
return maxImgWt;
}
//////////////////////////////////////////////
//BLOCK LINKING CHECKS ON OTHER RENDERABLES //
//////////////////////////////////////////////
/**
* Looks for links between this RenderableBlock and others.
* @return a BlockLink object with information on the closest possible linking
* between this RenderableBlock and another.
* If block is collapsed it will return null.
*/
public BlockLink getNearbyLink(){
return BlockLinkChecker.getLink(this, Workspace.getInstance().getBlockCanvas().getBlocks());
}
///////////////////////
/// SOCKET METHODS ////
///////////////////////
/**
* Returns the maximum width between all the socket connectors of this or 0 if this does not
* have any sockets
* @return the maximum width between all the socket connectors of this or 0 if this does not
* have any sockets
*/
public int getMaxSocketShapeWidth() {
int maxSocketWidth = 0;
for (BlockConnector socket : getBlock().getSockets()) {
int socketWidth = BlockConnectorShape.getConnectorDimensions(socket).width;
if (socketWidth > maxSocketWidth) {
maxSocketWidth = socketWidth;
}
}
return maxSocketWidth;
}
/**
* Returns a new Point object that represents the pixel location of this socket's center.
* Mutating the new Point will not affect future calls to getSocketPoint; that is, this
* method clones a new Point object. The new Point object MAY NOT BE NULL.
*
* @param socket - the socket whose point we want. socket MAY NOT BE NULL.
* @return a Point representing the socket's center
* @requires socket != null and socket is one of this block's socket
*/
public Point getSocketPixelPoint(BlockConnector socket) {
ConnectorTag tag = getConnectorTag(socket);
if (tag != null)
return tag.getPixelLocation();
if (DEBUG) {
System.out.println("Error, Socket has no connector tag: " + socket);
}
return new Point(0, -100); //JBT hopefully this doesn't hurt anything,
// this is masking a bug that needs to be tracked down, why is the connector tag missing?
}
/**
* Returns a new Point object that represents the abstract location of this socket's center.
* Mutating the new Point will not affect future calls to getSocketPoint; that is, this
* method clones a new Point object. The new Point object MAY NOT BE NULL.
*
* @param socket - the socket whose point we want. socket MAY NOT BE NULL.
* @return a Point representing the socket's center
* @requires socket != null and socket is one of this block's socket
*/
public Point getSocketAbstractPoint(BlockConnector socket) {
ConnectorTag tag = getConnectorTag(socket);
return tag.getAbstractLocation();
}
/**
* Updates the center point location of this socket
*
* @param socket - the socket whose point we will update. Socket MAY NOT BE NULL
* @param point - the ABSTRACT location of socket's center. ABSTRACT LOCATION!!!
*
* @requires socket != null and there exist a matching tag for the socket
*/
public void updateSocketPoint(BlockConnector socket, Point2D point) {
ConnectorTag tag = getConnectorTag(socket);
//TODO(user): what if tag does not exist? should we throw exception or add new tag?
tag.setAbstractLocation(point);
}
/**
* Updates the renderable block with the underlying block's before,
* after, and plug connectors.
*/
public void updateConnectors() {
Block b = Block.getBlock(blockID);
afterTag.setSocket(b.getAfterConnector());
beforeTag.setSocket(b.getBeforeConnector());
plugTag.setSocket(b.getPlug());
}
/////////////////////////////////////
// PARENT WORKSPACE WIDGET METHODS //
/////////////////////////////////////
/**
* Returns the parent WorkspaceWidget containing this
* @return the parent WorkspaceWidget containing this
*/
public WorkspaceWidget getParentWidget(){
return parent;
}
/**
* Sets the parent WorkspaceWidget containing this
* @param widget the desired WorkspaceWidget
*/
public void setParentWidget(WorkspaceWidget widget){
parent = widget;
}
/**
* Overriding JComponent.contains(int x, int y) so that this component's
* boundaries are defined by the actual area occupied by the Renderable
* Block shape. Returns true iff the specified coordinates are contained
* within the area of the BlockShape.
* @return true iff the specified coordinates are contained within the Area
* of the BlockShape
*/
@Override
public boolean contains(int x, int y) {
return blockImage.area.contains(x, y);
}
//////////////////////
// BLOCK MANAGEMENT //
//////////////////////
/**
* Shortcut to get block with current BlockID of this renderable block.
*/
public Block getBlock() {
return Block.getBlock(blockID);
}