1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control.skin; 27 28 import com.sun.javafx.scene.NodeHelper; 29 import com.sun.javafx.scene.ParentHelper; 30 import com.sun.javafx.scene.control.CustomColorDialog; 31 import com.sun.javafx.scene.control.skin.Utils; 32 import com.sun.javafx.scene.traversal.Algorithm; 33 import com.sun.javafx.scene.traversal.Direction; 34 import com.sun.javafx.scene.traversal.ParentTraversalEngine; 35 import com.sun.javafx.scene.traversal.TraversalContext; 36 import javafx.collections.FXCollections; 37 import javafx.collections.ListChangeListener; 38 import javafx.collections.ObservableList; 39 import javafx.event.ActionEvent; 40 import javafx.event.Event; 41 import javafx.event.EventHandler; 42 import javafx.geometry.Bounds; 43 import javafx.geometry.NodeOrientation; 44 import javafx.geometry.Pos; 45 import javafx.geometry.Side; 46 import javafx.scene.Node; 47 import javafx.scene.control.ColorPicker; 48 import javafx.scene.control.ContextMenu; 49 import javafx.scene.control.Hyperlink; 50 import javafx.scene.control.Label; 51 import javafx.scene.control.MenuItem; 52 import javafx.scene.control.PopupControl; 53 import javafx.scene.control.Separator; 54 import javafx.scene.control.Tooltip; 55 import javafx.scene.input.KeyCode; 56 import javafx.scene.input.KeyEvent; 57 import javafx.scene.input.MouseButton; 58 import javafx.scene.input.MouseEvent; 59 import javafx.scene.layout.GridPane; 60 import javafx.scene.layout.Region; 61 import javafx.scene.layout.StackPane; 62 import javafx.scene.layout.VBox; 63 import javafx.scene.paint.Color; 64 import javafx.scene.shape.Rectangle; 65 import javafx.scene.shape.StrokeType; 66 67 import java.util.List; 68 69 import static com.sun.javafx.scene.control.Properties.getColorPickerString; 70 71 // Not public API - this is (presently) an implementation detail only 72 class ColorPalette extends Region { 73 74 private static final int SQUARE_SIZE = 15; 75 76 // package protected for testing purposes 77 ColorPickerGrid colorPickerGrid; 78 final Hyperlink customColorLink = new Hyperlink(getColorPickerString("customColorLink")); 79 CustomColorDialog customColorDialog = null; 80 81 private ColorPicker colorPicker; 82 private final GridPane standardColorGrid = new GridPane(); 83 private final GridPane customColorGrid = new GridPane(); 84 private final Separator separator = new Separator(); 85 private final Label customColorLabel = new Label(getColorPickerString("customColorLabel")); 86 87 private PopupControl popupControl; 88 private ColorSquare focusedSquare; 89 private ContextMenu contextMenu = null; 90 91 private Color mouseDragColor = null; 92 private boolean dragDetected = false; 93 94 // Metrics for custom colors 95 private int customColorNumber = 0; 96 private int customColorRows = 0; 97 private int customColorLastRowLength = 0; 98 99 private final ColorSquare hoverSquare = new ColorSquare(); 100 101 public ColorPalette(final ColorPicker colorPicker) { 102 getStyleClass().add("color-palette-region"); 103 this.colorPicker = colorPicker; 104 colorPickerGrid = new ColorPickerGrid(); 105 colorPickerGrid.getChildren().get(0).requestFocus(); 106 customColorLabel.setAlignment(Pos.CENTER_LEFT); 107 customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1)); 108 customColorLink.setAlignment(Pos.CENTER); 109 customColorLink.setFocusTraversable(true); 110 customColorLink.setVisited(true); // so that it always appears blue 111 customColorLink.setOnAction(new EventHandler<ActionEvent>() { 112 @Override public void handle(ActionEvent t) { 113 if (customColorDialog == null) { 114 customColorDialog = new CustomColorDialog(popupControl); 115 customColorDialog.customColorProperty().addListener((ov, t1, t2) -> { 116 colorPicker.setValue(customColorDialog.customColorProperty().get()); 117 }); 118 customColorDialog.setOnSave(() -> { 119 Color customColor = customColorDialog.customColorProperty().get(); 120 buildCustomColors(); 121 colorPicker.getCustomColors().add(customColor); 122 updateSelection(customColor); 123 Event.fireEvent(colorPicker, new ActionEvent()); 124 colorPicker.hide(); 125 }); 126 customColorDialog.setOnUse(() -> { 127 Event.fireEvent(colorPicker, new ActionEvent()); 128 colorPicker.hide(); 129 }); 130 } 131 customColorDialog.setCurrentColor(colorPicker.valueProperty().get()); 132 if (popupControl != null) popupControl.setAutoHide(false); 133 customColorDialog.show(); 134 customColorDialog.setOnHidden(event -> { 135 if (popupControl != null) popupControl.setAutoHide(true); 136 }); 137 } 138 }); 139 140 initNavigation(); 141 142 buildStandardColors(); 143 standardColorGrid.getStyleClass().add("color-picker-grid"); 144 standardColorGrid.setVisible(true); 145 customColorGrid.getStyleClass().add("color-picker-grid"); 146 customColorGrid.setVisible(false); 147 buildCustomColors(); 148 colorPicker.getCustomColors().addListener(new ListChangeListener<Color>() { 149 @Override public void onChanged(Change<? extends Color> change) { 150 buildCustomColors(); 151 } 152 }); 153 154 VBox paletteBox = new VBox(); 155 paletteBox.getStyleClass().add("color-palette"); 156 paletteBox.getChildren().addAll(standardColorGrid, colorPickerGrid, customColorLabel, customColorGrid, separator, customColorLink); 157 158 hoverSquare.setMouseTransparent(true); 159 hoverSquare.getStyleClass().addAll("hover-square"); 160 setFocusedSquare(null); 161 162 getChildren().addAll(paletteBox, hoverSquare); 163 } 164 165 private void setFocusedSquare(ColorSquare square) { 166 if (square == focusedSquare) { 167 return; 168 } 169 focusedSquare = square; 170 171 hoverSquare.setVisible(focusedSquare != null); 172 if (focusedSquare == null) { 173 return; 174 } 175 176 if (!focusedSquare.isFocused()) { 177 focusedSquare.requestFocus(); 178 } 179 180 hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill()); 181 182 Bounds b = square.localToScene(square.getLayoutBounds()); 183 184 double x = b.getMinX(); 185 double y = b.getMinY(); 186 187 double xAdjust; 188 double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0; 189 190 if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 191 x = focusedSquare.getLayoutX(); 192 xAdjust = -focusedSquare.getWidth() + scaleAdjust; 193 } else { 194 xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust; 195 } 196 197 hoverSquare.setLayoutX(snapPositionX(x) - xAdjust); 198 hoverSquare.setLayoutY(snapPositionY(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0)); 199 } 200 201 private void buildStandardColors() { 202 final Color[] STANDARD_COLORS = { 203 Color.TEAL, 204 Color.BLUE, 205 Color.NAVY, 206 Color.FUCHSIA, 207 Color.PURPLE, 208 Color.RED, 209 Color.MAROON, 210 Color.ORANGE, 211 Color.GOLD, 212 Color.YELLOW, 213 Color.OLIVE, 214 Color.GREEN 215 }; 216 217 standardColorGrid.getChildren().clear(); 218 219 for (int i = 0; i < NUM_OF_COLUMNS; i++) { 220 standardColorGrid.add(new ColorSquare(STANDARD_COLORS[i], i, ColorType.STANDARD), i, 0); 221 } 222 standardColorGrid.setVisible(true); 223 } 224 225 private void buildCustomColors() { 226 final ObservableList<Color> customColors = colorPicker.getCustomColors(); 227 customColorNumber = customColors.size(); 228 229 customColorGrid.getChildren().clear(); 230 if (customColors.isEmpty()) { 231 customColorLabel.setVisible(false); 232 customColorLabel.setManaged(false); 233 customColorGrid.setVisible(false); 234 customColorGrid.setManaged(false); 235 return; 236 } else { 237 customColorLabel.setVisible(true); 238 customColorLabel.setManaged(true); 239 customColorGrid.setVisible(true); 240 customColorGrid.setManaged(true); 241 if (contextMenu == null) { 242 MenuItem item = new MenuItem(getColorPickerString("removeColor")); 243 item.setOnAction(e -> { 244 ColorSquare square = (ColorSquare)contextMenu.getOwnerNode(); 245 customColors.remove(square.rectangle.getFill()); 246 buildCustomColors(); 247 }); 248 contextMenu = new ContextMenu(item); 249 } 250 } 251 252 int customColumnIndex = 0; 253 int customRowIndex = 0; 254 int remainingSquares = customColors.size() % NUM_OF_COLUMNS; 255 int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares; 256 customColorLastRowLength = remainingSquares == 0 ? 12 : remainingSquares; 257 258 for (int i = 0; i < customColors.size(); i++) { 259 Color c = customColors.get(i); 260 ColorSquare square = new ColorSquare(c, i, ColorType.CUSTOM); 261 square.addEventHandler(KeyEvent.KEY_PRESSED, e -> { 262 if (e.getCode() == KeyCode.DELETE) { 263 customColors.remove(square.rectangle.getFill()); 264 buildCustomColors(); 265 } 266 }); 267 customColorGrid.add(square, customColumnIndex, customRowIndex); 268 customColumnIndex++; 269 if (customColumnIndex == NUM_OF_COLUMNS) { 270 customColumnIndex = 0; 271 customRowIndex++; 272 } 273 } 274 for (int i = 0; i < numEmpty; i++) { 275 ColorSquare emptySquare = new ColorSquare(); 276 emptySquare.setDisable(true); 277 customColorGrid.add(emptySquare, customColumnIndex, customRowIndex); 278 customColumnIndex++; 279 } 280 customColorRows = customRowIndex + 1; 281 requestLayout(); 282 283 } 284 285 private void initNavigation() { 286 setOnKeyPressed(ke -> { 287 switch (ke.getCode()) { 288 case SPACE: 289 case ENTER: 290 processSelectKey(ke); 291 ke.consume(); 292 break; 293 default: // no-op 294 } 295 }); 296 297 ParentHelper.setTraversalEngine(this, new ParentTraversalEngine(this, new Algorithm() { 298 @Override 299 public Node select(Node owner, Direction dir, TraversalContext context) { 300 final Node subsequentNode = context.selectInSubtree(context.getRoot(), owner, dir); 301 switch (dir) { 302 case NEXT: 303 case NEXT_IN_LINE: 304 case PREVIOUS: 305 return subsequentNode; 306 // Here, we need to intercept the standard algorithm in a few cases to get the desired traversal 307 // For right or left direction we want to continue on the next or previous row respectively 308 // For up and down, the custom color panel might be skipped by the standard algorithm (if not wide enough 309 // to be between the current color and custom color button), so we need to include it in the path explicitly. 310 case LEFT: 311 case RIGHT: 312 case UP: 313 case DOWN: 314 if (owner instanceof ColorSquare) { 315 Node result = processArrow((ColorSquare)owner, dir); 316 return result != null ? result : subsequentNode; 317 } else { 318 return subsequentNode; 319 } 320 } 321 return null; 322 } 323 324 private Node processArrow(ColorSquare owner, Direction dir) { 325 int row = 0; 326 int column = 0; 327 328 if (owner.colorType == ColorType.STANDARD) { 329 row = 0; 330 column = owner.index; 331 } else { 332 row = owner.index / NUM_OF_COLUMNS; 333 column = owner.index % NUM_OF_COLUMNS; 334 } 335 336 // Adjust the direction according to color picker orientation 337 dir = dir.getDirectionForNodeOrientation(colorPicker.getEffectiveNodeOrientation()); 338 // This returns true for all the cases which we need to override 339 if (isAtBorder(dir, row, column, (owner.colorType == ColorType.CUSTOM))) { 340 // There's no other node in the direction from the square, so we need to continue on some other row 341 // or cycle 342 int subsequentRow = row; 343 int subsequentColumn = column; 344 boolean subSequentSquareCustom = (owner.colorType == ColorType.CUSTOM); 345 boolean subSequentSquareStandard = (owner.colorType == ColorType.STANDARD); 346 switch (dir) { 347 case LEFT: 348 case RIGHT: 349 // The next row is either the first or the last, except when cycling in custom colors, the last row 350 // might have different number of columns 351 if (owner.colorType == ColorType.STANDARD) { 352 subsequentRow = 0; 353 subsequentColumn = (dir == Direction.LEFT)? NUM_OF_COLUMNS - 1 : 0; 354 } 355 else if (owner.colorType == ColorType.CUSTOM) { 356 subsequentRow = Math.floorMod(dir == Direction.LEFT ? row - 1 : row + 1, customColorRows); 357 subsequentColumn = dir == Direction.LEFT ? subsequentRow == customColorRows - 1 ? 358 customColorLastRowLength - 1 : NUM_OF_COLUMNS - 1 : 0; 359 } else { 360 subsequentRow = Math.floorMod(dir == Direction.LEFT ? row - 1 : row + 1, NUM_OF_ROWS); 361 subsequentColumn = dir == Direction.LEFT ? NUM_OF_COLUMNS - 1 : 0; 362 } 363 break; 364 case UP: // custom color are not handled here 365 if (owner.colorType == ColorType.NORMAL && row == 0) { 366 subSequentSquareStandard = true; 367 } 368 break; 369 case DOWN: // custom color are not handled here 370 if (customColorNumber > 0) { 371 subSequentSquareCustom = true; 372 subsequentRow = 0; 373 subsequentColumn = customColorRows > 1 ? column : Math.min(customColorLastRowLength - 1, column); 374 break; 375 } else { 376 return null; // Let the default algorithm handle this 377 } 378 379 } 380 if (subSequentSquareCustom) { 381 return customColorGrid.getChildren().get(subsequentRow * NUM_OF_COLUMNS + subsequentColumn); 382 } else if (subSequentSquareStandard) { 383 return standardColorGrid.getChildren().get(subsequentColumn); 384 } else { 385 return colorPickerGrid.getChildren().get(subsequentRow * NUM_OF_COLUMNS + subsequentColumn); 386 } 387 } 388 return null; 389 } 390 391 private boolean isAtBorder(Direction dir, int row, int column, boolean custom) { 392 switch (dir) { 393 case LEFT: 394 return column == 0; 395 case RIGHT: 396 return custom && row == customColorRows - 1 ? 397 column == customColorLastRowLength - 1 : column == NUM_OF_COLUMNS - 1; 398 case UP: 399 return !custom && row == 0; 400 case DOWN: 401 return !custom && row == NUM_OF_ROWS - 1; 402 } 403 return false; 404 } 405 406 @Override 407 public Node selectFirst(TraversalContext context) { 408 return standardColorGrid.getChildren().get(0); 409 } 410 411 @Override 412 public Node selectLast(TraversalContext context) { 413 return customColorLink; 414 } 415 })); 416 } 417 418 private void processSelectKey(KeyEvent ke) { 419 if (focusedSquare != null) focusedSquare.selectColor(ke); 420 } 421 422 public void setPopupControl(PopupControl pc) { 423 this.popupControl = pc; 424 } 425 426 public ColorPickerGrid getColorGrid() { 427 return colorPickerGrid; 428 } 429 430 public boolean isCustomColorDialogShowing() { 431 if (customColorDialog != null) return customColorDialog.isVisible(); 432 return false; 433 } 434 435 436 enum ColorType { 437 NORMAL, 438 STANDARD, 439 CUSTOM 440 }; 441 442 class ColorSquare extends StackPane { 443 Rectangle rectangle; 444 int index; 445 boolean isEmpty; 446 ColorType colorType = ColorType.NORMAL; 447 448 public ColorSquare() { 449 this(null, -1, ColorType.NORMAL); 450 } 451 452 public ColorSquare(Color color, int index) { 453 this(color, index, ColorType.NORMAL); 454 } 455 456 public ColorSquare(Color color, int index, ColorType type) { 457 // Add style class to handle selected color square 458 getStyleClass().add("color-square"); 459 if (color != null) { 460 setFocusTraversable(true); 461 462 focusedProperty().addListener((s, ov, nv) -> { 463 setFocusedSquare(nv ? this : null); 464 }); 465 466 addEventHandler(MouseEvent.MOUSE_ENTERED, event -> { 467 setFocusedSquare(ColorSquare.this); 468 }); 469 addEventHandler(MouseEvent.MOUSE_EXITED, event -> { 470 setFocusedSquare(null); 471 }); 472 473 addEventHandler(MouseEvent.MOUSE_RELEASED, event -> { 474 if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) { 475 if (!isEmpty) { 476 Color fill = (Color) rectangle.getFill(); 477 colorPicker.setValue(fill); 478 colorPicker.fireEvent(new ActionEvent()); 479 updateSelection(fill); 480 event.consume(); 481 } 482 colorPicker.hide(); 483 } else if (event.getButton() == MouseButton.SECONDARY || 484 event.getButton() == MouseButton.MIDDLE) { 485 if ((colorType == ColorType.CUSTOM) && contextMenu != null) { 486 if (!contextMenu.isShowing()) { 487 contextMenu.show(ColorSquare.this, Side.RIGHT, 0, 0); 488 Utils.addMnemonics(contextMenu, ColorSquare.this.getScene(), NodeHelper.isShowMnemonics(colorPicker)); 489 } else { 490 contextMenu.hide(); 491 Utils.removeMnemonics(contextMenu, ColorSquare.this.getScene()); 492 } 493 } 494 } 495 }); 496 } 497 this.index = index; 498 this.colorType = type; 499 rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE); 500 if (color == null) { 501 rectangle.setFill(Color.WHITE); 502 isEmpty = true; 503 } else { 504 rectangle.setFill(color); 505 } 506 507 rectangle.setStrokeType(StrokeType.INSIDE); 508 509 String tooltipStr = ColorPickerSkin.tooltipString(color); 510 Tooltip.install(this, new Tooltip((tooltipStr == null) ? "" : tooltipStr)); 511 512 rectangle.getStyleClass().add("color-rect"); 513 514 getChildren().add(rectangle); 515 } 516 517 public void selectColor(KeyEvent event) { 518 if (rectangle.getFill() != null) { 519 if (rectangle.getFill() instanceof Color) { 520 colorPicker.setValue((Color) rectangle.getFill()); 521 colorPicker.fireEvent(new ActionEvent()); 522 } 523 event.consume(); 524 } 525 colorPicker.hide(); 526 } 527 } 528 529 // The skin can update selection if colorpicker value changes.. 530 public void updateSelection(Color color) { 531 setFocusedSquare(null); 532 533 // check standard colors 534 for (Node n : standardColorGrid.getChildren()) { 535 ColorSquare c = (ColorSquare) n; 536 if (c.rectangle.getFill().equals(color)) { 537 setFocusedSquare(c); 538 return; 539 } 540 } 541 542 for (ColorSquare c : colorPickerGrid.getSquares()) { 543 if (c.rectangle.getFill().equals(color)) { 544 setFocusedSquare(c); 545 return; 546 } 547 } 548 // check custom colors 549 for (Node n : customColorGrid.getChildren()) { 550 ColorSquare c = (ColorSquare) n; 551 if (c.rectangle.getFill().equals(color)) { 552 setFocusedSquare(c); 553 return; 554 } 555 } 556 } 557 558 class ColorPickerGrid extends GridPane { 559 560 private final List<ColorSquare> squares; 561 562 public ColorPickerGrid() { 563 getStyleClass().add("color-picker-grid"); 564 setId("ColorCustomizerColorGrid"); 565 int columnIndex = 0, rowIndex = 0; 566 squares = FXCollections.observableArrayList(); 567 final int numColors = RAW_VALUES.length / 3; 568 Color[] colors = new Color[numColors]; 569 for (int i = 0; i < numColors; i++) { 570 colors[i] = new Color(RAW_VALUES[(i * 3)] / 255, 571 RAW_VALUES[(i * 3) + 1] / 255, RAW_VALUES[(i * 3) + 2] / 255, 572 1.0); 573 ColorSquare cs = new ColorSquare(colors[i], i); 574 squares.add(cs); 575 } 576 577 for (ColorSquare square : squares) { 578 add(square, columnIndex, rowIndex); 579 columnIndex++; 580 if (columnIndex == NUM_OF_COLUMNS) { 581 columnIndex = 0; 582 rowIndex++; 583 } 584 } 585 setOnMouseDragged(t -> { 586 if (!dragDetected) { 587 dragDetected = true; 588 mouseDragColor = colorPicker.getValue(); 589 } 590 int xIndex = com.sun.javafx.util.Utils.clamp(0, 591 (int)t.getX()/(SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1); 592 int yIndex = com.sun.javafx.util.Utils.clamp(0, 593 (int)t.getY()/(SQUARE_SIZE + 1), NUM_OF_ROWS - 1); 594 int index = xIndex + yIndex*NUM_OF_COLUMNS; 595 colorPicker.setValue((Color) squares.get(index).rectangle.getFill()); 596 updateSelection(colorPicker.getValue()); 597 }); 598 addEventHandler(MouseEvent.MOUSE_RELEASED, t -> { 599 if(colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) { 600 updateSelection(colorPicker.getValue()); 601 colorPicker.fireEvent(new ActionEvent()); 602 colorPicker.hide(); 603 } else { 604 // restore color as mouse release happened outside the grid. 605 if (mouseDragColor != null) { 606 colorPicker.setValue(mouseDragColor); 607 updateSelection(mouseDragColor); 608 } 609 } 610 dragDetected = false; 611 }); 612 } 613 614 public List<ColorSquare> getSquares() { 615 return squares; 616 } 617 618 @Override protected double computePrefWidth(double height) { 619 return (SQUARE_SIZE + 1)*NUM_OF_COLUMNS; 620 } 621 622 @Override protected double computePrefHeight(double width) { 623 return (SQUARE_SIZE + 1)*NUM_OF_ROWS; 624 } 625 } 626 627 private static final int NUM_OF_COLUMNS = 12; 628 private static double[] RAW_VALUES = { 629 // WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS 630 255, 255, 255, // first row 631 242, 242, 242, 632 230, 230, 230, 633 204, 204, 204, 634 179, 179, 179, 635 153, 153, 153, 636 128, 128, 128, 637 102, 102, 102, 638 77, 77, 77, 639 51, 51, 51, 640 26, 26, 26, 641 0, 0, 0, 642 0, 51, 51, // second row 643 0, 26, 128, 644 26, 0, 104, 645 51, 0, 51, 646 77, 0, 26, 647 153, 0, 0, 648 153, 51, 0, 649 153, 77, 0, 650 153, 102, 0, 651 153, 153, 0, 652 102, 102, 0, 653 0, 51, 0, 654 26, 77, 77, // third row 655 26, 51, 153, 656 51, 26, 128, 657 77, 26, 77, 658 102, 26, 51, 659 179, 26, 26, 660 179, 77, 26, 661 179, 102, 26, 662 179, 128, 26, 663 179, 179, 26, 664 128, 128, 26, 665 26, 77, 26, 666 51, 102, 102, // fourth row 667 51, 77, 179, 668 77, 51, 153, 669 102, 51, 102, 670 128, 51, 77, 671 204, 51, 51, 672 204, 102, 51, 673 204, 128, 51, 674 204, 153, 51, 675 204, 204, 51, 676 153, 153, 51, 677 51, 102, 51, 678 77, 128, 128, // fifth row 679 77, 102, 204, 680 102, 77, 179, 681 128, 77, 128, 682 153, 77, 102, 683 230, 77, 77, 684 230, 128, 77, 685 230, 153, 77, 686 230, 179, 77, 687 230, 230, 77, 688 179, 179, 77, 689 77, 128, 77, 690 102, 153, 153, // sixth row 691 102, 128, 230, 692 128, 102, 204, 693 153, 102, 153, 694 179, 102, 128, 695 255, 102, 102, 696 255, 153, 102, 697 255, 179, 102, 698 255, 204, 102, 699 255, 255, 77, 700 204, 204, 102, 701 102, 153, 102, 702 128, 179, 179, // seventh row 703 128, 153, 255, 704 153, 128, 230, 705 179, 128, 179, 706 204, 128, 153, 707 255, 128, 128, 708 255, 153, 128, 709 255, 204, 128, 710 255, 230, 102, 711 255, 255, 102, 712 230, 230, 128, 713 128, 179, 128, 714 153, 204, 204, // eigth row 715 153, 179, 255, 716 179, 153, 255, 717 204, 153, 204, 718 230, 153, 179, 719 255, 153, 153, 720 255, 179, 128, 721 255, 204, 153, 722 255, 230, 128, 723 255, 255, 128, 724 230, 230, 153, 725 153, 204, 153, 726 179, 230, 230, // ninth row 727 179, 204, 255, 728 204, 179, 255, 729 230, 179, 230, 730 230, 179, 204, 731 255, 179, 179, 732 255, 179, 153, 733 255, 230, 179, 734 255, 230, 153, 735 255, 255, 153, 736 230, 230, 179, 737 179, 230, 179, 738 204, 255, 255, // tenth row 739 204, 230, 255, 740 230, 204, 255, 741 255, 204, 255, 742 255, 204, 230, 743 255, 204, 204, 744 255, 204, 179, 745 255, 230, 204, 746 255, 255, 179, 747 255, 255, 204, 748 230, 230, 204, 749 204, 255, 204 750 }; 751 752 private static final int NUM_OF_COLORS = RAW_VALUES.length / 3; 753 private static final int NUM_OF_ROWS = NUM_OF_COLORS / NUM_OF_COLUMNS; 754 }