Index: mythplugins/mythgallery/mythgallery/galleryutil.h
===================================================================
--- mythplugins/mythgallery/mythgallery/galleryutil.h	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/galleryutil.h	(working copy)
@@ -1,3 +1,4 @@
+// -*- Mode: c++ -*-
 /* ============================================================
  * File  : exifutil.h
  * Description : 
@@ -23,8 +24,7 @@
 
 class GalleryUtil
 {
-
- public:
+  public:
     static bool isImage(const char* filePath);
     static bool isMovie(const char* filePath);
     static long getNaturalRotation(const char* filePath);
@@ -34,6 +34,18 @@
     static bool loadDirectory(ThumbList& itemList,
                               const QString& dir, bool recurse,
                               ThumbDict *itemDict, ThumbGenerator* thumbGen);
+
+    static bool CopyMove(const QFileInfo &src, QFileInfo &dst, bool move)
+        { if (move) return Move(src, dst); else return Copy(src, dst); }
+
+    static bool Copy(const QFileInfo &src, QFileInfo &dst);
+    static bool Move(const QFileInfo &src, QFileInfo &dst);
+    static bool Delete(const QFileInfo &file);
+
+  private:
+    static bool CopyDirectory(const QFileInfo src, QFileInfo &dst);
+    static bool MoveDirectory(const QFileInfo src, QFileInfo &dst);
+    static bool DeleteDirectory(const QFileInfo &dir);
 };
 
 #endif /* EXIFUTIL_H */
Index: mythplugins/mythgallery/mythgallery/iconview.cpp
===================================================================
--- mythplugins/mythgallery/mythgallery/iconview.cpp	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/iconview.cpp	(working copy)
@@ -31,6 +31,8 @@
 #include <mythtv/xmlparse.h>
 #include <mythtv/dialogbox.h>
 #include <mythtv/mythdbcon.h>
+#include <mythtv/mythmediamonitor.h>
+#include <mythtv/mythdialogs.h>
 
 #include "galleryutil.h"
 #include "gallerysettings.h"
@@ -84,13 +86,14 @@
     return false;
 }
 
-IconView::IconView(const QString& galleryDir, MythMainWindow* parent, 
-                   const char* name )
+IconView::IconView(const QString& galleryDir, MythMediaDevice *initialDevice,
+                   MythMainWindow* parent, const char* name )
     : MythDialog(parent, name)
 {
     m_galleryDir = galleryDir;    
 
     m_inMenu     = false;
+    m_inSubMenu = false;
     m_itemList.setAutoDelete(true);
     m_itemDict.setAutoDelete(false);
 
@@ -108,21 +111,34 @@
     m_lastCol = 0;
     m_topRow  = 0;
     m_isGallery  = false;
+#ifndef _WIN32
+    m_showDevices = false;
+    m_currDevice = initialDevice;
+#endif
 
     srand(time(NULL));
-    loadDirectory(galleryDir);
+
+#ifndef _WIN32
+    if (m_currDevice)
+    {
+        if (!m_currDevice->isMounted(true))
+            m_currDevice->mount();
+
+        connect(m_currDevice, SIGNAL(statusChanged(MediaStatus, MythMediaDevice*)),
+                SLOT(mediaStatusChanged(MediaStatus, MythMediaDevice*)));
+
+        loadDirectory(m_currDevice->getMountPath());
+    }
+    else
+#endif
+        loadDirectory(galleryDir);
 }
 
 IconView::~IconView()
 {
-    UIListBtnTypeItem* item = m_menuType->GetItemFirst();
-    while (item) {
-        Action *act = (Action*) item->getData();
-        if (act)
-            delete act;
-        item = m_menuType->GetItemNext(item);
-    }
-    
+    clearMenu(m_submenuType);
+    clearMenu(m_menuType);
+
     delete m_thumbGen;
     delete m_theme;
 }
@@ -258,6 +274,9 @@
                                  item->pixmap->height()/2-bh2+sh,
                                  bw-2*sw, bh-2*sh-(int)(15*hmult));
 
+                if (m_itemMarked.contains(item->path))
+                    p.drawPixmap(xpos, ypos, m_MrkPix);
+
             }
             else {
 
@@ -272,6 +291,9 @@
                                  item->pixmap->width()/2-bw2+sw,
                                  item->pixmap->height()/2-bh2+sh,
                                  bw-2*sw, bh-2*sh);
+
+                if (m_itemMarked.contains(item->path))
+                    p.drawPixmap(xpos, ypos, m_MrkPix);
             }
            
             curPos++;
