Root/src/menu.cpp

1/***************************************************************************
2 * Copyright (C) 2006 by Massimiliano Torromeo *
3 * massimiliano.torromeo@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21#include <sys/stat.h>
22#include <sys/types.h>
23#include <dirent.h>
24#include <algorithm>
25#include <math.h>
26#include <fstream>
27#include <unistd.h>
28#include <cassert>
29#include <cerrno>
30#include <cstring>
31
32#ifdef HAVE_LIBOPK
33#include <opk.h>
34#endif
35
36#include "gmenu2x.h"
37#include "linkapp.h"
38#include "menu.h"
39#include "monitor.h"
40#include "filelister.h"
41#include "utilities.h"
42#include "debug.h"
43
44using namespace std;
45
46
47Menu::Animation::Animation()
48    : curr(0)
49{
50}
51
52void Menu::Animation::adjust(int delta)
53{
54    curr += delta;
55}
56
57void Menu::Animation::step()
58{
59    if (curr == 0) {
60        ERROR("Computing step past animation end\n");
61    } else if (curr < 0) {
62        const int v = ((1 << 16) - curr) / 32;
63        curr = std::min(0, curr + v);
64    } else {
65        const int v = ((1 << 16) + curr) / 32;
66        curr = std::max(0, curr - v);
67    }
68}
69
70Menu::Menu(GMenu2X& gmenu2x)
71    : gmenu2x(gmenu2x)
72    , btnContextMenu(gmenu2x, "skin:imgs/menu.png", "",
73            std::bind(&GMenu2X::showContextMenu, &gmenu2x))
74{
75    readSections(GMENU2X_SYSTEM_DIR "/sections");
76    readSections(GMenu2X::getHome() + "/sections");
77
78    setSectionIndex(0);
79    readLinks();
80
81#ifdef HAVE_LIBOPK
82    {
83        DIR *dirp = opendir(CARD_ROOT);
84        if (dirp) {
85            struct dirent *dptr;
86            while ((dptr = readdir(dirp))) {
87                if (dptr->d_type != DT_DIR)
88                    continue;
89
90                if (!strcmp(dptr->d_name, ".") || !strcmp(dptr->d_name, ".."))
91                    continue;
92
93                openPackagesFromDir((string) CARD_ROOT + "/" +
94                            dptr->d_name + "/apps");
95            }
96            closedir(dirp);
97        }
98    }
99#endif
100
101    btnContextMenu.setPosition(gmenu2x.resX - 38, gmenu2x.bottomBarIconY);
102}
103
104Menu::~Menu()
105{
106}
107
108void Menu::readSections(std::string const& parentDir)
109{
110    DIR *dirp = opendir(parentDir.c_str());
111    if (!dirp) return;
112
113    struct dirent *dptr;
114    while ((dptr = readdir(dirp))) {
115        if (dptr->d_name[0] != '.' && dptr->d_type == DT_DIR) {
116            // Create section if it doesn't exist yet.
117            sectionNamed(dptr->d_name);
118        }
119    }
120
121    closedir(dirp);
122}
123
124string Menu::createSectionDir(string const& sectionName)
125{
126    string parentDir = GMenu2X::getHome() + "/sections";
127    if (mkdir(parentDir.c_str(), 0755) && errno != EEXIST) {
128        WARNING("Failed to create parent sections dir: %s\n", strerror(errno));
129        return "";
130    }
131
132    string childDir = parentDir + "/" + sectionName;
133    if (mkdir(childDir.c_str(), 0755) && errno != EEXIST) {
134        WARNING("Failed to create child section dir: %s\n", strerror(errno));
135        return "";
136    }
137
138    return childDir;
139}
140
141void Menu::skinUpdated() {
142    ConfIntHash &skinConfInt = gmenu2x.skinConfInt;
143
144    //recalculate some coordinates based on the new element sizes
145    linkColumns = (gmenu2x.resX - 10) / skinConfInt["linkWidth"];
146    linkRows = (gmenu2x.resY - 35 - skinConfInt["topBarHeight"]) / skinConfInt["linkHeight"];
147
148    //reload section icons
149    decltype(links)::size_type i = 0;
150    for (auto& sectionName : sections) {
151        gmenu2x.sc["skin:sections/" + sectionName + ".png"];
152
153        for (auto& link : links[i]) {
154            link->loadIcon();
155        }
156
157        i++;
158    }
159}
160
161void Menu::calcSectionRange(int &leftSection, int &rightSection) {
162    ConfIntHash &skinConfInt = gmenu2x.skinConfInt;
163    const int linkWidth = skinConfInt["linkWidth"];
164    const int screenWidth = gmenu2x.resX;
165    const int numSections = sections.size();
166    rightSection = min(
167            max(1, (screenWidth - 20 - linkWidth) / (2 * linkWidth)),
168            numSections / 2);
169    leftSection = max(
170            -rightSection,
171            rightSection - numSections + 1);
172}
173
174bool Menu::runAnimations() {
175    if (sectionAnimation.isRunning()) {
176        sectionAnimation.step();
177    }
178    return sectionAnimation.isRunning();
179}
180
181void Menu::paint(Surface &s) {
182    const uint width = s.width(), height = s.height();
183    Font &font = *gmenu2x.font;
184    SurfaceCollection &sc = gmenu2x.sc;
185
186    ConfIntHash &skinConfInt = gmenu2x.skinConfInt;
187    const int topBarHeight = skinConfInt["topBarHeight"];
188    const int bottomBarHeight = skinConfInt["bottomBarHeight"];
189    const int linkWidth = skinConfInt["linkWidth"];
190    const int linkHeight = skinConfInt["linkHeight"];
191    RGBAColor &selectionBgColor = gmenu2x.skinConfColors[COLOR_SELECTION_BG];
192
193    // Apply section header animation.
194    int leftSection, rightSection;
195    calcSectionRange(leftSection, rightSection);
196    int sectionFP = sectionAnimation.currentValue();
197    int sectionDelta = (sectionFP * linkWidth + (1 << 15)) >> 16;
198    int centerSection = iSection - sectionDelta / linkWidth;
199    sectionDelta %= linkWidth;
200    if (sectionDelta < 0) {
201        rightSection++;
202    } else if (sectionDelta > 0) {
203        leftSection--;
204    }
205
206    // Paint section headers.
207    s.box(width / 2 - linkWidth / 2, 0, linkWidth, topBarHeight, selectionBgColor);
208    const uint sectionLinkPadding = (topBarHeight - 32 - font.getLineSpacing()) / 3;
209    const uint numSections = sections.size();
210    for (int i = leftSection; i <= rightSection; i++) {
211        uint j = (centerSection + numSections + i) % numSections;
212        string sectionIcon = "skin:sections/" + sections[j] + ".png";
213        Surface *icon = sc.exists(sectionIcon)
214                ? sc[sectionIcon]
215                : sc.skinRes("icons/section.png");
216        int x = width / 2 + i * linkWidth + sectionDelta;
217        if (i == leftSection) {
218            int t = sectionDelta > 0 ? linkWidth - sectionDelta : -sectionDelta;
219            x -= (((t * t) / linkWidth) * t) / linkWidth;
220        } else if (i == rightSection) {
221            int t = sectionDelta < 0 ? sectionDelta + linkWidth : sectionDelta;
222            x += (((t * t) / linkWidth) * t) / linkWidth;
223        }
224        icon->blit(s, x - 16, sectionLinkPadding, 32, 32);
225        font.write(s, sections[j], x, topBarHeight - sectionLinkPadding,
226                Font::HAlignCenter, Font::VAlignBottom);
227    }
228    sc.skinRes("imgs/section-l.png")->blit(s, 0, 0);
229    sc.skinRes("imgs/section-r.png")->blit(s, width - 10, 0);
230
231    auto& sectionLinks = links[iSection];
232    auto numLinks = sectionLinks.size();
233    gmenu2x.drawScrollBar(
234            linkRows, (numLinks + linkColumns - 1) / linkColumns, iFirstDispRow);
235
236    //Links
237    const uint linksPerPage = linkColumns * linkRows;
238    const int linkSpacingX = (width - 10 - linkColumns * linkWidth) / linkColumns;
239    const int linkMarginX = (
240            width - linkWidth * linkColumns - linkSpacingX * (linkColumns - 1)
241            ) / 2;
242    const int linkSpacingY = (height - 35 - topBarHeight - linkRows * linkHeight) / linkRows;
243    for (uint i = iFirstDispRow * linkColumns; i < iFirstDispRow * linkColumns + linksPerPage && i < numLinks; i++) {
244        const int ir = i - iFirstDispRow * linkColumns;
245        const int x = linkMarginX + (ir % linkColumns) * (linkWidth + linkSpacingX);
246        const int y = ir / linkColumns * (linkHeight + linkSpacingY) + topBarHeight + 2;
247        sectionLinks.at(i)->setPosition(x, y);
248
249        if (i == (uint)iLink) {
250            sectionLinks.at(i)->paintHover();
251        }
252
253        sectionLinks.at(i)->paint();
254    }
255
256    if (selLink()) {
257        font.write(s, selLink()->getDescription(),
258                width / 2, height - bottomBarHeight + 2,
259                Font::HAlignCenter, Font::VAlignBottom);
260    }
261
262    LinkApp *linkApp = selLinkApp();
263    if (linkApp) {
264#ifdef ENABLE_CPUFREQ
265        font.write(s, linkApp->clockStr(gmenu2x.confInt["maxClock"]),
266                gmenu2x.cpuX, gmenu2x.bottomBarTextY,
267                Font::HAlignLeft, Font::VAlignMiddle);
268#endif
269        //Manual indicator
270        if (!linkApp->getManual().empty())
271            sc.skinRes("imgs/manual.png")->blit(
272                    s, gmenu2x.manualX, gmenu2x.bottomBarIconY);
273    }
274}
275
276bool Menu::handleButtonPress(InputManager::Button button) {
277    switch (button) {
278        case InputManager::ACCEPT:
279            if (selLink() != NULL) selLink()->run();
280            return true;
281        case InputManager::UP:
282            linkUp();
283            return true;
284        case InputManager::DOWN:
285            linkDown();
286            return true;
287        case InputManager::LEFT:
288            linkLeft();
289            return true;
290        case InputManager::RIGHT:
291            linkRight();
292            return true;
293        case InputManager::ALTLEFT:
294            decSectionIndex();
295            return true;
296        case InputManager::ALTRIGHT:
297            incSectionIndex();
298            return true;
299        case InputManager::MENU:
300            gmenu2x.showContextMenu();
301            return true;
302        default:
303            return false;
304    }
305}
306
307/*====================================
308   SECTION MANAGEMENT
309  ====================================*/
310
311vector<unique_ptr<Link>> *Menu::sectionLinks(int i)
312{
313    if (i<0 || i>=(int)links.size()) {
314        i = selSectionIndex();
315    }
316
317    if (i<0 || i>=(int)links.size()) {
318        return nullptr;
319    }
320
321    return &links[i];
322}
323
324void Menu::decSectionIndex() {
325    sectionAnimation.adjust(-1 << 16);
326    setSectionIndex(iSection - 1);
327}
328
329void Menu::incSectionIndex() {
330    sectionAnimation.adjust(1 << 16);
331    setSectionIndex(iSection + 1);
332}
333
334int Menu::selSectionIndex() {
335    return iSection;
336}
337
338const string &Menu::selSection() {
339    return sections[iSection];
340}
341
342void Menu::setSectionIndex(int i) {
343    if (i<0)
344        i=sections.size()-1;
345    else if (i>=(int)sections.size())
346        i=0;
347    iSection = i;
348
349    iLink = 0;
350    iFirstDispRow = 0;
351}
352
353/*====================================
354   LINKS MANAGEMENT
355  ====================================*/
356void Menu::addActionLink(uint section, string const& title, Action action,
357        string const& description, string const& icon)
358{
359    assert(section < sections.size());
360
361    Link *link = new Link(gmenu2x, action);
362    link->setSize(gmenu2x.skinConfInt["linkWidth"], gmenu2x.skinConfInt["linkHeight"]);
363    link->setTitle(title);
364    link->setDescription(description);
365    if (gmenu2x.sc.exists(icon)
366            || (icon.substr(0,5)=="skin:"
367                && !gmenu2x.sc.getSkinFilePath(icon.substr(5,icon.length())).empty())
368            || fileExists(icon)) {
369        link->setIcon(icon);
370    }
371
372    links[section].emplace_back(link);
373}
374
375bool Menu::addLink(string const& path, string const& file)
376{
377    string const& sectionName = selSection();
378
379    string sectionDir = createSectionDir(sectionName);
380    if (sectionDir.empty()) {
381        return false;
382    }
383
384    //strip the extension from the filename
385    string title = file;
386    string::size_type pos = title.rfind(".");
387    if (pos!=string::npos && pos>0) {
388        string ext = title.substr(pos, title.length());
389        transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
390        title = title.substr(0, pos);
391    }
392
393    string linkpath = uniquePath(sectionDir, title);
394    INFO("Adding link: '%s'\n", linkpath.c_str());
395
396    string dirPath = path;
397    if (dirPath.empty() || dirPath.back() != '/') dirPath += '/';
398
399    //search for a manual
400    pos = file.rfind(".");
401    string exename = dirPath + file.substr(0, pos);
402    string manual = "";
403    if (fileExists(exename+".man.png")) {
404        manual = exename+".man.png";
405    } else if (fileExists(exename+".man.txt")) {
406        manual = exename+".man.txt";
407    } else {
408        //scan directory for a file like *readme*
409        FileLister fl;
410        fl.setShowDirectories(false);
411        fl.setFilter(".txt");
412        fl.browse(dirPath);
413        bool found = false;
414        for (uint x=0; x<fl.size() && !found; x++) {
415            string lcfilename = fl[x];
416
417            if (lcfilename.find("readme") != string::npos) {
418                found = true;
419                manual = dirPath + fl.getFiles()[x];
420            }
421        }
422    }
423
424    INFO("Manual: '%s'\n", manual.c_str());
425
426    string shorttitle=title, description="", exec=dirPath+file, icon="";
427    if (fileExists(exename+".png")) icon = exename+".png";
428
429    //Reduce title lenght to fit the link width
430    if (gmenu2x.font->getTextWidth(shorttitle)>gmenu2x.skinConfInt["linkWidth"]) {
431        while (gmenu2x.font->getTextWidth(shorttitle+"..")>gmenu2x.skinConfInt["linkWidth"])
432            shorttitle = shorttitle.substr(0,shorttitle.length()-1);
433        shorttitle += "..";
434    }
435
436    ofstream f(linkpath.c_str());
437    if (f.is_open()) {
438        f << "title=" << shorttitle << endl;
439        f << "exec=" << exec << endl;
440        if (!description.empty()) f << "description=" << description << endl;
441        if (!icon.empty()) f << "icon=" << icon << endl;
442        if (!manual.empty()) f << "manual=" << manual << endl;
443        f.close();
444         sync();
445
446        auto idx = sectionNamed(sectionName);
447        auto link = new LinkApp(gmenu2x, linkpath, true);
448        link->setSize(gmenu2x.skinConfInt["linkWidth"], gmenu2x.skinConfInt["linkHeight"]);
449        links[idx].emplace_back(link);
450    } else {
451
452        ERROR("Error while opening the file '%s' for write.\n", linkpath.c_str());
453
454        return false;
455    }
456
457    return true;
458}
459
460int Menu::sectionNamed(const char *sectionName)
461{
462    auto it = lower_bound(sections.begin(), sections.end(), sectionName);
463    int idx = it - sections.begin();
464    if (it == sections.end() || *it != sectionName) {
465        sections.emplace(it, sectionName);
466        links.emplace(links.begin() + idx);
467        // Make sure the selected section doesn't change.
468        if (idx <= iSection) {
469            iSection++;
470        }
471    }
472    return idx;
473}
474
475void Menu::deleteSelectedLink()
476{
477    string iconpath = selLink()->getIconPath();
478
479    INFO("Deleting link '%s'\n", selLink()->getTitle().c_str());
480
481    if (selLinkApp()!=NULL)
482        unlink(selLinkApp()->getFile().c_str());
483    sectionLinks()->erase( sectionLinks()->begin() + selLinkIndex() );
484    setLinkIndex(selLinkIndex());
485
486    bool icon_used = false;
487    for (auto& section : links) {
488        for (auto& link : section) {
489            if (iconpath == link->getIconPath()) {
490                icon_used = true;
491            }
492        }
493    }
494    if (!icon_used) {
495        gmenu2x.sc.del(iconpath);
496    }
497}
498
499void Menu::deleteSelectedSection()
500{
501    string const& sectionName = selSection();
502    INFO("Deleting section '%s'\n", sectionName.c_str());
503
504    gmenu2x.sc.del("sections/" + sectionName + ".png");
505    auto idx = selSectionIndex();
506    links.erase(links.begin() + idx);
507    sections.erase(sections.begin() + idx);
508    setSectionIndex(0); //reload sections
509
510    string path = GMenu2X::getHome() + "/sections/" + sectionName;
511    if (rmdir(path.c_str()) && errno != ENOENT) {
512        WARNING("Removal of section dir \"%s\" failed: %s\n",
513                path.c_str(), strerror(errno));
514    }
515}
516
517bool Menu::moveSelectedLink(string const& newSection)
518{
519    LinkApp *linkApp = selLinkApp();
520    if (!linkApp) {
521        return false;
522    }
523
524    // Note: Get new index first, since it might move the selected index.
525    auto const newSectionIndex = sectionNamed(newSection);
526    auto const oldSectionIndex = iSection;
527
528    string const& file = linkApp->getFile();
529    string linkTitle = file.substr(file.rfind('/') + 1);
530
531    string sectionDir = createSectionDir(newSection);
532    if (sectionDir.empty()) {
533        return false;
534    }
535
536    string newFileName = uniquePath(sectionDir, linkTitle);
537
538    if (rename(file.c_str(), newFileName.c_str())) {
539        WARNING("Link file move from '%s' to '%s' failed: %s\n",
540                file.c_str(), newFileName.c_str(), strerror(errno));
541        return false;
542    }
543    linkApp->setFile(newFileName);
544
545    // Fetch sections.
546    auto& newSectionLinks = links[newSectionIndex];
547    auto& oldSectionLinks = links[oldSectionIndex];
548
549    // Move link.
550    auto it = oldSectionLinks.begin() + iLink;
551    auto link = it->release();
552    oldSectionLinks.erase(it);
553    newSectionLinks.emplace_back(link);
554
555    // Select the same link in the new section.
556    setSectionIndex(newSectionIndex);
557    setLinkIndex(newSectionLinks.size() - 1);
558
559    return true;
560}
561
562void Menu::linkLeft() {
563    if (iLink % linkColumns == 0)
564        setLinkIndex(sectionLinks()->size() > iLink + linkColumns - 1
565                ? iLink + linkColumns - 1 : sectionLinks()->size() - 1);
566    else
567        setLinkIndex(iLink - 1);
568}
569
570void Menu::linkRight() {
571    if (iLink % linkColumns == linkColumns - 1
572            || iLink == (int)sectionLinks()->size() - 1)
573        setLinkIndex(iLink - iLink % linkColumns);
574    else
575        setLinkIndex(iLink + 1);
576}
577
578#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
579
580void Menu::linkUp() {
581    int l = iLink - linkColumns;
582    if (l < 0) {
583        const auto numLinks = sectionLinks()->size();
584        unsigned int rows = DIV_ROUND_UP(numLinks, linkColumns);
585        l = (rows * linkColumns) + l;
586        if (l >= static_cast<int>(numLinks))
587            l -= linkColumns;
588    }
589    setLinkIndex(l);
590}
591
592void Menu::linkDown() {
593    int l = iLink + linkColumns;
594    const auto numLinks = sectionLinks()->size();
595    if (l >= static_cast<int>(numLinks)) {
596        unsigned int rows = DIV_ROUND_UP(numLinks, linkColumns);
597        unsigned int curCol = DIV_ROUND_UP(iLink + 1, linkColumns);
598        if (rows > curCol)
599            l = numLinks - 1;
600        else
601            l %= linkColumns;
602    }
603    setLinkIndex(l);
604}
605
606int Menu::selLinkIndex() {
607    return iLink;
608}
609
610Link *Menu::selLink() {
611    auto curSectionLinks = sectionLinks();
612    if (curSectionLinks->empty()) {
613        return nullptr;
614    } else {
615        return curSectionLinks->at(iLink).get();
616    }
617}
618
619LinkApp *Menu::selLinkApp() {
620    return dynamic_cast<LinkApp*>(selLink());
621}
622
623void Menu::setLinkIndex(int i) {
624    auto links = sectionLinks();
625    if (!links) {
626        // No sections.
627        return;
628    }
629    const int numLinks = static_cast<int>(sectionLinks()->size());
630    if (i < 0)
631        i = numLinks - 1;
632    else if (i >= numLinks)
633        i = 0;
634    iLink = i;
635
636    int row = i / linkColumns;
637    if (row >= (int)(iFirstDispRow + linkRows - 1))
638        iFirstDispRow = min(row + 1, (int)DIV_ROUND_UP(numLinks, linkColumns) - 1)
639                        - linkRows + 1;
640    else if (row <= (int)iFirstDispRow)
641        iFirstDispRow = max(row - 1, 0);
642}
643
644#ifdef HAVE_LIBOPK
645void Menu::openPackagesFromDir(std::string const& path)
646{
647    DEBUG("Opening packages from directory: %s\n", path.c_str());
648    if (readPackages(path)) {
649#ifdef ENABLE_INOTIFY
650        monitors.emplace_back(new Monitor(path.c_str()));
651#endif
652    }
653}
654
655void Menu::openPackage(std::string const& path, bool order)
656{
657    /* First try to remove existing links of the same OPK
658     * (needed for instance when an OPK is modified) */
659    removePackageLink(path);
660
661    struct OPK *opk = opk_open(path.c_str());
662    if (!opk) {
663        ERROR("Unable to open OPK %s\n", path.c_str());
664        return;
665    }
666
667    for (;;) {
668        bool has_metadata = false;
669        const char *name;
670
671        for (;;) {
672            string::size_type pos;
673            int ret = opk_open_metadata(opk, &name);
674            if (ret < 0) {
675                ERROR("Error while loading meta-data\n");
676                break;
677            } else if (!ret)
678              break;
679
680            /* Strip .desktop */
681            string metadata(name);
682            pos = metadata.rfind('.');
683            metadata = metadata.substr(0, pos);
684
685            /* Keep only the platform name */
686            pos = metadata.rfind('.');
687            metadata = metadata.substr(pos + 1);
688
689            if (metadata == PLATFORM || metadata == "all") {
690                has_metadata = true;
691                break;
692            }
693        }
694
695        if (!has_metadata)
696          break;
697
698        // Note: OPK links can only be deleted by removing the OPK itself,
699        // but that is not something we want to do in the menu,
700        // so consider this link undeletable.
701        auto link = new LinkApp(gmenu2x, path, false, opk, name);
702        link->setSize(gmenu2x.skinConfInt["linkWidth"], gmenu2x.skinConfInt["linkHeight"]);
703
704        auto idx = sectionNamed(link->getCategory());
705        links[idx].emplace_back(link);
706    }
707
708    opk_close(opk);
709
710    if (order)
711        orderLinks();
712}
713
714bool Menu::readPackages(std::string const& parentDir)
715{
716    DIR *dirp = opendir(parentDir.c_str());
717    if (!dirp) {
718        return false;
719    }
720
721    while (struct dirent *dptr = readdir(dirp)) {
722        if (dptr->d_type != DT_REG)
723            continue;
724
725        char *c = strrchr(dptr->d_name, '.');
726        if (!c) /* File without extension */
727            continue;
728
729        if (strcasecmp(c + 1, "opk"))
730            continue;
731
732        if (dptr->d_name[0] == '.') {
733            // Ignore hidden files.
734            // Mac OS X places these on SD cards, probably to store metadata.
735            continue;
736        }
737
738        openPackage(parentDir + '/' + dptr->d_name, false);
739    }
740
741    closedir(dirp);
742    orderLinks();
743
744    return true;
745}
746
747#ifdef ENABLE_INOTIFY
748/* Remove all links that correspond to the given path.
749 * If "path" is a directory, it will remove all links that
750 * correspond to an OPK present in the directory. */
751void Menu::removePackageLink(std::string const& path)
752{
753    for (auto section = links.begin(); section != links.end(); ++section) {
754        for (auto link = section->begin(); link != section->end(); ++link) {
755            LinkApp *app = dynamic_cast<LinkApp *>(link->get());
756            if (!app || !app->isOpk() || app->getOpkFile().empty())
757                continue;
758
759            if (app->getOpkFile().compare(0, path.size(), path) == 0) {
760                DEBUG("Removing link corresponding to package %s\n",
761                            app->getOpkFile().c_str());
762                section->erase(link);
763                if (section - links.begin() == iSection
764                            && iLink == (int) section->size()) {
765                    setLinkIndex(iLink - 1);
766                }
767                --link;
768            }
769        }
770    }
771
772    /* Remove registered monitors */
773    for (auto it = monitors.begin(); it < monitors.end(); ++it) {
774        if ((*it)->getPath().compare(0, path.size(), path) == 0) {
775            monitors.erase(it);
776        }
777    }
778}
779#endif
780#endif
781
782static bool compare_links(unique_ptr<Link> const& a, unique_ptr<Link> const& b)
783{
784    LinkApp *app1 = dynamic_cast<LinkApp *>(a.get());
785    LinkApp *app2 = dynamic_cast<LinkApp *>(b.get());
786    bool app1_is_opk = app1 && app1->isOpk(),
787         app2_is_opk = app2 && app2->isOpk();
788
789    if (app1_is_opk && !app2_is_opk) {
790        return false;
791    }
792    if (app2_is_opk && !app1_is_opk) {
793        return true;
794    }
795    return a->getTitle().compare(b->getTitle()) < 0;
796}
797
798void Menu::orderLinks()
799{
800    for (auto& section : links) {
801        sort(section.begin(), section.end(), compare_links);
802    }
803}
804
805void Menu::readLinks()
806{
807    iLink = 0;
808    iFirstDispRow = 0;
809
810    for (uint i=0; i<links.size(); i++) {
811        links[i].clear();
812
813        int correct = (i>sections.size() ? iSection : i);
814        string const& section = sections[correct];
815
816        readLinksOfSection(
817                links[i], GMENU2X_SYSTEM_DIR "/sections/" + section, false);
818        readLinksOfSection(
819                links[i], GMenu2X::getHome() + "/sections/" + section, true);
820    }
821
822    orderLinks();
823}
824
825void Menu::readLinksOfSection(
826        vector<unique_ptr<Link>>& links, string const& path, bool deletable)
827{
828    DIR *dirp = opendir(path.c_str());
829    if (!dirp) return;
830
831    while (struct dirent *dptr = readdir(dirp)) {
832        if (dptr->d_type != DT_REG) continue;
833        string linkfile = path + '/' + dptr->d_name;
834
835        LinkApp *link = new LinkApp(gmenu2x, linkfile, deletable);
836        if (link->targetExists()) {
837            link->setSize(
838                    gmenu2x.skinConfInt["linkWidth"],
839                    gmenu2x.skinConfInt["linkHeight"]);
840            links.emplace_back(link);
841        } else {
842            delete link;
843        }
844    }
845
846    closedir(dirp);
847}
848

Archive Download this file



interactive