2 * Copyright 2017 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
10 #include "RepositoriesView.h"
16 #include <ColumnTypes.h>
17 #include <LayoutBuilder.h>
18 #include <MessageRunner.h>
19 #include <ScrollBar.h>
20 #include <SeparatorView.h>
22 #include <package/PackageRoster.h>
23 #include <package/RepositoryConfig.h>
25 #include "constants.h"
27 #undef B_TRANSLATION_CONTEXT
28 #define B_TRANSLATION_CONTEXT "RepositoriesView"
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");
53 RepositoriesListView::RepositoriesListView(const char* name
)
55 BColumnListView(name
, B_NAVIGABLE
, B_PLAIN_BORDER
)
61 RepositoriesListView::KeyDown(const char* bytes
, int32 numBytes
)
65 Window()->PostMessage(DELETE_KEY_PRESSED
);
69 BColumnListView::KeyDown(bytes
, numBytes
);
74 RepositoriesView::RepositoriesView()
76 BGroupView("RepositoriesView"),
78 fShowCompletedStatus(false),
80 fLastCompletedTimerId(0)
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
));
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
);
103 // Set a smaller fixed font size and slightly lighten text color
104 BFont
font(be_plain_font
);
106 fListStatusView
->SetFont(&font
, B_FONT_SIZE
);
107 fListStatusView
->SetHighUIColor(fListStatusView
->HighUIColor(), .9f
);
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)
120 .AddGroup(B_HORIZONTAL
, 0)
121 .SetInsets(2, 0, 0, 0)
122 .Add(fListStatusView
)
125 .Add(new BSeparatorView(B_HORIZONTAL
))
128 fListView
->AddStatusView(fStatusContainerView
);
131 fEnableButton
= new BButton(kLabelEnable
,
132 new BMessage(ENABLE_BUTTON_PRESSED
));
133 fDisableButton
= new BButton(kLabelDisable
,
134 new BMessage(DISABLE_BUTTON_PRESSED
));
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
);
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
);
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:",
158 .AddStrut(B_USE_DEFAULT_SPACING
)
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
)
171 .Add(new BSeparatorView(B_HORIZONTAL
))
173 .Add(new BSeparatorView(B_VERTICAL
))
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)
190 RepositoriesView::~RepositoriesView()
201 RepositoriesView::AllAttached()
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);
217 RepositoriesView::AttachedToWindow()
219 fTaskLooper
= new TaskLooper(BMessenger(this));
224 RepositoriesView::MessageReceived(BMessage
* message
)
226 switch (message
->what
)
230 RepoRow
* rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection());
231 if (!rowItem
|| !fRemoveButton
->IsEnabled())
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"));
241 // Only one selected row
243 text
.SetTo(B_TRANSLATE_COMMENT("Remove this repository?",
244 "Removal alert confirmation message"));
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
));
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
267 rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection());
269 RepoRow
* oldRow
= rowItem
;
270 rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection(rowItem
));
271 fListView
->RemoveRow(oldRow
);
278 case LIST_SELECTION_CHANGED
:
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
);
295 case ENABLE_BUTTON_PRESSED
:
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());
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.",
309 kOKLabel
, NULL
, NULL
,
310 B_WIDTH_AS_USUAL
, B_STOP_ALERT
))->Go(NULL
);
314 names
.Add(rowItem
->Name());
315 rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection(rowItem
));
318 _AddSelectedRowsToQueue();
324 case DISABLE_BUTTON_PRESSED
:
325 _AddSelectedRowsToQueue();
332 status_t result1
= message
->FindInt16(key_count
, &count
);
334 status_t result2
= message
->FindPointer(key_rowptr
, (void**)&rowItem
);
335 if (result1
== B_OK
&& result2
== B_OK
)
336 _TaskStarted(rowItem
, count
);
340 case TASK_COMPLETED_WITH_ERRORS
:
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
);
348 BString repoName
= message
->GetString(key_name
,
349 kNewRepoDefaultName
.String());
351 status_t result1
= message
->FindInt16(key_count
, &count
);
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
366 BString repoName
= message
->GetString(key_name
,
367 kNewRepoDefaultName
.String());
369 status_t result1
= message
->FindInt16(key_count
, &count
);
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())
386 status_t result1
= message
->FindInt16(key_count
, &count
);
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
403 case STATUS_VIEW_COMPLETED_TIMEOUT
:
406 status_t result
= message
->FindInt32(key_ID
, &timerID
);
407 if (result
== B_OK
&& timerID
== fLastCompletedTimerId
)
413 BView::MessageReceived(message
);
419 RepositoriesView::_AddSelectedRowsToQueue()
421 RepoRow
* rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection());
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
));
433 RepositoriesView::_TaskStarted(RepoRow
* rowItem
, int16 count
)
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
440 fShowCompletedStatus
= true;
446 RepositoriesView::_TaskCompleted(RepoRow
* rowItem
, int16 count
, BString
& newName
)
448 fRunningTaskCount
= count
;
449 _ShowCompletedStatusIfDone();
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());
458 _UpdateFromRepoConfig(rowItem
);
463 RepositoriesView::_TaskCanceled(RepoRow
* rowItem
, int16 count
)
465 fRunningTaskCount
= count
;
466 _ShowCompletedStatusIfDone();
468 // Update row state and values
469 rowItem
->SetTaskState(STATE_NOT_IN_QUEUE
);
470 _UpdateFromRepoConfig(rowItem
);
475 RepositoriesView::_ShowCompletedStatusIfDone()
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;
491 RepositoriesView::_UpdateFromRepoConfig(RepoRow
* rowItem
)
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);
501 rowItem
->SetEnabled(false);
506 RepositoriesView::AddManualRepository(BString url
)
508 BUrl
newRepoUrl(url
);
509 if (!newRepoUrl
.IsValid())
512 BString
name(kNewRepoDefaultName
);
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.",
523 kOKLabel
))->Go(NULL
);
527 RepoRow
* newRepo
= _AddRepo(name
, url
, false);
529 fListView
->DeselectAll();
530 fListView
->AddToSelection(newRepo
);
533 if (fEnableButton
->IsEnabled())
534 fEnableButton
->Invoke();
539 RepositoriesView::_EmptyList()
541 BRow
* row
= fListView
->RowAt((int32
)0, NULL
);
542 while (row
!= NULL
) {
543 fListView
->RemoveRow(row
);
545 row
= fListView
->RowAt((int32
)0, NULL
);
552 RepositoriesView::_InitList()
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
) {
560 for (index
= 0; index
< repoCount
; index
++) {
561 name
= nameList
.StringAt(index
);
562 url
= urlList
.StringAt(index
);
563 _AddRepo(name
, url
, false);
566 _UpdateListFromRoster();
567 fListView
->SetSortColumn(fListView
->ColumnAt(kUrlColumn
), false, true);
568 fListView
->ResizeAllColumnsToPreferred();
573 RepositoriesView::_RefreshList()
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);
582 // Get current list of enabled repositories
583 _UpdateListFromRoster();
588 RepositoriesView::_UpdateListFromRoster()
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
) {
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
);
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
);
607 _AddRepo(repoName
, repoConfig
.BaseURL(), true);
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
);
622 RepositoriesView::_SaveList()
624 BStringList nameList
, urlList
;
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());
632 fSettings
.SetRepositories(nameList
, urlList
);
637 RepositoriesView::_AddRepo(BString name
, BString url
, bool enabled
)
641 if (!repoUrl
.IsValid())
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
);
657 RepoRow
* addedRow
= new RepoRow(name
, url
, enabled
);
658 fListView
->AddRow(addedRow
);
664 RepositoriesView::_FindSiblings()
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)
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
);
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
));
692 RepositoriesView::_UpdateButtons()
694 RepoRow
* rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection());
695 // At least one row is selected
697 bool someAreEnabled
= false;
698 bool someAreDisabled
= false;
699 bool someAreInQueue
= false;
700 int32 selectedCount
= 0;
701 RepoRow
* rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection());
704 uint32 taskState
= rowItem
->TaskState();
705 if ( taskState
== STATE_IN_QUEUE_WAITING
706 || taskState
== STATE_IN_QUEUE_RUNNING
) {
707 someAreInQueue
= true;
709 if (rowItem
->IsEnabled())
710 someAreEnabled
= true;
712 someAreDisabled
= true;
713 rowItem
= dynamic_cast<RepoRow
*>(fListView
->CurrentSelection(rowItem
));
715 // Change button labels depending on which rows are selected
716 if (selectedCount
> 1) {
717 fEnableButton
->SetLabel(kLabelEnableAll
);
718 fDisableButton
->SetLabel(kLabelDisableAll
);
720 fEnableButton
->SetLabel(kLabelEnable
);
721 fDisableButton
->SetLabel(kLabelDisable
);
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);
730 fEnableButton
->SetEnabled(someAreDisabled
);
731 fDisableButton
->SetEnabled(someAreEnabled
);
736 fEnableButton
->SetLabel(kLabelEnable
);
737 fDisableButton
->SetLabel(kLabelDisable
);
738 fEnableButton
->SetEnabled(false);
739 fDisableButton
->SetEnabled(false);
740 fRemoveButton
->SetEnabled(false);
746 RepositoriesView::_UpdateStatusView()
748 if (fRunningTaskCount
) {
749 BString
text(kStatusViewText
);
751 text
<< fRunningTaskCount
;
752 fListStatusView
->SetText(text
);
754 fListStatusView
->SetText("");