@@ -299,22 +321,31 @@
         QString action = actions[i];
         if (action == "MENU") {
             m_inMenu = !m_inMenu;
-            m_menuType->SetActive(m_inMenu);
+            m_menuType->SetActive(m_inMenu & !m_inSubMenu);
+            m_submenuType->SetActive(m_inMenu & m_inSubMenu);
             menuHandled = true;
         }
         else if (action == "UP") {
-            if (m_inMenu) {
+            if (m_inMenu & !m_inSubMenu) {
                 m_menuType->MoveUp();
                 menuHandled = true;
             }
+            else if (m_inMenu & m_inSubMenu) {
+                m_submenuType->MoveUp();
+                menuHandled = true;
+            }
             else
                 handled = moveUp();
         }
         else if (action == "DOWN") {
-            if (m_inMenu) {
+            if (m_inMenu & !m_inSubMenu) {
                 m_menuType->MoveDown();
                 menuHandled = true;
             }
+            else if (m_inMenu & m_inSubMenu) {
+                m_submenuType->MoveDown();
+                menuHandled = true;
+            }
             else
                 handled = moveDown();
         }
@@ -363,6 +394,24 @@
             actionDelete();
             handled = true;
         }
+        else if (action == "MARK")
+        {
+            int pos = m_currRow * m_nCols + m_currCol;
+            ThumbItem *item = m_itemList.at(pos);
+            if (!item) {
+                std::cerr << "The impossible happened" << std::endl;
+                break;
+            }
+
+            std::cout << "(un)marking " << item->path.ascii() << std::endl;
+
+            if (!m_itemMarked.contains(item->path))
+                m_itemMarked.append(item->path);
+            else
+                m_itemMarked.remove(item->path);
+
+            handled = true;
+        }
         else if (action == "SELECT" || action == "PLAY" || action == "SLIDESHOW" || action == "RANDOMSHOW" )
         {
             if (m_inMenu && (action == "SELECT" || action == "PLAY") ) {
@@ -378,6 +427,21 @@
                 }
         
                 QFileInfo fi(item->path);
+#ifndef _WIN32
+                // if the selected item is a mediaDevice attempt to mount it if it isn't already
+                if (item->mediaDevice && (action == "SELECT" || action == "PLAY") )
+                {
+                    m_currDevice = item->mediaDevice;
+
+                    if (!m_currDevice->isMounted())
+                        m_currDevice->mount();
+
+                    item->path = m_currDevice->getMountPath();
+
+                    connect(m_currDevice, SIGNAL(statusChanged(MediaStatus, MythMediaDevice*)),
+                            SLOT(mediaStatusChanged(MediaStatus, MythMediaDevice*)));
+                }
+#endif
                 if (item->isDir && (action == "SELECT" || action == "PLAY") ) {
                     loadDirectory(item->path);
                     handled = true;
@@ -426,25 +490,76 @@
         {
             QString action = actions[i];
             if (action == "ESCAPE") {
-                QDir d(m_currDir);
-                if (d != QDir(m_galleryDir)) {
+#ifndef _WIN32
+                if (m_showDevices)
+                {
+                    loadDirectory(m_galleryDir);
+                    handled = true;
+                }
+                else
+                {
+#endif
+                    QDir d(m_currDir);
+#ifndef _WIN32
+                    MediaMonitor *mon = MediaMonitor::GetMediaMonitor();
+                    if (mon)
+                    {
+                        QValueList <MythMediaDevice *> removables =
+                            mon->GetMedias(MEDIATYPE_DATA);
+                        QValueList <MythMediaDevice *>::Iterator it = removables.begin();
+                        for (; it != removables.end(); it++)
+                        {
+                            if (d == QDir((*it)->getMountPath()))
+                            {
+                                actionShowDevices();
 
-                    QString oldDirName = d.dirName();
-                    d.cdUp();
-                    loadDirectory(d.absPath());
+                                // make sure previous devies is visible and selected
+                                ThumbItem *item;
+                                if ((*it)->getVolumeID() != "")
+                                    item = m_itemDict.find((*it)->getVolumeID());
+                                else
+                                    item = m_itemDict.find((*it)->getDevicePath());
 
-                    // make sure up-directory is visible and selected
-                    ThumbItem* item = m_itemDict.find(oldDirName);
-                    if (item) {
-                        int pos = m_itemList.find(item);
-                        if (pos != -1) {
-                            m_currRow = pos/m_nCols;
-                            m_currCol = pos-m_currRow*m_nCols;
-                            m_topRow  = QMAX(0, m_currRow-(m_nRows-1));
+                                if (item)
+                                {
+                                    int pos = m_itemList.find(item);
+                                    if (pos != -1)
+                                    {
+                                        m_currRow = pos/m_nCols;
+                                        m_currCol = pos-m_currRow*m_nCols;
+                                        m_topRow = QMAX(0, m_currRow-(m_nRows-1));
+                                    }
+                                }
+                                handled = true;
+                                break;
+                            }
                         }
                     }
-                    handled = true;
+                    if (!handled)
+                    {
+#endif
+                        if (d != QDir(m_galleryDir)) {
+
+                            QString oldDirName = d.dirName();
+                            d.cdUp();
+                            loadDirectory(d.absPath());
+
+                            // make sure up-directory is visible and selected
+                            ThumbItem* item = m_itemDict.find(oldDirName);
+                            if (item) {
+                                int pos = m_itemList.find(item);
+                                if (pos != -1) {
+                                    m_currRow = pos/m_nCols;
+                                    m_currCol = pos-m_currRow*m_nCols;
+                                    m_topRow  = QMAX(0, m_currRow-(m_nRows-1));
+                                }
+                            }
+                            handled = true;
+                        }
+#ifndef _WIN32
+                    }
                 }
+#endif
             }
         }
     }
@@ -547,6 +662,13 @@
         exit(-1);
     }
 
+    m_submenuType = (UIListBtnType*)container->GetType("submenu");
+    if (!m_menuType) {
+        std::cerr << "MythGallery: Failed to get submenu area."
+                  << std::endl;
+        exit(-1);
+    }
+
     // Menu Actions
 
     UIListBtnTypeItem* item;
@@ -554,14 +676,14 @@
     item->setData(new Action(&IconView::actionSlideShow));
     item = new UIListBtnTypeItem(m_menuType, tr("Random"));
     item->setData(new Action(&IconView::actionRandomShow));
-    item = new UIListBtnTypeItem(m_menuType, tr("Rotate CW"));
-    item->setData(new Action(&IconView::actionRotateCW));
-    item = new UIListBtnTypeItem(m_menuType, tr("Rotate CCW"));
-    item->setData(new Action(&IconView::actionRotateCCW));
+    item = new UIListBtnTypeItem(m_menuType, tr("Meta Data..."));
+    item->setData(new Action(&IconView::actionSubMenuMetadata));
+    item = new UIListBtnTypeItem(m_menuType, tr("Marking..."));
+    item->setData(new Action(&IconView::actionSubMenuMark));
+    item = new UIListBtnTypeItem(m_menuType, tr("File..."));
+    item->setData(new Action(&IconView::actionSubMenuFile));
     item = new UIListBtnTypeItem(m_menuType, tr("Delete"));
     item->setData(new Action(&IconView::actionDelete));
-    item = new UIListBtnTypeItem(m_menuType, tr("Import"));
-    item->setData(new Action(&IconView::actionImport));
     item = new UIListBtnTypeItem(m_menuType, tr("Settings"));
     item->setData(new Action(&IconView::actionSettings));
     
@@ -618,6 +740,15 @@
         m_folderSelPix = QPixmap(*img);
         delete img;
 
+        img = gContext->LoadScaleImage("gallery-mark.png");
+        if (!img) {
+            std::cerr << "Failed to load gallery-mark.png"
+                      << std::endl;
+            exit(-1);
+        }
+        m_MrkPix = QPixmap(*img);
+        delete img;
+
         m_thumbW = m_backRegPix.width();
         m_thumbH = m_backRegPix.height();
         m_nCols  = m_viewRect.width()/m_thumbW - 1;
@@ -634,6 +765,10 @@
     if (!d.exists())
         return;
 
+#ifndef _WIN32
+    m_showDevices = false;
+#endif
+
     m_currDir = d.absPath();
     m_itemList.clear();
     m_itemDict.clear();
@@ -778,15 +913,89 @@
 
 void IconView::pressMenu()
 {
-    UIListBtnTypeItem* item = m_menuType->GetItemCurrent();
+    UIListBtnTypeItem* item;
 
+    if (m_inSubMenu)
+        item = m_submenuType->GetItemCurrent();
+    else
+        item = m_menuType->GetItemCurrent();
+
     if (!item || !item->getData())
         return;
 
     Action *act = (Action*) item->getData();
     (this->*(*act))();
+
+    m_menuType->SetActive(m_inMenu & !m_inSubMenu);
+    m_submenuType->SetActive(m_inMenu & m_inSubMenu);
 }
 
+void IconView::actionMainMenu()
+{
+    clearMenu(m_submenuType);
+    m_submenuType->Reset();
+
+    m_inSubMenu = false;
+}
+
+void IconView::actionSubMenuMetadata()
+{
+    clearMenu(m_submenuType);
+    m_submenuType->Reset();
+
+    UIListBtnTypeItem *item;
+    item = new UIListBtnTypeItem(m_submenuType, tr("Return"));
+    item->setData(new Action(&IconView::actionMainMenu));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Rotate CW"));
+    item->setData(new Action(&IconView::actionRotateCW));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Rotate CCW"));
+    item->setData(new Action(&IconView::actionRotateCCW));
+
+    m_inSubMenu = true;
+}
+
+void IconView::actionSubMenuMark()
+{
+    clearMenu(m_submenuType);
+    m_submenuType->Reset();
+
+    UIListBtnTypeItem *item;
+    item = new UIListBtnTypeItem(m_submenuType, tr("Return"));
+    item->setData(new Action(&IconView::actionMainMenu));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Clear Marked"));
+    item->setData(new Action(&IconView::actionClearMarked));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Select All"));
+    item->setData(new Action(&IconView::actionSelectAll));
+
+    m_inSubMenu = true;
+}
+
+void IconView::actionSubMenuFile()
+{
+    clearMenu(m_submenuType);
+    m_submenuType->Reset();
+
+    UIListBtnTypeItem *item;
+    item = new UIListBtnTypeItem(m_submenuType, tr("Return"));
+    item->setData(new Action(&IconView::actionMainMenu));
+#ifndef _WIN32
+    item = new UIListBtnTypeItem(m_submenuType, tr("Show Devices"));
+    item->setData(new Action(&IconView::actionShowDevices));
+#endif
+    item = new UIListBtnTypeItem(m_submenuType, tr("Import"));
+    item->setData(new Action(&IconView::actionImport));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Copy here"));
+    item->setData(new Action(&IconView::actionCopyHere));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Move here"));
+    item->setData(new Action(&IconView::actionMoveHere));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Delete Marked"));
+    item->setData(new Action(&IconView::actionDeleteMarked));
+    item = new UIListBtnTypeItem(m_submenuType, tr("Create Dir"));
+    item->setData(new Action(&IconView::actionMkDir));
+
+    m_inSubMenu = true;
+}
+
 void IconView::actionRotateCW()
 {
     ThumbItem* item = m_itemList.at(m_currRow * m_nCols +
@@ -980,6 +1189,125 @@
     }
 }
 
+#ifndef _WIN32
+void IconView::actionShowDevices()
+{
+    if (m_currDevice)
+    {
+        m_currDevice->disconnect(this);
+        m_currDevice = NULL;
+    }
+
+    m_showDevices = true;
+
+    m_itemList.clear();
+    m_itemDict.clear();
+
+    m_thumbGen->cancel();
+
+    /* add gallery directory */
+    ThumbItem *item = new ThumbItem;
+    item->name = QString("Gallery");
+    item->path = m_galleryDir;
+    item->isDir = true;
+    m_itemList.append(item);
+    m_itemDict.insert(item->name, item);
+
+    MediaMonitor *mon = MediaMonitor::GetMediaMonitor();
+    if (mon)
+    {
+        QValueList <MythMediaDevice *> removables =
+            mon->GetMedias(MEDIATYPE_DATA);
+        QValueList <MythMediaDevice *>::Iterator it = removables.begin();
+        for (; it != removables.end(); it++)
+        {
+            item = new ThumbItem;
+            if ((*it)->getVolumeID() != "")
+                item->name = (*it)->getVolumeID();
+            else
+                item->name = (*it)->getDevicePath();
+            item->path = (*it)->getMountPath();
+            item->isDir = true;
+            item->mediaDevice = *it;
+            m_itemList.append(item);
+            m_itemDict.insert(item->name, item);
+        }
+    }
+
+    m_lastRow = QMAX((int)ceilf((float)m_itemList.count()/(float)m_nCols)-1,0);
+    m_lastCol = QMAX(m_itemList.count()-m_lastRow*m_nCols-1,0);
+}
+#endif
+
+void IconView::actionCopyHere()
+{
+    CopyMarkedFiles(false);
+}
+
+void IconView::actionMoveHere()
+{
+    CopyMarkedFiles(true);
+}
+
+void IconView::actionDeleteMarked()
+{
+    std::cerr << "deleting marked files\n";
+
+    bool cont = MythPopupBox::showOkCancelPopup(gContext->GetMainWindow(), tr("Delete Marked Files"),
+                                                QString(tr("Deleting %1 images and folders"))
+                                                .arg(m_itemMarked.count()), false);
+
+    if (cont)
+    {
+        QStringList::iterator it;
+        QFileInfo fi;
+
+        for (it = m_itemMarked.begin(); it != m_itemMarked.end(); it++)
+        {
+            fi.setFile(*it);
+
+            std::cerr << "fi = " << fi.absFilePath() << endl;
+
+            GalleryUtil::Delete(fi);
+        }
+
+        m_itemMarked.clear();
+
+        loadDirectory(m_currDir);
+    }
+}
+
+void IconView::actionClearMarked()
+{
+    m_itemMarked.clear();
+}
+
+void IconView::actionSelectAll()
+{
+    ThumbItem *item;
+    for (item = m_itemList.first(); item; item = m_itemList.next())
+    {
+        if (!m_itemMarked.contains(item->path))
+            m_itemMarked.append(item->path);
+    }
+}
+
+void IconView::actionMkDir()
+{
+    QString folderName = tr("New Folder");
+
+    bool res = MythPopupBox::showGetTextPopup(gContext->GetMainWindow(), tr("Create New Folder"),
+                                              tr("Create New Folder"), folderName);
+
+    if (res)
+    {
+        QDir cdir(m_currDir);
+        cdir.mkdir(folderName);
+
+        loadDirectory(m_currDir);
+    }
+}
+
 void IconView::importFromDir(const QString &fromDir, const QString &toDir)
 {
     QDir d(fromDir);
@@ -1019,3 +1347,52 @@
         }
     }
 }
+
+void IconView::CopyMarkedFiles(bool move)
+{
+    QStringList::iterator it;
+    QFileInfo fi;
+    QFileInfo dest;
+
+    for (it = m_itemMarked.begin(); it != m_itemMarked.end(); it++)
+    {
+        fi.setFile(*it);
+        dest.setFile(QDir(m_currDir), fi.fileName());
+
+        if (fi.exists())
+        {
+            GalleryUtil::CopyMove(fi, dest, move);
+            *it = dest.absFilePath();
+        }
+    }
+
+    loadDirectory(m_currDir);
+}
+
+void IconView::clearMenu(UIListBtnType *menu)
+{
+    UIListBtnTypeItem* item = menu->GetItemFirst();
+    while (item)
+    {
+        Action *act = (Action*) item->getData();
+        if (act)
+            delete act;
+        item = menu->GetItemNext(item);
+    }
+}
+
+void IconView::mediaStatusChanged(MediaStatus oldStatus,
+                                  MythMediaDevice *pMedia)
+{
+    (void) oldStatus;
+    if (m_currDevice == pMedia)
+    {
+        actionShowDevices();
+
+        m_currRow = 0;
+        m_currCol = 0;
+
+        updateView();
+        updateText();
+    }
+}
Index: mythplugins/mythgallery/mythgallery/images/gallery-mark.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: mythplugins/mythgallery/mythgallery/images/gallery-mark.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: mythplugins/mythgallery/mythgallery/main.cpp
===================================================================
--- mythplugins/mythgallery/mythgallery/main.cpp	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/main.cpp	(working copy)
@@ -14,6 +14,7 @@
 #include <mythtv/mythdialogs.h>
 #include <mythtv/mythplugin.h>
 #include <mythtv/dialogbox.h>
+#include <mythtv/mythmedia.h>
 
 extern "C" {
 int mythplugin_init(const char *libversion);
@@ -33,13 +34,34 @@
         diag.AddButton(QObject::tr("Ok"));
         diag.exec();
     }
-    else {
-        IconView icv(startdir, gContext->GetMainWindow(), "IconView");
+    else
+    {
+        IconView icv(startdir, NULL, gContext->GetMainWindow(), "IconView");
         icv.exec();
     }
     gContext->removeCurrentLocation();
 }
+ 
+void handleMedia(MythMediaDevice *dev)
+{
+    QString galleryDir = gContext->GetSetting("GalleryDir");
+    QDir dir(galleryDir);
+    if (!dir.exists() || !dir.isReadable())
+    {
+        DialogBox diag(gContext->GetMainWindow(),
+                       QObject::tr("Gallery Directory does not "
+                                   "exist or is unreadable."));
+        diag.AddButton(QObject::tr("Ok"));
+        diag.exec();
+    }
+    else
+    {
+        IconView icv(galleryDir, dev, gContext->GetMainWindow(), "IconView");
+        icv.exec();
+    }
+}
 
+
 void setupKeys(void)
 {
     REG_JUMP("MythGallery", "Image viewer / slideshow", "", runGallery);
@@ -68,6 +90,9 @@
             "PgDown");
     REG_KEY("Gallery", "INFO", "Toggle Showing Information about Image", "I");
     REG_KEY("Gallery", "DELETE", "Delete current image", "D");
+    REG_KEY("Gallery", "MARK", "Mark image", "T");
+
+    REG_MEDIA_HANDLER("MythGallery Media Handler", "", "", handleMedia, MEDIATYPE_DATA | MEDIATYPE_MIXED);
 }
 
 int mythplugin_init(const char *libversion)
Index: mythplugins/mythgallery/mythgallery/thumbgenerator.cpp
===================================================================
--- mythplugins/mythgallery/mythgallery/thumbgenerator.cpp	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/thumbgenerator.cpp	(working copy)
@@ -308,15 +308,17 @@
 
 QString ThumbGenerator::getThumbcacheDir(const QString& inDir)
 {
+    QString galleryDir = gContext->GetSetting("GalleryDir");
+
     // For directory "/my/images/january", this function either returns 
     // "/my/images/january/.thumbcache" or "~/.mythtv/mythgallery/january/.thumbcache"
     QString aPath = inDir + QString("/.thumbcache/");
     if ( gContext->GetNumSetting("GalleryThumbnailLocation") 
-            && ! QDir(aPath).exists() )
+            && ! QDir(aPath).exists() && inDir.startsWith(galleryDir))
     {
         mkpath(aPath);
     }
-    if ( ! gContext->GetNumSetting("GalleryThumbnailLocation") || ! QDir(aPath).exists() ) 
+    if ( ! gContext->GetNumSetting("GalleryThumbnailLocation") || ! QDir(aPath).exists() || ! inDir.startsWith(galleryDir))
     {
         // Arrive here if storing thumbs in home dir, 
         // OR failed to create thumb dir in gallery pics location
Index: mythplugins/mythgallery/mythgallery/iconview.h
===================================================================
--- mythplugins/mythgallery/mythgallery/iconview.h	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/iconview.h	(working copy)
@@ -26,6 +26,7 @@
 #include <qpixmap.h>
 
 #include <mythtv/mythdialogs.h>
+#include <mythtv/mythmedia.h>
 
 class XMLParse;
 class UIListBtnType;
@@ -42,6 +43,7 @@
         caption= "";
         path   = "";
         isDir  = false;
+        mediaDevice = NULL;
     }
 
     ~ThumbItem() {
@@ -58,6 +60,7 @@
     QString  caption;
     QString  path;
     bool     isDir;
+    MythMediaDevice *mediaDevice;
 };
 
 typedef QPtrList<ThumbItem> ThumbList;
@@ -69,8 +72,8 @@
 
 public:
 
-    IconView(const QString& galleryDir, MythMainWindow* parent, 
-             const char* name = 0);
+    IconView(const QString& galleryDir, MythMediaDevice *initialDevice,
+             MythMainWindow* parent, const char* name = 0);
     ~IconView();
 
 protected:
@@ -93,6 +96,11 @@
     bool moveLeft();
     bool moveRight();
 
+    void actionMainMenu();
+    void actionSubMenuMetadata();
+    void actionSubMenuMark();
+    void actionSubMenuFile();
+
     void actionRotateCW();
     void actionRotateCCW();
     void actionDelete();
@@ -100,14 +108,28 @@
     void actionRandomShow();
     void actionSettings();
     void actionImport();
+#ifndef _WIN32
+    void actionShowDevices();
+#endif
+    void actionCopyHere();
+    void actionMoveHere();
+    void actionDeleteMarked();
+    void actionClearMarked();
+    void actionSelectAll();
+    void actionMkDir();
 
+
     void pressMenu();
 
     void loadThumbnail(ThumbItem *item);
     void importFromDir(const QString &fromDir, const QString &toDir);
+    void CopyMarkedFiles(bool move = false);
+
+    void clearMenu(UIListBtnType *menu);
     
     QPtrList<ThumbItem> m_itemList;
     QDict<ThumbItem>    m_itemDict;
+    QStringList         m_itemMarked;
     QString             m_galleryDir;
 
     XMLParse           *m_theme;
@@ -116,15 +138,22 @@
     QRect               m_viewRect;
 
     bool                m_inMenu;
+    bool                m_inSubMenu;
     UIListBtnType      *m_menuType;
+    UIListBtnType      *m_submenuType;
     
     QPixmap             m_backRegPix;
     QPixmap             m_backSelPix;
     QPixmap             m_folderRegPix;
     QPixmap             m_folderSelPix;
+    QPixmap             m_MrkPix;
 
     QString             m_currDir;
     bool                m_isGallery;
+#ifndef _WIN32
+    bool                m_showDevices;
+    MythMediaDevice    *m_currDevice;
+#endif
 
     int                 m_currRow;
     int                 m_currCol;
@@ -143,6 +172,11 @@
     int                 m_showcaption;
 
     typedef void (IconView::*Action)();
+
+#ifndef _WIN32
+public slots:
+    void mediaStatusChanged( MediaStatus oldStatus, MythMediaDevice* pMedia);
+#endif
 };
 
 
