root/src/preferences/joysticks/JoyWin.cpp
/*
 * Copyright 2007-2009 Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Oliver Ruiz Dorantes    oliver.ruiz.dorantes_at_gmail.com
 *              Ryan Leavengood                 leavengood@gmail.com
 *              Fredrik Modéen                         fredrik@modeen.se
 */

#include "JoyWin.h"
#include "MessageWin.h"
#include "CalibWin.h"
#include "Global.h"
#include "PortItem.h"
//#include "FileReadWrite.h"

#include <stdio.h>
#include <stdlib.h>

#include <Box.h>
#include <Button.h>
#include <CheckBox.h>
#include <ListView.h>
#include <ListItem.h>
#include <ScrollView.h>
#include <String.h>
#include <StringView.h>
#include <Application.h>
#include <View.h>
#include <Path.h>
#include <Entry.h>
#include <Directory.h>
#include <Alert.h>
#include <File.h>
#include <SymLink.h>
#include <Messenger.h>
#include <Directory.h>
#include <Joystick.h>
#include <FindDirectory.h>
#include <Joystick.h>

#define JOYSTICKPATH "/dev/joystick/"
#define JOYSTICKFILEPATH "/boot/system/data/joysticks/"
#define JOYSTICKFILESETTINGS "/boot/home/config/settings/joysticks/"
#define SELECTGAMEPORTFIRST "Select a game port first"

static int
ShowMessage(char* string)
{
        BAlert *alert = new BAlert("Message", string, "OK");
        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
        return alert->Go();
}

JoyWin::JoyWin(BRect frame, const char *title)
        : BWindow(frame, title, B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
                B_NOT_ZOOMABLE), fSystemUsedSelect(false),
                fJoystick(new BJoystick)
{
        fProbeButton = new BButton(BRect(15.00, 260.00, 115.00, 285.00),
                "ProbeButton", "Probe", new BMessage(PROBE));

        fCalibrateButton = new BButton(BRect(270.00, 260.00, 370.00, 285.00),
                "CalibrateButton", "Calibrate", new BMessage(CALIBRATE));

        fGamePortL = new BListView(BRect(15.00, 30.00, 145.00, 250.00),
                "gamePort");
        fGamePortL->SetSelectionMessage(new BMessage(PORT_SELECTED));
        fGamePortL->SetInvocationMessage(new BMessage(PORT_INVOKE));

        fConControllerL = new BListView(BRect(175.00,30.00,370.00,250.00),
                "conController");
        fConControllerL->SetSelectionMessage(new BMessage(JOY_SELECTED));
        fConControllerL->SetInvocationMessage(new BMessage(JOY_INVOKE));

        fGamePortS = new BStringView(BRect(15, 5, 160, 25), "gpString",
                "Game port");
        fGamePortS->SetFont(be_bold_font);
        fConControllerS = new BStringView(BRect(170, 5, 330, 25), "ccString",
                "Connected controller");

        fConControllerS->SetFont(be_bold_font);

        fCheckbox = new BCheckBox(BRect(131.00, 260.00, 227.00, 280.00),
                "Disabled", "Disabled", new BMessage(DISABLEPORT));
        BBox *box = new BBox( Bounds(),"box", B_FOLLOW_ALL,
                B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE,
                B_PLAIN_BORDER);

        box->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        // Add listViews with their scrolls
        box->AddChild(new BScrollView("PortScroll", fGamePortL,
                B_FOLLOW_LEFT | B_FOLLOW_TOP_BOTTOM, B_WILL_DRAW, false, true));

        box->AddChild(new BScrollView("ConScroll", fConControllerL, B_FOLLOW_ALL,
                B_WILL_DRAW, false, true));

        // Adding object
        box->AddChild(fCheckbox);
        box->AddChild(fGamePortS);
        box->AddChild(fConControllerS);
        box->AddChild(fProbeButton);
        box->AddChild(fCalibrateButton);
        AddChild(box);

        SetSizeLimits(400, 600, Bounds().Height(), Bounds().Height());

        /* Add all the devices */
        int32 nr = fJoystick->CountDevices();
        for (int32 i = 0; i < nr;i++) {
                //BString str(path.Path());
                char buf[B_OS_NAME_LENGTH];
                fJoystick->GetDeviceName(i, buf, B_OS_NAME_LENGTH);
                fGamePortL->AddItem(new PortItem(buf));
        }
        fGamePortL->Select(0);

        /* Add the joysticks specifications */
        _AddToList(fConControllerL, JOY_SELECTED, JOYSTICKFILEPATH);

        _GetSettings();
}


