root/src/preferences/bluetooth/InquiryPanel.cpp
/*
 * Copyright 2008-2009, Oliver Ruiz Dorantes, <oliver.ruiz.dorantes@gmail.com>
 * Copyright 2021, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Fredrik Modéen <fredrik_at_modeen.se>
 */

#include <Alert.h>
#include <Button.h>
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <ListItem.h>
#include <MessageRunner.h>
#include <ScrollView.h>
#include <StatusBar.h>
#include <SpaceLayoutItem.h>
#include <TextView.h>
#include <TabView.h>

#include <bluetooth/bdaddrUtils.h>
#include <bluetooth/DiscoveryAgent.h>
#include <bluetooth/DiscoveryListener.h>
#include <bluetooth/LocalDevice.h>

#include "defs.h"
#include "DeviceListItem.h"
#include "InquiryPanel.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Inquiry panel"

using Bluetooth::DeviceListItem;

// private funcionaility provided by kit
extern uint8 GetInquiryTime();

static const uint32 kMsgStart = 'InSt';
static const uint32 kMsgFinish = 'InFn';
static const uint32 kMsgShowDebug = 'ShDG';

static const uint32 kMsgInquiry = 'iQbt';
static const uint32 kMsgAddListDevice = 'aDdv';

static const uint32 kMsgSelected = 'isLt';
static const uint32 kMsgSecond = 'sCMs';
static const uint32 kMsgRetrieve = 'IrEt';


class PanelDiscoveryListener : public DiscoveryListener {

public:

        PanelDiscoveryListener(InquiryPanel* iPanel)
                :
                DiscoveryListener(),
                fInquiryPanel(iPanel)
        {

        }


        void
        DeviceDiscovered(RemoteDevice* btDevice, DeviceClass cod)
        {
                BMessage* message = new BMessage(kMsgAddListDevice);
                message->AddPointer("remoteItem", new DeviceListItem(btDevice));
                fInquiryPanel->PostMessage(message);
        }


        void
        InquiryCompleted(int discType)
        {
                BMessage* message = new BMessage(kMsgFinish);
                fInquiryPanel->PostMessage(message);
        }


        void
        InquiryStarted(status_t status)
        {
                BMessage* message = new BMessage(kMsgStart);
                fInquiryPanel->PostMessage(message);
        }

private:
        InquiryPanel*   fInquiryPanel;

};


InquiryPanel::InquiryPanel(BRect frame, LocalDevice* lDevice)
        :
        BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Bluetooth"), B_FLOATING_WINDOW,
        B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS,     B_ALL_WORKSPACES ),
        fMessenger(this),
        fScanning(false),
        fRetrieving(false),
        fLocalDevice(lDevice)

{
        fScanProgress = new BStatusBar("status",
                B_TRANSLATE("Scanning progress"), "");
        activeColor = fScanProgress->BarColor();

        if (fLocalDevice == NULL)
                fLocalDevice = LocalDevice::GetLocalDevice();

        fMessage = new BTextView("description", B_WILL_DRAW);
        fMessage->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        fMessage->SetLowColor(fMessage->ViewColor());
        fMessage->MakeEditable(false);
        fMessage->MakeSelectable(false);

        fInquiryButton = new BButton("Inquiry", B_TRANSLATE("Inquiry"),
                new BMessage(kMsgInquiry), B_WILL_DRAW);

        fAddButton = new BButton("add", B_TRANSLATE("Add device to list"),
                new BMessage(kMsgAddToRemoteList), B_WILL_DRAW);
        fAddButton->SetEnabled(false);

        fRemoteList = new BListView("AttributeList", B_SINGLE_SELECTION_LIST);
        fRemoteList->SetSelectionMessage(new BMessage(kMsgSelected));

        fScrollView = new BScrollView("ScrollView", fRemoteList, 0, false, true);
        fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        if (fLocalDevice != NULL) {
                fMessage->SetText(B_TRANSLATE(
                        "Check that the Bluetooth capabilities of your"
                        " remote device are activated. Press 'Inquiry' to start scanning."
                        " The needed time for the retrieval of the names is unknown, "
                        "although should not take more than 3 seconds per device. "
                        "Afterwards you will be able to add them to your main list,"
                        " where you will be able to pair with them."));
                fInquiryButton->SetEnabled(true);
                fDiscoveryAgent = fLocalDevice->GetDiscoveryAgent();
                fDiscoveryListener = new PanelDiscoveryListener(this);

                SetTitle((const char*)(fLocalDevice->GetFriendlyName().String()));
        } else {
                fMessage->SetText(B_TRANSLATE("There isn't any Bluetooth LocalDevice "
                        "registered on the system."));
                fInquiryButton->SetEnabled(false);
        }

        fRetrieveMessage = new BMessage(kMsgRetrieve);
        fSecondsMessage = new BMessage(kMsgSecond);

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(B_USE_SMALL_SPACING)
                .Add(fMessage, 0)
                .Add(fScanProgress, 10)
                .Add(fScrollView, 20)
                .AddGroup(B_HORIZONTAL, 10)
                        .Add(fAddButton)
                        .AddGlue()
                        .Add(fInquiryButton)
                .End()
        .End();
}