Index: mythplugins/mythgallery/mythgallery/gallery-ui.xml
===================================================================
--- mythplugins/mythgallery/mythgallery/gallery-ui.xml	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/gallery-ui.xml	(working copy)
@@ -21,7 +21,7 @@
     </font>
 
     <container name="menu">
-      <area>20,20,140,240</area>
+      <area>20,20,140,500</area>
 
       <listbtnarea name="menu" draworder="0">
         <area>0,0,140,240</area>
@@ -35,6 +35,18 @@
         <showscrollarrows>no</showscrollarrows>
       </listbtnarea>
 
+      <listbtnarea name="submenu" draworder="0">
+        <area>0,260,140,240</area>
+        <gradient type="unselected" start="#000000" end="#505050" alpha="100">
+        </gradient>
+        <gradient type="selected" start="#52CA38" end="#349838" alpha="255">
+        </gradient>
+        <fcnfont name="active" function="active"></fcnfont>
+        <fcnfont name="inactive" function="inactive"></fcnfont>
+        <showarrow>no</showarrow>
+        <showscrollarrows>no</showscrollarrows>
+      </listbtnarea>
+
     </container>
 
     <container name="text">
Index: mythplugins/mythgallery/mythgallery/galleryutil.cpp
===================================================================
--- mythplugins/mythgallery/mythgallery/galleryutil.cpp	(revision 9369)
+++ mythplugins/mythgallery/mythgallery/galleryutil.cpp	(working copy)
@@ -16,10 +16,13 @@
  * 
  * ============================================================ */
 
