void WatcherData::updatePaths() { // printf("updating paths...\n"); { std::lock_guard<std::mutex> updateLocker(updateMutex); handleToPath.clear(); pathToHandle.clear(); pathData.clear(); } for (HANDLE& h : changes) { //printf("closing %d\n", h); FindCloseChangeNotification(h); } changes.clear(); std::lock_guard<std::mutex> locker(changeMutex); for(const Path& path : paths) { #ifdef HAVE_CYGWIN const ssize_t len = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, path.constData(), 0, 0); //printf("win path size %d\n", len); String winPath(len, '\0'); cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, path.constData(), winPath.data(), winPath.size()); //printf("hello %s\n", winPath.constData()); const HANDLE h = FindFirstChangeNotification(winPath.constData(), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); #else const HANDLE h = FindFirstChangeNotification(path.constData(), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); #endif if (h == INVALID_HANDLE_VALUE) { fprintf(stderr, "Unable to watch: %lu (%s)\n", static_cast<unsigned long>(GetLastError()), path.constData()); } else { changes.push_back(h); std::lock_guard<std::mutex> updateLocker(updateMutex); handleToPath[h] = path; pathToHandle[path] = h; PathData& data = pathData[path]; path.visit([&data](const Path &p) { if (p.isFile()) { data.modified[p] = p.lastModifiedMs(); return Path::Continue; } return Path::Recurse; }); } } }
/** * \brief Set the delay between each image in an animation */ void MythUIImage::SetDelay(int delayms) { QWriteLocker updateLocker(&d->m_UpdateLock); m_Delay = delayms; m_LastDisplay = QTime::currentTime(); m_CurPos = 0; }
/** * \brief Set the image filename pattern and integer range for an animated * image, does not load the image. See Load() */ void MythUIImage::SetFilepattern(const QString &filepattern, int low, int high) { QWriteLocker updateLocker(&d->m_UpdateLock); m_Filename = filepattern; m_LowNum = low; m_HighNum = high; }
/** * \brief Set the image filename, does not load the image. See Load() */ void MythUIImage::SetFilename(const QString &filename) { QWriteLocker updateLocker(&d->m_UpdateLock); m_imageProperties.isThemeImage = false; m_imageProperties.filename = filename; if (filename == m_OrigFilename) emit DependChanged(true); else emit DependChanged(false); }
/** * \brief Set the image filename pattern and integer range for an animated * image, does not load the image. See Load() */ void MythUIImage::SetFilepattern(const QString &filepattern, int low, int high) { QWriteLocker updateLocker(&d->m_UpdateLock); m_imageProperties.isThemeImage = false; m_imageProperties.filename = filepattern; m_LowNum = low; m_HighNum = high; if (filepattern == m_OrigFilename) emit DependChanged(true); else emit DependChanged(false); }
/** * \brief Assign a set of MythImages to the widget for animation. * Use is strongly discouraged, use SetFilepattern() instead. * */ void MythUIImage::SetImages(QVector<MythImage *> &images) { Clear(); QWriteLocker updateLocker(&d->m_UpdateLock); QSize aSize = GetArea().size(); QVector<MythImage *>::iterator it; for (it = images.begin(); it != images.end(); ++it) { MythImage *im = (*it); if (!im) { QMutexLocker locker(&m_ImagesLock); m_Images[m_Images.size()] = im; continue; } im->UpRef(); if (!m_ForceSize.isNull()) { int w = (m_ForceSize.width() <= 0) ? im->width() : m_ForceSize.width(); int h = (m_ForceSize.height() <= 0) ? im->height() : m_ForceSize.height(); im->Resize(QSize(w, h), m_preserveAspect); } if (m_isReflected && !im->IsReflected()) im->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, m_reflectLength, m_reflectSpacing); if (m_isGreyscale && !im->isGrayscale()) im->ToGreyscale(); m_ImagesLock.lock(); m_Images[m_Images.size()] = im; m_ImagesLock.unlock(); aSize = aSize.expandedTo(im->size()); } SetImageCount(1, m_Images.size()); if (m_ForceSize.isNull()) SetSize(aSize); m_CurPos = 0; SetRedraw(); }
/** * \brief Sets the delays between each image in an animation */ void MythUIImage::SetDelays(QVector<int> delays) { QWriteLocker updateLocker(&d->m_UpdateLock); QMutexLocker imageLocker(&m_ImagesLock); QVector<int>::iterator it; for (it = delays.begin(); it != delays.end(); ++it) m_Delays[m_Delays.size()] = *it; if (m_Delay == -1) m_Delay = m_Delays[0]; m_LastDisplay = QTime::currentTime(); m_CurPos = 0; }
/** * \copydoc MythUIType::Pulse() */ void MythUIImage::Pulse(void) { QWriteLocker updateLocker(&d->m_UpdateLock); int delay = -1; if (m_Delays.contains(m_CurPos)) delay = m_Delays[m_CurPos]; else if (m_Delay > 0) delay = m_Delay; if (delay > 0 && abs(m_LastDisplay.msecsTo(QTime::currentTime())) > delay) { m_ImagesLock.lock(); if (m_animationCycle == kCycleStart) { ++m_CurPos; if (m_CurPos >= (uint)m_Images.size()) m_CurPos = 0; } else if (m_animationCycle == kCycleReverse) { if ((m_CurPos + 1) >= (uint)m_Images.size()) { m_animationReverse = true; } else if (m_CurPos == 0) { m_animationReverse = false; } if (m_animationReverse) --m_CurPos; else ++m_CurPos; } m_ImagesLock.unlock(); SetRedraw(); m_LastDisplay = QTime::currentTime(); } MythUIType::Pulse(); }
/** * \brief Remove all images from the widget */ void MythUIImage::Clear(void) { QWriteLocker updateLocker(&d->m_UpdateLock); QMutexLocker locker(&m_ImagesLock); while (!m_Images.isEmpty()) { QHash<int, MythImage*>::iterator it = m_Images.begin(); if (*it) (*it)->DownRef(); m_Images.remove(it.key()); } m_Delays.clear(); if (m_animatedImage) { m_LowNum = 0; m_HighNum = 0; m_animatedImage = false; } }
/** * \brief Assign a MythImage to the widget. Use is strongly discouraged, use * SetFilename() instead. */ void MythUIImage::SetImage(MythImage *img) { QWriteLocker updateLocker(&d->m_UpdateLock); if (!img) { Reset(); return; } m_Filename = img->GetFileName(); Clear(); m_Delay = -1; img->UpRef(); if (!m_ForceSize.isNull()) { int w = (m_ForceSize.width() <= 0) ? img->width() : m_ForceSize.width(); int h = (m_ForceSize.height() <= 0) ? img->height() : m_ForceSize.height(); img->Resize(QSize(w, h), m_preserveAspect); } if (m_isReflected && !img->IsReflected()) img->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, m_reflectLength, m_reflectSpacing); if (m_isGreyscale && !img->isGrayscale()) img->ToGreyscale(); if (m_ForceSize.isNull()) SetSize(img->size()); m_ImagesLock.lock(); m_Images[0] = img; m_Delays.clear(); m_ImagesLock.unlock(); m_CurPos = 0; SetRedraw(); }
/** * \brief Generates a unique identifying string for this image which is used * as a key in the image cache. */ QString MythUIImage::GenImageLabel(const QString &filename, int w, int h) const { QReadLocker updateLocker(&d->m_UpdateLock); QString imagelabel; QString s_Attrib; if (m_isMasked) s_Attrib = "masked"; if (m_isReflected) s_Attrib += "reflected"; if (m_isGreyscale) s_Attrib += "greyscale"; imagelabel = QString("%1-%2-%3x%4.png") .arg(filename) .arg(s_Attrib) .arg(w) .arg(h); imagelabel.replace('/','-'); return imagelabel; }
/** * \brief Set the integer range for an animated image pattern */ void MythUIImage::SetImageCount(int low, int high) { QWriteLocker updateLocker(&d->m_UpdateLock); m_LowNum = low; m_HighNum = high; }
/** * \copydoc MythUIType::CreateCopy() */ void MythUIImage::CreateCopy(MythUIType *parent) { QReadLocker updateLocker(&d->m_UpdateLock); MythUIImage *im = new MythUIImage(parent, objectName()); im->CopyFrom(this); }
/** * \copydoc MythUIType::ParseElement() */ bool MythUIImage::ParseElement( const QString &filename, QDomElement &element, bool showWarnings) { QWriteLocker updateLocker(&d->m_UpdateLock); if (element.tagName() == "filename") { m_imageProperties.isThemeImage = true; // This is an image distributed with the them m_OrigFilename = m_imageProperties.filename = getFirstText(element); if (m_imageProperties.filename.endsWith('/')) { m_showingRandomImage = true; m_imageDirectory = m_imageProperties.filename; FindRandomImage(); } } else if (element.tagName() == "filepattern") { m_imageProperties.isThemeImage = true; // This is an image distributed with the theme m_OrigFilename = m_imageProperties.filename = getFirstText(element); QString tmp = element.attribute("low"); if (!tmp.isEmpty()) m_LowNum = tmp.toInt(); tmp = element.attribute("high"); if (!tmp.isEmpty()) m_HighNum = tmp.toInt(); tmp = element.attribute("cycle", "start"); if (tmp == "reverse") m_animationCycle = kCycleReverse; } else if (element.tagName() == "area") { SetArea(parseRect(element)); m_imageProperties.forceSize = m_Area.size(); } else if (element.tagName() == "preserveaspect") m_imageProperties.preserveAspect = parseBool(element); else if (element.tagName() == "crop") m_imageProperties.cropRect = parseRect(element); else if (element.tagName() == "delay") { QString value = getFirstText(element); if (value.contains(",")) { QVector<int> delays; QStringList tokens = value.split(","); QStringList::iterator it = tokens.begin(); for (; it != tokens.end(); ++it) { if ((*it).isEmpty()) { if (delays.size()) delays.append(delays[delays.size()-1]); else delays.append(0); // Default 0ms delay before first image } else { delays.append((*it).toInt()); } } if (delays.size()) { m_Delay = delays[0]; SetDelays(delays); } } else { m_Delay = value.toInt(); } } else if (element.tagName() == "reflection") { m_imageProperties.isReflected = true; QString tmp = element.attribute("axis"); if (!tmp.isEmpty()) { if (tmp.toLower() == "horizontal") m_imageProperties.reflectAxis = ReflectHorizontal; else m_imageProperties.reflectAxis = ReflectVertical; } tmp = element.attribute("shear"); if (!tmp.isEmpty()) m_imageProperties.reflectShear = tmp.toInt(); tmp = element.attribute("scale"); if (!tmp.isEmpty()) m_imageProperties.reflectScale = tmp.toInt(); tmp = element.attribute("length"); if (!tmp.isEmpty()) m_imageProperties.reflectLength = tmp.toInt(); tmp = element.attribute("spacing"); if (!tmp.isEmpty()) m_imageProperties.reflectSpacing = tmp.toInt(); } else if (element.tagName() == "mask") { QString maskfile = getFirstText(element); MythImage *newMaskImage = GetPainter()->GetFormatImage(); if (newMaskImage->Load(maskfile)) { float wmult; // Width multipler float hmult; // Height multipler GetMythUI()->GetScreenSettings(wmult, hmult); if (wmult != 1.0f || hmult != 1.0f) { int width = newMaskImage->size().width() * wmult; int height = newMaskImage->size().height() * hmult; newMaskImage->Resize(QSize(width, height)); } m_imageProperties.SetMaskImage(newMaskImage); } else m_imageProperties.SetMaskImage(NULL); newMaskImage->DecrRef(); } else if (element.tagName() == "grayscale" || element.tagName() == "greyscale") { m_imageProperties.isGreyscale = parseBool(element); } else { return MythUIType::ParseElement(filename, element, showWarnings); } m_NeedLoad = true; if (m_Parent && m_Parent->IsDeferredLoading(true)) m_NeedLoad = false; return true; }
/** * \brief Set the size of the widget */ void MythUIImage::SetSize(const QSize &size) { QWriteLocker updateLocker(&d->m_UpdateLock); MythUIType::SetSize(size); m_NeedLoad = true; }
/** * \brief Generates a unique identifying string for this image which is used * as a key in the image cache. */ QString MythUIImage::GenImageLabel(int w, int h) const { QReadLocker updateLocker(&d->m_UpdateLock); return GenImageLabel(m_Filename, w, h); }
/** * \brief Set the image filename, does not load the image. See Load() */ void MythUIImage::SetFilename(const QString &filename) { QWriteLocker updateLocker(&d->m_UpdateLock); m_Filename = filename; }
/** * \brief Assign a set of MythImages to the widget for animation. * Use is strongly discouraged, use SetFilepattern() instead. * */ void MythUIImage::SetImages(QVector<MythImage *> *images) { Clear(); QWriteLocker updateLocker(&d->m_UpdateLock); QSize aSize = GetFullArea().size(); m_imageProperties.isThemeImage = false; QVector<MythImage *>::iterator it; for (it = images->begin(); it != images->end(); ++it) { MythImage *im = (*it); if (!im) { QMutexLocker locker(&m_ImagesLock); m_Images[m_Images.size()] = im; continue; } im->IncrRef(); QSize forceSize = m_imageProperties.forceSize; if (!forceSize.isNull()) { int w = (forceSize.width() <= 0) ? im->width() : forceSize.width(); int h = (forceSize.height() <= 0) ? im->height() : forceSize.height(); im->Resize(QSize(w, h), m_imageProperties.preserveAspect); } if (m_imageProperties.isReflected && !im->IsReflected()) im->Reflect(m_imageProperties.reflectAxis, m_imageProperties.reflectShear, m_imageProperties.reflectScale, m_imageProperties.reflectLength, m_imageProperties.reflectSpacing); if (m_imageProperties.isGreyscale && !im->isGrayscale()) im->ToGreyscale(); if (m_imageProperties.isOriented && !im->IsOriented() && (m_imageProperties.orientation >= 1 && m_imageProperties.orientation <= 8)) im->Orientation(m_imageProperties.orientation); m_ImagesLock.lock(); m_Images[m_Images.size()] = im; m_ImagesLock.unlock(); aSize = aSize.expandedTo(im->size()); } SetImageCount(1, m_Images.size()); if (m_imageProperties.forceSize.isNull()) SetSize(aSize); MythRect rect(GetFullArea()); rect.setSize(aSize); SetMinArea(rect); m_CurPos = 0; m_animatedImage = true; m_Initiator = m_EnableInitiator; SetRedraw(); }
void WatcherData::run() { for (;;) { if (hasChanged()) { slices.clear(); updatePaths(); auto where = changes.begin(); const auto end = changes.end(); while (where != end) { auto to = where + MAXIMUM_WAIT_OBJECTS - 1; // - 1 since we want a wakeup event as well if (to > end) to = end; slices.push_back(std::unique_ptr<WatcherSlice>(new WatcherSlice(where, to, std::bind(&WatcherData::updated, this, std::placeholders::_1), handleToPath))); where = to; } } const DWORD res = WaitForSingleObject(wakeupHandle, INFINITE); if (res == WAIT_FAILED) { fprintf(stderr, "Wait failed in WatcherData::run() %lu\n", static_cast<unsigned long>(GetLastError())); break; } assert(res - WAIT_OBJECT_0 == 0); // woken up //printf("!!!Woken up\n"); std::lock_guard<std::mutex> changeLocker(changeMutex); if (stopped) { //printf("!!!! Stopped?\n"); break; } std::lock_guard<std::mutex> updateLocker(updateMutex); if (!changedPaths.empty()) { for (const Path& p : changedPaths) { //printf("path was modified... %s\n", p.constData()); PathData& data = pathData[p]; p.visit([&data](const Path &pp) { if (pp.isFile()) { //printf("updateDir %s\n", p.constData()); const auto modif = data.modified.find(pp); if (modif == data.modified.end()) { //printf("added\n"); // new file data.added.insert(pp); return Path::Continue; } data.seen.insert(pp); // possibly modified file if (pp.lastModifiedMs() != modif->second) { //printf("modified\n"); // really modified data.changed.insert(pp); } return Path::Continue; } return Path::Recurse; }); Set<Path> removed; // calculate the removed files (modified - seen) const auto send = data.seen.end(); for (const std::pair<Path, uint64_t>& mod : data.modified) { if (data.seen.find(mod.first) == send) { removed.insert(mod.first); } else { // update to our new time data.modified[mod.first] = mod.first.lastModifiedMs(); } } // update the modified structure for (const Path& ap : data.added) { data.modified[ap] = ap.lastModifiedMs(); } for (const Path& rp : removed) { data.modified.erase(rp); } //printf("hei, removed %u, added %u, changed %u\n", removed.size(), data.added.size(), data.changed.size()); if (!removed.empty()) EventLoop::mainEventLoop()->callLaterMove(std::bind(&FileSystemWatcher::pathsRemoved, watcher, std::placeholders::_1), std::move(removed)); if (!data.added.empty()) EventLoop::mainEventLoop()->callLaterMove(std::bind(&FileSystemWatcher::pathsAdded, watcher, std::placeholders::_1), std::move(data.added)); if (!data.changed.empty()) EventLoop::mainEventLoop()->callLaterMove(std::bind(&FileSystemWatcher::pathsModified, watcher, std::placeholders::_1), std::move(data.changed)); data.added.clear(); data.changed.clear(); data.seen.clear(); } changedPaths.clear(); } } slices.clear(); }
/** * \brief Crop the image using the given rectangle, useful for removing * unsightly edges from imported images or zoom effects */ void MythUIImage::SetCropRect(const MythRect &rect) { QWriteLocker updateLocker(&d->m_UpdateLock); m_imageProperties.cropRect = rect; SetRedraw(); }
/** * \copydoc MythUIType::ParseElement() */ bool MythUIImage::ParseElement( const QString &filename, QDomElement &element, bool showWarnings) { QWriteLocker updateLocker(&d->m_UpdateLock); if (element.tagName() == "filename") { m_OrigFilename = m_Filename = getFirstText(element); if (m_Filename.endsWith('/')) { QDir imageDir(m_Filename); if (!imageDir.exists()) { QString themeDir = GetMythUI()->GetThemeDir() + '/'; imageDir = themeDir + m_Filename; } QStringList imageTypes; QList< QByteArray > exts = QImageReader::supportedImageFormats(); QList< QByteArray >::Iterator it = exts.begin(); for (;it != exts.end();++it) { imageTypes.append( QString("*.").append(*it) ); } imageDir.setNameFilters(imageTypes); QStringList imageList = imageDir.entryList(); srand(time(NULL)); QString randFile; if (imageList.size()) randFile = QString("%1%2").arg(m_Filename) .arg(imageList.takeAt(rand() % imageList.size())); m_OrigFilename = m_Filename = randFile; } } else if (element.tagName() == "filepattern") { m_OrigFilename = m_Filename = getFirstText(element); QString tmp = element.attribute("low"); if (!tmp.isEmpty()) m_LowNum = tmp.toInt(); tmp = element.attribute("high"); if (!tmp.isEmpty()) m_HighNum = tmp.toInt(); tmp = element.attribute("cycle", "start"); if (tmp == "reverse") m_animationCycle = kCycleReverse; } else if (element.tagName() == "area") { SetArea(parseRect(element)); m_ForceSize = m_Area.size(); } else if (element.tagName() == "preserveaspect") m_preserveAspect = parseBool(element); else if (element.tagName() == "crop") m_cropRect = parseRect(element); else if (element.tagName() == "delay") { QString value = getFirstText(element); if (value.contains(",")) { QVector<int> delays; QStringList tokens = value.split(","); QStringList::iterator it = tokens.begin(); for (; it != tokens.end(); ++it) { if ((*it).isEmpty()) { if (delays.size()) delays.append(delays[delays.size()-1]); else delays.append(0); // Default 0ms delay before first image } else { delays.append((*it).toInt()); } } if (delays.size()) { m_Delay = delays[0]; SetDelays(delays); } } else { m_Delay = value.toInt(); } } else if (element.tagName() == "reflection") { m_isReflected = true; QString tmp = element.attribute("axis"); if (!tmp.isEmpty()) { if (tmp.toLower() == "horizontal") m_reflectAxis = ReflectHorizontal; else m_reflectAxis = ReflectVertical; } tmp = element.attribute("shear"); if (!tmp.isEmpty()) m_reflectShear = tmp.toInt(); tmp = element.attribute("scale"); if (!tmp.isEmpty()) m_reflectScale = tmp.toInt(); tmp = element.attribute("length"); if (!tmp.isEmpty()) m_reflectLength = tmp.toInt(); tmp = element.attribute("spacing"); if (!tmp.isEmpty()) m_reflectSpacing = tmp.toInt(); } else if (element.tagName() == "mask") { QString maskfile = getFirstText(element); if (m_maskImage) { m_maskImage->DownRef(); m_maskImage = NULL; } m_maskImage = GetPainter()->GetFormatImage(); m_maskImage->UpRef(); if (m_maskImage->Load(maskfile)) m_isMasked = true; else { m_maskImage->DownRef(); m_maskImage = NULL; m_isMasked = false; } } else if (element.tagName() == "grayscale" || element.tagName() == "greyscale") { m_isGreyscale = parseBool(element); } else { return MythUIType::ParseElement(filename, element, showWarnings); } m_NeedLoad = true; if (m_Parent && m_Parent->IsDeferredLoading(true)) m_NeedLoad = false; return true; }