diff --git a/aboutbox.cpp b/aboutbox.cpp index 4c3549e..6fa2f1f 100644 --- a/aboutbox.cpp +++ b/aboutbox.cpp @@ -15,8 +15,7 @@ aboutbox::aboutbox(QWidget *parent) : ui->topText->setText("wfview version " + QString(WFVIEW_VERSION)); QString head = QString(""); - QString copyright = QString("Copyright 2017-2022 Elliott H. Liggett, W6EL. All rights reserved.
wfview source code is licensed under the GNU GPLv3."); - QString nacode = QString("

Networking, audio, rigctl server, and much more written by Phil Taylor, M0VSE"); + QString copyright = QString("Copyright 2017-2023 Elliott H. Liggett, W6EL and Phil E. Taylor, M0VSE. All rights reserved.
wfview source code is licensed under the GNU GPLv3."); QString scm = QString("

Source code and issues managed by Roeland Jansen, PA3MET"); QString doctest = QString("

Testing and development mentorship from Jim Nijkamp, PA8E."); @@ -86,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."); // String it all together: - QString aboutText = head + copyright + "\n" + nacode + "\n" + scm + "\n" + doctest + dedication + wfviewcommunityack; + QString aboutText = head + copyright + "\n" + "\n" + scm + "\n" + doctest + dedication + wfviewcommunityack; aboutText.append(website + "\n" + donate + "\n"+ docs + support + contact +"\n"); aboutText.append("\n" + ssCredit + "\n" + rsCredit + "\n"); diff --git a/controllersetup.cpp b/controllersetup.cpp index 466825b..8689e69 100644 --- a/controllersetup.cpp +++ b/controllersetup.cpp @@ -20,8 +20,19 @@ controllerSetup::~controllerSetup() qInfo(logUsbControl()) << "Deleting controllerSetup() window"; delete noControllersText; delete updateDialog; - delete ui; + delete ui; // Will delete all content in all tabs + tabs.clear(); + // Step through ALL buttons and knobs setting their text to NULL (just in case) + for (auto b = buttons->begin(); b != buttons->end(); b++) + { + b->text=Q_NULLPTR; + b->bgRect=Q_NULLPTR; + } + for (auto k= knobs->begin(); k != knobs->end(); k++) + { + k->text=Q_NULLPTR; + } } void controllerSetup::hideEvent(QHideEvent *event) @@ -33,21 +44,20 @@ void controllerSetup::hideEvent(QHideEvent *event) void controllerSetup::on_tabWidget_currentChanged(int index) { + // We may get the indexChanged event before the tabWidget has been initialized if (ui->tabWidget->widget(index) != Q_NULLPTR) { - QWidget* widget = ui->tabWidget->widget(index); QString path = ui->tabWidget->widget(index)->objectName(); auto tab = tabs.find(path); if (tab != tabs.end()) { - tabContent* c = tab.value(); this->resize(this->sizeHint()); - //this->resize(c->bgImage->boundingRect().width() + 20, c->bgImage->boundingRect().height() + 150); } } if (updateDialog != Q_NULLPTR) + { updateDialog->hide(); - + } } @@ -468,180 +478,193 @@ void controllerSetup::newDevice(USBDEVICE* dev) tabContent* c = new tabContent(); - c->tab.setObjectName(dev->path); - ui->tabWidget->addTab(&c->tab,dev->product); - c->tab.setLayout(&c->mainLayout); - c->mainLayout.addLayout(&c->topLayout); - c->mainLayout.addWidget(&c->widget); + c->tab = new QWidget(); + c->widget = new QWidget(); - c->widget.setLayout(&c->layout); + c->tab->setObjectName(dev->path); + ui->tabWidget->addTab(c->tab,dev->product); + // Create the different layouts required + c->mainLayout = new QVBoxLayout(c->tab); + c->layout = new QVBoxLayout(); + c->topLayout = new QHBoxLayout(); + c->sensLayout = new QHBoxLayout(); + c->grid = new QGridLayout(); - c->topLayout.addWidget(&c->disabled); - c->disabled.setText("Disable"); - connect(&c->disabled, qOverload(&QCheckBox::clicked), - [dev,this,c](bool checked) { this->disableClicked(dev,checked,&c->widget); }); - c->disabled.setChecked(dev->disabled); + c->mainLayout->addLayout(c->topLayout); + c->mainLayout->addWidget(c->widget); + c->widget->setLayout(c->layout); + c->disabled = new QCheckBox("Disable"); + c->topLayout->addWidget(c->disabled); + connect(c->disabled, qOverload(&QCheckBox::clicked), + [dev,this,c](bool checked) { this->disableClicked(dev,checked,c->widget); }); + c->disabled->setChecked(dev->disabled); + + c->message = new QLabel(); if (dev->connected) { - c->message.setStyleSheet("QLabel { color : green; }"); - c->message.setText("Connected"); + c->message->setStyleSheet("QLabel { color : green; }"); + c->message->setText("Connected"); } else { - c->message.setStyleSheet("QLabel { color : red; }"); - c->message.setText("Not Connected"); + c->message->setStyleSheet("QLabel { color : red; }"); + c->message->setText("Not Connected"); } + c->topLayout->addWidget(c->message); + c->message->setAlignment(Qt::AlignRight); - c->topLayout.addWidget(&c->message); - c->message.setAlignment(Qt::AlignRight); + c->view = new QGraphicsView(); + c->layout->addWidget(c->view); - c->layout.addWidget(&c->view); - - - c->layout.addLayout(&c->sensLayout); - c->sensLabel.setText("Sensitivity:"); - c->sensLayout.addWidget(&c->sensLabel); - c->sens.setMinimum(1); - c->sens.setMaximum(21); - c->sens.setOrientation(Qt::Horizontal); - c->sens.setInvertedAppearance(true); - c->sensLayout.addWidget(&c->sens); - c->sens.setValue(dev->sensitivity); - connect(&c->sens, &QSlider::valueChanged, + c->layout->addLayout(c->sensLayout); + c->sensLabel = new QLabel("Sensitivity"); + c->sensLayout->addWidget(c->sensLabel); + c->sens = new QSlider(Qt::Horizontal); + c->sens->setMinimum(1); + c->sens->setMaximum(21); + c->sens->setInvertedAppearance(true); + c->sensLayout->addWidget(c->sens); + c->sens->setValue(dev->sensitivity); + connect(c->sens, &QSlider::valueChanged, [dev,this](int val) { this->sensitivityMoved(dev,val); }); - c->sensLayout.addStretch(0); - c->pageLabel.setText("Page:"); - c->sensLayout.addWidget(&c->pageLabel); - c->page.setObjectName("Page SpinBox"); - c->page.setValue(1); - c->page.setMinimum(1); - c->page.setMaximum(dev->pages); - c->page.setToolTip("Select current page to edit"); - c->sensLayout.addWidget(&c->page); + c->sensLayout->addStretch(0); - dev->pageSpin = &c->page; + c->pageLabel = new QLabel("Page:"); + c->sensLayout->addWidget(c->pageLabel); + c->page = new QSpinBox(); + c->page->setValue(1); + c->page->setMinimum(1); + c->page->setMaximum(dev->pages); + c->page->setToolTip("Select current page to edit"); + c->sensLayout->addWidget(c->page); + dev->pageSpin = c->page; + c->image = new QImage(); switch (dev->type.model) { case shuttleXpress: - c->image.load(":/resources/shuttlexpress.png"); + c->image->load(":/resources/shuttlexpress.png"); break; case shuttlePro2: - c->image.load(":/resources/shuttlepro.png"); + c->image->load(":/resources/shuttlepro.png"); break; case RC28: - c->image.load(":/resources/rc28.png"); + c->image->load(":/resources/rc28.png"); break; case xBoxGamepad: - c->image.load(":/resources/xbox.png"); + c->image->load(":/resources/xbox.png"); break; case eCoderPlus: - c->image.load(":/resources/ecoder.png"); + c->image->load(":/resources/ecoder.png"); break; case QuickKeys: - c->image.load(":/resources/quickkeys.png"); + c->image->load(":/resources/quickkeys.png"); break; case StreamDeckOriginal: case StreamDeckOriginalV2: case StreamDeckOriginalMK2: - c->image.load(":/resources/streamdeck.png"); + c->image->load(":/resources/streamdeck.png"); break; case StreamDeckMini: case StreamDeckMiniV2: - c->image.load(":/resources/streamdeckmini.png"); + c->image->load(":/resources/streamdeckmini.png"); break; case StreamDeckXL: case StreamDeckXLV2: - c->image.load(":/resources/streamdeckxl.png"); + c->image->load(":/resources/streamdeckxl.png"); break; case StreamDeckPlus: - c->image.load(":/resources/streamdeckplus.png"); + c->image->load(":/resources/streamdeckplus.png"); break; case StreamDeckPedal: - c->image.load(":/resources/streamdeckpedal.png"); + c->image->load(":/resources/streamdeckpedal.png"); break; default: this->adjustSize(); break; } - c->bgImage = new QGraphicsPixmapItem(QPixmap::fromImage(c->image)); - c->view.setMinimumSize(c->bgImage->boundingRect().width() + 2, c->bgImage->boundingRect().height() + 2); + c->bgImage = new QGraphicsPixmapItem(QPixmap::fromImage(*c->image)); + c->view->setMinimumSize(c->bgImage->boundingRect().width() + 2, c->bgImage->boundingRect().height() + 2); ui->tabWidget->show(); - c->scene = new controllerScene(); - c->view.setScene(c->scene); + c->view->setScene(c->scene); connect(c->scene, SIGNAL(showMenu(controllerScene*,QPoint)), this, SLOT(showMenu(controllerScene*,QPoint))); c->scene->addItem(c->bgImage); - c->layout.addLayout(&c->grid); - c->brightLabel.setText("Brightness"); - c->grid.addWidget(&c->brightLabel,0,0); - c->brightness.addItem("Off"); - c->brightness.addItem("Low"); - c->brightness.addItem("Medium"); - c->brightness.addItem("High"); - c->brightness.setCurrentIndex(dev->brightness); - c->grid.addWidget(&c->brightness,1,0); - connect(&c->brightness, qOverload(&QComboBox::currentIndexChanged), + c->layout->addLayout(c->grid); + + c->brightLabel = new QLabel("Brightness"); + c->grid->addWidget(c->brightLabel,0,0); + c->brightness = new QComboBox(); + c->brightness->addItem("Off"); + c->brightness->addItem("Low"); + c->brightness->addItem("Medium"); + c->brightness->addItem("High"); + c->brightness->setCurrentIndex(dev->brightness); + c->grid->addWidget(c->brightness,1,0); + connect(c->brightness, qOverload(&QComboBox::currentIndexChanged), [dev,this](int index) { this->brightnessChanged(dev,index); }); - c->speedLabel.setText("Speed"); - c->grid.addWidget(&c->speedLabel,0,1); - c->speed.setObjectName("Speed"); - c->speed.addItem("Fastest"); - c->speed.addItem("Faster"); - c->speed.addItem("Normal"); - c->speed.addItem("Slower"); - c->speed.addItem("Slowest"); - c->speed.setCurrentIndex(dev->speed); - c->grid.addWidget(&c->speed,1,1); - connect(&c->speed, qOverload(&QComboBox::currentIndexChanged), + c->speedLabel = new QLabel("Speed"); + c->grid->addWidget(c->speedLabel,0,1); + c->speed = new QComboBox(); + c->speed->addItem("Fastest"); + c->speed->addItem("Faster"); + c->speed->addItem("Normal"); + c->speed->addItem("Slower"); + c->speed->addItem("Slowest"); + c->speed->setCurrentIndex(dev->speed); + c->grid->addWidget(c->speed,1,1); + connect(c->speed, qOverload(&QComboBox::currentIndexChanged), [dev,this](int index) { this->speedChanged(dev,index); }); - c->orientLabel.setText("Orientation"); - c->grid.addWidget(&c->orientLabel,0,2); - c->orientation.addItem("Rotate 0"); - c->orientation.addItem("Rotate 90"); - c->orientation.addItem("Rotate 180"); - c->orientation.addItem("Rotate 270"); - c->orientation.setCurrentIndex(dev->orientation); - c->grid.addWidget(&c->orientation,1,2); - connect(&c->orientation, qOverload(&QComboBox::currentIndexChanged), + c->orientLabel = new QLabel("Orientation"); + c->grid->addWidget(c->orientLabel,0,2); + c->orientation = new QComboBox(); + c->orientation->addItem("Rotate 0"); + c->orientation->addItem("Rotate 90"); + c->orientation->addItem("Rotate 180"); + c->orientation->addItem("Rotate 270"); + c->orientation->setCurrentIndex(dev->orientation); + c->grid->addWidget(c->orientation,1,2); + connect(c->orientation, qOverload(&QComboBox::currentIndexChanged), [dev,this](int index) { this->orientationChanged(dev,index); }); - c->color.setText("Color"); - c->grid.addWidget(&c->colorLabel,0,3); - c->color.setStyleSheet(QString("background-color: %1").arg(dev->color.name(QColor::HexArgb))); - c->grid.addWidget(&c->color,1,3); - connect(&c->color, &QPushButton::clicked, - [dev,c,this]() { this->colorPicker(dev,&c->color,dev->color); }); + c->color = new QPushButton("Color"); + c->color->setStyleSheet(QString("background-color: %1").arg(dev->color.name(QColor::HexArgb))); + c->grid->addWidget(c->color,1,3); + connect(c->color, &QPushButton::clicked, + [dev,c,this]() { this->colorPicker(dev,c->color,dev->color); }); - c->timeoutLabel.setText("Timeout"); - c->grid.addWidget(&c->timeoutLabel,0,4); - c->timeout.setValue(dev->timeout); - c->grid.addWidget(&c->timeout,1,4); - connect(&c->timeout, qOverload(&QSpinBox::valueChanged), + c->timeoutLabel = new QLabel("Timeout"); + c->grid->addWidget(c->timeoutLabel,0,4); + c->timeout = new QSpinBox(); + c->timeout->setValue(dev->timeout); + c->grid->addWidget(c->timeout,1,4); + connect(c->timeout, qOverload(&QSpinBox::valueChanged), [dev,this](int index) { this->timeoutChanged(dev,index); }); - c->pagesLabel.setText("Num Pages"); - c->grid.addWidget(&c->pagesLabel,0,5); - c->pages.setValue(dev->pages); - c->pages.setMinimum(1); - c->grid.addWidget(&c->pages,1,5); - connect(&c->pages, qOverload(&QSpinBox::valueChanged), + c->pagesLabel = new QLabel("Num Pages"); + c->grid->addWidget(c->pagesLabel,0,5); + c->pages = new QSpinBox(); + c->pages->setValue(dev->pages); + c->pages->setMinimum(1); + c->grid->addWidget(c->pages,1,5); + connect(c->pages, qOverload(&QSpinBox::valueChanged), [dev,this](int index) { this->pagesChanged(dev,index); }); for (int i=0;i<6;i++) - c->grid.setColumnStretch(i,1); + c->grid->setColumnStretch(i,1); - c->helpText.setText("