void
InquiryPanel::MessageReceived(BMessage* message)
{
        static float timer = 0; // expected time of the inquiry process
        static float scanningTime = 0;
        static int32 retrievalIndex = 0;
        static bool labelPlaced = false;

        switch (message->what) {
                case kMsgInquiry:

                        fDiscoveryAgent->StartInquiry(BT_GIAC, fDiscoveryListener, GetInquiryTime());

                        timer = BT_BASE_INQUIRY_TIME * GetInquiryTime() + 1;
                        // does it works as expected?
                        fScanProgress->SetMaxValue(timer);

                break;

                case kMsgAddListDevice:
                {
                        DeviceListItem* listItem;

                        message->FindPointer("remoteItem", (void **)&listItem);

                        fRemoteList->AddItem(listItem);
                }
                break;

                case kMsgAddToRemoteList:
                {
                        message->PrintToStream();
                        int32 index = fRemoteList->CurrentSelection(0);
                        DeviceListItem* item = (DeviceListItem*) fRemoteList->RemoveItem(index);;

                        BMessage message(kMsgAddToRemoteList);
                        message.AddPointer("device", item);

                        be_app->PostMessage(&message);
                        // TODO: all others listitems can be deleted
                }
                break;

                case kMsgSelected:
                        UpdateListStatus();
                break;

                case kMsgStart:
                        fRemoteList->MakeEmpty();
                        fScanProgress->Reset();
                        fScanProgress->SetTo(1);
                        fScanProgress->SetTrailingText(B_TRANSLATE("Starting scan"
                                B_UTF8_ELLIPSIS));
                        fScanProgress->SetBarColor(activeColor);

                        fAddButton->SetEnabled(false);
                        fInquiryButton->SetEnabled(false);

                        BMessageRunner::StartSending(fMessenger, fSecondsMessage, 1000000, timer);

                        scanningTime = 1;
                        fScanning = true;

                break;

                case kMsgFinish:

                        retrievalIndex = 0;
                        fScanning = false;
                        fRetrieving = true;
                        labelPlaced = false;
                        fScanProgress->SetTo(100);
                        fScanProgress->SetTrailingText(B_TRANSLATE("Retrieving names"
                                B_UTF8_ELLIPSIS));
                        BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 1000000, 1);

                break;

                case kMsgSecond:
                        if (fScanning && scanningTime < timer) {
                                // TODO time formatting could use Locale Kit

                                // TODO should not be needed if SetMaxValue works...
                                fScanProgress->SetTo(scanningTime * 100 / timer);
                                BString elapsedTime = B_TRANSLATE("Remaining %1 seconds");

                                BString seconds("");
                                seconds << (int)(timer - scanningTime);

                                elapsedTime.ReplaceFirst("%1", seconds.String());
                                fScanProgress->SetTrailingText(elapsedTime.String());

                                scanningTime = scanningTime + 1;
                        }
                break;

                case kMsgRetrieve:

                        if (fRetrieving) {

                                if (retrievalIndex < fDiscoveryAgent->RetrieveDevices(0).CountItems()) {

                                        if (!labelPlaced) {

                                                labelPlaced = true;
                                                BString progressText(B_TRANSLATE("Retrieving name of %1"));

                                                BString namestr;
                                                namestr << bdaddrUtils::ToString(fDiscoveryAgent
                                                        ->RetrieveDevices(0).ItemAt(retrievalIndex)
                                                        ->GetBluetoothAddress());
                                                progressText.ReplaceFirst("%1", namestr.String());
                                                fScanProgress->SetTrailingText(progressText.String());

                                        } else {
                                                // Really erally expensive operation should be done in a separate thread
                                                // once Haiku gets a BarberPole in API replacing the progress bar
                                                ((DeviceListItem*)fRemoteList->ItemAt(retrievalIndex))
                                                        ->SetDevice(fDiscoveryAgent->RetrieveDevices(0).ItemAt(retrievalIndex));
                                                fRemoteList->InvalidateItem(retrievalIndex);

                                                retrievalIndex++;
                                                labelPlaced = false;
                                        }

                                        BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 500000, 1);

                                } else {

                                        fRetrieving = false;
                                        retrievalIndex = 0;

                                        fScanProgress->SetBarColor(
                                                ui_color(B_PANEL_BACKGROUND_COLOR));
                                        fScanProgress->SetTrailingText(
                                                B_TRANSLATE("Scanning completed."));
                                        fInquiryButton->SetEnabled(true);
                                        UpdateListStatus();
                                }
                        }

                break;

                default:
                        BWindow::MessageReceived(message);
                        break;
        }
}


void
InquiryPanel::UpdateListStatus(void)
{
        if (fRemoteList->CurrentSelection() < 0 || fScanning || fRetrieving)
                fAddButton->SetEnabled(false);
        else
                fAddButton->SetEnabled(true);
}


bool
InquiryPanel::QuitRequested(void)
{

        return true;
}