JoyWin::~JoyWin()
{
        //delete fFileTempProbeJoystick;
        be_app_messenger.SendMessage(B_QUIT_REQUESTED);
}


void
JoyWin::MessageReceived(BMessage *message)
{
//      message->PrintToStream();
        switch(message->what)
        {
                case DISABLEPORT:
                break;
                {
                        PortItem *item = _GetSelectedItem(fGamePortL);
                        if (item != NULL) {
                                //ToDo: item->SetEnabled(true);
                                //don't work as you can't select a item that are disabled
                                if(fCheckbox->Value()) {
                                        item->SetEnabled(false);
                                        _SelectDeselectJoystick(fConControllerL, false);
                                } else {
                                        item->SetEnabled(true);
                                        _SelectDeselectJoystick(fConControllerL, true);
                                        _PerformProbe(item->Text());
                                }
                        } //else
                                //printf("We have a null value\n");
                break;
                }

                case PORT_SELECTED:
                {
                        PortItem *item = _GetSelectedItem(fGamePortL);
                        if (item != NULL) {
                                fSystemUsedSelect = true;
                                if (item->IsEnabled()) {
                                        //printf("SetEnabled = false\n");
                                        fCheckbox->SetValue(false);
                                        _SelectDeselectJoystick(fConControllerL, true);
                                } else {
                                        //printf("SetEnabled = true\n");
                                        fCheckbox->SetValue(true);
                                        _SelectDeselectJoystick(fConControllerL, false);
                                }

                                if (_CheckJoystickExist(item->Text()) == B_ERROR) {
                                        if (_ShowCantFindFileMessage(item->Text()) == B_OK) {
                                                _PerformProbe(item->Text());
                                        }
                                } else {
                                        BString str(_FindFilePathForSymLink(JOYSTICKFILESETTINGS,
                                                item));
                                        if (str != NULL) {
                                                BString str(_FixPathToName(str.String()));
                                                int32 id = _FindStringItemInList(fConControllerL,
                                                                        new PortItem(str.String()));
                                                if (id > -1) {
                                                        fConControllerL->Select(id);
                                                        item->SetJoystickName(BString(str.String()));
                                                }
                                        }
                                }
                        } else {
                                fConControllerL->DeselectAll();
                                ShowMessage((char*)SELECTGAMEPORTFIRST);
                        }
                break;
                }

                case PROBE:
                case PORT_INVOKE:
                {
                        PortItem *item = _GetSelectedItem(fGamePortL);
                        if (item != NULL) {
                                //printf("invoke.. inte null\n");
                                _PerformProbe(item->Text());
                        } else
                                ShowMessage((char*)SELECTGAMEPORTFIRST);
                break;
                }

                case JOY_SELECTED:
                {
                        if (!fSystemUsedSelect) {
                                PortItem *controllerName = _GetSelectedItem(fConControllerL);
                                PortItem *portname = _GetSelectedItem(fGamePortL);
                                if (portname != NULL && controllerName != NULL) {
                                        portname->SetJoystickName(BString(controllerName->Text()));

                                        BString str = portname->GetOldJoystickName();
                                        if (str != NULL) {
                                                BString strOldFile(JOYSTICKFILESETTINGS);
                                                strOldFile.Append(portname->Text());
                                                BEntry entry(strOldFile.String());
                                                entry.Remove();
                                        }
                                        BString strLinkPlace(JOYSTICKFILESETTINGS);
                                        strLinkPlace.Append(portname->Text());

                                        BString strLinkTo(JOYSTICKFILEPATH);
                                        strLinkTo.Append(controllerName->Text());

                                        BDirectory *dir = new BDirectory();
                                        dir->CreateSymLink(strLinkPlace.String(),
                                                strLinkTo.String(), NULL);
                                } else
                                        ShowMessage((char*)SELECTGAMEPORTFIRST);
                        }

                        fSystemUsedSelect = false;
                break;
                }

                case CALIBRATE:
                case JOY_INVOKE:
                {
                        PortItem *controllerName = _GetSelectedItem(fConControllerL);
                        PortItem *portname = _GetSelectedItem(fGamePortL);
                        if (portname != NULL) {
                                if (controllerName == NULL)
                                        _ShowNoDeviceConnectedMessage("known", portname->Text());
                                else {
                                        _ShowNoDeviceConnectedMessage(controllerName->Text(), portname->Text());
                                        /*
                                        ToDo:
                                        Check for a device, and show calibrate window if so
                                        */
                                }
                        } else
                                ShowMessage((char*)SELECTGAMEPORTFIRST);
                break;
                }
                default:
                        BWindow::MessageReceived(message);
                        break;
        }
}