-#include <iostream>
 #include <qfileinfo.h>
 #include <qdir.h>
 
+#include <mythtv/mythcontext.h>
+#include <mythtv/mythdbcon.h>
+#include <mythtv/util.h>
+
 #include "config.h"
 #include "constants.h"
 #include "galleryutil.h"
@@ -31,6 +34,15 @@
 // include "exif.hpp"
 #endif // EXIF_SUPPORT
 
+#define LOC QString("GalleryUtil:")
+#define LOC_ERR QString("GalleryUtil, Error:")
+
+static QFileInfo MakeUnique(const QFileInfo &dest);
+static QFileInfo MakeUniqueDirectory(const QFileInfo &dest);
+static bool FileCopy(const QFileInfo &src, const QFileInfo &dst);
+static bool FileMove(const QFileInfo &src, const QFileInfo &dst);
+static bool FileDelete(const QFileInfo &file);
+
 bool GalleryUtil::isImage(const char* filePath)
 {
     QFileInfo fi(filePath);
@@ -81,7 +93,10 @@
         }
         else
         {
-                std::cerr << "Could not load exif data from " << filePath << std::endl;
+            VERBOSE(VB_IMPORTANT, LOC_ERR +
+                    QString("Could not load exif data from '%1'")
+                    .arg(filePath));
+                            
         }
         
         delete [] exifvalue;
