/*---------------------------------------------------------*\ | DeviceView.cpp | | | | OpenRGB Device view widget for Qt | | | | Adam Honse (calcprogrammer1@gmail.com) | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ #include #include #include #include #include #include #include "DeviceView.h" #include "ResourceManager.h" #include "RGBControllerKeyNames.h" #include "RGBController.h" #include "SettingsManager.h" #define MAX_COLS 20 #define PAD_LED 0.1f #define PAD_TEXT 0.1f #define PAD_ZONE 1.0f #define PAD_SEGMENT 0.9f #define SIZE_TEXT 0.5f DeviceView::DeviceView(QWidget *parent) : QWidget(parent), initSize(128,128), mouseDown(false) { controller = NULL; numerical_labels = false; per_led = true; setMouseTracking(1); size = width(); } DeviceView::~DeviceView() { } struct led_label { QString label_text; QString label_utf8; }; static const std::map led_label_lookup = { { KEY_EN_A, { "A" , "A", }}, { KEY_EN_B, { "B" , "B", }}, { KEY_EN_C, { "C" , "C", }}, { KEY_EN_D, { "D" , "D", }}, { KEY_EN_E, { "E" , "E", }}, { KEY_EN_F, { "F" , "F", }}, { KEY_EN_G, { "G" , "G", }}, { KEY_EN_H, { "H" , "H", }}, { KEY_EN_I, { "I" , "I", }}, { KEY_EN_J, { "J" , "J", }}, { KEY_EN_K, { "K" , "K", }}, { KEY_EN_L, { "L" , "L", }}, { KEY_EN_M, { "M" , "M", }}, { KEY_EN_N, { "N" , "N", }}, { KEY_EN_O, { "O" , "O", }}, { KEY_EN_P, { "P" , "P", }}, { KEY_EN_Q, { "Q" , "Q", }}, { KEY_EN_R, { "R" , "R", }}, { KEY_EN_S, { "S" , "S", }}, { KEY_EN_T, { "T" , "T", }}, { KEY_EN_U, { "U" , "U", }}, { KEY_EN_V, { "V" , "V", }}, { KEY_EN_W, { "W" , "W", }}, { KEY_EN_X, { "X" , "X", }}, { KEY_EN_Y, { "Y" , "Y", }}, { KEY_EN_Z, { "Z" , "Z", }}, { KEY_EN_0, { "0" , "0", }}, { KEY_EN_1, { "1" , "1", }}, { KEY_EN_2, { "2" , "2", }}, { KEY_EN_3, { "3" , "3", }}, { KEY_EN_4, { "4" , "4", }}, { KEY_EN_5, { "5" , "5", }}, { KEY_EN_6, { "6" , "6", }}, { KEY_EN_7, { "7" , "7", }}, { KEY_EN_8, { "8" , "8", }}, { KEY_EN_9, { "9" , "9", }}, { KEY_EN_F1, { "F1" , "F1", }}, { KEY_EN_F2, { "F2" , "F2", }}, { KEY_EN_F3, { "F3" , "F3", }}, { KEY_EN_F4, { "F4" , "F4", }}, { KEY_EN_F5, { "F5" , "F5", }}, { KEY_EN_F6, { "F6" , "F6", }}, { KEY_EN_F7, { "F7" , "F7", }}, { KEY_EN_F8, { "F8" , "F8", }}, { KEY_EN_F9, { "F9" , "F9", }}, { KEY_EN_F10, { "F10" , "F10", }}, { KEY_EN_F11, { "F11" , "F11", }}, { KEY_EN_F12, { "F12" , "F12", }}, { KEY_EN_BACK_TICK, { "`" , "`", }}, { KEY_EN_MINUS, { "-" , "-", }}, { KEY_EN_PLUS, { "+" , "+", }}, { KEY_EN_EQUALS, { "=" , "=", }}, { KEY_EN_LEFT_BRACKET, { "[" , "[", }}, { KEY_EN_RIGHT_BRACKET, { "]" , "]", }}, { KEY_EN_BACK_SLASH, { "\\" , "\\", }}, { KEY_EN_ANSI_BACK_SLASH, { "\\" , "\\", }}, { KEY_EN_ISO_BACK_SLASH, { "\\" , "\\", }}, { KEY_EN_SEMICOLON, { ";" , ";", }}, { KEY_EN_QUOTE, { "'" , "'", }}, { KEY_EN_POUND, { "#" , "#", }}, { KEY_EN_COMMA, { "," , ",", }}, { KEY_EN_PERIOD, { "." , ".", }}, { KEY_EN_FORWARD_SLASH, { "/" , "/", }}, { KEY_EN_ESCAPE, { "Esc" , "Esc", }}, { KEY_EN_PRINT_SCREEN, { "Prt" , "Prt", }}, { KEY_EN_SCROLL_LOCK, { "Scr" , "Scr", }}, { KEY_EN_PAUSE_BREAK, { "Brk" , "Brk", }}, { KEY_EN_BACKSPACE, { "Bks" , "\xE2\x8C\xAB" }}, // ⌫ { KEY_EN_INSERT, { "Ins" , "Ins", }}, { KEY_EN_HOME, { "Hom" , "Hom", }}, { KEY_EN_PAGE_UP, { "PUp" , "PUp", }}, { KEY_EN_TAB, { "Tab" , "\xE2\x86\xb9" }}, // ⭾ ↹ ⇄ ⇥ { KEY_EN_DELETE, { "Del" , "Del", }}, { KEY_EN_END, { "End" , "End", }}, { KEY_EN_PAGE_DOWN, { "PDn" , "PDn", }}, { KEY_EN_CAPS_LOCK, { "Cap" , "Cap", }}, { KEY_EN_ANSI_ENTER, { "Ent" , "\xE2\x86\xB5" }}, // ↵ ⏎ { KEY_EN_ISO_ENTER, { "Ent" , "\xE2\x86\xB5" }}, // ↵ ⏎ { KEY_EN_LEFT_SHIFT, { "Sft" , "\xE2\x87\xA7" }}, // ⇧ { KEY_EN_RIGHT_SHIFT, { "Sft" , "\xE2\x87\xA7" }}, // ⇧ { KEY_EN_UP_ARROW, { "Up" , "\xF0\x9F\xA1\xB9" }}, // ↑ 🡹 { KEY_EN_LEFT_CONTROL, { "Ctl" , "Ctl", }}, { KEY_EN_LEFT_WINDOWS, { "Win" , "\xe2\x9d\x96" }}, // ❖ { KEY_EN_LEFT_FUNCTION, { "Fn" , "Fn", }}, { KEY_EN_LEFT_ALT, { "Alt" , "Alt", }}, { KEY_EN_SPACE, { "Spc" , "Spc", }}, { KEY_EN_RIGHT_ALT, { "Alt" , "Alt", }}, { KEY_EN_RIGHT_FUNCTION, { "Fn" , "Fn", }}, { KEY_EN_RIGHT_WINDOWS, { "Win" , "\xe2\x9d\x96" }}, // ❖ { KEY_EN_MENU, { "Mnu" , "\xE2\x98\xB0" }}, // ▤ ☰ 𝌆 🗏 { KEY_EN_RIGHT_CONTROL, { "Ctl" , "Ctl", }}, { KEY_EN_LEFT_ARROW, { "Lft" , "\xF0\x9F\xA1\xB8" }}, // ↑ 🡹 { KEY_EN_DOWN_ARROW, { "Dn" , "\xF0\x9F\xA1\xBB" }}, // ↑ 🡹 { KEY_EN_RIGHT_ARROW, { "Rgt" , "\xF0\x9F\xA1\xBA" }}, // ↑ 🡹 { KEY_EN_NUMPAD_LOCK, { "Num" , "Num", }}, { KEY_EN_NUMPAD_DIVIDE, { "/" , "/", }}, { KEY_EN_NUMPAD_TIMES, { "*" , "*", }}, { KEY_EN_NUMPAD_MINUS, { "-" , "-", }}, { KEY_EN_NUMPAD_PLUS, { "+" , "+", }}, { KEY_EN_NUMPAD_PERIOD, { "." , ".", }}, { KEY_EN_NUMPAD_ENTER, { "Ent" , "\xE2\x86\xB5" }}, // ↵ ⏎ { KEY_EN_NUMPAD_0, { "0" , "0", }}, { KEY_EN_NUMPAD_1, { "1" , "1", }}, { KEY_EN_NUMPAD_2, { "2" , "2", }}, { KEY_EN_NUMPAD_3, { "3" , "3", }}, { KEY_EN_NUMPAD_4, { "4" , "4", }}, { KEY_EN_NUMPAD_5, { "5" , "5", }}, { KEY_EN_NUMPAD_6, { "6" , "6", }}, { KEY_EN_NUMPAD_7, { "7" , "7", }}, { KEY_EN_NUMPAD_8, { "8" , "8", }}, { KEY_EN_NUMPAD_9, { "9" , "9", }}, { KEY_EN_MEDIA_PLAY_PAUSE, { "Ply" , "\xE2\x8F\xAF", }}, // ⏯ { KEY_EN_MEDIA_PREVIOUS, { "Prv" , "\xE2\x8F\xAE", }}, // ⏮ { KEY_EN_MEDIA_NEXT, { "Nxt" , "\xE2\x8F\xAD", }}, // ⏭ { KEY_EN_MEDIA_STOP, { "Stp" , "\xE2\x8F\xB9", }}, // ⏹ { KEY_EN_MEDIA_MUTE, { "Mte" , "\xF0\x9F\x94\x87", }}, // 🔇 { KEY_EN_MEDIA_VOLUME_DOWN, { "Vl-" , "\xF0\x9F\x94\x88", }}, // 🔉 { KEY_EN_MEDIA_VOLUME_UP, { "Vl+" , "\xF0\x9F\x94\x89", }}, // 🔊 { KEY_EN_POWER, { "Pwr" , "\xE2\x8F\xBB", }}, // ⏻ { KEY_JP_RO, { "_" , "_", }}, { KEY_JP_EJ, { "E/J" , "E/J", }}, { KEY_JP_ZENKAKU, { "Zen" , "\xE5\x8D\x8A\xE8" "\xA7\x92\x2F\xE5" "\x85\xA8\xE8\xA7" "\x92", }}, // 半角/全角 { KEY_JP_KANA, { "Kana" , "\xE3\x81\x8B\xE3" "\x81\xAA", }}, // かな { KEY_JP_HENKAN, { "Hnkn" , "\xE5\xA4\x89\xE6" "\x8F\x9B", }}, // 変換 { KEY_JP_MUHENKAN, { "MuKn" , "\xE7\x84\xA1\xE5" "\xA4\x89\xE6\x8F" "\x9B", }}, // 無変換 { KEY_JP_YEN, { "Yen" , "\xC2\xA5", }}, // ¥ { KEY_JP_AT, { "@" , "@", }}, { KEY_JP_CHEVRON, { "^" , "^", }}, { KEY_JP_COLON, { ":" , ":", }}, { KEY_KR_HAN, { "Hayg" , "\xED\x95\x9C\x2F" "\xEC\x98\x81", }}, // 한/영 { KEY_KR_HANJA, { "Haja" , "\xED\x95\x9C\xEC" "\x9E\x90", }}, // 한자 { KEY_NORD_AAL, { "Å" , "\xC3\x85", }}, // Å { KEY_NORD_A_OE, { "Ä Ø" , "\xC3\x84\x20\xC3" "\x98" }}, // Ä Ø { KEY_NORD_O_AE, { "Ö Æ" , "\xC3\x96\x20\xC3" "\x86" }}, // Ö Æ { KEY_NORD_HALF, { "§ ½" , "\xC2\xA7\x20\xC2" "\xBD" }}, // § ½ { KEY_NORD_HYPHEN, { "- _" , "- _" }}, { KEY_NORD_PLUS_QUESTION, { "+ ?" , "+ ?", }}, { KEY_NORD_ACUTE_GRAVE, { "´ `" , "\xC2\xB4\x20\x60", }}, // ´ ` { KEY_NORD_DOTS_CARET, { "¨ ^" , "\xC2\xA8\x20\x5E", }}, // ¨ ^ { KEY_NORD_QUOTE, { "' *" , "' *", }}, { KEY_NORD_ANGLE_BRACKET, { "< >" , "< >" }}, { KEY_DE_ESZETT, { "ß" , "\xc3\x9F", }}, { KEY_DE_DIAERESIS_A, { "Ä" , "\xC3\x84", }}, { KEY_DE_DIAERESIS_O, { "Ö" , "\xC3\x96", }}, { KEY_DE_DIAERESIS_U, { "Ü" , "\xC3\x9C" }}, { KEY_FR_SUPER_2, { "²" , "\xc2\xb2" }}, { KEY_FR_AMPERSAND, { "&" , "&" }}, { KEY_FR_ACUTE_E, { "é" , "\xc3\xa9" }}, { KEY_FR_DOUBLEQUOTE, { "\"" , "\"" }}, { KEY_FR_LEFT_PARENTHESIS, { "(" , "(" }}, { KEY_FR_GRAVE_E, { "è" , "\xc3\xa8" }}, { KEY_FR_UNDERSCORE, { "_" , "_" }}, { KEY_FR_CEDILLA_C, { "ç" , "\xc3\xa7" }}, { KEY_FR_GRAVE_A, { "à" , "\xc3\xa0" }}, { KEY_FR_RIGHT_PARENTHESIS, { ")" , ")" }}, { KEY_FR_DOLLAR, { "$" , "$" }}, { KEY_FR_GRAVE_U, { "ù" , "\xc3\xb9" }}, { KEY_FR_ASTERIX, { "*" , "*" }}, { KEY_FR_EXCLAIMATION, { "!" , "!" }}, { KEY_ES_OPEN_QUESTION_MARK,{ "¿" , "¡" }}, { KEY_ES_TILDE, { "´" , "¨" }}, { KEY_ES_ENIE, { "ñ" , "Ñ" }}, { KEY_BR_TILDE, { "~" , "~" }} }; void DeviceView::setController(RGBController * controller_ptr) { /*-----------------------------------------------------*\ | Store the controller pointer | \*-----------------------------------------------------*/ controller = controller_ptr; } void DeviceView::InitDeviceView() { /*-----------------------------------------------------*\ | Set the size of the selection flags vector | \*-----------------------------------------------------*/ selectionFlags.resize((int)controller->leds.size()); /*-----------------------------------------------------*\ | Set the size of the zone and LED position vectors | \*-----------------------------------------------------*/ zone_pos.resize(controller->zones.size()); led_pos.resize(controller->leds.size()); led_labels.resize(controller->leds.size()); /*-----------------------------------------------------*\ | Process position and size for zones | \*-----------------------------------------------------*/ unsigned int maxWidth = 0; unsigned int segment_count = 0; float totalHeight = 0.0f; /*-----------------------------------------------------*\ | Get device view settings | \*-----------------------------------------------------*/ SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager(); std::string ui_string = "UserInterface"; json ui_settings; bool disable_expansion = false; ui_settings = settings_manager->GetSettings(ui_string); if(ui_settings.contains("disable_key_expansion")) { disable_expansion = ui_settings["disable_key_expansion"]; } /*-----------------------------------------------------*\ | Determine the total height (in LEDs) of all zones | \*-----------------------------------------------------*/ for(std::size_t zone_idx = 0; zone_idx < controller->zones.size(); zone_idx++) { /*-----------------------------------------------------*\ | For matrix zones, use matrix height from the map | \*-----------------------------------------------------*/ if((controller->zones[zone_idx].type == ZONE_TYPE_MATRIX) && (controller->zones[zone_idx].matrix_map)) { totalHeight += controller->zones[zone_idx].matrix_map->height; zone_pos[zone_idx].matrix_w = controller->zones[zone_idx].matrix_map->width; } /*-----------------------------------------------------*\ | For all other zones, compute the height including | | wrap-around | \*-----------------------------------------------------*/ else if(controller->zones[zone_idx].segments.size() > 0) { for(std::size_t segment_idx = 0; segment_idx < controller->zones[zone_idx].segments.size(); segment_idx++) { unsigned int count = controller->zones[zone_idx].segments[segment_idx].leds_count; zone_pos[zone_idx].matrix_w = std::min(count, (unsigned int)MAX_COLS); totalHeight += (count / MAX_COLS) + ((count % MAX_COLS) > 0); segment_count++; } } else { unsigned int count = controller->GetLEDsInZone((unsigned int)zone_idx); zone_pos[zone_idx].matrix_w = std::min(count, (unsigned int)MAX_COLS); totalHeight += (count / MAX_COLS) + ((count % MAX_COLS) > 0); } /*-----------------------------------------------------*\ | Determine the maximum width (in LEDs) in the view | \*-----------------------------------------------------*/ if(zone_pos[zone_idx].matrix_w > maxWidth) { maxWidth = zone_pos[zone_idx].matrix_w; } } segment_pos.resize(segment_count); /*-----------------------------------------------------*\ | Add some space for zone names and padding | \*-----------------------------------------------------*/ totalHeight += controller->zones.size() * PAD_ZONE; totalHeight += segment_count * PAD_SEGMENT; float current_y = 0; // We will be descending, placing each zone one unit below the previous one matrix_h = totalHeight; segment_count = 0; for(std::size_t zone_idx = 0; zone_idx < controller->zones.size(); zone_idx++) { /*-----------------------------------------------------*\ | Calculate zone label position and size | \*-----------------------------------------------------*/ zone_pos[zone_idx].matrix_x = (maxWidth - zone_pos[zone_idx].matrix_w) / 2.0f; zone_pos[zone_idx].matrix_y = current_y + SIZE_TEXT; zone_pos[zone_idx].matrix_h = SIZE_TEXT - PAD_TEXT; current_y += PAD_ZONE; /*-----------------------------------------------------*\ | Calculate LEDs position and size for zone | \*-----------------------------------------------------*/ if((controller->zones[zone_idx].type == ZONE_TYPE_MATRIX) && (controller->zones[zone_idx].matrix_map)) { matrix_map_type * map = controller->zones[zone_idx].matrix_map; for(unsigned int led_x = 0; led_x < map->width; led_x++) { for(unsigned int led_y = 0; led_y < map->height; led_y++) { unsigned int map_idx = led_y * map->width + led_x; unsigned int color_idx = map->map[map_idx] + controller->zones[zone_idx].start_idx; if(map->map[map_idx] != 0xFFFFFFFF && color_idx < led_pos.size()) { led_pos[color_idx].matrix_x = (zone_pos[zone_idx].matrix_x + led_x + PAD_LED); led_pos[color_idx].matrix_y = current_y + (led_y + PAD_LED); /*-----------------------------------------------------*\ | LED is a 1x1 square, minus padding on all sides | \*-----------------------------------------------------*/ led_pos[color_idx].matrix_w = (1.0f - (2.0f * PAD_LED)); led_pos[color_idx].matrix_h = (1.0f - (2.0f * PAD_LED)); if(!disable_expansion) { /*-----------------------------------------------------*\ | Expand large keys to fill empty spaces in matrix, if | | possible. Large keys can fill left, down, up, or wide| | Fill Left: | | Tab | | Caps Lock | | Left Shift | | Right Shift | | Backspace | | Number Pad 0 | | | | Fill Up or Down: | | Number Pad Enter | | Number Pad + | | | | Fill Wide: | | Space | \*-----------------------------------------------------*/ if(led_x < map->width - 1 && map->map[map_idx + 1] == 0xFFFFFFFF) { if( ( controller->GetLEDName(color_idx) == KEY_EN_TAB ) || ( controller->GetLEDName(color_idx) == KEY_EN_CAPS_LOCK ) || ( controller->GetLEDName(color_idx) == KEY_EN_LEFT_SHIFT ) || ( controller->GetLEDName(color_idx) == KEY_EN_RIGHT_SHIFT) || ( controller->GetLEDName(color_idx) == KEY_EN_BACKSPACE ) || ( controller->GetLEDName(color_idx) == KEY_EN_NUMPAD_0 ) ) { led_pos[color_idx].matrix_w += 1.0f; } } if( ( controller->GetLEDName(color_idx) == KEY_EN_NUMPAD_ENTER ) || ( controller->GetLEDName(color_idx) == KEY_EN_NUMPAD_PLUS ) ) { if(led_y < map->height - 1 && map->map[map_idx + map->width] == 0xFFFFFFFF) { led_pos[color_idx].matrix_h += 1.0f; } /* TODO: check if there isn't another widened key above */ else if(led_y > 0 && map->map[map_idx - map->width] == 0xFFFFFFFF) { led_pos[color_idx].matrix_y -= 1.0f; led_pos[color_idx].matrix_h += 1.0f; } } else if(controller->GetLEDName(color_idx) == KEY_EN_SPACE) { for(unsigned int map_idx2 = map_idx - 1; map_idx2 > led_y * map->width && map->map[map_idx2] == 0xFFFFFFFF; map_idx2--) { led_pos[color_idx].matrix_x -= 1.0f; led_pos[color_idx].matrix_w += 1.0f; } for(unsigned int map_idx2 = map_idx + 1; map_idx2 < (led_y + 1) * map->width && map->map[map_idx2] == 0xFFFFFFFF; map_idx2++) { led_pos[color_idx].matrix_w += 1.0f; } } } } } } current_y += map->height; } else if(controller->zones[zone_idx].segments.size() > 0) { for(std::size_t segment_idx = 0; segment_idx < controller->zones[zone_idx].segments.size(); segment_idx++) { /*-----------------------------------------------------*\ | Calculate segment label position and size | \*-----------------------------------------------------*/ segment_pos[segment_count].matrix_x = (maxWidth - zone_pos[zone_idx].matrix_w) / 2.0f; segment_pos[segment_count].matrix_y = current_y + SIZE_TEXT; segment_pos[segment_count].matrix_w = zone_pos[zone_idx].matrix_w; segment_pos[segment_count].matrix_h = SIZE_TEXT - PAD_TEXT; current_y += PAD_SEGMENT; segment_count++; /*-----------------------------------------------------*\ | Calculate LED box positions for segmented zones | \*-----------------------------------------------------*/ unsigned int leds_count = controller->zones[zone_idx].segments[segment_idx].leds_count; for(unsigned int led_idx = 0; led_idx < leds_count; led_idx++) { unsigned int led_pos_idx = controller->zones[zone_idx].start_idx + controller->zones[zone_idx].segments[segment_idx].start_idx + led_idx; led_pos[led_pos_idx].matrix_x = zone_pos[zone_idx].matrix_x + ((led_idx % MAX_COLS) + PAD_LED); led_pos[led_pos_idx].matrix_y = current_y + ((led_idx / MAX_COLS) + PAD_LED); /*-----------------------------------------------------*\ | LED is a 1x1 square, minus padding on all sides | \*-----------------------------------------------------*/ led_pos[led_pos_idx].matrix_w = (1.0f - (2.0f * PAD_LED)); led_pos[led_pos_idx].matrix_h = (1.0f - (2.0f * PAD_LED)); } current_y += (leds_count / MAX_COLS) + ((leds_count % MAX_COLS) > 0); } } else { /*-----------------------------------------------------*\ | Calculate LED box positions for single/linear zones | \*-----------------------------------------------------*/ unsigned int leds_count = controller->GetLEDsInZone((unsigned int)zone_idx); for(unsigned int led_idx = 0; led_idx < leds_count; led_idx++) { unsigned int led_pos_idx = controller->zones[zone_idx].start_idx + led_idx; led_pos[led_pos_idx].matrix_x = zone_pos[zone_idx].matrix_x + ((led_idx % MAX_COLS) + PAD_LED); led_pos[led_pos_idx].matrix_y = current_y + ((led_idx / MAX_COLS) + PAD_LED); /*-----------------------------------------------------*\ | LED is a 1x1 square, minus padding on all sides | \*-----------------------------------------------------*/ led_pos[led_pos_idx].matrix_w = (1.0f - (2.0f * PAD_LED)); led_pos[led_pos_idx].matrix_h = (1.0f - (2.0f * PAD_LED)); } current_y += (leds_count / MAX_COLS) + ((leds_count % MAX_COLS) > 0); } } /*-----------------------------------------------------*\ | Update LED labels | \*-----------------------------------------------------*/ for(std::size_t led_idx = 0; led_idx < controller->leds.size(); led_idx++) { std::map::const_iterator it = led_label_lookup.find(controller->GetLEDName((unsigned int)led_idx)); if(it != led_label_lookup.end()) { led_labels[led_idx] = it->second.label_utf8; } else if(numerical_labels) { led_labels[led_idx] = QString::number(led_idx); } } /*-----------------------------------------------------*\ | Scale the zones, segments, and LEDs | | | | Atom is the width of a single square; if the whole | | thing becomes too tall, we ignore it and let the view | | widget take care of it | \*-----------------------------------------------------*/ float atom = 1.0f / maxWidth; for(std::size_t zone_idx = 0; zone_idx < zone_pos.size(); zone_idx++) { zone_pos[zone_idx].matrix_x *= atom; zone_pos[zone_idx].matrix_y *= atom; zone_pos[zone_idx].matrix_w *= atom; zone_pos[zone_idx].matrix_h *= atom; } for(std::size_t segment_idx = 0; segment_idx < segment_pos.size(); segment_idx++) { segment_pos[segment_idx].matrix_x *= atom; segment_pos[segment_idx].matrix_y *= atom; segment_pos[segment_idx].matrix_w *= atom; segment_pos[segment_idx].matrix_h *= atom; } for(std::size_t led_idx = 0; led_idx < led_pos.size(); led_idx++) { led_pos[led_idx].matrix_x *= atom; led_pos[led_idx].matrix_y *= atom; led_pos[led_idx].matrix_w *= atom; led_pos[led_idx].matrix_h *= atom; } matrix_h *= atom; /*-----------------------------------------------------*\ | Update cached size and offset | \*-----------------------------------------------------*/ size = width(); offset_x = 0; if(height() < size * matrix_h) { size = height() / matrix_h; offset_x = (width() - size) / 2; } } void DeviceView::setNumericalLabels(bool enable) { numerical_labels = enable; } void DeviceView::setPerLED(bool per_led_mode) { per_led = per_led_mode; update(); } QSize DeviceView::sizeHint () const { return QSize(height() - 1, height() - 1); } QSize DeviceView::minimumSizeHint () const { return initSize; } void DeviceView::mousePressEvent(QMouseEvent *event) { if(per_led) { ctrlDown = event->modifiers().testFlag(Qt::ControlModifier); mouseDown = true; mouseMoved = false; if(ctrlDown) { previousFlags = selectionFlags; previousSelection = selectedLeds; } /*-----------------------------------------------------*\ | It's okay if the size becomes negative | \*-----------------------------------------------------*/ #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) selectionRect.setLeft(event->position().x()); selectionRect.setTop(event->position().y()); selectionRect.setRight(event->position().x()); selectionRect.setBottom(event->position().y()); #else selectionRect.setLeft(event->x()); selectionRect.setTop(event->y()); selectionRect.setRight(event->x()); selectionRect.setBottom(event->y()); #endif updateSelection(); update(); } } void DeviceView::mouseMoveEvent(QMouseEvent *event) { if(per_led) { lastMousePos = event->pos(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) selectionRect.setRight(event->position().x()); selectionRect.setBottom(event->position().y()); #else selectionRect.setRight(event->x()); selectionRect.setBottom(event->y()); #endif if(mouseDown) { mouseMoved = true; ctrlDown = event->modifiers().testFlag(Qt::ControlModifier); /*-----------------------------------------------------*\ | Clear the previous selection in case ctrl is released | \*-----------------------------------------------------*/ if(!ctrlDown) { previousSelection.clear(); previousFlags.clear(); previousFlags.resize((int)controller->leds.size()); } updateSelection(); } update(); } } void DeviceView::mouseReleaseEvent(QMouseEvent* event) { if(per_led) { selectionRect = selectionRect.normalized(); mouseDown = false; /*-----------------------------------------------------*\ | Check if the user clicked a zone name & select it | \*-----------------------------------------------------*/ if(!mouseMoved) { int size = width(); int offset_x = 0; if(height() < size * matrix_h) { size = height() / matrix_h; offset_x = (width() - size) / 2; } unsigned int segment_count = 0; for(unsigned int zone_idx = 0; zone_idx < controller->zones.size(); zone_idx++) { int posx = zone_pos[zone_idx].matrix_x * size + offset_x + 12; int posy = zone_pos[zone_idx].matrix_y * size; int posw = zone_pos[zone_idx].matrix_w * size; int posh = zone_pos[zone_idx].matrix_h * size; QRect rect = {posx, posy, posw, posh}; if(rect.contains(event->pos())) { selectZone(zone_idx, ctrlDown); } for(unsigned int segment_idx = 0; segment_idx < controller->zones[zone_idx].segments.size(); segment_idx++) { posx = segment_pos[segment_count].matrix_x * size + offset_x + 12; posy = segment_pos[segment_count].matrix_y * size; posw = segment_pos[segment_count].matrix_w * size; posh = segment_pos[segment_count].matrix_h * size; segment_count++; rect = {posx, posy, posw, posh}; if(rect.contains(event->pos())) { selectSegment(zone_idx, segment_idx, ctrlDown); } } } } update(); } } void DeviceView::resizeEvent(QResizeEvent* /*event*/) { size = width(); offset_x = 0; if(height() < size * matrix_h) { size = height() / matrix_h; offset_x = (width() - size) / 2; } update(); } void DeviceView::paintEvent(QPaintEvent* /* event */) { QPainter painter(this); QFont font = painter.font(); /*-----------------------------------------------------*\ | If Device View is hidden, don't paint | \*-----------------------------------------------------*/ if(isHidden() || !per_led) { return; } /*-----------------------------------------------------*\ | If controller has resized, reinitialize local data | \*-----------------------------------------------------*/ if(controller->leds.size() != led_pos.size()) { InitDeviceView(); } /*-----------------------------------------------------*\ | If segments have resized, reinitialize local data | \*-----------------------------------------------------*/ unsigned int segments = 0; for(std::size_t zone_idx = 0; zone_idx < controller->zones.size(); zone_idx++) { for(std::size_t segment_idx = 0; segment_idx < controller->zones[zone_idx].segments.size(); segment_idx++) { segments++; } } if(segments != segment_pos.size()) { InitDeviceView(); } /*-----------------------------------------------------*\ | LED rectangles | \*-----------------------------------------------------*/ for(unsigned int led_idx = 0; led_idx < controller->leds.size(); led_idx++) { int posx = led_pos[led_idx].matrix_x * size + offset_x; int posy = led_pos[led_idx].matrix_y * size; int posw = led_pos[led_idx].matrix_w * size; int posh = led_pos[led_idx].matrix_h * size; QRect rect = {posx, posy, posw, posh}; /*-----------------------------------------------------*\ | Fill color | \*-----------------------------------------------------*/ QColor currentColor = QColor::fromRgb( RGBGetRValue(controller->colors[led_idx]), RGBGetGValue(controller->colors[led_idx]), RGBGetBValue(controller->colors[led_idx])); painter.setBrush(currentColor); /*-----------------------------------------------------*\ | Border color | \*-----------------------------------------------------*/ if(selectionFlags[led_idx]) { painter.setPen(palette().highlight().color()); } else { painter.setPen(palette().dark().color()); } painter.drawRect(rect); /*-----------------------------------------------------*\ | Label | | Set the font color so that the text is visible | \*-----------------------------------------------------*/ font.setPixelSize(std::max(1, posh / 2)); painter.setFont(font); unsigned int luma = (unsigned int)(0.2126f * currentColor.red() + 0.7152f * currentColor.green() + 0.0722f * currentColor.blue()); if(luma > 127) { painter.setPen(Qt::black); } else { painter.setPen(Qt::white); } painter.drawText(rect, Qt::AlignVCenter | Qt::AlignHCenter, QString(led_labels[led_idx])); } font.setPixelSize(12); painter.setFont(font); /*-----------------------------------------------------*\ | Zone and Segment names | \*-----------------------------------------------------*/ unsigned int segment_count = 0; for(std::size_t zone_idx = 0; zone_idx < controller->zones.size(); zone_idx++) { int posx = zone_pos[zone_idx].matrix_x * size + offset_x; int posy = zone_pos[zone_idx].matrix_y * size; int posw = zone_pos[zone_idx].matrix_w * size; int posh = zone_pos[zone_idx].matrix_h * size; QRect rect = {posx, posy, posw, posh}; if(rect.contains(lastMousePos) && (!mouseDown || !mouseMoved)) { painter.setPen(palette().highlight().color()); } else { painter.setPen(palette().windowText().color()); } painter.drawText(posx, posy + posh, QString(controller->GetZoneName((unsigned int)zone_idx).c_str())); for(std::size_t segment_idx = 0; segment_idx < controller->zones[zone_idx].segments.size(); segment_idx++) { posx = segment_pos[segment_count].matrix_x * size + offset_x; posy = segment_pos[segment_count].matrix_y * size; posw = segment_pos[segment_count].matrix_w * size; posh = segment_pos[segment_count].matrix_h * size; segment_count++; rect = {posx, posy, posw, posh}; if(rect.contains(lastMousePos) && (!mouseDown || !mouseMoved)) { painter.setPen(palette().highlight().color()); } else { painter.setPen(palette().windowText().color()); } painter.drawText(posx, posy + posh, QString(controller->zones[zone_idx].segments[segment_idx].name.c_str())); } } /*-----------------------------------------------------*\ | Selection area | \*-----------------------------------------------------*/ if(mouseDown) { QRect rect = selectionRect.normalized(); QColor color = palette().highlight().color(); color.setAlpha(63); painter.fillRect(rect, color); color.setAlpha(127); painter.setBrush(color); painter.drawRect(rect); } } void DeviceView::updateSelection() { selectedLeds.clear(); selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); QRect sel = selectionRect.normalized(); std::vector& leds = controller->leds; for(unsigned int led_idx = 0; led_idx < leds.size(); led_idx++) { /*-----------------------------------------------------*\ | Check intersection | \*-----------------------------------------------------*/ int posx = led_pos[led_idx].matrix_x * size + offset_x; int posy = led_pos[led_idx].matrix_y * size; int posw = led_pos[led_idx].matrix_w * size; int posh = led_pos[led_idx].matrix_h * size; QRect rect = {posx, posy, posw, posh}; selectionFlags[led_idx] = 0; if(sel.intersects(rect)) { selectionFlags[led_idx] = 1; } if(ctrlDown) { selectionFlags[led_idx] ^= previousFlags[led_idx]; } if(selectionFlags[led_idx]) { selectedLeds.push_back(led_idx); } } update(); /*-----------------------------------------------------*\ | Send selection changed signal | \*-----------------------------------------------------*/ emit selectionChanged(selectedLeds); } bool DeviceView::selectLed(int target) { if(target < 0 || size_t(target) >= controller->leds.size()) { return false; } selectedLeds.resize(1); selectedLeds[0] = target; selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); selectionFlags[target] = 1; update(); /*-----------------------------------------------------*\ | Send selection changed signal | \*-----------------------------------------------------*/ emit selectionChanged(selectedLeds); return true; } bool DeviceView::selectLeds(QVector target) { for(int item: target) { if(item < 0 || size_t(item) >= controller->leds.size()) { return false; } } selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); for(int item: target) { selectionFlags[item] = 1; } //selectedLeds = target; /*-----------------------------------------------------*\ | Filter out duplicate items | \*-----------------------------------------------------*/ selectedLeds.clear(); for(int i = 0; i < selectionFlags.size(); ++i) { if(selectionFlags[i]) { selectedLeds.push_back(i); } } update(); /*-----------------------------------------------------*\ | Send selection changed signal | \*-----------------------------------------------------*/ emit selectionChanged(selectedLeds); return true; } bool DeviceView::selectSegment(int zone, int segment, bool add) { if(zone < 0 || size_t(zone) >= controller->zones.size()) { return false; } if(segment < 0 || size_t(segment) >= controller->zones[zone].segments.size()) { return false; } if(!add) { selectedLeds.clear(); selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); } int zoneStart = controller->zones[zone].start_idx; int segStart = controller->zones[zone].segments[segment].start_idx; for(int led_idx = 0; led_idx < (int)controller->zones[zone].segments[segment].leds_count; led_idx++) { if(!selectionFlags[zoneStart + segStart + led_idx]) { selectedLeds.push_back(zoneStart + segStart + led_idx); selectionFlags[zoneStart + segStart + led_idx] = 1; } } update(); /*-----------------------------------------------------*\ | Send selection changed signal | \*-----------------------------------------------------*/ emit selectionChanged(selectedLeds); return true; } bool DeviceView::selectZone(int zone, bool add) { if(zone < 0 || size_t(zone) >= controller->zones.size()) { return false; } if(!add) { selectedLeds.clear(); selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); } int zoneStart = controller->zones[zone].start_idx; for(std::size_t led_idx = 0; led_idx < controller->GetLEDsInZone(zone); led_idx++) { if(!selectionFlags[zoneStart + (int)led_idx]) { selectedLeds.push_back(zoneStart + (int)led_idx); selectionFlags[zoneStart + (int)led_idx] = 1; } } update(); /*-----------------------------------------------------*\ | Send selection changed signal | \*-----------------------------------------------------*/ emit selectionChanged(selectedLeds); return true; } void DeviceView::clearSelection() { /*-----------------------------------------------------*\ | Same as selecting the entire device | \*-----------------------------------------------------*/ selectedLeds.clear(); selectionFlags.clear(); selectionFlags.resize((int)controller->leds.size()); } void DeviceView::setSelectionColor(RGBColor color) { if(selectedLeds.isEmpty()) { controller->SetAllLEDs(color); } else { for(int led_idx: selectedLeds) { controller->SetLED(led_idx, color); } } controller->UpdateLEDs(); update(); }