00001 #include <algorithm>
00002 #include <vector>
00003 #include <iterator>
00004 #include <map>
00005
00006 #include "mythlogging.h"
00007 #include "mythmainwindow.h"
00008 #include "mythdialogbox.h"
00009 #include "mythuibuttonlist.h"
00010 #include "mythuitextedit.h"
00011 #include "mythuicheckbox.h"
00012 #include "mythuibutton.h"
00013 #include "dbaccess.h"
00014 #include "videoutils.h"
00015
00016 #include "videofileassoc.h"
00017
00018 namespace
00019 {
00020 template <typename T, typename Inst, typename FuncType>
00021 void assign_if_changed_notify(T &oldVal, const T &newVal, Inst *inst,
00022 FuncType func)
00023 {
00024 if (oldVal != newVal)
00025 {
00026 oldVal = newVal;
00027 func(inst);
00028 }
00029 }
00030
00031 class FileAssociationWrap
00032 {
00033 public:
00034 enum FA_State {
00035 efsNONE,
00036 efsDELETE,
00037 efsSAVE
00038 };
00039
00040 public:
00041 FileAssociationWrap(const QString &new_extension) : m_state(efsSAVE)
00042 {
00043 m_fa.extension = new_extension;
00044 }
00045
00046 FileAssociationWrap(const FileAssociations::file_association &fa) :
00047 m_fa(fa), m_state(efsNONE) {}
00048
00049 int GetID() const { return m_fa.id; }
00050 QString GetExtension() const { return m_fa.extension; }
00051 QString GetCommand() const { return m_fa.playcommand; }
00052 bool GetDefault() const { return m_fa.use_default; }
00053 bool GetIgnore() const { return m_fa.ignore; }
00054
00055 FA_State GetState() const { return m_state; }
00056
00057 void CommitChanges()
00058 {
00059 switch (m_state)
00060 {
00061 case efsDELETE:
00062 {
00063 FileAssociations::getFileAssociation().remove(m_fa.id);
00064 m_fa.id = -1;
00065 m_state = efsNONE;
00066 break;
00067 }
00068 case efsSAVE:
00069 {
00070 if (FileAssociations::getFileAssociation().add(m_fa))
00071 {
00072 m_state = efsNONE;
00073 }
00074 break;
00075 }
00076 case efsNONE:
00077 default: {}
00078 }
00079 }
00080
00081 void MarkForDeletion()
00082 {
00083 m_state = efsDELETE;
00084 }
00085
00086 void SetDefault(bool yes_or_no)
00087 {
00088 assign_if_changed_notify(m_fa.use_default, yes_or_no, this,
00089 std::mem_fun(&FileAssociationWrap::SetChanged));
00090 }
00091
00092 void SetIgnore(bool yes_or_no)
00093 {
00094 assign_if_changed_notify(m_fa.ignore, yes_or_no, this,
00095 std::mem_fun(&FileAssociationWrap::SetChanged));
00096 }
00097
00098 void SetCommand(const QString &new_command)
00099 {
00100 assign_if_changed_notify(m_fa.playcommand, new_command, this,
00101 std::mem_fun(&FileAssociationWrap::SetChanged));
00102 }
00103
00104 private:
00105 void SetChanged() { m_state = efsSAVE; }
00106
00107 private:
00108 FileAssociations::file_association m_fa;
00109 FA_State m_state;
00110 };
00111
00112 class BlockSignalsGuard
00113 {
00114 public:
00115 void Block(QObject *o)
00116 {
00117 o->blockSignals(true);
00118 m_objects.push_back(o);
00119 }
00120
00121 ~BlockSignalsGuard()
00122 {
00123 for (list_type::iterator p = m_objects.begin();
00124 p != m_objects.end(); ++p)
00125 {
00126 (*p)->blockSignals(false);
00127 }
00128 }
00129
00130 private:
00131 typedef std::vector<QObject *> list_type;
00132
00133 private:
00134 list_type m_objects;
00135 };
00136
00137 struct UIDToFAPair
00138 {
00139 typedef unsigned int UID_type;
00140
00141 UIDToFAPair() : m_uid(0), m_file_assoc(0) {}
00142
00143 UIDToFAPair(UID_type uid, FileAssociationWrap *assoc) :
00144 m_uid(uid), m_file_assoc(assoc) {}
00145
00146 UID_type m_uid;
00147 FileAssociationWrap *m_file_assoc;
00148 };
00149
00150
00151 bool operator<(const UIDToFAPair &lhs, const UIDToFAPair &rhs)
00152 {
00153 if (lhs.m_file_assoc && rhs.m_file_assoc)
00154 return QString::localeAwareCompare(lhs.m_file_assoc->GetExtension(),
00155 rhs.m_file_assoc->GetExtension()) < 0;
00156
00157 return rhs.m_file_assoc;
00158 }
00159 }
00160
00162
00163 class FileAssocDialogPrivate
00164 {
00165 public:
00166 typedef std::vector<UIDToFAPair> UIReadyList_type;
00167
00168 public:
00169 FileAssocDialogPrivate() : m_nextFAID(0), m_selectionOverride(0)
00170 {
00171 LoadFileAssociations();
00172 }
00173
00174 ~FileAssocDialogPrivate()
00175 {
00176 for (FA_collection::iterator p = m_fileAssociations.begin();
00177 p != m_fileAssociations.end(); ++p)
00178 {
00179 delete p->second;
00180 }
00181 }
00182
00183 void SaveFileAssociations()
00184 {
00185 for (FA_collection::iterator p = m_fileAssociations.begin();
00186 p != m_fileAssociations.end(); ++p)
00187 {
00188 p->second->CommitChanges();
00189 }
00190 }
00191
00192 bool AddExtension(QString newExtension, UIDToFAPair::UID_type &new_id)
00193 {
00194 if (newExtension.length())
00195 {
00196 new_id = ++m_nextFAID;
00197 m_fileAssociations.insert(FA_collection::value_type(new_id,
00198 new FileAssociationWrap(newExtension)));
00199 return true;
00200 }
00201
00202 return false;
00203 }
00204
00205 bool DeleteExtension(UIDToFAPair::UID_type uid)
00206 {
00207 FA_collection::iterator p = m_fileAssociations.find(uid);
00208 if (p != m_fileAssociations.end())
00209 {
00210 p->second->MarkForDeletion();
00211
00212 return true;
00213 }
00214
00215 return false;
00216 }
00217
00218
00219 UIReadyList_type GetUIReadyList()
00220 {
00221 UIReadyList_type ret;
00222 std::transform(m_fileAssociations.begin(), m_fileAssociations.end(),
00223 std::back_inserter(ret), fa_col_ent_2_UIDFAPair());
00224 UIReadyList_type::iterator deleted = std::remove_if(ret.begin(),
00225 ret.end(), test_fa_state<FileAssociationWrap::efsDELETE>());
00226
00227 if (deleted != ret.end())
00228 ret.erase(deleted, ret.end());
00229
00230 std::sort(ret.begin(), ret.end());
00231
00232 return ret;
00233 }
00234
00235 FileAssociationWrap *GetCurrentFA(MythUIButtonList *buttonList)
00236 {
00237 MythUIButtonListItem *item = buttonList->GetItemCurrent();
00238 if (item)
00239 {
00240 UIDToFAPair key = item->GetData().value<UIDToFAPair>();
00241 if (key.m_file_assoc)
00242 {
00243 return key.m_file_assoc;
00244 }
00245 }
00246
00247 return NULL;
00248 }
00249
00250 void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
00251 {
00252 m_selectionOverride = new_sel;
00253 }
00254
00255 UIDToFAPair::UID_type GetSelectionOverride() const
00256 {
00257 return m_selectionOverride;
00258 }
00259
00260 private:
00261 typedef std::map<UIDToFAPair::UID_type, FileAssociationWrap *>
00262 FA_collection;
00263
00264 private:
00265 struct fa_col_ent_2_UIDFAPair
00266 {
00267 UIDToFAPair operator()(
00268 const FileAssocDialogPrivate::FA_collection::value_type &from)
00269 {
00270 return UIDToFAPair(from.first, from.second);
00271 }
00272 };
00273
00274 template <FileAssociationWrap::FA_State against>
00275 struct test_fa_state
00276 {
00277 bool operator()(const UIDToFAPair &item)
00278 {
00279 if (item.m_file_assoc && item.m_file_assoc->GetState() == against)
00280 return true;
00281 return false;
00282 }
00283 };
00284
00285 void LoadFileAssociations()
00286 {
00287 typedef std::vector<UIDToFAPair> tmp_fa_list;
00288
00289 const FileAssociations::association_list &fa_list =
00290 FileAssociations::getFileAssociation().getList();
00291 tmp_fa_list tmp_fa;
00292 tmp_fa.reserve(fa_list.size());
00293
00294 for (FileAssociations::association_list::const_iterator p =
00295 fa_list.begin(); p != fa_list.end(); ++p)
00296 {
00297 tmp_fa.push_back(UIDToFAPair(++m_nextFAID,
00298 new FileAssociationWrap(*p)));
00299 }
00300
00301 std::random_shuffle(tmp_fa.begin(), tmp_fa.end());
00302
00303 for (tmp_fa_list::const_iterator p = tmp_fa.begin(); p != tmp_fa.end();
00304 ++p)
00305 {
00306 m_fileAssociations.insert(FA_collection::value_type(p->m_uid,
00307 p->m_file_assoc));
00308 }
00309
00310 if (m_fileAssociations.empty())
00311 {
00312 LOG(VB_GENERAL, LOG_ERR,
00313 QString("%1: Couldn't get any filetypes from your database.")
00314 .arg(__FILE__));
00315 }
00316 }
00317
00318 private:
00319 FA_collection m_fileAssociations;
00320 UIDToFAPair::UID_type m_nextFAID;
00321 UIDToFAPair::UID_type m_selectionOverride;
00322 };
00323
00325
00326 FileAssocDialog::FileAssocDialog(MythScreenStack *screenParent,
00327 const QString &lname) :
00328 MythScreenType(screenParent, lname), m_commandEdit(0),
00329 m_extensionList(0), m_defaultCheck(0), m_ignoreCheck(0), m_doneButton(0),
00330 m_newButton(0), m_deleteButton(0), m_private(new FileAssocDialogPrivate)
00331 {
00332 }
00333
00334 FileAssocDialog::~FileAssocDialog()
00335 {
00336 delete m_private;
00337 }
00338
00339 bool FileAssocDialog::Create()
00340 {
00341 if (!LoadWindowFromXML("video-ui.xml", "file_associations", this))
00342 return false;
00343
00344 bool err = false;
00345 UIUtilE::Assign(this, m_extensionList, "extension_select", &err);
00346 UIUtilE::Assign(this, m_commandEdit, "command", &err);
00347 UIUtilE::Assign(this, m_ignoreCheck, "ignore_check", &err);
00348 UIUtilE::Assign(this, m_defaultCheck, "default_check", &err);
00349
00350 UIUtilE::Assign(this, m_doneButton, "done_button", &err);
00351 UIUtilE::Assign(this, m_newButton, "new_button", &err);
00352 UIUtilE::Assign(this, m_deleteButton, "delete_button", &err);
00353
00354 if (err)
00355 {
00356 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'file_associations'");
00357 return false;
00358 }
00359
00360 connect(m_extensionList, SIGNAL(itemSelected(MythUIButtonListItem *)),
00361 SLOT(OnFASelected(MythUIButtonListItem *)));
00362 connect(m_commandEdit, SIGNAL(valueChanged()),
00363 SLOT(OnPlayerCommandChanged()));
00364 connect(m_defaultCheck, SIGNAL(valueChanged()), SLOT(OnUseDefaltChanged()));
00365 connect(m_ignoreCheck, SIGNAL(valueChanged()), SLOT(OnIgnoreChanged()));
00366
00367 connect(m_doneButton, SIGNAL(Clicked()), SLOT(OnDonePressed()));
00368 connect(m_newButton, SIGNAL(Clicked()),
00369 SLOT(OnNewExtensionPressed()));
00370 connect(m_deleteButton, SIGNAL(Clicked()), SLOT(OnDeletePressed()));
00371
00372 m_extensionList->SetHelpText(tr("Select a file extension from this list "
00373 "to modify or delete its settings."));
00374 m_commandEdit->SetHelpText(tr("The command to use when playing this kind "
00375 "of file. To use MythTV's Internal player, "
00376 "use \"Internal\" as the player. For all other "
00377 "players, you can use %s to substitute the filename."));
00378 m_ignoreCheck->SetHelpText(tr("When checked, this will cause the file extension "
00379 "to be ignored in scans of your library."));
00380 m_defaultCheck->SetHelpText(tr("When checked, this will cause the global player "
00381 "settings to override this one."));
00382 m_doneButton->SetHelpText(tr("Save and exit this screen."));
00383 m_newButton->SetHelpText(tr("Create a new file extension."));
00384 m_deleteButton->SetHelpText(tr("Delete this file extension."));
00385
00386 UpdateScreen();
00387
00388 BuildFocusList();
00389
00390 return true;
00391 }
00392
00393 void FileAssocDialog::OnFASelected(MythUIButtonListItem *item)
00394 {
00395 (void) item;
00396 UpdateScreen();
00397 }
00398
00399 void FileAssocDialog::OnUseDefaltChanged()
00400 {
00401 if (m_private->GetCurrentFA(m_extensionList))
00402 m_private->GetCurrentFA(m_extensionList)->
00403 SetDefault(m_defaultCheck->GetBooleanCheckState());
00404 }
00405
00406 void FileAssocDialog::OnIgnoreChanged()
00407 {
00408 if (m_private->GetCurrentFA(m_extensionList))
00409 m_private->GetCurrentFA(m_extensionList)->
00410 SetIgnore(m_ignoreCheck->GetBooleanCheckState());
00411 }
00412
00413 void FileAssocDialog::OnPlayerCommandChanged()
00414 {
00415 if (m_private->GetCurrentFA(m_extensionList))
00416 m_private->GetCurrentFA(m_extensionList)->
00417 SetCommand(m_commandEdit->GetText());
00418 }
00419
00420 void FileAssocDialog::OnDonePressed()
00421 {
00422 m_private->SaveFileAssociations();
00423 Close();
00424 }
00425
00426 void FileAssocDialog::OnDeletePressed()
00427 {
00428 MythUIButtonListItem *item = m_extensionList->GetItemCurrent();
00429 if (item)
00430 {
00431 UIDToFAPair key = item->GetData().value<UIDToFAPair>();
00432 if (key.m_file_assoc && m_private->DeleteExtension(key.m_uid))
00433 delete item;
00434 }
00435
00436 UpdateScreen();
00437 }
00438
00439 void FileAssocDialog::OnNewExtensionPressed()
00440 {
00441 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00442
00443 QString message = tr("Enter the new extension:");
00444
00445 MythTextInputDialog *newextdialog =
00446 new MythTextInputDialog(popupStack, message);
00447
00448 if (newextdialog->Create())
00449 popupStack->AddScreen(newextdialog);
00450
00451 connect(newextdialog, SIGNAL(haveResult(QString)),
00452 SLOT(OnNewExtensionComplete(QString)));
00453 }
00454
00455 void FileAssocDialog::OnNewExtensionComplete(QString newExtension)
00456 {
00457 UIDToFAPair::UID_type new_sel = 0;
00458 if (m_private->AddExtension(newExtension, new_sel))
00459 {
00460 m_private->SetSelectionOverride(new_sel);
00461 UpdateScreen(true);
00462 }
00463 }
00464
00465 void FileAssocDialog::UpdateScreen(bool useSelectionOverride )
00466 {
00467 BlockSignalsGuard bsg;
00468 bsg.Block(m_extensionList);
00469 bsg.Block(m_commandEdit);
00470 bsg.Block(m_defaultCheck);
00471 bsg.Block(m_ignoreCheck);
00472
00473 FileAssocDialogPrivate::UIReadyList_type tmp_list =
00474 m_private->GetUIReadyList();
00475
00476 if (tmp_list.empty())
00477 {
00478 m_extensionList->SetVisible(false);
00479 m_commandEdit->SetVisible(false);
00480 m_defaultCheck->SetVisible(false);
00481 m_ignoreCheck->SetVisible(false);
00482 m_deleteButton->SetVisible(false);
00483 }
00484 else
00485 {
00486 UIDToFAPair::UID_type selected_id = 0;
00487 MythUIButtonListItem *current_item = m_extensionList->GetItemCurrent();
00488 if (current_item)
00489 {
00490 UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
00491 if (key.m_file_assoc)
00492 {
00493 selected_id = key.m_uid;
00494 }
00495 }
00496
00497 if (useSelectionOverride)
00498 selected_id = m_private->GetSelectionOverride();
00499
00500 m_extensionList->SetVisible(true);
00501 m_extensionList->Reset();
00502
00503 for (FileAssocDialogPrivate::UIReadyList_type::iterator p =
00504 tmp_list.begin(); p != tmp_list.end(); ++p)
00505 {
00506 if (p->m_file_assoc)
00507 {
00508 MythUIButtonListItem *new_item =
00509 new MythUIButtonListItem(m_extensionList,
00510 p->m_file_assoc->GetExtension(),
00511 QVariant::fromValue(*p));
00512 if (selected_id && p->m_uid == selected_id)
00513 m_extensionList->SetItemCurrent(new_item);
00514 }
00515 }
00516
00517 current_item = m_extensionList->GetItemCurrent();
00518 if (current_item)
00519 {
00520 UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
00521 if (key.m_file_assoc)
00522 {
00523 m_commandEdit->SetVisible(true);
00524 m_commandEdit->SetText(key.m_file_assoc->GetCommand());
00525
00526 m_defaultCheck->SetVisible(true);
00527 m_defaultCheck->SetCheckState(key.m_file_assoc->GetDefault() ?
00528 MythUIStateType::Full : MythUIStateType::Off);
00529
00530 m_ignoreCheck->SetVisible(true);
00531 m_ignoreCheck->SetCheckState(key.m_file_assoc->GetIgnore() ?
00532 MythUIStateType::Full : MythUIStateType::Off);
00533
00534 m_deleteButton->SetVisible(true);
00535 }
00536 }
00537 }
00538
00539 BuildFocusList();
00540 }
00541
00542 Q_DECLARE_METATYPE(UIDToFAPair)