MythTV master
customedit.cpp
Go to the documentation of this file.
1// Qt
2#include <QSqlError>
3
4// MythTV
14
15// MythFrontend
16#include "customedit.h"
17#include "proglist.h"
18#include "scheduleeditor.h"
19
20
22 : MythScreenType(parent, "CustomEdit")
23{
24 if (pginfo)
25 m_pginfo = new ProgramInfo(*pginfo);
26 else
27 m_pginfo = new ProgramInfo();
28
31
32 m_seSuffix = QString(" (%1)").arg(tr("stored search"));
33 m_exSuffix = QString(" (%1)").arg(tr("stored example"));
34
36}
37
39{
40 delete m_pginfo;
41
43}
44
46{
47 if (!LoadWindowFromXML("schedule-ui.xml", "customedit", this))
48 return false;
49
50 bool err = false;
51 UIUtilE::Assign(this, m_ruleList, "rules", &err);
52 UIUtilE::Assign(this, m_clauseList, "clauses", &err);
53
54 UIUtilE::Assign(this, m_titleEdit, "title", &err);
55 UIUtilE::Assign(this, m_subtitleEdit, "subtitle", &err);
56 UIUtilE::Assign(this, m_descriptionEdit, "description", &err);
57 UIUtilE::Assign(this, m_clauseText, "clausetext", &err);
58 UIUtilE::Assign(this, m_testButton, "test", &err);
59 UIUtilE::Assign(this, m_recordButton, "record", &err);
60 UIUtilE::Assign(this, m_storeButton, "store", &err);
61 UIUtilE::Assign(this, m_cancelButton, "cancel", &err);
62
63 if (err)
64 {
65 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'customedit'");
66 return false;
67 }
68
78
83
88
89 loadData();
91
92 return true;
93}
94
95
97{
98 CustomRuleInfo rule;
99
100 // New Rule defaults
101 rule.recordid = '0';
102 rule.title = m_baseTitle;
103 if (!m_baseTitle.isEmpty())
104 {
105 QString quoteTitle = m_baseTitle;
106 quoteTitle.replace("\'","\'\'");
107 rule.description = QString("program.title = '%1' ").arg(quoteTitle);
108 }
109
110 new MythUIButtonListItem(m_ruleList, tr("<New rule>"),
111 QVariant::fromValue(rule));
112
114 result.prepare("SELECT recordid, title, subtitle, description "
115 "FROM record WHERE search = :SEARCH ORDER BY title;");
116 result.bindValue(":SEARCH", kPowerSearch);
117
118 if (result.exec())
119 {
120 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
121 while (result.next())
122 {
123 QString trimTitle = result.value(1).toString();
124 trimTitle.remove(RecordingInfo::kReSearchTypeName);
125
126 rule.recordid = result.value(0).toString();
127 rule.title = trimTitle;
128 rule.subtitle = result.value(2).toString();
129 rule.description = result.value(3).toString();
130
131 // No memory leak. MythUIButtonListItem adds the new item
132 // into m_ruleList.
133 auto *item = new MythUIButtonListItem(m_ruleList, rule.title,
134 QVariant::fromValue(rule));
135
136 if (trimTitle == m_baseTitle ||
137 result.value(0).toUInt() == m_pginfo->GetRecordingRuleID())
139 }
140 }
141 else
142 {
143 MythDB::DBError("Get power search rules query", result);
144 }
145
146 loadClauses();
147
148 textChanged();
149}
150
151QString CustomEdit::evaluate(QString clause)
152{
153 static const QRegularExpression replacement { "\\{([A-Z]+)\\}" };
154
155 while (true) {
156 QRegularExpressionMatch match;
157 if (!clause.contains(replacement, &match))
158 break;
159
160 QString mid = match.captured(1);
161 QString repl = "";
162
163 if (mid.compare("TITLE") == 0) {
164 repl = m_pginfo->GetTitle();
165 repl.replace("\'","\'\'");
166 } else if (mid.compare("SUBTITLE") == 0) {
167 repl = m_pginfo->GetSubtitle();
168 repl.replace("\'","\'\'");
169 } else if (mid.compare("DESCR") == 0) {
170 repl = m_pginfo->GetDescription();
171 repl.replace("\'","\'\'");
172 } else if (mid.compare("SERIESID") == 0) {
173 repl = QString("%1").arg(m_pginfo->GetSeriesID());
174 } else if (mid.compare("PROGID") == 0) {
175 repl = m_pginfo->GetProgramID();
176 } else if (mid.compare("SEASON") == 0) {
177 repl = QString::number(m_pginfo->GetSeason());
178 } else if (mid.compare("EPISODE") == 0) {
179 repl = QString::number(m_pginfo->GetEpisode());
180 } else if (mid.compare("CATEGORY") == 0) {
181 repl = m_pginfo->GetCategory();
182 } else if (mid.compare("CHANID") == 0) {
183 repl = QString("%1").arg(m_pginfo->GetChanID());
184 } else if (mid.compare("CHANNUM") == 0) {
185 repl = m_pginfo->GetChanNum();
186 } else if (mid.compare("SCHEDID") == 0) {
188 } else if (mid.compare("CHANNAME") == 0) {
189 repl = m_pginfo->GetChannelName();
190 } else if (mid.compare("DAYNAME") == 0) {
191 repl = m_pginfo->GetScheduledStartTime().toString("dddd");
192 } else if (mid.compare("STARTDATE") == 0) {
193 repl = m_pginfo->GetScheduledStartTime().toString("yyyy-mm-dd hh:mm:ss");
194 } else if (mid.compare("ENDDATE") == 0) {
195 repl = m_pginfo->GetScheduledEndTime().toString("yyyy-mm-dd hh:mm:ss");
196 } else if (mid.compare("STARTTIME") == 0) {
197 repl = m_pginfo->GetScheduledStartTime().toString("hh:mm");
198 } else if (mid.compare("ENDTIME") == 0) {
199 repl = m_pginfo->GetScheduledEndTime().toString("hh:mm");
200 } else if (mid.compare("STARTSEC") == 0) {
201 QDateTime date = m_pginfo->GetScheduledStartTime();
202 QDateTime midnight = date.date().startOfDay();
203 repl = QString("%1").arg(midnight.secsTo(date));
204 } else if (mid.compare("ENDSEC") == 0) {
205 QDateTime date = m_pginfo->GetScheduledEndTime();
206 QDateTime midnight = date.date().startOfDay();
207 repl = QString("%1").arg(midnight.secsTo(date));
208 }
209 // unknown tags are simply removed
210 clause.replace(match.capturedStart(), match.capturedLength(), repl);
211 }
212 return clause;
213}
214
216{
217 CustomRuleInfo rule;
218
219 rule.title = tr("Match an exact title");
220 if (!m_baseTitle.isEmpty())
221 rule.description = QString("program.title = '{TITLE}' ");
222 else
223 rule.description = "program.title = 'Nova' ";
225 QVariant::fromValue(rule));
226
227 if (!m_pginfo->GetSeriesID().isEmpty())
228 {
229 rule.title = tr("Match this series");
230 rule.subtitle.clear();
231 rule.description = QString("program.seriesid = '{SERIESID}' ");
233 QVariant::fromValue(rule));
234 }
235
236 rule.title = tr("Match words in the title");
237 rule.subtitle.clear();
238 if (!m_pginfo->GetTitle().isEmpty())
239 rule.description = QString("program.title LIKE '%{TITLE}%' ");
240 else
241 rule.description = "program.title LIKE 'CSI: %' ";
243 QVariant::fromValue(rule));
244
245 rule.title = tr("Match words in the subtitle");
246 rule.subtitle.clear();
247 if (!m_pginfo->GetSubtitle().isEmpty())
248 rule.description = QString("program.subtitle LIKE '%{SUBTITLE}%' ");
249 else
250 rule.description = "program.subtitle LIKE '%Las Vegas%' ";
252 QVariant::fromValue(rule));
253
254 if (!m_pginfo->GetProgramID().isEmpty())
255 {
256 rule.title = tr("Match this episode");
257 rule.subtitle.clear();
258 rule.description = QString("program.programid = '{PROGID}' ");
259 }
260 else if (!m_pginfo->GetSubtitle().isEmpty())
261 {
262 rule.title = tr("Match this episode");
263 rule.subtitle.clear();
264 rule.description = QString("program.subtitle = '{SUBTITLE}' \n"
265 "AND program.description = '{DESCR}' ");
266 }
267 else
268 {
269 rule.title = tr("Match an exact episode");
270 rule.subtitle.clear();
271 rule.description = QString("program.title = 'Seinfeld' \n"
272 "AND program.subtitle = 'The Soup' ");
273 }
275 QVariant::fromValue(rule));
276
277 rule.title = tr("Match in any descriptive field");
278 rule.subtitle.clear();
279 rule.description = QString("(program.title LIKE '%Japan%' \n"
280 " OR program.subtitle LIKE '%Japan%' \n"
281 " OR program.description LIKE '%Japan%') ");
283 QVariant::fromValue(rule));
284
285 rule.title = tr("New episodes only");
286 rule.subtitle.clear();
287 rule.description = "program.previouslyshown = 0 ";
289 QVariant::fromValue(rule));
290
291 rule.title = tr("Exclude unidentified episodes");
292 rule.subtitle.clear();
293 rule.description = "program.generic = 0 ";
295 QVariant::fromValue(rule));
296
297 rule.title = tr("First showing of each episode");
298 rule.subtitle.clear();
299 rule.description = "program.first > 0 ";
301 QVariant::fromValue(rule));
302
303 rule.title = tr("Last showing of each episode");
304 rule.subtitle.clear();
305 rule.description = "program.last > 0 ";
307 QVariant::fromValue(rule));
308
309 rule.title = tr("Anytime on a specific day of the week");
310 rule.subtitle.clear();
311 rule.description =
312 "DAYNAME(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) = '{DAYNAME}' ";
314 QVariant::fromValue(rule));
315
316 rule.title = tr("Only on weekdays (Monday through Friday)");
317 rule.subtitle.clear();
318 rule.description =
319 "WEEKDAY(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 5 ";
321 QVariant::fromValue(rule));
322
323 rule.title = tr("Only on weekends");
324 rule.subtitle.clear();
325 rule.description =
326 "WEEKDAY(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 5 ";
328 QVariant::fromValue(rule));
329
330 rule.title = tr("Only in prime time");
331 rule.subtitle.clear();
332 rule.description =
333 "HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 19 "
334 "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 23 ";
336 QVariant::fromValue(rule));
337
338 rule.title = tr("Not in prime time");
339 rule.subtitle.clear();
340 rule.description =
341 "(HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 19 "
342 " OR HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 23) ";
344 QVariant::fromValue(rule));
345
346 rule.title = tr("Only on a specific station");
347 rule.subtitle.clear();
348 if (!m_pginfo->GetChannelSchedulingID().isEmpty())
349 rule.description = QString("channel.callsign = '{SCHEDID}' ");
350 else
351 rule.description = "channel.callsign = 'ESPN' ";
353 QVariant::fromValue(rule));
354
355 rule.title = tr("Exclude one station");
356 rule.subtitle.clear();
357 rule.description = "channel.callsign != 'GOLF' ";
359 QVariant::fromValue(rule));
360
361 rule.title = tr("Match related callsigns");
362 rule.subtitle.clear();
363 rule.description = "channel.callsign LIKE 'HBO%' ";
365 QVariant::fromValue(rule));
366
367 rule.title = tr("Only channels from the Favorites group");
368 rule.subtitle = ", channelgroup cg, channelgroupnames cgn";
369 rule.description = "cgn.name = 'Favorites' \n"
370 "AND cg.grpid = cgn.grpid \n"
371 "AND program.chanid = cg.chanid ";
373 QVariant::fromValue(rule));
374
375 rule.title = tr("Only channels from a specific video source");
376 rule.subtitle.clear();
377 rule.description = "channel.sourceid = 2 ";
379 QVariant::fromValue(rule));
380
381 rule.title = tr("Only channels marked as commercial free");
382 rule.subtitle.clear();
383 rule.description = QString("channel.commmethod = %1 ")
386 QVariant::fromValue(rule));
387
388 rule.title = tr("Only shows marked as HDTV");
389 rule.subtitle.clear();
390 rule.description = "program.hdtv > 0 ";
392 QVariant::fromValue(rule));
393
394 rule.title = tr("Only shows marked as widescreen");
395 rule.subtitle.clear();
396 rule.description = "FIND_IN_SET('WIDESCREEN', program.videoprop) > 0 ";
398 QVariant::fromValue(rule));
399
400 rule.title = tr("Exclude H.264 encoded streams (EIT only)");
401 rule.subtitle.clear();
402 rule.description = "FIND_IN_SET('AVC', program.videoprop) = 0 ";
404 QVariant::fromValue(rule));
405
406 rule.title = tr("Only shows with in-vision signing");
407 rule.subtitle.clear();
408 rule.description = "FIND_IN_SET('SIGNED', program.subtitletypes) > 0 ";
410 QVariant::fromValue(rule));
411
412 rule.title = tr("Only shows with in-vision subtitles");
413 rule.subtitle.clear();
414 rule.description = "FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0 ";
416 QVariant::fromValue(rule));
417
418 rule.title = tr("Limit by category");
419 rule.subtitle.clear();
420 if (!m_pginfo->GetCategory().isEmpty())
421 rule.description = QString("program.category = '{CATEGORY}' ");
422 else
423 rule.description = "program.category = 'Reality' ";
425 QVariant::fromValue(rule));
426
427 rule.title = tr("All matches for a genre (Schedules Direct)");
428 rule.subtitle = "LEFT JOIN programgenres ON "
429 "program.chanid = programgenres.chanid AND "
430 "program.starttime = programgenres.starttime ";
431 if (!m_pginfo->GetCategory().isEmpty())
432 rule.description = QString("programgenres.genre = '{CATEGORY}' ");
433 else
434 rule.description = "programgenres.genre = 'Reality' ";
436 QVariant::fromValue(rule));
437
438 rule.title = tr("Limit by MPAA or VCHIP rating (Schedules Direct)");
439 rule.subtitle = "LEFT JOIN programrating ON "
440 "program.chanid = programrating.chanid AND "
441 "program.starttime = programrating.starttime ";
442 rule.description = "(programrating.rating = 'G' OR programrating.rating "
443 "LIKE 'TV-Y%') ";
445 QVariant::fromValue(rule));
446
447 rule.title = tr("Category type (%1)", "List of hardcoded category types")
448 .arg("'movie', 'series', 'sports', 'tvshow'");
449 rule.subtitle.clear();
450 rule.description = "program.category_type = 'sports' ";
452 QVariant::fromValue(rule));
453
454 rule.title = tr("Limit movies by the year of release");
455 rule.subtitle.clear();
456 rule.description = "program.category_type = 'movie' AND "
457 "program.airdate >= 2000 ";
459 QVariant::fromValue(rule));
460
461 rule.title = tr("Minimum star rating (0.0 to 1.0 for movies only)");
462 rule.subtitle.clear();
463 rule.description = "program.stars >= 0.75 ";
465 QVariant::fromValue(rule));
466
467 rule.title = tr("Person named in the credits (Schedules Direct)");
468 rule.subtitle = ", people, credits";
469 rule.description = "people.name = 'Tom Hanks' \n"
470 "AND credits.person = people.person \n"
471 "AND program.chanid = credits.chanid \n"
472 "AND program.starttime = credits.starttime ";
474 QVariant::fromValue(rule));
475
476/* This shows how to use oldprogram but is a bad idea in practice.
477 This would match all future showings until the day after the first
478 showing when all future showing are no longer 'new' titles.
479
480 rule.title = tr("Only titles from the New Titles list");
481 rule.subtitle = "LEFT JOIN oldprogram"
482 " ON oldprogram.oldtitle = program.title ";
483 rule.description = "oldprogram.oldtitle IS NULL ";
484 new MythUIButtonListItem(m_clauseList, rule.title,
485 QVariant::fromValue(rule));
486*/
487
488 rule.title = tr("Re-record SDTV in HDTV (disable duplicate matching)");
489 rule.subtitle = ", recordedprogram rp1 LEFT OUTER JOIN recordedprogram rp2"
490 " ON rp1.programid = rp2.programid AND rp2.hdtv = 1";
491 rule.description = "program.programid = rp1.programid \n"
492 "AND rp1.hdtv = 0 \n"
493 "AND program.hdtv = 1 \n"
494 "AND rp2.starttime IS NULL ";
496 QVariant::fromValue(rule));
497
498 rule.title = tr("Multiple sports teams (complete example)");
499 rule.subtitle.clear();
500 rule.description = "program.title = 'NBA Basketball' \n"
501 "AND program.subtitle REGEXP '(Miami|Cavaliers|Lakers)' \n"
502 "AND program.first > 0 \n";
504 QVariant::fromValue(rule));
505
506 rule.title = tr("Sci-fi B-movies (complete example)");
507 rule.subtitle.clear();
508 rule.description = "program.category_type='movie' \n"
509 "AND program.category='Science fiction' \n"
510 "AND program.stars <= 0.5 AND program.airdate < 1970 ";
512 QVariant::fromValue(rule));
513
514 rule.title =
515 tr("SportsCenter Overnight (complete example - use FindDaily)");
516 rule.subtitle.clear();
517 rule.description =
518 "program.title = 'SportsCenter' \n"
519 "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 2 \n"
520 "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) <= 6 ";
522 QVariant::fromValue(rule));
523
524 rule.title = tr("Movie of the Week (complete example - use FindWeekly)");
525 rule.subtitle.clear();
526 rule.description =
527 "program.category_type='movie' \n"
528 "AND program.stars >= 1.0 AND program.airdate >= 1965 \n"
529 "AND DAYNAME(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) = 'Friday' \n"
530 "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 12 ";
532 QVariant::fromValue(rule));
533
534 rule.title = tr("First Episodes (complete example for Schedules Direct)");
535 rule.subtitle.clear();
536 rule.description = "program.first > 0 \n"
537 "AND program.programid LIKE 'EP%0001' \n"
538 "AND program.originalairdate = "
539 "DATE(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) ";
541 QVariant::fromValue(rule));
542
544
546 result.prepare("SELECT rulename,fromclause,whereclause,search "
547 "FROM customexample;");
548
549 if (result.exec())
550 {
551 while (result.next())
552 {
553 QString str = result.value(0).toString();
554
555 if (result.value(3).toInt() > 0)
556 str += m_seSuffix;
557 else
558 str += m_exSuffix;
559
560 rule.title = str;
561 rule.subtitle = result.value(1).toString();
562 rule.description = result.value(2).toString();
564 QVariant::fromValue(rule));
565 }
566 }
567}
568
570{
571 if (!item || item == m_currentRuleItem)
572 return;
573
574 m_currentRuleItem = item;
575
576 auto rule = item->GetData().value<CustomRuleInfo>();
577
578 m_titleEdit->SetText(rule.title);
579 m_descriptionEdit->SetText(rule.description);
580 m_subtitleEdit->SetText(rule.subtitle);
581
582 textChanged();
583}
584
586{
587 bool hastitle = !m_titleEdit->GetText().isEmpty();
588 bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
589
590 m_testButton->SetEnabled(hasdesc);
591 m_recordButton->SetEnabled(hastitle && hasdesc);
593 (hastitle && hasdesc));
594}
595
597{
598 if (!item)
599 return;
600
601 auto rule = item->GetData().value<CustomRuleInfo>();
602
603 QString msg = (m_evaluate) ? evaluate(rule.description) : rule.description;
604 msg = QString("\"%1\"").arg(msg.simplified());
605 m_clauseText->SetText(msg);
606
607 bool hastitle = !m_titleEdit->GetText().isEmpty();
608 bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
609
611 (hastitle && hasdesc));
612}
613
615{
616 if (!item)
617 return;
618
619 auto rule = item->GetData().value<CustomRuleInfo>();
620
621 QString clause;
622 QString desc = m_descriptionEdit->GetText();
623
624 static const QRegularExpression kNonWhitespaceRE { "\\S" };
625 if (desc.contains(kNonWhitespaceRE))
626 clause = "AND ";
627 clause += (m_evaluate) ? evaluate(rule.description) : rule.description;
628
629 m_descriptionEdit->SetText(desc.append(clause));
630
631 QString sub = m_subtitleEdit->GetText();
632 m_subtitleEdit->SetText(sub.append(rule.subtitle));
633}
634
636{
637 if (!checkSyntax())
638 {
639 return;
640 }
641
643 auto *pl = new ProgLister(mainStack, plSQLSearch,
646 if (pl->Create())
647 {
648 mainStack->AddScreen(pl);
649 }
650 else
651 {
652 delete pl;
653 }
654}
655
670{
671 if (!checkSyntax())
672 return;
673
674 auto *record = new RecordingRule();
675
677 auto rule = item->GetData().value<CustomRuleInfo>();
678
679 int cur_recid = rule.recordid.toInt();
680 if (cur_recid > 0)
681 {
682 record->ModifyPowerSearchByID(cur_recid, m_titleEdit->GetText(),
685 }
686 else
687 {
688 record->LoadBySearch(kPowerSearch, m_titleEdit->GetText(),
691 m_pginfo->GetTitle().isEmpty() ? nullptr : m_pginfo);
692 }
693
695 auto *schededit = new ScheduleEditor(mainStack, record);
696 if (schededit->Create())
697 {
698 mainStack->AddScreen(schededit);
699 connect(schededit, &ScheduleEditor::ruleSaved, this, &CustomEdit::scheduleCreated);
700 }
701 else
702 {
703 delete schededit;
704 }
705}
706
708{
709 if (ruleID > 0)
710 Close();
711}
712
714{
715 bool exampleExists = false;
716
718 query.prepare("SELECT rulename,whereclause FROM customexample "
719 "WHERE rulename = :RULE;");
720 query.bindValue(":RULE", m_titleEdit->GetText());
721
722 if (query.exec() && query.next())
723 exampleExists = true;
724
725 QString msg = QString("%1: %2\n\n").arg(tr("Current Example"),
727
728 if (!m_subtitleEdit->GetText().isEmpty())
729 msg += m_subtitleEdit->GetText() + "\n\n";
730
731 msg += m_descriptionEdit->GetText();
732
734 auto *storediag = new MythDialogBox(msg, mainStack, "storePopup", true);
735
736 storediag->SetReturnEvent(this, "storeruledialog");
737 if (storediag->Create())
738 {
739 if (!m_titleEdit->GetText().isEmpty())
740 {
741 QString str;
742 // Keep strings whole for translation!
743 if (exampleExists)
744 str = tr("Replace as a search");
745 else
746 str = tr("Store as a search");
747 storediag->AddButton(str);
748
749 if (exampleExists)
750 str = tr("Replace as an example");
751 else
752 str = tr("Store as an example");
753 storediag->AddButton(str);
754 }
755
757 {
759 QString str = QString("%1 \"%2\"").arg(tr("Delete"),
760 item->GetText());
761 storediag->AddButton(str);
762 }
763 mainStack->AddScreen(storediag);
764 }
765 else
766 {
767 delete storediag;
768 }
769}
770
771
773{
774 bool ret = false;
775 QString msg;
776
777 QString desc = evaluate(m_descriptionEdit->GetText());
778 QString from = m_subtitleEdit->GetText();
779 if (desc.contains(RecordingInfo::kReLeadingAnd))
780 {
781 msg = tr("Power Search rules no longer require a leading \"AND\".");
782 }
783 else if (desc.contains(';'))
784 {
785 msg = tr("Power Search rules cannot include semicolon ( ; ) ");
786 msg += tr("statement terminators.");
787 }
788 else
789 {
791 query.prepare(QString("SELECT NULL FROM (program, channel, oldrecorded AS oldrecstatus) "
792 "%1 WHERE %2 LIMIT 5").arg(from, desc));
793
794 if (query.exec())
795 {
796 ret = true;
797 }
798 else
799 {
800 msg = tr("An error was found when checking") + ":\n\n";
801 msg += query.executedQuery();
802 msg += "\n\n" + tr("The database error was") + ":\n";
803 msg += query.lastError().databaseText();
804 }
805 }
806
807 if (!msg.isEmpty())
808 {
809 MythScreenStack *popupStack = GetMythMainWindow()->
810 GetStack("popup stack");
811 auto *checkSyntaxPopup =
812 new MythConfirmationDialog(popupStack, msg, false);
813
814 if (checkSyntaxPopup->Create())
815 {
816 checkSyntaxPopup->SetReturnEvent(this, "checkSyntaxPopup");
817 popupStack->AddScreen(checkSyntaxPopup);
818 }
819 else
820 {
821 delete checkSyntaxPopup;
822 }
823 ret = false;
824 }
825 return ret;
826}
827
828void CustomEdit::storeRule(bool is_search, bool is_new)
829{
830 CustomRuleInfo rule;
831 rule.recordid = '0';
832 rule.title = m_titleEdit->GetText();
835
837 query.prepare("REPLACE INTO customexample "
838 "(rulename,fromclause,whereclause,search) "
839 "VALUES(:RULE,:FROMC,:WHEREC,:SEARCH);");
840 query.bindValue(":RULE", rule.title);
841 query.bindValue(":FROMC", rule.subtitle);
842 query.bindValue(":WHEREC", rule.description);
843 query.bindValue(":SEARCH", is_search);
844
845 if (is_search)
846 rule.title += m_seSuffix;
847 else
848 rule.title += m_exSuffix;
849
850 if (!query.exec())
851 MythDB::DBError("Store custom example", query);
852 else if (is_new)
853 {
855 QVariant::fromValue(rule));
856 }
857 else
858 {
859 /* Modify the existing entry. We know one exists from the database
860 search but do not know its position in the clause list. It may
861 or may not be the current item. */
862 for (int i = m_maxex; i < m_clauseList->GetCount(); i++)
863 {
865 QString removedStr = item->GetText().remove(m_seSuffix)
866 .remove(m_exSuffix);
867 if (m_titleEdit->GetText() == removedStr)
868 {
869 item->SetData(QVariant::fromValue(rule));
870 clauseChanged(item);
871 break;
872 }
873 }
874 }
875
876
877}
878
880{
882 if (!item || m_clauseList->GetCurrentPos() < m_maxex)
883 return;
884
886 query.prepare("DELETE FROM customexample "
887 "WHERE rulename = :RULE;");
888 query.bindValue(":RULE", item->GetText().remove(m_seSuffix)
889 .remove(m_exSuffix));
890
891 if (!query.exec())
892 MythDB::DBError("Delete custom example", query);
893 else
894 {
896 }
897}
898
899void CustomEdit::customEvent(QEvent *event)
900{
901 if (event->type() == DialogCompletionEvent::kEventType)
902 {
903 auto *dce = (DialogCompletionEvent*)(event);
904
905 QString resultid = dce->GetId();
906 QString resulttext = dce->GetResultText();
907
908 if (resultid == "storeruledialog")
909 {
910 if (resulttext.startsWith(tr("Delete")))
911 {
912 deleteRule();
913 }
914 else if (!resulttext.isEmpty())
915 {
916 storeRule(resulttext.contains(tr("as a search")),
917 !resulttext.startsWith(tr("Replace")));
918 }
919 }
920 }
921}
922
923bool CustomEdit::keyPressEvent(QKeyEvent *event)
924{
925 if (GetFocusWidget()->keyPressEvent(event))
926 return true;
927
928 QStringList actions;
929 bool handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", event, actions);
930
931 for (int i = 0; i < actions.size() && !handled; i++)
932 {
933 const QString& action = actions[i];
934 handled = true;
935
936 if (action == "DELETE")
937 {
939 deleteRule();
940 // else if (GetFocusWidget() == m_ruleList)
941 // deleteRecordingRule();
942 }
943 else if (action == "EDIT")
944 {
945 // toggle evaluated/unevaluated sample view
948 clauseChanged(item);
949 }
950 else
951 {
952 handled = false;
953 }
954 }
955
956 if (!handled && MythScreenType::keyPressEvent(event))
957 handled = true;
958
959 return handled;
960}
void recordClicked(void)
The user clicked on the 'Record' button in the 'Custom Edit' window.
Definition: customedit.cpp:669
void deleteRule(void)
Definition: customedit.cpp:879
~CustomEdit(void) override
Definition: customedit.cpp:38
MythUIButton * m_recordButton
Definition: customedit.h:69
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: customedit.cpp:923
QString m_exSuffix
Definition: customedit.h:54
MythUIButton * m_cancelButton
Definition: customedit.h:71
void customEvent(QEvent *event) override
Definition: customedit.cpp:899
void scheduleCreated(int ruleID)
Definition: customedit.cpp:707
bool Create() override
Definition: customedit.cpp:45
CustomEdit(MythScreenStack *parent, ProgramInfo *m_pginfo=nullptr)
Definition: customedit.cpp:21
void clauseChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:596
QString m_baseTitle
Definition: customedit.h:48
void storeClicked(void)
Definition: customedit.cpp:713
MythUIButton * m_testButton
Definition: customedit.h:68
MythUIButtonList * m_ruleList
Definition: customedit.h:56
MythUIText * m_clauseText
Definition: customedit.h:67
int m_maxex
Definition: customedit.h:50
void textChanged(void)
Definition: customedit.cpp:585
void clauseClicked(MythUIButtonListItem *item)
Definition: customedit.cpp:614
void ruleChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:569
MythUITextEdit * m_titleEdit
Definition: customedit.h:59
MythUIButton * m_storeButton
Definition: customedit.h:70
MythUIButtonList * m_clauseList
Definition: customedit.h:57
bool m_evaluate
Definition: customedit.h:51
QString m_seSuffix
Definition: customedit.h:53
void loadClauses(void)
Definition: customedit.cpp:215
void storeRule(bool is_search, bool is_new)
Definition: customedit.cpp:828
void loadData(void)
Definition: customedit.cpp:96
bool checkSyntax(void)
Definition: customedit.cpp:772
MythUITextEdit * m_subtitleEdit
Definition: customedit.h:65
void testClicked(void)
Definition: customedit.cpp:635
const MythUIButtonListItem * m_currentRuleItem
Definition: customedit.h:73
MythUITextEdit * m_descriptionEdit
Definition: customedit.h:62
QString evaluate(QString clause)
Definition: customedit.cpp:151
ProgramInfo * m_pginfo
Definition: customedit.h:47
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
static const Type kEventType
Definition: mythdialogbox.h:56
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
QString executedQuery(void) const
Definition: mythdbcon.h:205
QSqlError lastError(void) const
Definition: mythdbcon.h:213
QVariant value(int i) const
Definition: mythdbcon.h:204
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
Dialog asking for user confirmation.
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
Basic menu dialog, message and a list of options.
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
virtual void Close()
void SetData(QVariant data)
QString GetText(const QString &name="") const
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
void RemoveItem(MythUIButtonListItem *item)
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemAt(int pos) const
void itemSelected(MythUIButtonListItem *item)
void Clicked()
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void SetMaxLength(int length)
void valueChanged()
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
void SetEnabled(bool enable)
Holds information on recordings and videos.
Definition: programinfo.h:68
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:373
uint GetRecordingRuleID(void) const
Definition: programinfo.h:453
QString GetSeriesID(void) const
Definition: programinfo.h:439
uint GetEpisode(void) const
Definition: programinfo.h:368
QString GetProgramID(void) const
Definition: programinfo.h:440
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:398
QString GetDescription(void) const
Definition: programinfo.h:366
QString GetChannelName(void) const
This is the channel name in the local market, i.e.
Definition: programinfo.h:387
QString GetTitle(void) const
Definition: programinfo.h:362
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:391
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:377
QString GetSubtitle(void) const
Definition: programinfo.h:364
QString GetCategory(void) const
Definition: programinfo.h:370
uint GetSeason(void) const
Definition: programinfo.h:367
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
Definition: programinfo.h:384
static const QRegularExpression kReLeadingAnd
static const QRegularExpression kReSearchTypeName
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:30
Construct a recording schedule.
void ruleSaved(int ruleId)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
@ plSQLSearch
Definition: proglist.h:24
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:128
@ kPowerSearch
QString recordid
Definition: customedit.h:77
QString title
Definition: customedit.h:78
QString subtitle
Definition: customedit.h:79
QString description
Definition: customedit.h:80
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27