Button configuration: Right-click on each button to configure it.

"); - c->helpText.setAlignment(Qt::AlignCenter); - c->layout.addWidget(&c->helpText); + c->helpText = new QLabel(); + c->helpText->setText("

Button configuration: Right-click on each button to configure it.

"); + c->helpText->setAlignment(Qt::AlignCenter); + c->layout->addWidget(c->helpText); - c->view.setSceneRect(c->scene->itemsBoundingRect()); + c->view->setSceneRect(c->scene->itemsBoundingRect()); this->adjustSize(); @@ -661,7 +684,7 @@ void controllerSetup::newDevice(USBDEVICE* dev) // Attach pageChanged() here so we have access to all necessary vars - connect(&c->page, qOverload(&QSpinBox::valueChanged), + connect(c->page, qOverload(&QSpinBox::valueChanged), [dev, this](int index) { this->pageChanged(dev, index); }); // Hide all controls that are not relevant to this controller @@ -675,22 +698,22 @@ void controllerSetup::newDevice(USBDEVICE* dev) case QuickKeys: break; case StreamDeckPedal: - c->sensLabel.setVisible(false); - c->sens.setVisible(false); + c->sensLabel->setVisible(false); + c->sens->setVisible(false); case shuttleXpress: case shuttlePro2: case RC28: case xBoxGamepad: case eCoderPlus: - c->brightLabel.setVisible(false); - c->speedLabel.setVisible(false); - c->timeoutLabel.setVisible(false); - c->orientLabel.setVisible(false); - c->brightness.setVisible(false); - c->speed.setVisible(false); - c->timeout.setVisible(false); - c->orientation.setVisible(false); - c->color.setVisible(false); + c->brightLabel->setVisible(false); + c->speedLabel->setVisible(false); + c->timeoutLabel->setVisible(false); + c->orientLabel->setVisible(false); + c->brightness->setVisible(false); + c->speed->setVisible(false); + c->timeout->setVisible(false); + c->orientation->setVisible(false); + c->color->setVisible(false); break; case StreamDeckOriginal: case StreamDeckOriginalV2: @@ -699,15 +722,15 @@ void controllerSetup::newDevice(USBDEVICE* dev) case StreamDeckMiniV2: case StreamDeckXL: case StreamDeckXLV2: - c->sensLabel.setVisible(false); - c->sens.setVisible(false); // No knobs! + c->sensLabel->setVisible(false); + c->sens->setVisible(false); // No knobs! case StreamDeckPlus: - c->speedLabel.setVisible(false); - c->timeoutLabel.setVisible(false); - c->orientLabel.setVisible(false); - c->speed.setVisible(false); - c->timeout.setVisible(false); - c->orientation.setVisible(false); + c->speedLabel->setVisible(false); + c->timeoutLabel->setVisible(false); + c->orientLabel->setVisible(false); + c->speed->setVisible(false); + c->timeout->setVisible(false); + c->orientation->setVisible(false); break; default: break; @@ -871,11 +894,11 @@ void controllerSetup::setConnected(USBDEVICE* dev) { if (dev->connected) { - tab.value()->message.setStyleSheet("QLabel { color : green; }"); - tab.value()->message.setText("Connected"); + tab.value()->message->setStyleSheet("QLabel { color : green; }"); + tab.value()->message->setText("Connected"); } else { - tab.value()->message.setStyleSheet("QLabel { color : red; }"); - tab.value()->message.setText("Not Connected"); + tab.value()->message->setStyleSheet("QLabel { color : red; }"); + tab.value()->message->setText("Not Connected"); } } } diff --git a/controllersetup.h b/controllersetup.h index 5d0bdf5..b1b617d 100644 --- a/controllersetup.h +++ b/controllersetup.h @@ -75,36 +75,36 @@ protected: struct tabContent { - QWidget tab; - QVBoxLayout mainLayout; - QHBoxLayout topLayout; - QWidget widget; - QVBoxLayout layout; - QCheckBox disabled; - QLabel message; - QGraphicsView view; - QLabel pageLabel; - QSpinBox page; - QHBoxLayout sensLayout; - QLabel sensLabel; - QSlider sens; - QImage image; + QWidget* tab; + QVBoxLayout* mainLayout; + QHBoxLayout* topLayout; + QWidget* widget; + QVBoxLayout* layout; + QCheckBox* disabled; + QLabel* message; + QGraphicsView* view; + QLabel* pageLabel; + QSpinBox* page; + QHBoxLayout* sensLayout; + QLabel* sensLabel; + QSlider* sens; + QImage* image; QGraphicsItem* bgImage = Q_NULLPTR; controllerScene* scene = Q_NULLPTR; - QGridLayout grid; - QLabel brightLabel; - QComboBox brightness; - QLabel speedLabel; - QComboBox speed; - QLabel orientLabel; - QComboBox orientation; - QLabel colorLabel; - QPushButton color; - QLabel timeoutLabel; - QSpinBox timeout; - QLabel pagesLabel; - QSpinBox pages; - QLabel helpText; + QGridLayout* grid; + QLabel* brightLabel; + QComboBox* brightness; + QLabel* speedLabel; + QComboBox* speed; + QLabel* orientLabel; + QComboBox* orientation; + QLabel* colorLabel; + QPushButton* color; + QLabel* timeoutLabel; + QSpinBox* timeout; + QLabel* pagesLabel; + QSpinBox* pages; + QLabel* helpText; }; diff --git a/usbcontroller.cpp b/usbcontroller.cpp index 17a9a09..412d75f 100644 --- a/usbcontroller.cpp +++ b/usbcontroller.cpp @@ -18,11 +18,12 @@ usbController::usbController() loadKnobs(); // This is a the "master" list of supported devices. Maybe move to settings at some point? + // usbDeviceType, manufacturer, product, usage, usagePage, butons, knobs, maxPayload, iconSize knownDevices.append(USBTYPE(shuttleXpress,0x0b33,0x0020,0x0000,0x0000,15,2,5,0)); knownDevices.append(USBTYPE(shuttlePro2,0x0b33,0x0030,0x0000,0x0000,15,2,5,0)); knownDevices.append(USBTYPE(shuttlePro2,0x0b33,0x0011,0x0000,0x0000,15,2,5,0)); // Actually a ShuttlePro but hopefully will work? knownDevices.append(USBTYPE(RC28,0x0c26,0x001e,0x0000,0x0000,3,1,64,0)); - knownDevices.append(USBTYPE(eCoderPlus, 0x1fc9, 0x0003,0x0000,0x0000,16,4,32,0)); + knownDevices.append(USBTYPE(eCoderPlus, 0x1fc9, 0x0003,0x0000,0x0000,22,4,32,0)); // Actually 20 but some bit0 and bit15 aren't used knownDevices.append(USBTYPE(QuickKeys, 0x28bd, 0x5202,0x0001,0xff0a,10,1,32,0)); knownDevices.append(USBTYPE(StreamDeckMini, 0x0fd9, 0x0063, 0x0000, 0x0000,6,0,1024,80)); knownDevices.append(USBTYPE(StreamDeckMiniV2, 0x0fd9, 0x0090, 0x0000, 0x0000,6,0,1024,80)); @@ -48,8 +49,12 @@ usbController::~usbController() if (dev->type.model == RC28) { sendRequest(dev,usbFeatureType::featureLEDControl,3,"off"); } - emit removeDevice(dev); hid_close(dev->handle); + dev->handle = NULL; + dev->connected = false; + dev->detected = false; + dev->uiCreated = false; + devicesConnected--; } ++devIt; } @@ -1255,12 +1260,12 @@ void usbController::sendRequest(USBDEVICE *dev, usbFeatureType feature, int val, break; case usbFeatureType::featureLEDControl: data[1] = 0x01; - data[2] = 0x07; - //if (text == "on") - //data[2] &= quint8(~(1UL << val)); - //else - // data[2] |= quint8(1UL << val); - //break; + if (text == "on") + dev->ledStatus &= ~(1UL << val); + else + dev->ledStatus |= 1UL << val; + data[2] = dev->ledStatus; + break; default: return; // No command break; diff --git a/usbcontroller.h b/usbcontroller.h index a274fbf..c30185e 100644 --- a/usbcontroller.h +++ b/usbcontroller.h @@ -133,7 +133,7 @@ struct USBDEVICE { QGraphicsScene* scene = Q_NULLPTR; QSpinBox* pageSpin = Q_NULLPTR; QImage image; - + quint8 ledStatus=0x07; }; struct COMMAND { diff --git a/wfmain.cpp b/wfmain.cpp index e5984f4..f877198 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -1729,32 +1729,35 @@ void wfmain::setupUsbControllerDevice() if (usbWindow == Q_NULLPTR) { usbWindow = new controllerSetup(); } + usbControllerDev = new usbController(); usbControllerThread = new QThread(this); + usbControllerThread->setObjectName("usb()"); usbControllerDev->moveToThread(usbControllerThread); connect(usbControllerThread, SIGNAL(started()), usbControllerDev, SLOT(run())); connect(usbControllerThread, SIGNAL(finished()), usbControllerDev, SLOT(deleteLater())); + connect(usbControllerThread, SIGNAL(finished()), usbWindow, SLOT(deleteLater())); // Delete window when controller is deleted connect(usbControllerDev, SIGNAL(sendJog(int)), this, SLOT(changeFrequency(int))); connect(usbControllerDev, SIGNAL(doShuttle(bool,unsigned char)), this, SLOT(doShuttle(bool,unsigned char))); connect(usbControllerDev, SIGNAL(button(const COMMAND*)), this, SLOT(buttonControl(const COMMAND*))); connect(usbControllerDev, SIGNAL(setBand(int)), this, SLOT(setBand(int))); connect(usbControllerDev, SIGNAL(removeDevice(USBDEVICE*)), usbWindow, SLOT(removeDevice(USBDEVICE*))); connect(usbControllerDev, SIGNAL(initUI(usbDevMap*, QVector