xref: /haiku/src/preferences/repositories/RepositoriesView.cpp (revision 65ff9f27e1421cfa36f63b1b5bc7586d14c1b557)
1 /*
2  * Copyright 2017 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Brian Hill
7  */
8 
9 
10 #include "RepositoriesView.h"
11 
12 #include <stdlib.h>
13 #include <Alert.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <ColumnTypes.h>
17 #include <LayoutBuilder.h>
18 #include <MessageRunner.h>
19 #include <ScrollBar.h>
20 #include <SeparatorView.h>
21 #include <Url.h>
22 #include <package/PackageRoster.h>
23 #include <package/RepositoryConfig.h>
24 
25 #include "constants.h"
26 
27 #undef B_TRANSLATION_CONTEXT
28 #define B_TRANSLATION_CONTEXT "RepositoriesView"
29 
30 
31 static const BString kTitleEnabled =
32 	B_TRANSLATE_COMMENT("Status", "Column title");
33 static const BString kTitleName = B_TRANSLATE_COMMENT("Name", "Column title");
34 static const BString kTitleUrl = B_TRANSLATE_COMMENT("URL", "Column title");
35 static const BString kLabelRemove =
36 	B_TRANSLATE_COMMENT("Remove", "Button label");
37 static const BString kLabelRemoveAll =
38 	B_TRANSLATE_COMMENT("Remove all", "Button label");
39 static const BString kLabelEnable =
40 	B_TRANSLATE_COMMENT("Enable", "Button label");
41 static const BString kLabelEnableAll =
42 	B_TRANSLATE_COMMENT("Enable all", "Button label");
43 static const BString kLabelDisable =
44 	B_TRANSLATE_COMMENT("Disable", "Button label");
45 static const BString kLabelDisableAll =
46 	B_TRANSLATE_COMMENT("Disable all", "Button label");
47 static const BString kStatusViewText =
48 	B_TRANSLATE_COMMENT("Changes pending:", "Status view text");
49 static const BString kStatusCompletedText =
50 	B_TRANSLATE_COMMENT("Changes completed", "Status view text");
51 
52 
RepositoriesListView(const char * name)53 RepositoriesListView::RepositoriesListView(const char* name)
54 	:
55 	BColumnListView(name, B_NAVIGABLE, B_PLAIN_BORDER)
56 {
57 }
58 
59 
60 void
KeyDown(const char * bytes,int32 numBytes)61 RepositoriesListView::KeyDown(const char* bytes, int32 numBytes)
62 {
63 	switch (bytes[0]) {
64 		case B_DELETE:
65 			Window()->PostMessage(DELETE_KEY_PRESSED);
66 			break;
67 
68 		default:
69 			BColumnListView::KeyDown(bytes, numBytes);
70 	}
71 }
72 
73 
RepositoriesView()74 RepositoriesView::RepositoriesView()
75 	:
76 	BGroupView("RepositoriesView"),
77 	fTaskLooper(NULL),
78 	fShowCompletedStatus(false),
79 	fRunningTaskCount(0),
80 	fLastCompletedTimerId(0)
81 {
82 	// Column list view with 3 columns
83 	fListView = new RepositoriesListView("list");
84 	fListView->SetSelectionMessage(new BMessage(LIST_SELECTION_CHANGED));
85 	float col0width = be_plain_font->StringWidth(kTitleEnabled) + 15;
86 	float col1width = be_plain_font->StringWidth(kTitleName) + 15;
87 	float col2width = be_plain_font->StringWidth(kTitleUrl) + 15;
88 	fListView->AddColumn(new BStringColumn(kTitleEnabled, col0width, col0width,
89 		2 * col0width, B_TRUNCATE_END), kEnabledColumn);
90 	fListView->AddColumn(new BStringColumn(kTitleName, 90, col1width, 300,
91 		B_TRUNCATE_END), kNameColumn);
92 	fListView->AddColumn(new BStringColumn(kTitleUrl, 500, col2width, 5000,
93 		B_TRUNCATE_END), kUrlColumn);
94 	fListView->SetInvocationMessage(new BMessage(ITEM_INVOKED));
95 
96 	// Repository list status view
97 	fStatusContainerView = new BView("status", B_SUPPORTS_LAYOUT);
98 	BString templateText(kStatusViewText);
99 	templateText.Append(" 88");
100 		// Simulate a status text with two digit queue count
101 	fListStatusView = new BStringView("status", templateText);
102 
103 	// Set a smaller fixed font size and slightly lighten text color
104 	BFont font(be_plain_font);
105 	font.SetSize(10.0f);
106 	fListStatusView->SetFont(&font, B_FONT_SIZE);
107 	fListStatusView->SetHighUIColor(fListStatusView->HighUIColor(), .9f);
108 
109 	// Set appropriate explicit view sizes
110 	float viewWidth = std::max(fListStatusView->StringWidth(templateText),
111 		fListStatusView->StringWidth(kStatusCompletedText));
112 	BSize statusViewSize(viewWidth + 3, B_H_SCROLL_BAR_HEIGHT - 2);
113 	fListStatusView->SetExplicitSize(statusViewSize);
114 	statusViewSize.height += 1;
115 	fStatusContainerView->SetExplicitSize(statusViewSize);
116 	BLayoutBuilder::Group<>(fStatusContainerView, B_HORIZONTAL, 0)
117 		.Add(new BSeparatorView(B_VERTICAL))
118 		.AddGroup(B_VERTICAL, 0)
119 			.AddGlue()
120 			.AddGroup(B_HORIZONTAL, 0)
121 				.SetInsets(2, 0, 0, 0)
122 				.Add(fListStatusView)
123 				.AddGlue()
124 			.End()
125 			.Add(new BSeparatorView(B_HORIZONTAL))
126 		.End()
127 	.End();
128 	fListView->AddStatusView(fStatusContainerView);
129 
130 	// Standard buttons
131 	fEnableButton = new BButton(kLabelEnable,
132 		new BMessage(ENABLE_BUTTON_PRESSED));
133 	fDisableButton = new BButton(kLabelDisable,
134 		new BMessage(DISABLE_BUTTON_PRESSED));
135 
136 	// Create buttons with fixed size
137 	font_height fontHeight;
138 	GetFontHeight(&fontHeight);
139 	int16 buttonHeight = int16(fontHeight.ascent + fontHeight.descent + 12);
140 		// button size determined by font size
141 	BSize btnSize(buttonHeight, buttonHeight);
142 
143 	fAddButton = new BButton("plus", "+", new BMessage(ADD_REPO_WINDOW));
144 	fAddButton->SetExplicitSize(btnSize);
145 	fRemoveButton = new BButton("minus", "-", new BMessage(REMOVE_REPOS));
146 	fRemoveButton->SetExplicitSize(btnSize);
147 
148 	// Layout
149 	int16 buttonSpacing = 1;
150 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
151 		.SetInsets(B_USE_WINDOW_SPACING)
152 		.AddGroup(B_HORIZONTAL, 0, 0.0)
153 			.Add(new BStringView("instruction", B_TRANSLATE_COMMENT("Enable"
154 				" repositories to use with package management:",
155 				"Label text")), 0.0)
156 			.AddGlue()
157 		.End()
158 		.AddStrut(B_USE_DEFAULT_SPACING)
159 		.Add(fListView, 1)
160 		.AddGroup(B_HORIZONTAL, 0, 0.0)
161 			// Add and Remove buttons
162 			.AddGroup(B_VERTICAL, 0, 0.0)
163 				.AddGroup(B_HORIZONTAL, 0, 0.0)
164 					.Add(new BSeparatorView(B_VERTICAL))
165 					.AddGroup(B_VERTICAL, 0, 0.0)
166 						.AddGroup(B_HORIZONTAL, buttonSpacing, 0.0)
167 							.SetInsets(buttonSpacing)
168 							.Add(fAddButton)
169 							.Add(fRemoveButton)
170 						.End()
171 						.Add(new BSeparatorView(B_HORIZONTAL))
172 					.End()
173 					.Add(new BSeparatorView(B_VERTICAL))
174 				.End()
175 				.AddGlue()
176 			.End()
177 			// Enable and Disable buttons
178 			.AddGroup(B_HORIZONTAL)
179 				.SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
180 					B_USE_DEFAULT_SPACING, 0)
181 				.AddGlue()
182 				.Add(fEnableButton)
183 				.Add(fDisableButton)
184 			.End()
185 		.End()
186 	.End();
187 }
188 
189 
~RepositoriesView()190 RepositoriesView::~RepositoriesView()
191 {
192 	if (fTaskLooper) {
193 		fTaskLooper->Lock();
194 		fTaskLooper->Quit();
195 	}
196 	_EmptyList();
197 }
198 
199 
200 void
AllAttached()201 RepositoriesView::AllAttached()
202 {
203 	BView::AllAttached();
204 	fRemoveButton->SetTarget(this);
205 	fEnableButton->SetTarget(this);
206 	fDisableButton->SetTarget(this);
207 	fListView->SetTarget(this);
208 	fRemoveButton->SetEnabled(false);
209 	fEnableButton->SetEnabled(false);
210 	fDisableButton->SetEnabled(false);
211 	_UpdateStatusView();
212 	_InitList();
213 }
214 
215 
216 void
AttachedToWindow()217 RepositoriesView::AttachedToWindow()
218 {
219 	fTaskLooper = new TaskLooper(BMessenger(this));
220 }
221 
222 
223 void
MessageReceived(BMessage * message)224 RepositoriesView::MessageReceived(BMessage* message)
225 {
226 	switch (message->what)
227 	{
228 		case REMOVE_REPOS:
229 		{
230 			RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
231 			if (!rowItem || !fRemoveButton->IsEnabled())
232 				break;
233 
234 			BString text;
235 			// More than one selected row
236 			if (fListView->CurrentSelection(rowItem)) {
237 				text.SetTo(B_TRANSLATE_COMMENT("Remove these repositories?",
238 					"Removal alert confirmation message"));
239 				text.Append("\n");
240 			}
241 			// Only one selected row
242 			else {
243 				text.SetTo(B_TRANSLATE_COMMENT("Remove this repository?",
244 					"Removal alert confirmation message"));
245 				text.Append("\n");
246 			}
247 			float minWidth = 0;
248 			while (rowItem) {
249 				BString repoText;
250 				repoText.Append("\n").Append(rowItem->Name())
251 					.Append(" (").Append(rowItem->Url()).Append(")");
252 				minWidth = std::max(minWidth, StringWidth(repoText.String()));
253 				text.Append(repoText);
254 				rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
255 			}
256 			minWidth = std::min(minWidth, Frame().Width());
257 				// Ensure alert window isn't much larger than the main window
258 			BAlert* alert = new BAlert("confirm", text, kRemoveLabel,
259 				kCancelLabel, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
260 			alert->TextView()->SetExplicitMinSize(BSize(minWidth, B_SIZE_UNSET));
261 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
262 			int32 answer = alert->Go();
263 			// User presses Cancel button
264 			if (answer)
265 				break;
266 
267 			rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
268 			while (rowItem) {
269 				RepoRow* oldRow = rowItem;
270 				rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
271 				fListView->RemoveRow(oldRow);
272 				delete oldRow;
273 			}
274 			_SaveList();
275 			break;
276 		}
277 
278 		case LIST_SELECTION_CHANGED:
279 			_UpdateButtons();
280 			break;
281 
282 		case ITEM_INVOKED:
283 		{
284 			// Simulates pressing whichever is the enabled button
285 			if (fEnableButton->IsEnabled()) {
286 				BMessage invokeMessage(ENABLE_BUTTON_PRESSED);
287 				MessageReceived(&invokeMessage);
288 			} else if (fDisableButton->IsEnabled()) {
289 				BMessage invokeMessage(DISABLE_BUTTON_PRESSED);
290 				MessageReceived(&invokeMessage);
291 			}
292 			break;
293 		}
294 
295 		case ENABLE_BUTTON_PRESSED:
296 		{
297 			BStringList names;
298 			bool paramsOK = true;
299 			// Check if there are multiple selections of the same repository,
300 			// pkgman won't like that
301 			RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
302 			while (rowItem) {
303 				if (names.HasString(rowItem->Name())
304 					&& kNewRepoDefaultName.Compare(rowItem->Name()) != 0) {
305 					(new BAlert("duplicate",
306 						B_TRANSLATE_COMMENT("Only one URL for each repository can "
307 							"be enabled. Please change your selections.",
308 							"Error message"),
309 						kOKLabel, NULL, NULL,
310 						B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(NULL);
311 					paramsOK = false;
312 					break;
313 				} else
314 					names.Add(rowItem->Name());
315 				rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
316 			}
317 			if (paramsOK) {
318 				_AddSelectedRowsToQueue();
319 				_UpdateButtons();
320 			}
321 			break;
322 		}
323 
324 		case DISABLE_BUTTON_PRESSED:
325 			_AddSelectedRowsToQueue();
326 			_UpdateButtons();
327 			break;
328 
329 		case TASK_STARTED:
330 		{
331 			int16 count;
332 			status_t result1 = message->FindInt16(key_count, &count);
333 			RepoRow* rowItem;
334 			status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
335 			if (result1 == B_OK && result2 == B_OK)
336 				_TaskStarted(rowItem, count);
337 			break;
338 		}
339 
340 		case TASK_COMPLETED_WITH_ERRORS:
341 		{
342 			BString errorDetails;
343 			status_t result = message->FindString(key_details, &errorDetails);
344 			if (result == B_OK) {
345 				(new BAlert("error", errorDetails, kOKLabel, NULL, NULL,
346 					B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(NULL);
347 			}
348 			BString repoName = message->GetString(key_name,
349 				kNewRepoDefaultName.String());
350 			int16 count;
351 			status_t result1 = message->FindInt16(key_count, &count);
352 			RepoRow* rowItem;
353 			status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
354 			if (result1 == B_OK && result2 == B_OK) {
355 				_TaskCompleted(rowItem, count, repoName);
356 				// Refresh the enabled status of each row since it is unsure what
357 				// caused the error
358 				_RefreshList();
359 			}
360 			_UpdateButtons();
361 			break;
362 		}
363 
364 		case TASK_COMPLETED:
365 		{
366 			BString repoName = message->GetString(key_name,
367 				kNewRepoDefaultName.String());
368 			int16 count;
369 			status_t result1 = message->FindInt16(key_count, &count);
370 			RepoRow* rowItem;
371 			status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
372 			if (result1 == B_OK && result2 == B_OK) {
373 				_TaskCompleted(rowItem, count, repoName);
374 				// If the completed row has siblings then enabling this row may
375 				// have disabled one of the other siblings, do full refresh.
376 				if (rowItem->HasSiblings() && rowItem->IsEnabled())
377 					_RefreshList();
378 			}
379 			_UpdateButtons();
380 			break;
381 		}
382 
383 		case TASK_CANCELED:
384 		{
385 			int16 count;
386 			status_t result1 = message->FindInt16(key_count, &count);
387 			RepoRow* rowItem;
388 			status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
389 			if (result1 == B_OK && result2 == B_OK)
390 				_TaskCanceled(rowItem, count);
391 			// Refresh the enabled status of each row since it is unsure what
392 			// caused the cancelation
393 			_RefreshList();
394 			_UpdateButtons();
395 			break;
396 		}
397 
398 		case UPDATE_LIST:
399 			_RefreshList();
400 			_UpdateButtons();
401 			break;
402 
403 		case STATUS_VIEW_COMPLETED_TIMEOUT:
404 		{
405 			int32 timerID;
406 			status_t result = message->FindInt32(key_ID, &timerID);
407 			if (result == B_OK && timerID == fLastCompletedTimerId)
408 				_UpdateStatusView();
409 			break;
410 		}
411 
412 		default:
413 			BView::MessageReceived(message);
414 	}
415 }
416 
417 
418 void
_AddSelectedRowsToQueue()419 RepositoriesView::_AddSelectedRowsToQueue()
420 {
421 	RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
422 	while (rowItem) {
423 		rowItem->SetTaskState(STATE_IN_QUEUE_WAITING);
424 		BMessage taskMessage(DO_TASK);
425 		taskMessage.AddPointer(key_rowptr, rowItem);
426 		fTaskLooper->PostMessage(&taskMessage);
427 		rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
428 	}
429 }
430 
431 
432 void
_TaskStarted(RepoRow * rowItem,int16 count)433 RepositoriesView::_TaskStarted(RepoRow* rowItem, int16 count)
434 {
435 	fRunningTaskCount = count;
436 	rowItem->SetTaskState(STATE_IN_QUEUE_RUNNING);
437 	// Only present a status count if there is more than one task in queue
438 	if (count > 1) {
439 		_UpdateStatusView();
440 		fShowCompletedStatus = true;
441 	}
442 }
443 
444 
445 void
_TaskCompleted(RepoRow * rowItem,int16 count,BString & newName)446 RepositoriesView::_TaskCompleted(RepoRow* rowItem, int16 count, BString& newName)
447 {
448 	fRunningTaskCount = count;
449 	_ShowCompletedStatusIfDone();
450 
451 	// Update row state and values
452 	rowItem->SetTaskState(STATE_NOT_IN_QUEUE);
453 	if (kNewRepoDefaultName.Compare(rowItem->Name()) == 0
454 		&& newName.Compare("") != 0) {
455 		rowItem->SetName(newName.String());
456 		_SaveList();
457 	}
458 	_UpdateFromRepoConfig(rowItem);
459 }
460 
461 
462 void
_TaskCanceled(RepoRow * rowItem,int16 count)463 RepositoriesView::_TaskCanceled(RepoRow* rowItem, int16 count)
464 {
465 	fRunningTaskCount = count;
466 	_ShowCompletedStatusIfDone();
467 
468 	// Update row state and values
469 	rowItem->SetTaskState(STATE_NOT_IN_QUEUE);
470 	_UpdateFromRepoConfig(rowItem);
471 }
472 
473 
474 void
_ShowCompletedStatusIfDone()475 RepositoriesView::_ShowCompletedStatusIfDone()
476 {
477 	// If this is the last task show completed status text for 3 seconds
478 	if (fRunningTaskCount == 0 && fShowCompletedStatus) {
479 		fListStatusView->SetText(kStatusCompletedText);
480 		fLastCompletedTimerId = rand();
481 		BMessage timerMessage(STATUS_VIEW_COMPLETED_TIMEOUT);
482 		timerMessage.AddInt32(key_ID, fLastCompletedTimerId);
483 		new BMessageRunner(this, &timerMessage, 3000000, 1);
484 		fShowCompletedStatus = false;
485 	} else
486 		_UpdateStatusView();
487 }
488 
489 
490 void
_UpdateFromRepoConfig(RepoRow * rowItem)491 RepositoriesView::_UpdateFromRepoConfig(RepoRow* rowItem)
492 {
493 	BPackageKit::BPackageRoster pRoster;
494 	BPackageKit::BRepositoryConfig repoConfig;
495 	BString repoName(rowItem->Name());
496 	status_t result = pRoster.GetRepositoryConfig(repoName, &repoConfig);
497 	// Repo name was found and the URL matches
498 	if (result == B_OK && repoConfig.BaseURL() == rowItem->Url())
499 		rowItem->SetEnabled(true);
500 	else
501 		rowItem->SetEnabled(false);
502 }
503 
504 
505 void
AddManualRepository(BString url)506 RepositoriesView::AddManualRepository(BString url)
507 {
508 	BUrl newRepoUrl(url);
509 	if (!newRepoUrl.IsValid())
510 		return;
511 
512 	BString name(kNewRepoDefaultName);
513 	int32 index;
514 	int32 listCount = fListView->CountRows();
515 	for (index = 0; index < listCount; index++) {
516 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
517 		BUrl rowRepoUrl(repoItem->Url());
518 		// Find an already existing URL
519 		if (newRepoUrl == rowRepoUrl) {
520 			(new BAlert("duplicate",
521 				B_TRANSLATE_COMMENT("This repository URL already exists.",
522 					"Error message"),
523 				kOKLabel))->Go(NULL);
524 			return;
525 		}
526 	}
527 	RepoRow* newRepo = _AddRepo(name, url, false);
528 	_FindSiblings();
529 	fListView->DeselectAll();
530 	fListView->AddToSelection(newRepo);
531 	_UpdateButtons();
532 	_SaveList();
533 	if (fEnableButton->IsEnabled())
534 		fEnableButton->Invoke();
535 }
536 
537 
538 status_t
_EmptyList()539 RepositoriesView::_EmptyList()
540 {
541 	BRow* row = fListView->RowAt((int32)0, NULL);
542 	while (row != NULL) {
543 		fListView->RemoveRow(row);
544 		delete row;
545 		row = fListView->RowAt((int32)0, NULL);
546 	}
547 	return B_OK;
548 }
549 
550 
551 void
_InitList()552 RepositoriesView::_InitList()
553 {
554 	// Get list of known repositories from the settings file
555 	int32 index, repoCount;
556 	BStringList nameList, urlList;
557 	status_t result = fSettings.GetRepositories(repoCount, nameList, urlList);
558 	if (result == B_OK) {
559 		BString name, url;
560 		for (index = 0; index < repoCount; index++) {
561 			name = nameList.StringAt(index);
562 			url = urlList.StringAt(index);
563 			_AddRepo(name, url, false);
564 		}
565 	}
566 	_UpdateListFromRoster();
567 	fListView->SetSortColumn(fListView->ColumnAt(kUrlColumn), false, true);
568 	fListView->ResizeAllColumnsToPreferred();
569 }
570 
571 
572 void
_RefreshList()573 RepositoriesView::_RefreshList()
574 {
575 	// Clear enabled status on all rows
576 	int32 index, listCount = fListView->CountRows();
577 	for (index = 0; index < listCount; index++) {
578 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
579 		if (repoItem->TaskState() == STATE_NOT_IN_QUEUE)
580 			repoItem->SetEnabled(false);
581 	}
582 	// Get current list of enabled repositories
583 	_UpdateListFromRoster();
584 }
585 
586 
587 void
_UpdateListFromRoster()588 RepositoriesView::_UpdateListFromRoster()
589 {
590 	// Get list of currently enabled repositories
591 	BStringList repositoryNames;
592 	BPackageKit::BPackageRoster pRoster;
593 	status_t result = pRoster.GetRepositoryNames(repositoryNames);
594 	if (result != B_OK) {
595 		(new BAlert("error",
596 			B_TRANSLATE_COMMENT("Repositories could not retrieve the names of "
597 				"the currently enabled repositories.", "Alert error message"),
598 			kOKLabel, NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
599 		return;
600 	}
601 	BPackageKit::BRepositoryConfig repoConfig;
602 	int16 index, count = repositoryNames.CountStrings();
603 	for (index = 0; index < count; index++) {
604 		const BString& repoName = repositoryNames.StringAt(index);
605 		result = pRoster.GetRepositoryConfig(repoName, &repoConfig);
606 		if (result == B_OK)
607 			_AddRepo(repoName, repoConfig.BaseURL(), true);
608 		else {
609 			BString text(B_TRANSLATE_COMMENT("Error getting repository"
610 				" configuration for %name%.", "Alert error message, "
611 				"do not translate %name%"));
612 			text.ReplaceFirst("%name%", repoName);
613 			(new BAlert("error", text, kOKLabel))->Go(NULL);
614 		}
615 	}
616 	_FindSiblings();
617 	_SaveList();
618 }
619 
620 
621 void
_SaveList()622 RepositoriesView::_SaveList()
623 {
624 	BStringList nameList, urlList;
625 	int32 index;
626 	int32 listCount = fListView->CountRows();
627 	for (index = 0; index < listCount; index++) {
628 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
629 		nameList.Add(repoItem->Name());
630 		urlList.Add(repoItem->Url());
631 	}
632 	fSettings.SetRepositories(nameList, urlList);
633 }
634 
635 
636 RepoRow*
_AddRepo(BString name,BString url,bool enabled)637 RepositoriesView::_AddRepo(BString name, BString url, bool enabled)
638 {
639 	// URL must be valid
640 	BUrl repoUrl(url);
641 	if (!repoUrl.IsValid())
642 		return NULL;
643 	int32 index;
644 	int32 listCount = fListView->CountRows();
645 	// Find if the repo already exists in list
646 	for (index = 0; index < listCount; index++) {
647 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
648 		BUrl itemUrl(repoItem->Url());
649 		if (repoUrl == itemUrl) {
650 			// update name and enabled values
651 			if (name != repoItem->Name())
652 				repoItem->SetName(name.String());
653 			repoItem->SetEnabled(enabled);
654 			return repoItem;
655 		}
656 	}
657 	RepoRow* addedRow = new RepoRow(name, url, enabled);
658 	fListView->AddRow(addedRow);
659 	return addedRow;
660 }
661 
662 
663 void
_FindSiblings()664 RepositoriesView::_FindSiblings()
665 {
666 	BStringList namesFound, namesWithSiblings;
667 	int32 index, listCount = fListView->CountRows();
668 	// Find repository names that are duplicated
669 	for (index = 0; index < listCount; index++) {
670 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
671 		BString name = repoItem->Name();
672 		// Ignore newly added repos since we don't know the real name yet
673 		if (name.Compare(kNewRepoDefaultName)==0)
674 			continue;
675 		// First time a name is found- no sibling (yet)
676 		if (!namesFound.HasString(name))
677 			namesFound.Add(name);
678 		// Name was already found once so this name has 2 or more siblings
679 		else if (!namesWithSiblings.HasString(name))
680 			namesWithSiblings.Add(name);
681 	}
682 	// Set sibling values for each row
683 	for (index = 0; index < listCount; index++) {
684 		RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
685 		BString name = repoItem->Name();
686 		repoItem->SetHasSiblings(namesWithSiblings.HasString(name));
687 	}
688 }
689 
690 
691 void
_UpdateButtons()692 RepositoriesView::_UpdateButtons()
693 {
694 	RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
695 	// At least one row is selected
696 	if (rowItem) {
697 		bool someAreEnabled = false;
698 		bool someAreDisabled = false;
699 		bool someAreInQueue = false;
700 		int32 selectedCount = 0;
701 		RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
702 		while (rowItem) {
703 			selectedCount++;
704 			uint32 taskState = rowItem->TaskState();
705 			if ( taskState == STATE_IN_QUEUE_WAITING
706 				|| taskState == STATE_IN_QUEUE_RUNNING) {
707 				someAreInQueue = true;
708 			}
709 			if (rowItem->IsEnabled())
710 				someAreEnabled = true;
711 			else
712 				someAreDisabled = true;
713 			rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
714 		}
715 		// Change button labels depending on which rows are selected
716 		if (selectedCount > 1) {
717 			fEnableButton->SetLabel(kLabelEnableAll);
718 			fDisableButton->SetLabel(kLabelDisableAll);
719 		} else {
720 			fEnableButton->SetLabel(kLabelEnable);
721 			fDisableButton->SetLabel(kLabelDisable);
722 		}
723 		// Set which buttons should be enabled
724 		fRemoveButton->SetEnabled(!someAreEnabled && !someAreInQueue);
725 		if ((someAreEnabled && someAreDisabled) || someAreInQueue) {
726 			// there are a mix of enabled and disabled repositories selected
727 			fEnableButton->SetEnabled(false);
728 			fDisableButton->SetEnabled(false);
729 		} else {
730 			fEnableButton->SetEnabled(someAreDisabled);
731 			fDisableButton->SetEnabled(someAreEnabled);
732 		}
733 
734 	} else {
735 		// No selected rows
736 		fEnableButton->SetLabel(kLabelEnable);
737 		fDisableButton->SetLabel(kLabelDisable);
738 		fEnableButton->SetEnabled(false);
739 		fDisableButton->SetEnabled(false);
740 		fRemoveButton->SetEnabled(false);
741 	}
742 }
743 
744 
745 void
_UpdateStatusView()746 RepositoriesView::_UpdateStatusView()
747 {
748 	if (fRunningTaskCount) {
749 		BString text(kStatusViewText);
750 		text.Append(" ");
751 		text << fRunningTaskCount;
752 		fListStatusView->SetText(text);
753 	} else
754 		fListStatusView->SetText("");
755 }
756