1 /* 2 * Copyright 2011-2013, Haiku, Inc. All rights reserved. 3 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de> 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "FolderConfigWindow.h" 9 10 #include <Alert.h> 11 #include <Button.h> 12 #include <Catalog.h> 13 #include <ControlLook.h> 14 #include <LayoutBuilder.h> 15 #include <ListItem.h> 16 #include <ScrollView.h> 17 #include <SpaceLayoutItem.h> 18 #include <StringView.h> 19 20 #include <StringForSize.h> 21 22 #include "Settings.h" 23 #include "Utilities.h" 24 25 26 #undef B_TRANSLATION_CONTEXT 27 #define B_TRANSLATION_CONTEXT "IMAPFolderConfig" 28 29 30 class EditableListItem { 31 public: 32 EditableListItem(); 33 virtual ~EditableListItem() {} 34 35 virtual void MouseDown(BPoint where) = 0; 36 virtual void MouseUp(BPoint where) = 0; 37 38 void SetListView(BListView* list) 39 { fListView = list; } 40 41 protected: 42 BListView* fListView; 43 }; 44 45 46 class CheckBoxItem : public BStringItem, public EditableListItem { 47 public: 48 CheckBoxItem(const char* text, bool checked); 49 50 void DrawItem(BView* owner, BRect itemRect, 51 bool drawEverything = false); 52 53 void MouseDown(BPoint where); 54 void MouseUp(BPoint where); 55 56 bool Checked() { return fChecked; } 57 private: 58 BRect fBoxRect; 59 bool fChecked; 60 bool fMouseDown; 61 }; 62 63 64 class EditListView : public BListView { 65 public: 66 EditListView(const char* name, 67 list_view_type type 68 = B_SINGLE_SELECTION_LIST, 69 uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS 70 | B_NAVIGABLE); 71 72 virtual void MouseDown(BPoint where); 73 virtual void MouseUp(BPoint where); 74 virtual void FrameResized(float newWidth, float newHeight); 75 76 private: 77 EditableListItem* fLastMouseDown; 78 }; 79 80 81 class StatusWindow : public BWindow { 82 public: 83 StatusWindow(BWindow* parent, const char* text) 84 : 85 BWindow(BRect(0, 0, 10, 10), B_TRANSLATE("status"), B_MODAL_WINDOW_LOOK, 86 B_MODAL_APP_WINDOW_FEEL, B_NO_WORKSPACE_ACTIVATION | B_NOT_ZOOMABLE 87 | B_AVOID_FRONT | B_NOT_RESIZABLE | B_NOT_ZOOMABLE 88 | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS) 89 { 90 BLayoutBuilder::Group<>(this) 91 .SetInsets(B_USE_DEFAULT_SPACING) 92 .Add(new BStringView("text", text)); 93 CenterIn(parent->Frame()); 94 } 95 }; 96 97 98 const uint32 kMsgApplyButton = '&Abu'; 99 const uint32 kMsgInit = '&Ini'; 100 101 102 // #pragma mark - 103 104 105 EditableListItem::EditableListItem() 106 : 107 fListView(NULL) 108 { 109 110 } 111 112 113 // #pragma mark - 114 115 116 CheckBoxItem::CheckBoxItem(const char* text, bool checked) 117 : 118 BStringItem(text), 119 fChecked(checked), 120 fMouseDown(false) 121 { 122 } 123 124 125 void 126 CheckBoxItem::DrawItem(BView* owner, BRect itemRect, bool drawEverything) 127 { 128 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 129 rgb_color lowColor = owner->LowColor(); 130 uint32 flags = 0; 131 if (fMouseDown) 132 flags |= BControlLook::B_CLICKED; 133 if (fChecked) 134 flags |= BControlLook::B_ACTIVATED; 135 136 font_height fontHeight; 137 owner->GetFontHeight(&fontHeight); 138 BRect boxRect(0.0f, 2.0f, ceilf(3.0f + fontHeight.ascent), 139 ceilf(5.0f + fontHeight.ascent)); 140 141 owner->PushState(); 142 143 float left = itemRect.left; 144 fBoxRect.left = left + 3; 145 fBoxRect.top = itemRect.top + (itemRect.Height() - boxRect.Height()) / 2; 146 fBoxRect.right = fBoxRect.left + boxRect.Width(); 147 fBoxRect.bottom = itemRect.top + boxRect.Height(); 148 149 itemRect.left = fBoxRect.right + be_control_look->DefaultLabelSpacing(); 150 151 if (IsSelected() || drawEverything) { 152 if (IsSelected()) { 153 owner->SetHighColor(tint_color(lowColor, B_DARKEN_2_TINT)); 154 owner->SetLowColor(owner->HighColor()); 155 } else 156 owner->SetHighColor(lowColor); 157 158 owner->FillRect( 159 BRect(left, itemRect.top, itemRect.left, itemRect.bottom)); 160 } 161 162 be_control_look->DrawCheckBox(owner, fBoxRect, fBoxRect, base, flags); 163 owner->PopState(); 164 165 BStringItem::DrawItem(owner, itemRect, drawEverything); 166 } 167 168 169 void 170 CheckBoxItem::MouseDown(BPoint where) 171 { 172 if (!fBoxRect.Contains(where)) 173 return; 174 175 fMouseDown = true; 176 177 fListView->InvalidateItem(fListView->IndexOf(this)); 178 } 179 180 181 void 182 CheckBoxItem::MouseUp(BPoint where) 183 { 184 if (!fMouseDown) 185 return; 186 fMouseDown = false; 187 188 if (fBoxRect.Contains(where)) { 189 if (fChecked) 190 fChecked = false; 191 else 192 fChecked = true; 193 } 194 195 fListView->InvalidateItem(fListView->IndexOf(this)); 196 } 197 198 199 // #pragma mark - 200 201 202 EditListView::EditListView(const char* name, list_view_type type, uint32 flags) 203 : 204 BListView(name, type, flags), 205 fLastMouseDown(NULL) 206 { 207 } 208 209 210 void 211 EditListView::MouseDown(BPoint where) 212 { 213 BListView::MouseDown(where); 214 215 int32 index = IndexOf(where); 216 EditableListItem* handler = dynamic_cast<EditableListItem*>(ItemAt(index)); 217 if (handler == NULL) 218 return; 219 220 fLastMouseDown = handler; 221 handler->MouseDown(where); 222 SetMouseEventMask(B_POINTER_EVENTS); 223 } 224 225 226 void 227 EditListView::MouseUp(BPoint where) 228 { 229 BListView::MouseUp(where); 230 if (fLastMouseDown) { 231 fLastMouseDown->MouseUp(where); 232 fLastMouseDown = NULL; 233 } 234 235 int32 index = IndexOf(where); 236 EditableListItem* handler = dynamic_cast<EditableListItem*>(ItemAt(index)); 237 if (handler == NULL) 238 return; 239 240 handler->MouseUp(where); 241 } 242 243 244 void 245 EditListView::FrameResized(float newWidth, float newHeight) 246 { 247 Invalidate(); 248 } 249 250 251 // #pragma mark - 252 253 254 FolderConfigWindow::FolderConfigWindow(BRect parent, const BMessage& settings) 255 : 256 BWindow(BRect(0, 0, 350, 350), B_TRANSLATE("IMAP Folders"), 257 B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL, 258 B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 259 fSettings("in", settings) 260 { 261 fQuotaView = new BStringView("quota view", 262 B_TRANSLATE("Failed to fetch available storage.")); 263 fQuotaView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 264 B_ALIGN_VERTICAL_CENTER)); 265 fQuotaView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 266 267 fFolderListView = new EditListView(B_TRANSLATE("IMAP Folders")); 268 fFolderListView->SetExplicitPreferredSize(BSize(B_SIZE_UNLIMITED, 269 B_SIZE_UNLIMITED)); 270 271 BButton* cancelButton = new BButton("cancel", B_TRANSLATE("Cancel"), 272 new BMessage(B_QUIT_REQUESTED)); 273 BButton* applyButton = new BButton("apply", B_TRANSLATE("Apply"), 274 new BMessage(kMsgApplyButton)); 275 276 BLayoutBuilder::Group<>(this, B_VERTICAL) 277 .SetInsets(B_USE_DEFAULT_SPACING) 278 .Add(fQuotaView) 279 .Add(new BScrollView("scroller", fFolderListView, 0, false, true)) 280 .AddGroup(B_HORIZONTAL) 281 .AddGlue() 282 .Add(cancelButton) 283 .Add(applyButton); 284 285 PostMessage(kMsgInit); 286 CenterIn(parent); 287 } 288 289 290 void 291 FolderConfigWindow::MessageReceived(BMessage* message) 292 { 293 switch (message->what) { 294 case kMsgInit: 295 _LoadFolders(); 296 break; 297 298 case kMsgApplyButton: 299 _ApplyChanges(); 300 PostMessage(B_QUIT_REQUESTED); 301 break; 302 303 default: 304 BWindow::MessageReceived(message); 305 } 306 } 307 308 309 void 310 FolderConfigWindow::_LoadFolders() 311 { 312 StatusWindow* statusWindow = new StatusWindow(this, 313 B_TRANSLATE("Fetching IMAP folders, please be patient" B_UTF8_ELLIPSIS)); 314 statusWindow->Show(); 315 316 status_t status = fProtocol.Connect(fSettings.ServerAddress(), 317 fSettings.Username(), fSettings.Password(), fSettings.UseSSL()); 318 if (status != B_OK) { 319 statusWindow->PostMessage(B_QUIT_REQUESTED); 320 321 // Show error message on screen 322 BString message = B_TRANSLATE("Could not connect to server " 323 "\"%server%\":\n%error%"); 324 message.ReplaceFirst("%server%", fSettings.Server()); 325 message.ReplaceFirst("%error%", strerror(status)); 326 BAlert* alert = new BAlert("IMAP error", message.String(), 327 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 328 alert->Go(); 329 330 PostMessage(B_QUIT_REQUESTED); 331 return; 332 } 333 334 // TODO: don't get all of them at once, but retrieve them level by level 335 fFolderList.clear(); 336 BString separator; 337 fProtocol.GetFolders(fFolderList, separator); 338 for (size_t i = 0; i < fFolderList.size(); i++) { 339 IMAP::FolderEntry& entry = fFolderList[i]; 340 CheckBoxItem* item = new CheckBoxItem( 341 MailboxToFolderName(entry.folder, separator), entry.subscribed); 342 fFolderListView->AddItem(item); 343 item->SetListView(fFolderListView); 344 } 345 346 uint64 used, total; 347 if (fProtocol.GetQuota(used, total) == B_OK) { 348 BString quotaString; 349 char usedBuffer[128], totalBuffer[128]; 350 quotaString.SetToFormat(B_TRANSLATE("Server storage: %s / %s used."), 351 string_for_size(used, usedBuffer, 128), 352 string_for_size(total, totalBuffer, 128)); 353 fQuotaView->SetText(quotaString); 354 } 355 356 statusWindow->PostMessage(B_QUIT_REQUESTED); 357 } 358 359 360 void 361 FolderConfigWindow::_ApplyChanges() 362 { 363 bool haveChanges = false; 364 for (size_t i = 0; i < fFolderList.size(); i++) { 365 IMAP::FolderEntry& entry = fFolderList[i]; 366 CheckBoxItem* item = (CheckBoxItem*)fFolderListView->ItemAt(i); 367 if (entry.subscribed != item->Checked()) { 368 haveChanges = true; 369 break; 370 } 371 } 372 if (!haveChanges) 373 return; 374 375 StatusWindow* status = new StatusWindow(this, 376 B_TRANSLATE("Updating subscriptions to IMAP folders, please be patient" 377 B_UTF8_ELLIPSIS)); 378 status->Show(); 379 380 for (size_t i = 0; i < fFolderList.size(); i++) { 381 IMAP::FolderEntry& entry = fFolderList[i]; 382 CheckBoxItem* item = (CheckBoxItem*)fFolderListView->ItemAt(i); 383 if (entry.subscribed && !item->Checked()) 384 fProtocol.UnsubscribeFolder(entry.folder); 385 else if (!entry.subscribed && item->Checked()) 386 fProtocol.SubscribeFolder(entry.folder); 387 } 388 389 status->PostMessage(B_QUIT_REQUESTED); 390 } 391 392