bool
JoyWin::QuitRequested()
{
        _ApplyChanges();
        return BWindow::QuitRequested();
}


//---------------------- Private ---------------------------------//
status_t
JoyWin::_AddToList(BListView *list, uint32 command, const char* rootPath,
        BEntry *rootEntry)
{
        BDirectory root;

        if ( rootEntry != NULL )
                root.SetTo( rootEntry );
        else if ( rootPath != NULL )
                root.SetTo( rootPath );
        else
                return B_ERROR;

        BEntry entry;
        while ((root.GetNextEntry(&entry)) > B_ERROR ) {
                if (entry.IsDirectory()) {
                        _AddToList(list, command, rootPath, &entry);
                } else {
                        BPath path;
                        entry.GetPath(&path);
                        BString str(path.Path());
                        str.RemoveFirst(rootPath);
                        list->AddItem(new PortItem(str.String()));
                }
        }
        return B_OK;
}


status_t
JoyWin::_Calibrate()
{
        CalibWin* calibw;
        BRect rect(100, 100, 500, 400);
        calibw = new CalibWin(rect, "Calibrate", B_DOCUMENT_WINDOW_LOOK,
                B_NORMAL_WINDOW_FEEL, B_NOT_RESIZABLE | B_NOT_ZOOMABLE);
        calibw->Show();
        return B_OK;
}


status_t
JoyWin::_PerformProbe(const char* path)
{
        status_t err = B_ERROR;
        err = _ShowProbeMesage(path);
        if (err != B_OK) {
                return err;
        }

        MessageWin* mesgw = new MessageWin(Frame(),"Probing", B_MODAL_WINDOW_LOOK,
                B_MODAL_APP_WINDOW_FEEL, B_NOT_RESIZABLE | B_NOT_ZOOMABLE);

        mesgw->Show();
        int32 number = fConControllerL->CountItems();
        PortItem *item;
        for (int i = 0; i < number; i++) {
                // Do a search in JOYSTICKFILEPATH with item->Text() find the string
                // that starts with "gadget" (tex gadget = "GamePad Pro") remove
                // spacing and the "=" ty to open this one, if failed move to next and
                // try to open.. list those that suxesfully work
                fConControllerL->Select(i);
                int32 selected = fConControllerL->CurrentSelection();
                item = dynamic_cast<PortItem*>(fConControllerL->ItemAt(selected));
                BString str("Looking for: ");
                str << item->Text() << " in port " << path;
                mesgw->SetText(str.String());
                _FindSettingString(item->Text(), JOYSTICKFILEPATH);
                //Need a check to find joysticks (don't know how right now so show a
                // don't find message)
        }
        mesgw->Hide();

        //Need a if !found then show this message. else list joysticks.
        _ShowNoCompatibleJoystickMessage();
        return B_OK;
}


