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
31static const BString kTitleEnabled =
32	B_TRANSLATE_COMMENT("Status", "Column title");
33static const BString kTitleName = B_TRANSLATE_COMMENT("Name", "Column title");
34static const BString kTitleUrl = B_TRANSLATE_COMMENT("URL", "Column title");
35static const BString kLabelRemove =
36	B_TRANSLATE_COMMENT("Remove", "Button label");
37static const BString kLabelRemoveAll =
38	B_TRANSLATE_COMMENT("Remove all", "Button label");
39static const BString kLabelEnable =
40	B_TRANSLATE_COMMENT("Enable", "Button label");
41static const BString kLabelEnableAll =
42	B_TRANSLATE_COMMENT("Enable all", "Button label");
43static const BString kLabelDisable =
44	B_TRANSLATE_COMMENT("Disable", "Button label");
45static const BString kLabelDisableAll =
46	B_TRANSLATE_COMMENT("Disable all", "Button label");
47static const BString kStatusViewText =
48	B_TRANSLATE_COMMENT("Changes pending:", "Status view text");
49static const BString kStatusCompletedText =
50	B_TRANSLATE_COMMENT("Changes completed", "Status view text");
51
52
53RepositoriesListView::RepositoriesListView(const char* name)
54	:
55	BColumnListView(name, B_NAVIGABLE, B_PLAIN_BORDER)
56{
57}
58
59
60void
61RepositoriesListView::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
74RepositoriesView::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
190RepositoriesView::~RepositoriesView()
191{
192	if (fTaskLooper) {
193		fTaskLooper->Lock();
194		fTaskLooper->Quit();
195	}
196	_EmptyList();
197}
198
199
200void
201RepositoriesView::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
216void
217RepositoriesView::AttachedToWindow()
218{
219	fTaskLooper = new TaskLooper(BMessenger(this));
220}
221
222
223void
224RepositoriesView::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
418void
419RepositoriesView::_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
432void
433RepositoriesView::_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
445void
446RepositoriesView::_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
462void
463RepositoriesView::_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
474void
475RepositoriesView::_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
490void
491RepositoriesView::_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
505void
506RepositoriesView::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
538status_t
539RepositoriesView::_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
551void
552RepositoriesView::_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
572void
573RepositoriesView::_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
587void
588RepositoriesView::_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
621void
622RepositoriesView::_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
636RepoRow*
637RepositoriesView::_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
663void
664RepositoriesView::_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
691void
692RepositoriesView::_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
745void
746RepositoriesView::_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