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