1 /* 2 * Copyright 2008-2009, Oliver Ruiz Dorantes, <oliver.ruiz.dorantes@gmail.com> 3 * Copyright 2021, Haiku, Inc. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Fredrik Modéen <fredrik_at_modeen.se> 8 */ 9 10 #include <Alert.h> 11 #include <Button.h> 12 #include <Catalog.h> 13 #include <LayoutBuilder.h> 14 #include <ListView.h> 15 #include <ListItem.h> 16 #include <MessageRunner.h> 17 #include <ScrollView.h> 18 #include <StatusBar.h> 19 #include <SpaceLayoutItem.h> 20 #include <TextView.h> 21 #include <TabView.h> 22 23 #include <bluetooth/bdaddrUtils.h> 24 #include <bluetooth/DiscoveryAgent.h> 25 #include <bluetooth/DiscoveryListener.h> 26 #include <bluetooth/LocalDevice.h> 27 28 #include "defs.h" 29 #include "DeviceListItem.h" 30 #include "InquiryPanel.h" 31 32 33 #undef B_TRANSLATION_CONTEXT 34 #define B_TRANSLATION_CONTEXT "Inquiry panel" 35 36 using Bluetooth::DeviceListItem; 37 38 // private funcionaility provided by kit 39 extern uint8 GetInquiryTime(); 40 41 static const uint32 kMsgStart = 'InSt'; 42 static const uint32 kMsgFinish = 'InFn'; 43 static const uint32 kMsgShowDebug = 'ShDG'; 44 45 static const uint32 kMsgInquiry = 'iQbt'; 46 static const uint32 kMsgAddListDevice = 'aDdv'; 47 48 static const uint32 kMsgSelected = 'isLt'; 49 static const uint32 kMsgSecond = 'sCMs'; 50 static const uint32 kMsgRetrieve = 'IrEt'; 51 52 53 class PanelDiscoveryListener : public DiscoveryListener { 54 55 public: 56 57 PanelDiscoveryListener(InquiryPanel* iPanel) 58 : 59 DiscoveryListener(), 60 fInquiryPanel(iPanel) 61 { 62 63 } 64 65 66 void 67 DeviceDiscovered(RemoteDevice* btDevice, DeviceClass cod) 68 { 69 BMessage* message = new BMessage(kMsgAddListDevice); 70 message->AddPointer("remoteItem", new DeviceListItem(btDevice)); 71 fInquiryPanel->PostMessage(message); 72 } 73 74 75 void 76 InquiryCompleted(int discType) 77 { 78 BMessage* message = new BMessage(kMsgFinish); 79 fInquiryPanel->PostMessage(message); 80 } 81 82 83 void 84 InquiryStarted(status_t status) 85 { 86 BMessage* message = new BMessage(kMsgStart); 87 fInquiryPanel->PostMessage(message); 88 } 89 90 private: 91 InquiryPanel* fInquiryPanel; 92 93 }; 94 95 96 InquiryPanel::InquiryPanel(BRect frame, LocalDevice* lDevice) 97 : 98 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Bluetooth"), B_FLOATING_WINDOW, 99 B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS, B_ALL_WORKSPACES ), 100 fMessenger(this), 101 fScanning(false), 102 fRetrieving(false), 103 fLocalDevice(lDevice) 104 105 { 106 fScanProgress = new BStatusBar("status", 107 B_TRANSLATE("Scanning progress"), ""); 108 activeColor = fScanProgress->BarColor(); 109 110 if (fLocalDevice == NULL) 111 fLocalDevice = LocalDevice::GetLocalDevice(); 112 113 fMessage = new BTextView("description", B_WILL_DRAW); 114 fMessage->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 115 fMessage->SetLowColor(fMessage->ViewColor()); 116 fMessage->MakeEditable(false); 117 fMessage->MakeSelectable(false); 118 119 fInquiryButton = new BButton("Inquiry", B_TRANSLATE("Inquiry"), 120 new BMessage(kMsgInquiry), B_WILL_DRAW); 121 122 fAddButton = new BButton("add", B_TRANSLATE("Add device to list"), 123 new BMessage(kMsgAddToRemoteList), B_WILL_DRAW); 124 fAddButton->SetEnabled(false); 125 126 fRemoteList = new BListView("AttributeList", B_SINGLE_SELECTION_LIST); 127 fRemoteList->SetSelectionMessage(new BMessage(kMsgSelected)); 128 129 fScrollView = new BScrollView("ScrollView", fRemoteList, 0, false, true); 130 fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 131 132 if (fLocalDevice != NULL) { 133 fMessage->SetText(B_TRANSLATE( 134 "Check that the Bluetooth capabilities of your" 135 " remote device are activated. Press 'Inquiry' to start scanning." 136 " The needed time for the retrieval of the names is unknown, " 137 "although should not take more than 3 seconds per device. " 138 "Afterwards you will be able to add them to your main list," 139 " where you will be able to pair with them.")); 140 fInquiryButton->SetEnabled(true); 141 fDiscoveryAgent = fLocalDevice->GetDiscoveryAgent(); 142 fDiscoveryListener = new PanelDiscoveryListener(this); 143 144 SetTitle((const char*)(fLocalDevice->GetFriendlyName().String())); 145 } else { 146 fMessage->SetText(B_TRANSLATE("There isn't any Bluetooth LocalDevice " 147 "registered on the system.")); 148 fInquiryButton->SetEnabled(false); 149 } 150 151 fRetrieveMessage = new BMessage(kMsgRetrieve); 152 fSecondsMessage = new BMessage(kMsgSecond); 153 154 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 155 .SetInsets(B_USE_SMALL_SPACING) 156 .Add(fMessage, 0) 157 .Add(fScanProgress, 10) 158 .Add(fScrollView, 20) 159 .AddGroup(B_HORIZONTAL, 10) 160 .Add(fAddButton) 161 .AddGlue() 162 .Add(fInquiryButton) 163 .End() 164 .End(); 165 } 166 167 168 void 169 InquiryPanel::MessageReceived(BMessage* message) 170 { 171 static float timer = 0; // expected time of the inquiry process 172 static float scanningTime = 0; 173 static int32 retrievalIndex = 0; 174 static bool labelPlaced = false; 175 176 switch (message->what) { 177 case kMsgInquiry: 178 179 fDiscoveryAgent->StartInquiry(BT_GIAC, fDiscoveryListener, GetInquiryTime()); 180 181 timer = BT_BASE_INQUIRY_TIME * GetInquiryTime() + 1; 182 // does it works as expected? 183 fScanProgress->SetMaxValue(timer); 184 185 break; 186 187 case kMsgAddListDevice: 188 { 189 DeviceListItem* listItem; 190 191 message->FindPointer("remoteItem", (void **)&listItem); 192 193 fRemoteList->AddItem(listItem); 194 } 195 break; 196 197 case kMsgAddToRemoteList: 198 { 199 message->PrintToStream(); 200 int32 index = fRemoteList->CurrentSelection(0); 201 DeviceListItem* item = (DeviceListItem*) fRemoteList->RemoveItem(index);; 202 203 BMessage message(kMsgAddToRemoteList); 204 message.AddPointer("device", item); 205 206 be_app->PostMessage(&message); 207 // TODO: all others listitems can be deleted 208 } 209 break; 210 211 case kMsgSelected: 212 UpdateListStatus(); 213 break; 214 215 case kMsgStart: 216 fRemoteList->MakeEmpty(); 217 fScanProgress->Reset(); 218 fScanProgress->SetTo(1); 219 fScanProgress->SetTrailingText(B_TRANSLATE("Starting scan" 220 B_UTF8_ELLIPSIS)); 221 fScanProgress->SetBarColor(activeColor); 222 223 fAddButton->SetEnabled(false); 224 fInquiryButton->SetEnabled(false); 225 226 BMessageRunner::StartSending(fMessenger, fSecondsMessage, 1000000, timer); 227 228 scanningTime = 1; 229 fScanning = true; 230 231 break; 232 233 case kMsgFinish: 234 235 retrievalIndex = 0; 236 fScanning = false; 237 fRetrieving = true; 238 labelPlaced = false; 239 fScanProgress->SetTo(100); 240 fScanProgress->SetTrailingText(B_TRANSLATE("Retrieving names" 241 B_UTF8_ELLIPSIS)); 242 BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 1000000, 1); 243 244 break; 245 246 case kMsgSecond: 247 if (fScanning && scanningTime < timer) { 248 // TODO time formatting could use Locale Kit 249 250 // TODO should not be needed if SetMaxValue works... 251 fScanProgress->SetTo(scanningTime * 100 / timer); 252 BString elapsedTime = B_TRANSLATE("Remaining %1 seconds"); 253 254 BString seconds(""); 255 seconds << (int)(timer - scanningTime); 256 257 elapsedTime.ReplaceFirst("%1", seconds.String()); 258 fScanProgress->SetTrailingText(elapsedTime.String()); 259 260 scanningTime = scanningTime + 1; 261 } 262 break; 263 264 case kMsgRetrieve: 265 266 if (fRetrieving) { 267 268 if (retrievalIndex < fDiscoveryAgent->RetrieveDevices(0).CountItems()) { 269 270 if (!labelPlaced) { 271 272 labelPlaced = true; 273 BString progressText(B_TRANSLATE("Retrieving name of %1")); 274 275 BString namestr; 276 namestr << bdaddrUtils::ToString(fDiscoveryAgent 277 ->RetrieveDevices(0).ItemAt(retrievalIndex) 278 ->GetBluetoothAddress()); 279 progressText.ReplaceFirst("%1", namestr.String()); 280 fScanProgress->SetTrailingText(progressText.String()); 281 282 } else { 283 // Really erally expensive operation should be done in a separate thread 284 // once Haiku gets a BarberPole in API replacing the progress bar 285 ((DeviceListItem*)fRemoteList->ItemAt(retrievalIndex)) 286 ->SetDevice((BluetoothDevice*) fDiscoveryAgent 287 ->RetrieveDevices(0).ItemAt(retrievalIndex)); 288 fRemoteList->InvalidateItem(retrievalIndex); 289 290 retrievalIndex++; 291 labelPlaced = false; 292 } 293 294 BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 500000, 1); 295 296 } else { 297 298 fRetrieving = false; 299 retrievalIndex = 0; 300 301 fScanProgress->SetBarColor( 302 ui_color(B_PANEL_BACKGROUND_COLOR)); 303 fScanProgress->SetTrailingText( 304 B_TRANSLATE("Scanning completed.")); 305 fInquiryButton->SetEnabled(true); 306 UpdateListStatus(); 307 } 308 } 309 310 break; 311 312 default: 313 BWindow::MessageReceived(message); 314 break; 315 } 316 } 317 318 319 void 320 InquiryPanel::UpdateListStatus(void) 321 { 322 if (fRemoteList->CurrentSelection() < 0 || fScanning || fRetrieving) 323 fAddButton->SetEnabled(false); 324 else 325 fAddButton->SetEnabled(true); 326 } 327 328 329 bool 330 InquiryPanel::QuitRequested(void) 331 { 332 333 return true; 334 } 335