status_t
JoyWin::_ApplyChanges()
{
        BString str = _BuildDisabledJoysticksFile();
        //ToDo; Save the string as the file "disabled_joysticks" under settings
        //   (/boot/home/config/settings/disabled_joysticks)
        return B_OK;
}


status_t
JoyWin::_GetSettings()
{
        // ToDo; Read the file "disabled_joysticks" and make the port with the
        // same name disabled (/boot/home/config/settings/disabled_joysticks)
        return B_OK;
}


status_t
JoyWin::_CheckJoystickExist(const char* path)
{
        BString str(JOYSTICKFILESETTINGS);
        str << path;

        BFile file;
        status_t status = file.SetTo(str.String(), B_READ_ONLY | B_FAIL_IF_EXISTS);

        if (status == B_FILE_EXISTS || status == B_OK)
                return B_OK;
        else
                return B_ERROR;
}


status_t
JoyWin::_ShowProbeMesage(const char* device)
{
        BString str("An attempt will be made to probe the port '");
        str << device << "' to try to figure out what kind of joystick (if any) ";
        str << "are attached. There is a small chance this process might cause ";
        str << "your machine to lock up and require a reboot. Make sure you have ";
        str << "saved changes in all open applications before you start probing.";

        BAlert *alert = new BAlert("test1", str.String(), "Probe", "Cancel");
        alert->SetShortcut(1, B_ESCAPE);
        int32 bindex = alert->Go();

        if (bindex == 0)
                return B_OK;
        else
                return B_ERROR;
}


//Used when a files/joysticks are no were to be found
status_t
JoyWin::_ShowCantFindFileMessage(const char* port)
{
        BString str("The file '");
        str <<  _FixPathToName(port).String() << "' used by '" << port;
        str << "' cannot be found.\n Do you want to ";
        str << "try auto-detecting a joystick for this port?";

        BAlert *alert = new BAlert("test1", str.String(), "Stop", "Probe");
        alert->SetShortcut(1, B_ENTER);
        int32 bindex = alert->Go();

        if (bindex == 1)
                return B_OK;
        else
                return B_ERROR;
}


void
JoyWin::_ShowNoCompatibleJoystickMessage()
{
        BString str("There were no compatible joysticks detected on this game");
        str << " port. Try another port, or ask the manufacturer of your joystick";
        str << " for a driver designed for Haiku or BeOS.";

        BAlert *alert = new BAlert("test1", str.String(), "OK");
        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
        alert->Go();
}

void
JoyWin::_ShowNoDeviceConnectedMessage(const char* joy, const char* port)
{
        BString str("There does not appear to be a ");
        str << joy << " device connected to the port '" << port << "'.";

        BAlert *alert = new BAlert("test1", str.String(), "Stop");
        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
        alert->Go();
}


// Use this function to get a string of disabled ports
BString
JoyWin::_BuildDisabledJoysticksFile()
{
        BString temp("# This is a list of disabled joystick devices.");
        temp << "# Do not include the /dev/joystick/ part of the device name.";

        int32 number = fGamePortL->CountItems();
        PortItem *item;
        for (int i = 0; i < number; i++) {
                item = dynamic_cast<PortItem*>(fGamePortL->ItemAt(i));
                if (!item->IsEnabled())
                        temp << "disable = \"" <<  item->Text() << "\"";
        }
        return temp;
}


PortItem*
JoyWin::_GetSelectedItem(BListView* list)
{
        int32 id = list->CurrentSelection();
        if (id > -1) {
                //PortItem *item = dynamic_cast<PortItem*>(list->ItemAt(id));
                return dynamic_cast<PortItem*>(list->ItemAt(id));
        } else {
                return NULL;
        }
}


BString
JoyWin::_FixPathToName(const char* port)
{
        BString temp(port);
        temp = temp.Remove(0, temp.FindLast('/') + 1) ;
        return temp;
}


