Ticket #13147: 0001-Submitting-new-preflet-Repositories-for-consideratio.patch

File 0001-Submitting-new-preflet-Repositories-for-consideratio.patch, 77.8 KB (added by perelandra, 7 years ago)

Repositories preflet patch

  • new file 2001

    From 895635b4c0cd4bca2b147f99d8df35a6bc5e9546 Mon Sep 17 00:00:00 2001
    From: Brian Hill <supernova@warpmail.net>
    Date: Sun, 1 Jan 2017 17:01:37 -0500
    Subject: [PATCH] Submitting new preflet Repositories for consideration.  This
     preflet allows the user to change repositories used by package management.
    
    ---
     data/artwork/icons/Prefs_Repositories              | Bin 0 -> 17417 bytes
     src/apps/haikudepot/ui/FilterView.cpp              |   6 +-
     src/apps/haikudepot/ui/MainWindow.cpp              |  24 +-
     src/apps/haikudepot/ui/MainWindow.h                |   1 +
     src/preferences/Jamfile                            |   1 +
     src/preferences/repositories/AddRepoWindow.cpp     | 138 ++++
     src/preferences/repositories/AddRepoWindow.h       |  34 +
     src/preferences/repositories/Jamfile               |  31 +
     src/preferences/repositories/RepoRow.cpp           |  72 ++
     src/preferences/repositories/RepoRow.h             |  49 ++
     src/preferences/repositories/Repositories.cpp      |  57 ++
     src/preferences/repositories/Repositories.h        |  27 +
     src/preferences/repositories/Repositories.rdef     |  43 ++
     .../repositories/RepositoriesSettings.cpp          | 131 ++++
     .../repositories/RepositoriesSettings.h            |  40 ++
     src/preferences/repositories/RepositoriesView.cpp  | 763 +++++++++++++++++++++
     src/preferences/repositories/RepositoriesView.h    |  73 ++
     .../repositories/RepositoriesWindow.cpp            | 183 +++++
     src/preferences/repositories/RepositoriesWindow.h  |  41 ++
     src/preferences/repositories/TaskLooper.cpp        | 327 +++++++++
     src/preferences/repositories/TaskLooper.h          |  66 ++
     src/preferences/repositories/TaskTimer.cpp         | 174 +++++
     src/preferences/repositories/TaskTimer.h           |  61 ++
     src/preferences/repositories/constants.h           | 100 +++
     24 files changed, 2435 insertions(+), 7 deletions(-)
     create mode 100644 data/artwork/icons/Prefs_Repositories
     create mode 100644 src/preferences/repositories/AddRepoWindow.cpp
     create mode 100644 src/preferences/repositories/AddRepoWindow.h
     create mode 100644 src/preferences/repositories/Jamfile
     create mode 100644 src/preferences/repositories/RepoRow.cpp
     create mode 100644 src/preferences/repositories/RepoRow.h
     create mode 100644 src/preferences/repositories/Repositories.cpp
     create mode 100644 src/preferences/repositories/Repositories.h
     create mode 100644 src/preferences/repositories/Repositories.rdef
     create mode 100644 src/preferences/repositories/RepositoriesSettings.cpp
     create mode 100644 src/preferences/repositories/RepositoriesSettings.h
     create mode 100644 src/preferences/repositories/RepositoriesView.cpp
     create mode 100644 src/preferences/repositories/RepositoriesView.h
     create mode 100644 src/preferences/repositories/RepositoriesWindow.cpp
     create mode 100644 src/preferences/repositories/RepositoriesWindow.h
     create mode 100644 src/preferences/repositories/TaskLooper.cpp
     create mode 100644 src/preferences/repositories/TaskLooper.h
     create mode 100644 src/preferences/repositories/TaskTimer.cpp
     create mode 100644 src/preferences/repositories/TaskTimer.h
     create mode 100644 src/preferences/repositories/constants.h
    
    diff --git a/data/artwork/icons/Prefs_Repositories b/data/artwork/icons/Prefs_Repositories
    new file mode 100644
    index 0000000000000000000000000000000000000000..75691796ba9145a755ed6596f54d2df46460f4f9
    GIT binary patch
    literal 17417
    zcmeHOZH!b`89u|v@=e#4%2KtR5>gkWg(6KUt;}WlSkR?Q3W+9G+F^H=9hlu&W(FwO
    zG^0te_JgGnG?ugit+1xVuEZZrvF;YJYuak7V6aV1F#Iy6DX1h8CFAp)bI+Z7XU|N}
    z!YmM(lN|2e_nxo&Jn#FS_q=mizG7A5vK3436euCoe2KDkkq}`#A6e7}x3XVI)QHB$
    zRVz5g*FR0E8Y?75dGb)L5Nq&k1fKcj<EIMIcB^XN70b3~#A>v%S=GNWB4^aA(zEK9
    z2;m7=H4YE{iMIMVXAv)>r_CfM(W?ekPr|`D5GPizGEzLSdetJ{kLLpvLKQ}@)%UM#
    zVBguOM1_6BVoX5Z2sy_Ni?IQD4zf?V@}}lyIS$8q4ONXZUQo_?S>JNyow1I%7>%3M
    zrff3N85dotL}wQF!V#ipp}L9&A!<>Vp|+#G2RW}#;QCwCy^ib^NpxOhM@&BCo-L`)
    z&Ui~U-YP<}kfumWGL@lZQ^=04eVEpkE~3QZr;CtBP$!`J>C*BqLGGtZ%O8Z?PZ$4Y
    z-HvLaYnLmRbg^MJ)isDJQdH+sRF;}@4TDseTLz&I0KeH1`2BbXTj0l_EsF{GiIAIs
    zp9{GO_-{gP0{*++I6*tv?aC$a959n;Ef|T0hu6Wz;WG-Gh6?%eEz2Ok#S-}!@e;Pk
    zk40M+6Y|$VZbJSp$W6$vf!u`r6W%yM<bUAGCGuQwTRH{L^hy3n;pc;1IA%xYZx~d1
    z_VBwr)v^rq&sswNI>xnyp1Tl>A9})CkIK!-551NzfZPwgmZu>1Lr*#DK2(#p>2>82
    z`cb&aw8vUgTRiLu$V${{sH;(PsHYYR(TXeg23}7s^l>nh4h1v#<SBxjgaV%ES|cR~
    z;2#0-ui{EVxDzZ!{WI?8;d%zw#SK1EhSEWiV%MV_DZL@PefdKuovo#~0c5~Z*q33{
    zXHfl?qUBAH`z=MwABWs;DU`GJp_(k^T~{tGg+ajD<feE8Xbp;`MuP(3D7)DSD!M#M
    z6g6}>4@3T*-)`7ps2vNw%)Vz4WkExF)RMCvydbe6uEdH3e;PwyZXPUt??&7ssFP6H
    zSBJh@J|A*FXIkC@xt}xs&H4<g8Yj51>~ZCiGrC#H+2Gy91Rt53<~O2J4EY<DXGFCO
    zG7xZN+br=vfLF1_pQ_4Y!vA{6P594)+=PD&aufbfdE*4}|B)+~_%pVX_!kGXL|;K;
    zVISoh2GKXSEQ9{HEYUxR7qLa3K`V<1{Tm=Rp??qLCiK@rZbJWQZ=4|d&$)7mKI1!y
    zejuvV81XvTP5E{Zn}!Ph@-552|9>CVu1hD9NiVYMci{h^`Ml1=Re*xo?zPVEci5m7
    z4|pxrP(jZYj0Mas%RryWZcWBB;sSiNP3B`C+QC+&lHwwjn2M~6PWmwGvB&lQd?u%n
    z&e~>{Dd;@{)wIcQG$wI$enp>NC})19@&4u}u8`)!1yC-{fOfk!8Ee@fT2jeWny-=n
    zDrT`+P#+r8j`f1LI2KOC3H%Gv83f9U7o;-?AtZcsZtTYxWU@PsTqsvgN2!)MFffo`
    zmyWe2;y4%WL0g;kzJehb(sR8mdfkQf)<^*P9yiGr>OI1MY)MnYVzy&s6+vYi{T}?A
    z@Ar_DQ9eqnZf;tt<sFbKKZPutkBCPhC;nkEF(7{&GKJIRK;r6$uDm4~%VdC+5RDpw
    zz)dzC>&&#J(jBpEBGoA#>sj$9k$)#T;o$UJ;&Wb(ZX31zAFm$n@0*lcIAZ(n{u;$N
    z&XnsM2iJ%H^`A+OLYNEq`^Wiws;w;(mnkYd&qcFayW;pm2&Bs5>8?~#%|U1qo$c{-
    zA{!~1KGXypZ0PC7AhznH^CAkSpnmJTNU=KAVU27r)rs6o&#&G#yK!SxpDgw1&`rOY
    zzw75GqIb8w)_gv3G@7e@VZp>x1AV7Yhd1_}_<;H(?s)wx4lbt>Q}1*2>5X@YeI@j1
    zg{3~dF$8_G@t4K=M1RaT^8BcZqm@D(IdY_`P?iey<>m7?9<Mptcgvd1?|x(8MD+O!
    z_rCPsGavLlw`6vA?xBG`;JkPH#|x=YPVcd$K83n);X(x*)D8@6<Dj@F(5LHtgXH7Z
    zxZmj<7Tf53f=-hAfX-n_#qs5sl;$9#pX2$>W<{4JB*0qo4_MmVn?tZUHxP2Sx}Xo&
    z2jOCyyP1nYk<b+#U=@5DcKxMlc$S9u_Suh?&c6AN(O(|!ZJs=<udjR0FKV3Ik44+-
    zzP4>l`lR&a`y0a!KzDvWwZS#KH&33+6Xi>f3|3ld)v+OHl|J5^3{M~Li?!-=eys`|
    z%au+Ie3-B5l%-C^8s2}tuKRd&*6zdi47Bt|4}9m_M{awizwc+_>AB<ncAPr3_t1=a
    z4z9Uh&6w}%)aj4%`4^5>B06{$1KYUH&!e5KGuKHAlTPWRh4e_lq=Hf&abIu#iZ4q2
    ziV<m8CDfuOOmEF!b=zL{if?-=BcUL|K71Uobs-Nhda9jH9lEuU2cWJ%#pG_qzgcu1
    zfPUxz+OkkCrxCWl+WZ8){Kr_1KuzY^NgXrC29A>u%MgW0W&iP$TFv99|Cr7#^|PoP
    zGhB4+{UYQHeZn~G`oE`G@3p^C9(&=e<vKDY<T^bkvmcRkyiK;2d0%}~92pfIDKRk0
    z8M!~Hzy1Ff%Wreqaac`st`njo(HYsC$RyS#l8NlrNTwy0jLWVau}4e~a?WC)YU55E
    zFu)uD0b<dlkPro$Jt-vDPYTmUB!vWr@ac@sk?a7{Ga)mP#XwBu7J_7r#5z^`AXyVj
    z$iLvq3uIwJ;gQ7~NfzH7NjLf1kH=O#EuO?Wh~I{EHbceW+@`V}&oq8)hiPRYcEr$+
    zhmNg@N3l}t*qR>+`IVjSp7VyqxPY8Wt#GRGay(qee6RiZfP9%Jzdj)6S1sg?#-VdX
    z9A~rJ-qYw+*>q|{To9;8ORNizg}PafuaD$i0w4FJBl4C=Y)Q1@BP><MI~@t6T_VW@
    zuCA=sQ62nGra#zy^j!U!Xr?`t&c@ZSZ0<3mvNmdjVtaAN9b{jZWJlCh8#~&M*@w%y
    zk&jZ&tsc*mod)d%PQz#~Bu?p5(2l<6$_w^F4n2F}jZ_8dG}D<^j*)IlMqb29hl>%$
    z+NBtoj%OxDXbUFfH<pk;>B<X?(6@Mu@J2GixnxH0%2Be@l9HFO)ZwCpA#5p1X5g8L
    zk_6-?O423dPr32}C3NK;CA^W8&@ro|+Lhzv+KtMn2_7e;-P^<5Z=#Aw$<>@Z-*f)#
    zpr~xozI(T>pL;fXcE+JOcSY-+<L^H<_Qtp7^<P3%wj3&Avzsh|b<^u}^(<6U8hj<9
    zGJbngDk{6vovS#6ej5gEips`fQ6>Yrp@e*<D=!!j-_RqAQ%JIy3<-Re<55-(%_<z4
    z)mAn%tE@5o`drU~l1H3oOJm~C!oy`u{HeE;G2M-4$~b~Q!~UU!G1ZnZnkgmZb1aRC
    znI69}MKT>Re7{gNDfP=ZRim<}ow}>pr0}-FXi~Jb-IjsePcg`F1#<j(rBom{3(rh}
    z98Zd-eP~Mw`A%0}*oV0Fc>54vP3}WHu}jl2Mst<*b(qRbj1W0zmnAVh7-YDJVcxA2
    zF?ZmZiI@(^O~hz}3ya_}wA*c8Acp4Y5yNwrB!)3w;VU@Lm8tZ=W#r}imb@IqdWVY_
    z21})QxgF0;yd)tv@v^Cee1|J9@WQw8cp*uW7ZN0SG2{p?BQ4kN;fEf}qlH!?X(3sS
    z{Aw{%*!C|bm4^RAc=0LeDk<_ugNl(->ws03OZlCkN=ju(i8zuHQClUYX(={GnX<6|
    uMQwQ8SG`M0U6quphm_eaDUm8ER}U$3T~el1Nx6DRkslqwmDXPhDgOhynMC6N
  • src/apps/haikudepot/ui/FilterView.cpp

    literal 0
    HcmV?d00001
    
    diff --git a/src/apps/haikudepot/ui/FilterView.cpp b/src/apps/haikudepot/ui/FilterView.cpp
    index e8eb056..be2d0ca 100644
    a b FilterView::FilterView()  
    4646    fShowField = new BMenuField("category", B_TRANSLATE("Category:"), showMenu);
    4747
    4848    // Construct repository popup
    49     BPopUpMenu* repositoryMenu = new BPopUpMenu(B_TRANSLATE("Depot"));
    50     fRepositoryField = new BMenuField("repository", B_TRANSLATE("Depot:"),
     49    BPopUpMenu* repositoryMenu = new BPopUpMenu(B_TRANSLATE("Repository"));
     50    fRepositoryField = new BMenuField("repository", B_TRANSLATE("Repository:"),
    5151        repositoryMenu);
    5252
    5353    // Construct search terms field
    FilterView::AdoptModel(const Model& model)  
    125125    BMenu* repositoryMenu = fRepositoryField->Menu();
    126126        repositoryMenu->RemoveItems(0, repositoryMenu->CountItems(), true);
    127127
    128     repositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All depots"),
     128    repositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
    129129        new BMessage(MSG_DEPOT_SELECTED)));
    130130
    131131    repositoryMenu->AddItem(new BSeparatorItem());
  • src/apps/haikudepot/ui/MainWindow.cpp

    diff --git a/src/apps/haikudepot/ui/MainWindow.cpp b/src/apps/haikudepot/ui/MainWindow.cpp
    index 8884f54..207483c 100644
    a b  
    2424#include <MenuBar.h>
    2525#include <MenuItem.h>
    2626#include <Messenger.h>
     27#include <Roster.h>
    2728#include <Screen.h>
    2829#include <ScrollView.h>
    2930#include <StringList.h>
     
    6364
    6465enum {
    6566    MSG_MODEL_WORKER_DONE       = 'mmwd',
    66     MSG_REFRESH_DEPOTS          = 'mrdp',
     67    MSG_REFRESH_REPOS           = 'mrrp',
     68    MSG_MANAGE_REPOS            = 'mmrp',
    6769    MSG_LOG_IN                  = 'lgin',
    6870    MSG_LOG_OUT                 = 'lgot',
    6971    MSG_AUTHORIZATION_CHANGED   = 'athc',
    MainWindow::MessageReceived(BMessage* message)  
    316318            _StartRefreshWorker(false);
    317319            break;
    318320
    319         case MSG_REFRESH_DEPOTS:
     321        case MSG_REFRESH_REPOS:
    320322            _StartRefreshWorker(true);
    321323            break;
    322324
     325        case MSG_MANAGE_REPOS:
     326            _OpenRepositoriesPreflet();
     327            break;
     328
    323329        case MSG_LOG_IN:
    324330            _OpenLoginWindow(BMessage());
    325331            break;
    void  
    567573MainWindow::_BuildMenu(BMenuBar* menuBar)
    568574{
    569575    BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
    570     menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh depots"),
    571             new BMessage(MSG_REFRESH_DEPOTS)));
     576    menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
     577            new BMessage(MSG_REFRESH_REPOS)));
     578    menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"),
     579            new BMessage(MSG_MANAGE_REPOS)));
    572580
    573581    menuBar->AddItem(menu);
    574582
    MainWindow::_StartRefreshWorker(bool force)  
    11001108}
    11011109
    11021110
     1111void
     1112MainWindow::_OpenRepositoriesPreflet()
     1113{
     1114    BRoster roster;
     1115    roster.Launch("application/x-vnd.Haiku-Repositories");
     1116}
     1117
     1118
    11031119status_t
    11041120MainWindow::_RefreshModelThreadWorker(void* arg)
    11051121{
  • src/apps/haikudepot/ui/MainWindow.h

    diff --git a/src/apps/haikudepot/ui/MainWindow.h b/src/apps/haikudepot/ui/MainWindow.h
    index 784350a..5064763 100644
    a b private:  
    7575            void                _RefreshPackageList(bool force);
    7676
    7777            void                _StartRefreshWorker(bool force = false);
     78            void                _OpenRepositoriesPreflet();
    7879    static  status_t            _RefreshModelThreadWorker(void* arg);
    7980    static  status_t            _PackageActionWorker(void* arg);
    8081    static  status_t            _PopulatePackageWorker(void* arg);
  • src/preferences/Jamfile

    diff --git a/src/preferences/Jamfile b/src/preferences/Jamfile
    index 39254de..f152be4 100644
    a b SubInclude HAIKU_TOP src preferences mouse ;  
    1717SubInclude HAIKU_TOP src preferences network ;
    1818SubInclude HAIKU_TOP src preferences notifications ;
    1919SubInclude HAIKU_TOP src preferences printers ;
     20SubInclude HAIKU_TOP src preferences repositories ;
    2021SubInclude HAIKU_TOP src preferences screen ;
    2122SubInclude HAIKU_TOP src preferences screensaver ;
    2223SubInclude HAIKU_TOP src preferences shortcuts ;
  • new file src/preferences/repositories/AddRepoWindow.cpp

    diff --git a/src/preferences/repositories/AddRepoWindow.cpp b/src/preferences/repositories/AddRepoWindow.cpp
    new file mode 100644
    index 0000000..9fefa56
    - +  
     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 "AddRepoWindow.h"
     11
     12#include <Alert.h>
     13#include <Application.h>
     14#include <Catalog.h>
     15#include <Clipboard.h>
     16#include <LayoutBuilder.h>
     17
     18#include "constants.h"
     19
     20#undef B_TRANSLATION_CONTEXT
     21#define B_TRANSLATION_CONTEXT "AddRepoWindow"
     22
     23static float sAddWindowWidth = 500.0;
     24
     25
     26AddRepoWindow::AddRepoWindow(BRect size, BLooper* looper)
     27    :
     28    BWindow(BRect(0, 0, sAddWindowWidth, 10), "AddWindow", B_MODAL_WINDOW,
     29        B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
     30    fReplyLooper(looper)
     31{
     32    fView = new BView("view", B_SUPPORTS_LAYOUT);
     33    fText = new BTextControl("text", B_TRANSLATE_COMMENT("Repository URL:",
     34        "Text box label"), "", new BMessage(ADD_BUTTON_PRESSED));
     35    fAddButton = new BButton(B_TRANSLATE_COMMENT("Add", "Button label"),
     36        new BMessage(ADD_BUTTON_PRESSED));
     37    fAddButton->MakeDefault(true);
     38    fCancelButton = new BButton(kCancelLabel,
     39        new BMessage(CANCEL_BUTTON_PRESSED));
     40
     41    BLayoutBuilder::Group<>(fView, B_VERTICAL)
     42        .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
     43            B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING)
     44        .Add(fText)
     45        .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
     46            .AddGlue()
     47            .Add(fCancelButton)
     48            .Add(fAddButton);
     49    BLayoutBuilder::Group<>(this, B_VERTICAL)
     50        .Add(fView);
     51    _GetClipboardData();
     52    fText->MakeFocus();
     53
     54    // Move to the center of the preflet window
     55    CenterIn(size);
     56    float widthDifference = size.Width() - Frame().Width();
     57    if (widthDifference < 0)
     58        MoveBy(widthDifference / 2.0, 0);
     59    Show();
     60}
     61
     62
     63void
     64AddRepoWindow::Quit()
     65{
     66    fReplyLooper->PostMessage(ADD_WINDOW_CLOSED);
     67    BWindow::Quit();
     68}
     69
     70
     71void
     72AddRepoWindow::MessageReceived(BMessage* message)
     73{
     74    switch (message->what)
     75    {
     76        case CANCEL_BUTTON_PRESSED: {
     77            if (QuitRequested())
     78                Quit();
     79            break;
     80        }
     81        case ADD_BUTTON_PRESSED: {
     82            BString url(fText->Text());
     83            if (url != "") {
     84                // URL must have a protocol
     85                if (url.FindFirst("://") == B_ERROR) {
     86                    BAlert* alert = new BAlert("error",
     87                        B_TRANSLATE_COMMENT("The URL must start with a "
     88                            "protocol, for example http:// or https://",
     89                            "Add URL error message"),
     90                        kOKLabel, NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
     91                    alert->SetFeel(B_MODAL_APP_WINDOW_FEEL);
     92                    alert->Go(NULL);
     93                    // Center the alert to this window and move down some
     94                    alert->CenterIn(Frame());
     95                    alert->MoveBy(0, kAddWindowOffset);
     96                } else {
     97                    BMessage* addMessage = new BMessage(ADD_REPO_URL);
     98                    addMessage->AddString(key_url, url);
     99                    fReplyLooper->PostMessage(addMessage);
     100                    Quit();
     101                }
     102            }
     103            break;
     104        }
     105        default:
     106            BWindow::MessageReceived(message);
     107    }
     108}
     109
     110
     111void
     112AddRepoWindow::FrameResized(float newWidth, float newHeight)
     113{
     114    sAddWindowWidth = newWidth;
     115}
     116
     117
     118status_t
     119AddRepoWindow::_GetClipboardData()
     120{
     121    if (be_clipboard->Lock()) {
     122        const char* string;
     123        ssize_t stringLen;
     124        BMessage* clip = be_clipboard->Data();
     125        clip->FindData("text/plain", B_MIME_TYPE, (const void **)&string,
     126            &stringLen);
     127        be_clipboard->Unlock();
     128
     129        // The string must contain a web protocol
     130        BString clipString(string, stringLen);
     131        int32 ww = clipString.FindFirst("://");
     132        if (ww == B_ERROR)
     133            return B_ERROR;
     134        else
     135            fText->SetText(clipString);
     136    }
     137    return B_OK;
     138}
  • new file src/preferences/repositories/AddRepoWindow.h

    diff --git a/src/preferences/repositories/AddRepoWindow.h b/src/preferences/repositories/AddRepoWindow.h
    new file mode 100644
    index 0000000..8a9f092
    - +  
     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#ifndef ADD_REPO_WINDOW_H
     9#define ADD_REPO_WINDOW_H
     10
     11
     12#include <Button.h>
     13#include <TextControl.h>
     14#include <View.h>
     15#include <Window.h>
     16
     17
     18class AddRepoWindow : public BWindow {
     19public:
     20                            AddRepoWindow(BRect size, BLooper* looper);
     21    virtual void            MessageReceived(BMessage*);
     22    virtual void            Quit();
     23    virtual void            FrameResized(float newWidth, float newHeight);
     24
     25private:
     26    BView                   *fView;
     27    BTextControl            *fText;
     28    BButton                 *fAddButton, *fCancelButton;
     29    BLooper                 *fReplyLooper;
     30    status_t                _GetClipboardData();
     31};
     32
     33
     34#endif
  • new file src/preferences/repositories/Jamfile

    diff --git a/src/preferences/repositories/Jamfile b/src/preferences/repositories/Jamfile
    new file mode 100644
    index 0000000..c53e076
    - +  
     1SubDir HAIKU_TOP src preferences repositories ;
     2
     3UsePrivateHeaders interface ;
     4UsePrivateHeaders package ;
     5
     6Preference Repositories :
     7    AddRepoWindow.cpp
     8    RepoRow.cpp
     9    Repositories.cpp
     10    RepositoriesView.cpp
     11    RepositoriesWindow.cpp
     12    RepositoriesSettings.cpp
     13    TaskLooper.cpp
     14    TaskTimer.cpp
     15    : be translation package libcolumnlistview.a [ TargetLibstdc++ ] localestub
     16    : Repositories.rdef
     17;
     18
     19Depends Repositories : libcolumnlistview.a ;
     20
     21DoCatalogs Repositories :
     22    x-vnd.Haiku-Repositories
     23    :
     24    AddRepoWindow.cpp
     25    constants.h
     26    Repositories.cpp
     27    RepositoriesView.cpp
     28    RepositoriesWindow.cpp
     29    TaskLooper.cpp
     30    TaskTimer.cpp
     31    ;
  • new file src/preferences/repositories/RepoRow.cpp

    diff --git a/src/preferences/repositories/RepoRow.cpp b/src/preferences/repositories/RepoRow.cpp
    new file mode 100644
    index 0000000..32768f7
    - +  
     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 "RepoRow.h"
     11
     12#include <ColumnTypes.h>
     13
     14#include "constants.h"
     15
     16
     17#undef B_TRANSLATION_CONTEXT
     18#define B_TRANSLATION_CONTEXT "RepoRow"
     19
     20
     21RepoRow::RepoRow(const char* repo_name, const char* repo_url, bool enabled)
     22    :
     23    BRow(),
     24    fName(repo_name),
     25    fUrl(repo_url),
     26    fEnabled(enabled),
     27    fTaskState(STATE_NOT_IN_QUEUE)
     28{
     29    SetField(new BStringField(""), kEnabledColumn);
     30    SetField(new BStringField(fName.String()), kNameColumn);
     31    SetField(new BStringField(fUrl.String()), kUrlColumn);
     32    if (enabled)
     33        SetEnabled(enabled);
     34}
     35
     36
     37void
     38RepoRow::SetName(const char* name)
     39{
     40    BStringField* field = (BStringField*)GetField(kNameColumn);
     41    field->SetString(name);
     42    fName.SetTo(name);
     43    Invalidate();
     44}
     45
     46
     47void
     48RepoRow::SetEnabled(bool enabled)
     49{
     50    fEnabled = enabled;
     51    RefreshEnabledField();
     52}
     53
     54
     55void
     56RepoRow::RefreshEnabledField()
     57{
     58    BStringField* field = (BStringField*)GetField(kEnabledColumn);
     59    if (fTaskState == STATE_NOT_IN_QUEUE)
     60        field->SetString(fEnabled ? "\xE2\x9C\x94" : "");
     61    else
     62        field->SetString(B_UTF8_ELLIPSIS);
     63    Invalidate();
     64}
     65
     66
     67void
     68RepoRow::SetTaskState(uint32 state)
     69{
     70    fTaskState = state;
     71    RefreshEnabledField();
     72}
  • new file src/preferences/repositories/RepoRow.h

    diff --git a/src/preferences/repositories/RepoRow.h b/src/preferences/repositories/RepoRow.h
    new file mode 100644
    index 0000000..d19c8cc
    - +  
     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#ifndef REPO_ROW_H
     9#define REPO_ROW_H
     10
     11
     12#include <ColumnListView.h>
     13#include <String.h>
     14
     15
     16enum {
     17    kEnabledColumn,
     18    kNameColumn,
     19    kUrlColumn
     20};
     21
     22
     23class RepoRow : public BRow {
     24public:
     25                                RepoRow(const char* repo_name,
     26                                    const char* repo_url, bool enabled);
     27
     28            const char*         Name() const { return fName.String(); }
     29            void                SetName(const char* name);
     30            const char*         Url() const { return fUrl.String(); }
     31            void                SetEnabled(bool enabled);
     32            void                RefreshEnabledField();
     33            bool                IsEnabled() { return fEnabled; }
     34            void                SetTaskState(uint32 state);
     35            uint32              TaskState() { return fTaskState; }
     36            void                SetHasSiblings(bool hasSiblings)
     37                                    { fHasSiblings = hasSiblings; }
     38            bool                HasSiblings() { return fHasSiblings; }
     39
     40private:
     41            BString             fName;
     42            BString             fUrl;
     43            bool                fEnabled;
     44            uint32              fTaskState;
     45            bool                fHasSiblings;
     46};
     47
     48
     49#endif
  • new file src/preferences/repositories/Repositories.cpp

    diff --git a/src/preferences/repositories/Repositories.cpp b/src/preferences/repositories/Repositories.cpp
    new file mode 100644
    index 0000000..ae09388
    - +  
     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 "Repositories.h"
     11
     12#include <Alert.h>
     13#include <Catalog.h>
     14#include <Cursor.h>
     15#include <LayoutBuilder.h>
     16#include <Roster.h>
     17
     18#include "constants.h"
     19
     20#undef B_TRANSLATION_CONTEXT
     21#define B_TRANSLATION_CONTEXT "RepositoriesApplication"
     22
     23const char* kAppSignature = "application/x-vnd.Haiku-Repositories";
     24
     25
     26RepositoriesApplication::RepositoriesApplication()
     27    :
     28    BApplication(kAppSignature)
     29{
     30    fWindow = new RepositoriesWindow();
     31}
     32
     33
     34void
     35RepositoriesApplication::AboutRequested()
     36{
     37    BString text(B_TRANSLATE_COMMENT("Repositories, written by Brian Hill",
     38        "About box line 1"));
     39    text.Append("\n\n")
     40        .Append(B_TRANSLATE_COMMENT("Copyright ©2017, Haiku.",
     41            "About box line 2"))
     42        .Append("\n\n")
     43        .Append(B_TRANSLATE_COMMENT("This preflet will enable and disable "
     44            "repositories used with Haiku package management.", "About box line 3"));
     45    BAlert* aboutAlert = new BAlert("About", text, kOKLabel);
     46    aboutAlert->SetFlags(aboutAlert->Flags() | B_CLOSE_ON_ESCAPE);
     47    aboutAlert->Go();
     48}
     49
     50
     51int
     52main()
     53{
     54    RepositoriesApplication myApp;
     55    myApp.Run();
     56    return 0;
     57}
  • new file src/preferences/repositories/Repositories.h

    diff --git a/src/preferences/repositories/Repositories.h b/src/preferences/repositories/Repositories.h
    new file mode 100644
    index 0000000..3ccde25
    - +  
     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#ifndef REPOSITORIES_H
     9#define REPOSITORIES_H
     10
     11
     12#include <Application.h>
     13
     14#include "RepositoriesWindow.h"
     15
     16
     17class RepositoriesApplication : public BApplication {
     18public:
     19                            RepositoriesApplication();
     20    virtual void            AboutRequested();
     21
     22private:
     23    RepositoriesWindow      *fWindow;
     24};
     25
     26
     27#endif
  • new file src/preferences/repositories/Repositories.rdef

    diff --git a/src/preferences/repositories/Repositories.rdef b/src/preferences/repositories/Repositories.rdef
    new file mode 100644
    index 0000000..e1b1be7
    - +  
     1
     2resource app_signature "application/x-vnd.Haiku-Repositories";
     3
     4resource app_name_catalog_entry "x-vnd.Haiku-Repositories:System name:Repositories";
     5
     6resource app_flags B_SINGLE_LAUNCH;
     7
     8resource app_version {
     9    major  = 1,
     10    middle = 0,
     11    minor  = 0,
     12
     13    /* 0 = development  1 = alpha           2 = beta
     14       3 = gamma        4 = golden master   5 = final */
     15    variety = 2,
     16
     17    internal = 0,
     18
     19    short_info = "Repositories",
     20    long_info = "Repositories ©2017 Haiku"
     21};
     22
     23
     24resource vector_icon {
     25    $"6E6369660C03010000020006023B9FE037664CBA16573E39B04A01E3449F7E00"
     26    $"FFFFFFFFEBEFFF020006023C96323A4D3FBAFC013D5A974B57A549844D00C1CC"
     27    $"FFFFFFFFFF02000602BA40DA3C98EBBD5E1FBAEBF04A3DF04AD89600C1CCFFFF"
     28    $"FDFDFD0401800500020006023C43C6B9E5E23A85A83CEE414268F44A445900C6"
     29    $"D7F5FF6B94DD020006023C71E33A0C78BA15E43C7D2149055549455700E3EDFF"
     30    $"FF9EC2FF03003CB0030D29640401740401D30B0A062235224044525A3A5A3139"
     31    $"250A04223544465A3139250A04444644525A3A5A310A0422352240445244460A"
     32    $"0544544955603C593A593C0A05305E376046513B4E3E510A0622422254325C3E"
     33    $"513E402E3A0A0422422254325C32490A04224232493E402E3A0A043249325C3E"
     34    $"513E400604672645264426464B284C46120A04010420202B0A00010030202B01"
     35    $"178300040A01010120202B0A02010220202B0A03010320202B0A0A010502403F"
     36    $"3500000000000040268D4707E6C919420A0501061A403F350000000000004026"
     37    $"8D4707E6C9194215FF01178400040A0501061A403F3500000000000040268D47"
     38    $"07E6C91942001501178600040A06010702403F3500000000000040268D4707E6"
     39    $"C919420A08010902403F3500000000000040268D4707E6C919420A0701080240"
     40    $"3F3500000000000040268D4707E6C919420A0B010A000A0B010A2024220A0B01"
     41    $"0A2028240A0B010A202C260A0B010A2030280A0B010A20342A0A0B010A20382C"
     42};
     43
  • new file src/preferences/repositories/RepositoriesSettings.cpp

    diff --git a/src/preferences/repositories/RepositoriesSettings.cpp b/src/preferences/repositories/RepositoriesSettings.cpp
    new file mode 100644
    index 0000000..ab21133
    - +  
     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 "RepositoriesSettings.h"
     11
     12#include <FindDirectory.h>
     13#include <StringList.h>
     14
     15#include "constants.h"
     16
     17#undef B_TRANSLATION_CONTEXT
     18#define B_TRANSLATION_CONTEXT "RepositoriesSettings"
     19
     20const char* settingsFilename = "Repositories_settings";
     21
     22
     23RepositoriesSettings::RepositoriesSettings()
     24{
     25    status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fFilePath);
     26    if (status == B_OK)
     27        status = fFilePath.Append(settingsFilename);
     28    BEntry fileEntry(fFilePath.Path());
     29    if (!fileEntry.Exists()) {
     30        // Create default repos
     31        BStringList nameList, urlList;
     32        int32 count = (sizeof(kDefaultRepos) / sizeof(Repository));
     33        for (int16 index = 0; index < count; index++) {
     34            nameList.Add(kDefaultRepos[index].name);
     35            urlList.Add(kDefaultRepos[index].url);
     36        }
     37        SetRepositories(nameList, urlList);
     38    }
     39    fInitStatus = status;
     40}
     41
     42
     43BRect
     44RepositoriesSettings::GetFrame()
     45{
     46    BMessage settings(_ReadFromFile());
     47    BRect frame;
     48    status_t status = settings.FindRect(key_frame, &frame);
     49    // Set default off screen so it will center itself
     50    if (status != B_OK)
     51        frame.Set(-10, -10, 750, 300);
     52    return frame;
     53}
     54
     55
     56void
     57RepositoriesSettings::SetFrame(BRect frame)
     58{
     59    BMessage settings(_ReadFromFile());
     60    settings.RemoveData(key_frame);
     61    settings.AddRect(key_frame, frame);
     62    _SaveToFile(settings);
     63}
     64
     65
     66status_t
     67RepositoriesSettings::GetRepositories(int32& repoCount, BStringList& nameList,
     68    BStringList& urlList)
     69{
     70    BMessage settings(_ReadFromFile());
     71    type_code type;
     72    int32 count;
     73    settings.GetInfo(key_name, &type, &count);
     74
     75    status_t result = B_OK;
     76    int32 index, total = 0;
     77    BString foundName, foundUrl;
     78    // get each repository and add to lists
     79    for (index = 0; index < count; index++) {
     80        status_t result1 = settings.FindString(key_name, index, &foundName);
     81        status_t result2 = settings.FindString(key_url, index, &foundUrl);
     82        if (result1 == B_OK && result2 == B_OK) {
     83            nameList.Add(foundName);
     84            urlList.Add(foundUrl);
     85            total++;
     86        } else
     87            result = B_ERROR;
     88    }
     89    repoCount = total;
     90    return result;
     91}
     92
     93
     94void
     95RepositoriesSettings::SetRepositories(BStringList& nameList, BStringList& urlList)
     96{
     97    BMessage settings(_ReadFromFile());
     98    settings.RemoveName(key_name);
     99    settings.RemoveName(key_url);
     100
     101    int32 index, count = nameList.CountStrings();
     102    for (index = 0; index < count; index++) {
     103        settings.AddString(key_name, nameList.StringAt(index));
     104        settings.AddString(key_url, urlList.StringAt(index));
     105    }
     106    _SaveToFile(settings);
     107}
     108
     109
     110BMessage
     111RepositoriesSettings::_ReadFromFile()
     112{
     113    BMessage settings;
     114    status_t status = fFile.SetTo(fFilePath.Path(), B_READ_ONLY);
     115    if (status == B_OK)
     116        status = settings.Unflatten(&fFile);
     117    fFile.Unset();
     118    return settings;
     119}
     120
     121
     122status_t
     123RepositoriesSettings::_SaveToFile(BMessage settings)
     124{
     125    status_t status = fFile.SetTo(fFilePath.Path(),
     126        B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
     127    if (status == B_OK)
     128        status = settings.Flatten(&fFile);
     129    fFile.Unset();
     130    return status;
     131}
  • new file src/preferences/repositories/RepositoriesSettings.h

    diff --git a/src/preferences/repositories/RepositoriesSettings.h b/src/preferences/repositories/RepositoriesSettings.h
    new file mode 100644
    index 0000000..8ceae1f
    - +  
     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#ifndef REPOSITORIES_SETTINGS_H
     9#define REPOSITORIES_SETTINGS_H
     10
     11
     12#include <File.h>
     13#include <Message.h>
     14#include <Path.h>
     15#include <Point.h>
     16#include <Rect.h>
     17#include <String.h>
     18#include <StringList.h>
     19
     20
     21class RepositoriesSettings {
     22public:
     23                            RepositoriesSettings();
     24    BRect                   GetFrame();
     25    void                    SetFrame(BRect frame);
     26    status_t                GetRepositories(int32& repoCount,
     27                                BStringList& nameList, BStringList& urlList);
     28    void                    SetRepositories(BStringList& nameList,
     29                                BStringList& urlList);
     30
     31private:
     32    BPath                   fFilePath;
     33    BFile                   fFile;
     34    status_t                fInitStatus;
     35    BMessage                _ReadFromFile();
     36    status_t                _SaveToFile(BMessage settings);
     37};
     38
     39
     40#endif
  • new file src/preferences/repositories/RepositoriesView.cpp

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

    diff --git a/src/preferences/repositories/RepositoriesView.h b/src/preferences/repositories/RepositoriesView.h
    new file mode 100644
    index 0000000..37de3a3
    - +  
     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#ifndef REPOSITORIES_VIEW_H
     9#define REPOSITORIES_VIEW_H
     10
     11
     12#include <ColumnListView.h>
     13#include <String.h>
     14#include <StringView.h>
     15#include <View.h>
     16
     17#include "RepositoriesSettings.h"
     18#include "RepoRow.h"
     19#include "TaskLooper.h"
     20
     21
     22class RepositoriesListView : public BColumnListView {
     23public:
     24                            RepositoriesListView(const char* name);
     25    virtual void            KeyDown(const char* bytes, int32 numBytes);
     26};
     27
     28
     29class RepositoriesView : public BView {
     30public:
     31                            RepositoriesView();
     32                            ~RepositoriesView();
     33    virtual void            AllAttached();
     34    virtual void            AttachedToWindow();
     35    virtual void            MessageReceived(BMessage*);
     36    void                    AddManualRepository(BString url);
     37    bool                    IsTaskRunning() { return fRunningTaskCount > 0; }
     38
     39private:
     40    RepositoriesSettings    fSettings;
     41    RepositoriesListView    *fListView;
     42    BView                   *fStatusContainerView;
     43    BStringView             *fListStatusView;
     44    TaskLooper              *fTaskLooper;
     45    bool                    fShowCompletedStatus;
     46    int                     fRunningTaskCount, fLastCompletedTimerId;
     47    BButton                 *fAboutButton, *fAddButton, *fRemoveButton,
     48                            *fEnableButton, *fDisableButton;
     49
     50    // Message helpers
     51    void                    _AddSelectedRowsToQueue();
     52    void                    _TaskStarted(RepoRow* rowItem, int16 count);
     53    void                    _TaskCompleted(RepoRow* rowItem, int16 count,
     54                                BString& newName);
     55    void                    _TaskCanceled(RepoRow* rowItem, int16 count);
     56    void                    _ShowCompletedStatusIfDone();
     57    void                    _UpdateFromRepoConfig(RepoRow* rowItem);
     58
     59    // GUI functions
     60    BString                 _GetRootUrl(BString url);
     61    status_t                _EmptyList();
     62    void                    _InitList();
     63    void                    _RefreshList();
     64    void                    _UpdateListFromRoster();
     65    void                    _SaveList();
     66    RepoRow*                _AddRepo(BString name, BString url, bool enabled);
     67    void                    _FindSiblings();
     68    void                    _UpdateButtons();
     69    void                    _UpdateStatusView();
     70};
     71
     72
     73#endif
  • new file src/preferences/repositories/RepositoriesWindow.cpp

    diff --git a/src/preferences/repositories/RepositoriesWindow.cpp b/src/preferences/repositories/RepositoriesWindow.cpp
    new file mode 100644
    index 0000000..5c6549b
    - +  
     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 "RepositoriesWindow.h"
     11
     12#include <Alert.h>
     13#include <Application.h>
     14#include <Catalog.h>
     15#include <FindDirectory.h>
     16#include <LayoutBuilder.h>
     17#include <NodeMonitor.h>
     18#include <Region.h>
     19#include <Screen.h>
     20
     21#include "AddRepoWindow.h"
     22#include "constants.h"
     23
     24
     25#undef B_TRANSLATION_CONTEXT
     26#define B_TRANSLATION_CONTEXT "RepositoriesWindow"
     27
     28
     29RepositoriesWindow::RepositoriesWindow()
     30    :
     31    BWindow(BRect(50, 50, 500, 400), B_TRANSLATE_SYSTEM_NAME("Repositories"),
     32        B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS),
     33    fAddWindow(NULL),
     34    fPackageNodeStatus(B_ERROR)
     35{
     36    fView = new RepositoriesView();
     37    BLayoutBuilder::Group<>(this, B_VERTICAL).Add(fView);
     38
     39    // Size and location on screen
     40    BSize viewSize(fView->MinSize());
     41    SetSizeLimits(viewSize.Width(), 9999, viewSize.Height(), 9999);
     42    BRect frame = fSettings.GetFrame();
     43    ResizeTo(frame.Width(), frame.Height());
     44    BScreen screen;
     45    BRect screenFrame = screen.Frame();
     46    if (screenFrame.right < frame.right || screenFrame.left > frame.left
     47        || screenFrame.top > frame.top || screenFrame.bottom < frame.bottom)
     48        CenterOnScreen();
     49    else
     50        MoveTo(frame.left, frame.top);
     51    Show();
     52
     53    // Find the pkgman settings or cache directory
     54    BPath packagePath;
     55    // /boot/system/settings/package-repositories
     56    status_t status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY,
     57        &packagePath);
     58    if (status == B_OK)
     59        status = packagePath.Append("package-repositories");
     60    else {
     61        // /boot/system/cache/package-repositories
     62        status = find_directory(B_SYSTEM_CACHE_DIRECTORY, &packagePath);
     63        if (status == B_OK)
     64            status = packagePath.Append("package-repositories");
     65    }
     66    if (status == B_OK) {
     67        BNode packageNode(packagePath.Path());
     68        if (packageNode.InitCheck()==B_OK && packageNode.IsDirectory())
     69            fPackageNodeStatus = packageNode.GetNodeRef(&fPackageNodeRef);
     70    }
     71
     72    // watch the pkgman settings or cache directory for changes
     73    _StartWatching();
     74}
     75
     76
     77RepositoriesWindow::~RepositoriesWindow()
     78{
     79    _StopWatching();
     80}
     81
     82
     83void
     84RepositoriesWindow::_StartWatching()
     85{
     86    if (fPackageNodeStatus == B_OK) {
     87        status_t result = watch_node(&fPackageNodeRef, B_WATCH_DIRECTORY, this);
     88        fWatchingPackageNode = (result == B_OK);
     89    }
     90}
     91
     92
     93void
     94RepositoriesWindow::_StopWatching()
     95{
     96    if (fPackageNodeStatus == B_OK && fWatchingPackageNode) {
     97        watch_node(&fPackageNodeRef, B_STOP_WATCHING, this);
     98        fWatchingPackageNode = false;
     99    }
     100}
     101
     102
     103bool
     104RepositoriesWindow::QuitRequested()
     105{
     106    if (fView->IsTaskRunning()) {
     107        BAlert *alert = new BAlert("tasks",
     108            B_TRANSLATE_COMMENT("Some tasks are still running. Stop these "
     109                "tasks and quit?", "Application quit alert message"),
     110            B_TRANSLATE_COMMENT("Stop and quit", "Button label"),
     111            kCancelLabel, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
     112        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
     113        int32 result = alert->Go();
     114        if (result != 0)
     115            return false;
     116    }
     117    fSettings.SetFrame(Frame());
     118    be_app->PostMessage(B_QUIT_REQUESTED);
     119    return BWindow::QuitRequested();
     120}
     121
     122
     123void
     124RepositoriesWindow::MessageReceived(BMessage* message)
     125{
     126    switch (message->what)
     127    {
     128        case ADD_REPO_WINDOW: {
     129            BRect frame = Frame();
     130            fAddWindow = new AddRepoWindow(frame, this);
     131            break;
     132        }
     133        case ADD_REPO_URL: {
     134            BString url;
     135            status_t result = message->FindString(key_url, &url);
     136            if (result == B_OK)
     137                fView->AddManualRepository(url);
     138            break;
     139        }
     140        case ADD_WINDOW_CLOSED: {
     141            fAddWindow = NULL;
     142            break;
     143        }
     144        case DELETE_KEY_PRESSED: {
     145            BMessage message(REMOVE_REPOS);
     146            fView->MessageReceived(&message);
     147            break;
     148        }
     149        case TASK_STARTED:
     150        case TASK_COMPLETED_WITH_ERRORS:
     151        case TASK_COMPLETED:
     152        case TASK_CANCELED: {
     153            fView->MessageReceived(message);
     154            break;
     155        }
     156        case SHOW_ABOUT: {
     157            be_app->AboutRequested();
     158            break;
     159        }
     160        // captures pkgman changes while the Repositories application is running
     161        case B_NODE_MONITOR: {
     162            // This preflet is making the changes, so ignore this message
     163            if (fView->IsTaskRunning())
     164                break;
     165
     166            int32 opcode;
     167            if (message->FindInt32("opcode", &opcode) == B_OK) {
     168                switch (opcode)
     169                {
     170                    case B_ATTR_CHANGED:
     171                    case B_ENTRY_CREATED:
     172                    case B_ENTRY_REMOVED: {
     173                        PostMessage(UPDATE_LIST, fView);
     174                        break;
     175                    }
     176                }
     177            }
     178            break;
     179        }
     180        default:
     181            BWindow::MessageReceived(message);
     182    }
     183}
  • new file src/preferences/repositories/RepositoriesWindow.h

    diff --git a/src/preferences/repositories/RepositoriesWindow.h b/src/preferences/repositories/RepositoriesWindow.h
    new file mode 100644
    index 0000000..cc0b8c6
    - +  
     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#ifndef REPOSITORIES_WINDOW_H
     9#define REPOSITORIES_WINDOW_H
     10
     11
     12#include <Node.h>
     13#include <Window.h>
     14
     15#include "AddRepoWindow.h"
     16#include "RepositoriesSettings.h"
     17#include "RepositoriesView.h"
     18
     19
     20class RepositoriesWindow : public BWindow {
     21public:
     22                            RepositoriesWindow();
     23                            ~RepositoriesWindow();
     24    virtual bool            QuitRequested();
     25    virtual void            MessageReceived(BMessage*);
     26
     27private:
     28    RepositoriesSettings    fSettings;
     29    RepositoriesView        *fView;
     30    AddRepoWindow           *fAddWindow;
     31    node_ref                fPackageNodeRef;
     32        // node_ref to watch for changes to package-repositories directory
     33    status_t                fPackageNodeStatus;
     34    bool                    fWatchingPackageNode;
     35        // true when package-repositories directory is being watched
     36    void                    _StartWatching();
     37    void                    _StopWatching();
     38};
     39
     40
     41#endif
  • new file src/preferences/repositories/TaskLooper.cpp

    diff --git a/src/preferences/repositories/TaskLooper.cpp b/src/preferences/repositories/TaskLooper.cpp
    new file mode 100644
    index 0000000..f76815b
    - +  
     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 "TaskLooper.h"
     11
     12#include <Catalog.h>
     13#include <MessageQueue.h>
     14#include <package/AddRepositoryRequest.h>
     15#include <package/DropRepositoryRequest.h>
     16#include <package/RefreshRepositoryRequest.h>
     17#include <package/PackageRoster.h>
     18#include <package/RepositoryConfig.h>
     19
     20#include "constants.h"
     21
     22#define DEBUGTASK 0
     23
     24#undef B_TRANSLATION_CONTEXT
     25#define B_TRANSLATION_CONTEXT "TaskLooper"
     26
     27static const BString kLogResultIndicator = "***";
     28static const BString kCompletedText =
     29    B_TRANSLATE_COMMENT("Completed", "Completed task status message");
     30static const BString kFailedText =
     31    B_TRANSLATE_COMMENT("Failed", "Failed task status message");
     32static const BString kAbortedText =
     33    B_TRANSLATE_COMMENT("Aborted", "Aborted task status message");
     34static const BString kDescriptionText =
     35    B_TRANSLATE_COMMENT("Description", "Failed task error description");
     36static const BString kDetailsText =
     37    B_TRANSLATE_COMMENT("Details", "Job log details header");
     38
     39using BSupportKit::BJob;
     40
     41
     42void
     43JobStateListener::JobStarted(BJob* job)
     44{
     45    fJobLog.Add(job->Title());
     46}
     47
     48
     49void
     50JobStateListener::JobSucceeded(BJob* job)
     51{
     52    BString resultText(kLogResultIndicator);
     53    fJobLog.Add(resultText.Append(kCompletedText));
     54}
     55
     56
     57void
     58JobStateListener::JobFailed(BJob* job)
     59{
     60    BString resultText(kLogResultIndicator);
     61    resultText.Append(kFailedText).Append(": ")
     62        .Append(strerror(job->Result()));
     63    fJobLog.Add(resultText);
     64    if (job->ErrorString().Length() > 0) {
     65        resultText.SetTo(kLogResultIndicator);
     66        resultText.Append(kDescriptionText).Append(": ")
     67            .Append(job->ErrorString());
     68        fJobLog.Add(resultText);
     69    }
     70}
     71
     72
     73void
     74JobStateListener::JobAborted(BJob* job)
     75{
     76    BString resultText(kLogResultIndicator);
     77    resultText.Append(kAbortedText).Append(": ")
     78        .Append(strerror(job->Result()));
     79    fJobLog.Add(resultText);
     80    if (job->ErrorString().Length() > 0) {
     81        resultText.SetTo(kLogResultIndicator);
     82        resultText.Append(kDescriptionText).Append(": ")
     83            .Append(job->ErrorString());
     84        fJobLog.Add(resultText);
     85    }
     86}
     87
     88
     89BString
     90JobStateListener::GetJobLog()
     91{
     92    return fJobLog.Join("\n");
     93}
     94
     95
     96TaskLooper::TaskLooper(BLooper* target)
     97    :BLooper("TaskLooper"),
     98    fReplyTarget(target)
     99{
     100    Run();
     101}
     102
     103
     104bool
     105TaskLooper::QuitRequested()
     106{
     107    return MessageQueue()->IsEmpty();
     108}
     109
     110
     111void
     112TaskLooper::MessageReceived(BMessage* message)
     113{
     114    switch (message->what)
     115    {
     116        case DO_TASK: {
     117            RepoRow* rowItem;
     118            status_t result = message->FindPointer(key_rowptr, (void**)&rowItem);
     119            if (result == B_OK) {
     120                // Check to make sure there isn't already an existing task for this
     121                int16 queueCount = fTaskQueue.CountItems();
     122                for (int16 index = 0; index<queueCount; index++) {
     123                    Task* task = fTaskQueue.ItemAt(index);
     124                    if (rowItem == task->rowItem)
     125                        break;
     126                }
     127
     128                // Initialize task
     129                Task* newTask = new Task();
     130                newTask->rowItem = rowItem;
     131                newTask->name = rowItem->Name();
     132                newTask->resultName = newTask->name;
     133                if (rowItem->IsEnabled()) {
     134                    newTask->taskType = DISABLE_REPO;
     135                    newTask->taskParam = newTask->name;
     136                } else {
     137                    newTask->taskType = ENABLE_REPO;
     138                    newTask->taskParam = rowItem->Url();
     139                }
     140                newTask->owner = this;
     141                newTask->fTimer = NULL;
     142
     143                // Add to queue and start
     144                fTaskQueue.AddItem(newTask);
     145                BString threadName(newTask->taskType == ENABLE_REPO ?
     146                    "enable_task" : "disable_task");
     147                newTask->threadId = spawn_thread(_DoTask, threadName.String(),
     148                    B_NORMAL_PRIORITY, (void*)newTask);
     149                status_t threadResult;
     150                if (newTask->threadId < B_OK)
     151                    threadResult = B_ERROR;
     152                else {
     153                    threadResult = resume_thread(newTask->threadId);
     154                    if (threadResult == B_OK) {
     155                        newTask->fTimer = new TaskTimer(this, newTask);
     156                        newTask->fTimer->Start(newTask->name);
     157                        // Reply to view
     158                        BMessage reply(*message);
     159                        reply.what = TASK_STARTED;
     160                        reply.AddInt16(key_count, fTaskQueue.CountItems());
     161                        fReplyTarget->PostMessage(&reply);
     162                    } else
     163                        kill_thread(newTask->threadId);
     164                }
     165                if (threadResult != B_OK) {
     166                    _RemoveAndDelete(newTask);
     167                }
     168            }
     169            break;
     170        }
     171        case TASK_COMPLETED:
     172        case TASK_COMPLETED_WITH_ERRORS:
     173        case TASK_CANCELED: {
     174            Task* task;
     175            status_t result = message->FindPointer(key_taskptr, (void**)&task);
     176            if (result == B_OK && fTaskQueue.HasItem(task)) {
     177                task->fTimer->Stop(task->resultName);
     178                BMessage reply(message->what);
     179                reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
     180                reply.AddPointer(key_rowptr, task->rowItem);
     181                if (message->what == TASK_COMPLETED_WITH_ERRORS)
     182                    reply.AddString(key_details, task->resultErrorDetails);
     183                if (task->taskType == ENABLE_REPO
     184                    && task->name.Compare(task->resultName) != 0)
     185                    reply.AddString(key_name, task->resultName);
     186                fReplyTarget->PostMessage(&reply);
     187                _RemoveAndDelete(task);
     188            }
     189            break;
     190        }
     191        case TASK_KILL_REQUEST: {
     192            Task* task;
     193            status_t result = message->FindPointer(key_taskptr, (void**)&task);
     194            if (result == B_OK && fTaskQueue.HasItem(task)) {
     195                kill_thread(task->threadId);
     196                BMessage reply(TASK_CANCELED);
     197                reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
     198                reply.AddPointer(key_rowptr, task->rowItem);
     199                fReplyTarget->PostMessage(&reply);
     200                _RemoveAndDelete(task);
     201            }
     202            break;
     203        }
     204    }
     205}
     206
     207
     208void
     209TaskLooper::_RemoveAndDelete(Task* task)
     210{
     211    fTaskQueue.RemoveItem(task);
     212    if (task->fTimer)
     213    {
     214        task->fTimer->Lock();
     215        task->fTimer->Quit();
     216        task->fTimer = NULL;
     217    }
     218    delete task;
     219}
     220
     221
     222status_t
     223TaskLooper::_DoTask(void* data)
     224{
     225    Task* task = (Task*)data;
     226    BString errorDetails, repoName("");
     227    status_t returnResult = B_OK;
     228    DecisionProvider decisionProvider;
     229    JobStateListener listener;
     230    switch (task->taskType)
     231    {
     232        case DISABLE_REPO: {
     233            BString nameParam(task->taskParam);
     234            BPackageKit::BContext context(decisionProvider, listener);
     235            BPackageKit::DropRepositoryRequest dropRequest(context, nameParam);
     236            status_t result = dropRequest.Process();
     237            if (result != B_OK) {
     238                returnResult = result;
     239                if (result != B_CANCELED) {
     240                    errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
     241                        "error disabling the repository %name%",
     242                        "Error message, do not translate %name%"));
     243                    BString nameString("\"");
     244                    nameString.Append(nameParam).Append("\"");
     245                    errorDetails.ReplaceFirst("%name%", nameString);
     246                    _AppendErrorDetails(errorDetails, &listener);
     247                }
     248            }
     249            break;
     250        }
     251        case ENABLE_REPO: {
     252            BString urlParam(task->taskParam);
     253            BPackageKit::BContext context(decisionProvider, listener);
     254            // Add repository
     255            bool asUserRepository = false;
     256                // TODO does this ever change?
     257            BPackageKit::AddRepositoryRequest addRequest(context, urlParam,
     258                asUserRepository);
     259            status_t result = addRequest.Process();
     260            if (result != B_OK) {
     261                returnResult = result;
     262                if (result != B_CANCELED) {
     263                    errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
     264                        "error enabling the repository %url%",
     265                        "Error message, do not translate %url%"));
     266                    errorDetails.ReplaceFirst("%url%", urlParam);
     267                    _AppendErrorDetails(errorDetails, &listener);
     268                }
     269                break;
     270            }
     271            // Continue on to refresh repo cache
     272            repoName = addRequest.RepositoryName();
     273            BPackageKit::BPackageRoster roster;
     274            BPackageKit::BRepositoryConfig repoConfig;
     275            roster.GetRepositoryConfig(repoName, &repoConfig);
     276            BPackageKit::BRefreshRepositoryRequest refreshRequest(context,
     277                repoConfig);
     278            result = refreshRequest.Process();
     279            if (result != B_OK) {
     280                returnResult = result;
     281                if (result != B_CANCELED) {
     282                    errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
     283                        "error refreshing the repository cache for %name%",
     284                        "Error message, do not translate %name%"));
     285                    BString nameString("\"");
     286                    nameString.Append(repoName).Append("\"");
     287                    errorDetails.ReplaceFirst("%name%", nameString);
     288                    _AppendErrorDetails(errorDetails, &listener);
     289                }
     290            }
     291            break;
     292        }
     293    }
     294    // Report completion status
     295    BMessage reply;
     296    if (returnResult == B_OK) {
     297        reply.what = TASK_COMPLETED;
     298        // Add the repo name if we need to update the list row value
     299        if (task->taskType == ENABLE_REPO)
     300            task->resultName = repoName;
     301    } else if (returnResult == B_CANCELED)
     302        reply.what = TASK_CANCELED;
     303    else {
     304        reply.what = TASK_COMPLETED_WITH_ERRORS;
     305        task->resultErrorDetails = errorDetails;
     306        if (task->taskType == ENABLE_REPO)
     307            task->resultName = repoName;
     308    }
     309    reply.AddPointer(key_taskptr, task);
     310    task->owner->PostMessage(&reply);
     311#if DEBUGTASK
     312    if (returnResult == B_OK || returnResult == B_CANCELED) {
     313        BString degubDetails("Debug info:\n");
     314        degubDetails.Append(listener.GetJobLog());
     315        (new BAlert("debug", degubDetails, "OK"))->Go(NULL);
     316    }
     317#endif // DEBUGTASK
     318    return 0;
     319}
     320
     321
     322void
     323TaskLooper::_AppendErrorDetails(BString& details, JobStateListener* listener)
     324{
     325    details.Append("\n\n").Append(kDetailsText).Append(":\n");
     326    details.Append(listener->GetJobLog());
     327}
  • new file src/preferences/repositories/TaskLooper.h

    diff --git a/src/preferences/repositories/TaskLooper.h b/src/preferences/repositories/TaskLooper.h
    new file mode 100644
    index 0000000..5192289
    - +  
     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#ifndef TASK_LOOPER_H
     9#define TASK_LOOPER_H
     10
     11
     12#include <Job.h>
     13#include <Looper.h>
     14#include <ObjectList.h>
     15#include <String.h>
     16#include <StringList.h>
     17#include <package/Context.h>
     18
     19#include "TaskTimer.h"
     20
     21
     22class DecisionProvider : public BPackageKit::BDecisionProvider {
     23public:
     24                                DecisionProvider() {}
     25
     26    virtual bool                YesNoDecisionNeeded(const BString& description,
     27                                    const BString& question,
     28                                    const BString& yes,
     29                                    const BString& no,
     30                                    const BString& defaultChoice)
     31                                    { return true; }
     32};
     33
     34
     35class JobStateListener : public BSupportKit::BJobStateListener {
     36public:
     37                                JobStateListener() {}
     38
     39    virtual void                JobStarted(BSupportKit::BJob* job);
     40    virtual void                JobSucceeded(BSupportKit::BJob* job);
     41    virtual void                JobFailed(BSupportKit::BJob* job);
     42    virtual void                JobAborted(BSupportKit::BJob* job);
     43    BString                     GetJobLog();
     44
     45private:
     46            BStringList         fJobLog;
     47};
     48
     49
     50class TaskLooper : public BLooper {
     51public:
     52                            TaskLooper(BLooper* target);
     53    virtual bool            QuitRequested();
     54    virtual void            MessageReceived(BMessage*);
     55
     56private:
     57    BObjectList<Task>       fTaskQueue;
     58    void                    _RemoveAndDelete(Task* task);
     59    static status_t         _DoTask(void* data);
     60    static void             _AppendErrorDetails(BString& details,
     61                                JobStateListener* listener);
     62    BLooper                 *fReplyTarget;
     63};
     64
     65
     66#endif
  • new file src/preferences/repositories/TaskTimer.cpp

    diff --git a/src/preferences/repositories/TaskTimer.cpp b/src/preferences/repositories/TaskTimer.cpp
    new file mode 100644
    index 0000000..e010351
    - +  
     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 "TaskTimer.h"
     11
     12#include <Application.h>
     13#include <Catalog.h>
     14
     15#include "constants.h"
     16
     17#undef B_TRANSLATION_CONTEXT
     18#define B_TRANSLATION_CONTEXT "TaskTimer"
     19
     20static int32 sAlertStackCount = 0;
     21
     22
     23TaskTimer::TaskTimer(BLooper* target, Task* owner)
     24    :
     25    BLooper(),
     26    fTimeoutMicroSeconds(kTimerTimeoutSeconds * 1000000),
     27    fTimerIsRunning(false),
     28    fReplyTarget(target),
     29    fMessageRunner(NULL),
     30    fTimeoutMessage(TASK_TIMEOUT),
     31    fTimeoutAlert(NULL),
     32    fOwner(owner)
     33{
     34    Run();
     35
     36    // Messenger for the Message Runner to use to send its message to the timer
     37    fMessenger.SetTo(this);
     38    // Invoker for the Alerts to use to send their messages to the timer
     39    fTimeoutAlertInvoker.SetMessage(
     40        new BMessage(TIMEOUT_ALERT_BUTTON_SELECTION));
     41    fTimeoutAlertInvoker.SetTarget(this);
     42}
     43
     44
     45TaskTimer::~TaskTimer()
     46{
     47    if (fTimeoutAlert) {
     48        fTimeoutAlert->Lock();
     49        fTimeoutAlert->Quit();
     50    }
     51    if (fMessageRunner)
     52        fMessageRunner->SetCount(0);
     53}
     54
     55
     56bool
     57TaskTimer::QuitRequested()
     58{
     59    return true;
     60}
     61
     62
     63void
     64TaskTimer::MessageReceived(BMessage* message)
     65{
     66    switch (message->what)
     67    {
     68        case TASK_TIMEOUT: {
     69            fMessageRunner = NULL;
     70            if (fTimerIsRunning) {
     71                BString text(B_TRANSLATE_COMMENT("The task for repository"
     72                    " %name% is taking a long time to complete.",
     73                    "Alert message.  Do not translate %name%"));
     74                BString nameString("\"");
     75                nameString.Append(fRepositoryName).Append("\"");
     76                text.ReplaceFirst("%name%", nameString);
     77                fTimeoutAlert = new BAlert("timeout", text,
     78                    B_TRANSLATE_COMMENT("Keep trying", "Button label"),
     79                    B_TRANSLATE_COMMENT("Cancel task", "Button label"),
     80                    NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
     81                fTimeoutAlert->SetShortcut(0, B_ESCAPE);
     82                // Calculate the position to correctly stack this alert
     83                BRect windowFrame = be_app->WindowAt(0)->Frame();
     84                int32 stackPos = _NextAlertStackCount();
     85                float xPos = windowFrame.left
     86                    + windowFrame.Width()/2 + stackPos * kTimerAlertOffset;
     87                float yPos = windowFrame.top
     88                    + (stackPos + 1) * kTimerAlertOffset;
     89                fTimeoutAlert->Go(&fTimeoutAlertInvoker);
     90                xPos -= fTimeoutAlert->Frame().Width()/2;
     91                    // The correct frame for the alert is not available until
     92                    // after Go is called
     93                fTimeoutAlert->MoveTo(xPos, yPos);
     94            }
     95            break;
     96        }
     97        case TIMEOUT_ALERT_BUTTON_SELECTION: {
     98            fTimeoutAlert = NULL;
     99            // Timeout alert was invoked by user and timer still has not
     100            // been stopped
     101            if (fTimerIsRunning) {
     102                // find which button was pressed
     103                int32 selection = -1;
     104                message->FindInt32("which", &selection);
     105                if (selection == 1) {
     106                    BMessage reply(TASK_KILL_REQUEST);
     107                    reply.AddPointer(key_taskptr, fOwner);
     108                    fReplyTarget->PostMessage(&reply);
     109                } else if (selection == 0) {
     110                    // Create new timer
     111                    fMessageRunner = new BMessageRunner(fMessenger,
     112                        &fTimeoutMessage, kTimerRetrySeconds * 1000000, 1);
     113                }
     114            }
     115            break;
     116        }
     117    }
     118}
     119
     120
     121void
     122TaskTimer::Start(const char* name)
     123{
     124    fTimerIsRunning = true;
     125    fRepositoryName.SetTo(name);
     126
     127    // Create a message runner that will send a TASK_TIMEOUT message if the
     128    // timer is not stopped
     129    if (fMessageRunner == NULL)
     130        fMessageRunner = new BMessageRunner(fMessenger, &fTimeoutMessage,
     131            fTimeoutMicroSeconds, 1);
     132    else
     133        fMessageRunner->SetInterval(fTimeoutMicroSeconds);
     134}
     135
     136
     137void
     138TaskTimer::Stop(const char* name)
     139{
     140    fTimerIsRunning = false;
     141
     142    // Reset max timeout so we can reuse the runner at the next Start call
     143    if (fMessageRunner != NULL)
     144        fMessageRunner->SetInterval(LLONG_MAX);
     145
     146    // If timeout alert is showing replace it
     147    if (fTimeoutAlert) {
     148        // Remove current alert
     149        BRect frame = fTimeoutAlert->Frame();
     150        fTimeoutAlert->Quit();
     151        fTimeoutAlert = NULL;
     152
     153        // Display new alert that won't send a message
     154        BString text(B_TRANSLATE_COMMENT("Good news! The task for repository "
     155            "%name% completed.", "Alert message.  Do not translate %name%"));
     156        BString nameString("\"");
     157        nameString.Append(name).Append("\"");
     158        text.ReplaceFirst("%name%", nameString);
     159        BAlert* newAlert = new BAlert("timeout", text, kOKLabel, NULL, NULL,
     160            B_WIDTH_AS_USUAL, B_WARNING_ALERT);
     161        newAlert->SetShortcut(0, B_ESCAPE);
     162        newAlert->MoveTo(frame.left, frame.top);
     163        newAlert->Go(NULL);
     164    }
     165}
     166
     167
     168int32
     169TaskTimer::_NextAlertStackCount()
     170{
     171    if (sAlertStackCount > 9)
     172        sAlertStackCount = 0;
     173    return sAlertStackCount++;
     174}
  • new file src/preferences/repositories/TaskTimer.h

    diff --git a/src/preferences/repositories/TaskTimer.h b/src/preferences/repositories/TaskTimer.h
    new file mode 100644
    index 0000000..86eb10f
    - +  
     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#ifndef TASKTIMER_H
     9#define TASKTIMER_H
     10
     11
     12#include <Alert.h>
     13#include <Invoker.h>
     14#include <Looper.h>
     15#include <Message.h>
     16#include <MessageRunner.h>
     17#include <Messenger.h>
     18#include <String.h>
     19
     20#include "RepoRow.h"
     21
     22class TaskTimer;
     23class TaskLooper;
     24
     25
     26typedef struct {
     27        RepoRow* rowItem;
     28        int32 taskType;
     29        BString name, taskParam;
     30        thread_id threadId;
     31        TaskLooper* owner;
     32        BString resultName, resultErrorDetails;
     33        TaskTimer* fTimer;
     34} Task;
     35
     36
     37class TaskTimer : public BLooper {
     38public:
     39                            TaskTimer(BLooper* target, Task* owner);
     40                            ~TaskTimer();
     41    virtual bool            QuitRequested();
     42    virtual void            MessageReceived(BMessage*);
     43    void                    Start(const char* name);
     44    void                    Stop(const char* name);
     45
     46private:
     47    int32                   fTimeoutMicroSeconds;
     48    bool                    fTimerIsRunning;
     49    BString                 fRepositoryName;
     50    BLooper                 *fReplyTarget;
     51    BMessenger              fMessenger;
     52    BMessageRunner          *fMessageRunner;
     53    BMessage                fTimeoutMessage;
     54    BAlert                  *fTimeoutAlert;
     55    BInvoker                fTimeoutAlertInvoker;
     56    Task                    *fOwner;
     57    int32                   _NextAlertStackCount();
     58};
     59
     60
     61#endif
  • new file src/preferences/repositories/constants.h

    diff --git a/src/preferences/repositories/constants.h b/src/preferences/repositories/constants.h
    new file mode 100644
    index 0000000..181b584
    - +  
     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#ifndef REPOSITORIES_CONSTANTS_H
     9#define REPOSITORIES_CONSTANTS_H
     10
     11
     12#include <Catalog.h>
     13#include <String.h>
     14
     15#undef B_TRANSLATION_CONTEXT
     16#define B_TRANSLATION_CONTEXT "Constants"
     17
     18static const float kAddWindowOffset = 10.0;
     19static const int16 kTimerAlertOffset = 15;
     20static const int16 kTimerTimeoutSeconds = 10;
     21static const int16 kTimerRetrySeconds = 20;
     22
     23static const BString kOKLabel = B_TRANSLATE_COMMENT("OK", "Button label");
     24static const BString kCancelLabel = B_TRANSLATE_COMMENT("Cancel",
     25    "Button label");
     26static const BString kRemoveLabel = B_TRANSLATE_COMMENT("Remove",
     27    "Button label");
     28static const BString kNewRepoDefaultName = B_TRANSLATE_COMMENT("Unknown",
     29    "Unknown repository name");
     30
     31
     32typedef struct {
     33    const char* name;
     34    const char* url;
     35} Repository;
     36
     37
     38static const Repository kDefaultRepos[] = {
     39    { "Haiku", "http://packages.haiku-os.org/haiku/master/"B_HAIKU_ABI_NAME
     40        "/current"},
     41    { "Haikuports", "http://packages.haiku-os.org/haikuports/master/repo/"
     42        B_HAIKU_ABI_NAME"/current" },
     43    { "BeSly Software Solutions", "http://software.besly.de/repo"},
     44    { "clasqm's repo", "http://clasquin-johnson.co.za/michel/repo"},
     45    { "clasqm's x86_64 repo", "http://clasquin-johnson.co.za/michel/repo_64"},
     46    { "FatElk", "http://coquillemartialarts.com/fatelk/repo"}
     47};
     48
     49
     50// Message keys
     51#define key_frame "frame"
     52#define key_name "repo_name"
     53#define key_url "repo_url"
     54#define key_text "text"
     55#define key_details "details"
     56#define key_rowptr "row_ptr"
     57#define key_taskptr "task_ptr"
     58#define key_count "count"
     59#define key_ID "ID"
     60
     61
     62// Messages
     63enum {
     64    ADD_REPO_WINDOW = 'BHDa',
     65    ADD_BUTTON_PRESSED,
     66    CANCEL_BUTTON_PRESSED,
     67    ADD_REPO_URL,
     68    ADD_WINDOW_CLOSED,
     69    REMOVE_REPOS,
     70    LIST_SELECTION_CHANGED,
     71    ENABLE_BUTTON_PRESSED,
     72    DISABLE_BUTTON_PRESSED,
     73    ITEM_INVOKED,
     74    DELETE_KEY_PRESSED,
     75    DO_TASK,
     76    STATUS_VIEW_COMPLETED_TIMEOUT,
     77    TASK_STARTED,
     78    TASK_COMPLETED,
     79    TASK_COMPLETED_WITH_ERRORS,
     80    TASK_CANCELED,
     81    UPDATE_LIST,
     82    SHOW_ABOUT,
     83    NO_TASKS,
     84    ENABLE_REPO,
     85    DISABLE_REPO,
     86    TASK_TIMEOUT,
     87    TIMEOUT_ALERT_BUTTON_SELECTION,
     88    TASK_KILL_REQUEST
     89};
     90
     91
     92// Repo row task state
     93enum {
     94    STATE_NOT_IN_QUEUE = 0,
     95    STATE_IN_QUEUE_WAITING,
     96    STATE_IN_QUEUE_RUNNING
     97};
     98
     99
     100#endif