@@ -115,8 +130,9 @@
     }
     catch (...)
     {
-        std::cerr << "GalleryUtil: Failed to extract EXIF headers from "
-        << filePath << std::endl;
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("Failed to extract EXIF headers from '%1'")
+                .arg(filePath));
     }
 
     return rotateAngle;
@@ -249,7 +265,9 @@
         }
         else
         {
-           std::cerr << "Could not load exif data from " << filePath << std::endl;
+           VERBOSE(VB_IMPORTANT, LOC_ERR +
+                   QString("Could not load exif data from '%1'")
+                   .arg(filePath));
         }
         
         delete [] exifvalue;
@@ -257,9 +275,245 @@
     }
     catch (...)
     {
-        std::cerr << "GalleryUtil: Failed to extract EXIF headers from "
-        << filePath << std::endl;
+        VERBOSE(VB_IMPORTANT, LOC_ERR +
+                QString("Failed to extract EXIF headers from '%1'")
+                .arg(filePath));
     }
 
     return caption;
 }
+
+bool GalleryUtil::Copy(const QFileInfo &src, QFileInfo &dst)
+{
+    if (src.isDir())
+        return CopyDirectory(src, dst);
+
+    dst = MakeUnique(dst);
+
+    if (!FileCopy(src, dst))
+        return false;
+
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("INSERT INTO gallerymetadata (image, keywords, angle) "
+                  "SELECT :IMAGENEW , keywords, angle "
+                  "FROM gallerymetadata "
+                  "WHERE image = :IMAGEOLD");
+    query.bindValue(":IMAGENEW", dst.absFilePath().utf8());
+    query.bindValue(":IMAGEOLD", src.absFilePath().utf8());
+    if (query.exec())
+        return true;
+
+    // try to undo copy on DB failure
+    FileDelete(dst);
+    return false;
+}
+
+bool GalleryUtil::Move(const QFileInfo &src, QFileInfo &dst)
+{
+    if (src.isDir())
+        return MoveDirectory(src, dst);
+
+    dst = MakeUnique(dst);
+
+    if (!FileMove(src, dst))
+        return false;
+
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("UPDATE gallerymetadata "
+                  "SET image = :IMAGENEW "
+                  "WHERE image = :IMAGEOLD");
+    query.bindValue(":IMAGENEW", dst.absFilePath().utf8());
+    query.bindValue(":IMAGEOLD", src.absFilePath().utf8());
+    if (query.exec())
+        return true;
+
+    // try to undo move on DB failure
+    FileMove(dst, src);
+    return false;
+}
+
+bool GalleryUtil::Delete(const QFileInfo &file)
+{
+    if (!file.exists())
+        return false;
+
+    if (file.isDir())
+        return DeleteDirectory(file);
+
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("DELETE FROM gallerymetadata "
+                  "WHERE image = :IMAGE ;");
+    query.bindValue(":IMAGE", file.absFilePath().utf8());
+    if (query.exec())
+        return FileDelete(file);
+
+    return false;
+}
+
+bool GalleryUtil::CopyDirectory(const QFileInfo src, QFileInfo &dst)
+{
+    QDir srcDir(src.absFilePath());
+
+    dst = MakeUniqueDirectory(dst);
+    if (!dst.exists())
+    {
+        srcDir.mkdir(dst.absFilePath());
+        dst.refresh();
+    }
+
+    if (!dst.exists() || !dst.isDir())
+        return false;
+
+    bool ok = true;
+    QDir dstDir(dst.absFilePath());
+    QFileInfoListIterator it(*srcDir.entryInfoList());
+    for (; it.current(); ++it)
+    {
+        const QString fn = (it.current())->fileName();
+        if (fn != "." && fn != "..")
+        {
+            QFileInfo dfi(dstDir, fn);
+            ok &= Copy(*(it.current()), dfi);
+        }
+    }
+
+    return ok;
+}
+
+bool GalleryUtil::MoveDirectory(const QFileInfo src, QFileInfo &dst)
+{
+    QDir srcDir(src.absFilePath());
+
+    dst = MakeUniqueDirectory(dst);
+    if (!dst.exists())
+    {
+        srcDir.mkdir(dst.absFilePath());
+        dst.refresh();
+    }
+
+    if (!dst.exists() || !dst.isDir())
+        return false;
+
+    bool ok = true;
+    QDir dstDir(dst.absFilePath());
+    QFileInfoListIterator it(*srcDir.entryInfoList());
+    for (; it.current(); ++it)
+    {
+        const QString fn = (it.current())->fileName();
+        if (fn != "." && fn != "..")
+        {
+            QFileInfo dfi(dstDir, fn);
+            ok &= Move(*(it.current()), dfi);
+        }
+    }
+
+    return ok && FileDelete(src);
+}
+
+bool GalleryUtil::DeleteDirectory(const QFileInfo &dir)
+{
+    if (!dir.exists())
+        return false;
+
+    QDir srcDir(dir.absFilePath());
+    QFileInfoListIterator it(*srcDir.entryInfoList());
+    for (; it.current(); ++it)
+    {
+        const QString fn = (it.current())->fileName();
+        if (fn != "." && fn != "..")
+            Delete(*(it.current()));
+    }
+
+    return FileDelete(dir);
+}
+
+static QFileInfo MakeUnique(const QFileInfo &dest)
+{
+    QFileInfo newDest = dest;
+
+    for (uint i = 0; newDest.exists(); i++)
+    {
+        QString basename = QString("%1_%2.%3")
+            .arg(dest.baseName(false)).arg(i).arg(dest.extension());
+
+        newDest.setFile(dest.dir(), basename);
+
+        VERBOSE(VB_GENERAL, LOC_ERR +
+                QString("Need to find a new name for '%1' trying '%2'")
+                .arg(dest.absFilePath()).arg(newDest.absFilePath()));
+    }
+
+    return newDest;
+}
+
+static QFileInfo MakeUniqueDirectory(const QFileInfo &dest)
+{
+    QFileInfo newDest = dest;
+
+    for (uint i = 0; newDest.exists() && !newDest.isDir(); i++)
+    {
+        QString fullname = QString("%1_%2").arg(dest.absFilePath()).arg(i);
+        newDest.setFile(fullname);
+
+        VERBOSE(VB_GENERAL, LOC_ERR +
+                QString("Need to find a new name for '%1' trying '%2'")
+                .arg(dest.absFilePath()).arg(newDest.absFilePath()));
+    }
+
+    return newDest;
+}
+
+static bool FileCopy(const QFileInfo &src, const QFileInfo &dst)
+{
+    QString cmd = "cp ";
+    cmd += "\"" + src.absFilePath().local8Bit() + "\" \"";
+    cmd += dst.absFilePath().local8Bit() + "\"";
+    myth_system(cmd);
+    return true;
+}
+
+static bool FileMove(const QFileInfo &src, const QFileInfo &dst)
+{
+    QString cmd = "mv ";
+    cmd += "\"" + src.absFilePath().local8Bit() + "\" \"";
+    cmd += dst.absFilePath().local8Bit() + "\"";
+    myth_system(cmd);
+    return true;
+}
+
+static bool FileDelete(const QFileInfo &file)
+{
+    if (!file.isDir())
+    {
+        QString cmd = "rm \"" + file.absFilePath().local8Bit() + "\"";
+        myth_system(cmd);
+        return true;
+    }
+
+    QDir srcDir(file.absFilePath());
+    srcDir.setNameFilter(MEDIA_FILENAMES);
+    srcDir.setSorting(QDir::Name | QDir::DirsFirst | QDir::IgnoreCase);
+    srcDir.setMatchAllDirs(true);
+
+    // delete .thumbcache
+    QString cmd = "rm -rf \"" + srcDir.absPath().local8Bit();
+    cmd += "/.thumbcache\"";
+
+    myth_system(cmd);
+
+    srcDir.rmdir(srcDir.absPath());
+    return true;
+}
+
+// Clear marks after operation
+// Allow wholesale clearing of marks
+// If there are no marks and operation is delete,
+//   mark then delete selected item
+// Exit submenu on ESCAPE
+// Bind DELETE to 'Delete Marked'
+// Show progress bar while performing operation
+// Use Qt or C API for renaming/unlinking/copying files or directories.
+// Exit "Show Devices" on exit from submenu
+// GetMedias() is unsafe if a device is removed/unmounted while you are
+//   looking at its returned list...
+// Avoid using #define and #ifdef when possible.
Index: mythtv/themes/blue/gallery-ui.xml
===================================================================
--- mythtv/themes/blue/gallery-ui.xml	(revision 9369)
+++ mythtv/themes/blue/gallery-ui.xml	(working copy)
@@ -7,7 +7,7 @@
     
 
     <container name="menu">
-      <area>20,20,140,200</area>
+      <area>20,20,140,500</area>
 
       <listbtnarea name="menu" draworder="0">
         <area>0,0,140,200</area>
@@ -21,6 +21,18 @@
         <showscrollarrows>no</showscrollarrows>
       </listbtnarea>
 
+      <listbtnarea name="submenu" draworder="0">
+        <area>0,220,140,500</area>
+        <gradient type="unselected" start="#000000" end="#505050" alpha="100">
+        </gradient>
+        <gradient type="selected" start="#52CA38" end="#349838" alpha="255">
+        </gradient>
+        <fcnfont name="list-active" function="active"></fcnfont>
+        <fcnfont name="list-inactive" function="inactive"></fcnfont>
+        <showarrow>no</showarrow>
+        <showscrollarrows>no</showscrollarrows>
+      </listbtnarea>
+
     </container>
 
     <container name="text">