void
JoyWin::_SelectDeselectJoystick(BListView* list, bool enable)
{
        list->DeselectAll();
        int32 number = fGamePortL->CountItems();
        PortItem *item;
        for (int i = 0; i < number; i++) {
                item = dynamic_cast<PortItem*>(list->ItemAt(i));
                if (!item) {
                        printf("%s: PortItem at %d is null!\n", __func__, i);
                        continue;
                }
                item->SetEnabled(enable);
        }
}


int32
JoyWin::_FindStringItemInList(BListView *view, PortItem *item)
{
        PortItem *strItem = NULL;
        int32 number = view->CountItems();
        for (int32 i = 0; i < number; i++) {
                strItem = dynamic_cast<PortItem*>(view->ItemAt(i));
                if (!strcmp(strItem->Text(), item->Text())) {
                        return i;
                }
        }
        delete strItem;
        return -1;
}


BString
JoyWin::_FindFilePathForSymLink(const char* symLinkPath, PortItem *item)
{
        BPath path(symLinkPath);
        path.Append(item->Text());
        BEntry entry(path.Path());
        if (entry.IsSymLink()) {
                BSymLink symLink(&entry);
                BDirectory parent;
                entry.GetParent(&parent);
                symLink.MakeLinkedPath(&parent, &path);
                BString str(path.Path());
                return str;
        }
        return NULL;
}


status_t
JoyWin::_FindStick(const char* name)
{
        BJoystick *stick = new BJoystick();
        return stick->Open(name);
}


const char*
JoyWin::_FindSettingString(const char* name, const char* strPath)
{
        //Make a BJoystick try open it
        BString str;

        BPath path(strPath);
        path.Append(name);
        fFileTempProbeJoystick = new BFile(path.Path(), B_READ_ONLY);

        //status_t err = find_directory(B_SYSTEM_ETC_DIRECTORY, &path);
//      if (err == B_OK) {
                //BString str(path.Path());
                //str << "/joysticks/" << name;
                //printf("path'%s'\n", str.String());
                //err = file->SetTo(strPath, B_READ_ONLY);
                status_t err = fFileTempProbeJoystick->InitCheck();
                if (err == B_OK) {
                        //FileReadWrite frw(fFileTempProbeJoystick);
                        //printf("Get going\n");
                        //printf("Opening file = %s\n", path.Path());
                        //while (frw.Next(str)) {
                                //printf("In While loop\n");
                        //      printf("getline %s \n", str.String());
                                //Test to open joystick with x number
                        //}
                        /*while (_GetLine(str)) {
                                //printf("In While loop\n");
                                printf("getline %s \n", str.String());
                                //Test to open joystick with x number
                        }*/
                        return "";
                } else
                        printf("BFile.SetTo error: %s, Path = %s\n", strerror(err), str.String());
//      } else
//              printf("find_directory error: %s\n", strerror(err));

//      delete file;
        return "";
}

/*
//Function to get a line from a file
bool
JoyWin::_GetLine(BString& string)
{
        // Fill up the buffer with the first chunk of code
        if (fPositionInBuffer == 0)
                fAmtRead = fFileTempProbeJoystick->Read(&fBuffer, sizeof(fBuffer));
        while (fAmtRead > 0) {
                while (fPositionInBuffer < fAmtRead) {
                        // Return true if we hit a newline or the end of the file
                        if (fBuffer[fPositionInBuffer] == '\n') {
                                fPositionInBuffer++;
                                //Convert begin
                                int32 state = 0;
                                int32 bufferLen = string.Length();
                                int32 destBufferLen = bufferLen;
                                char destination[destBufferLen];
//                              if (fSourceEncoding)
//                                      convert_to_utf8(fSourceEncoding, string.String(), &bufferLen, destination, &destBufferLen, &state);
                                string = destination;
                                return true;
                        }
                        string += fBuffer[fPositionInBuffer];
                        fPositionInBuffer++;
                }

                // Once the buffer runs out, grab some more and start again
                fAmtRead = fFileTempProbeJoystick->Read(&fBuffer, sizeof(fBuffer));
                fPositionInBuffer = 0;
        }
        return false;
}
*/