mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-14 19:59:59 +01:00
New Playlists and new main panel
Favorite and custom playlists of Sessions. Main panel separate control of current session (with preview) and selection of session in playlists. Bugfix in history of files.
This commit is contained in:
Binary file not shown.
@@ -98,6 +98,7 @@ set(VMIX_SRCS
|
||||
ScreenCaptureSource.cpp
|
||||
MousePointer.cpp
|
||||
Grid.cpp
|
||||
Playlist.cpp
|
||||
)
|
||||
|
||||
#####
|
||||
|
||||
@@ -140,6 +140,54 @@ void DialogToolkit::OpenSessionDialog::open()
|
||||
}
|
||||
}
|
||||
|
||||
std::list<std::string> selectSessionsFileDialog(const std::string &label,const std::string &path);
|
||||
void DialogToolkit::MultipleSessionsDialog::open()
|
||||
{
|
||||
if ( !busy_ && promisedlist_.empty() ) {
|
||||
promisedlist_.emplace_back( std::async(std::launch::async, selectSessionsFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogToolkit::MultipleSessionsDialog::closed()
|
||||
{
|
||||
if ( !promisedlist_.empty() ) {
|
||||
// check that file dialog thread finished
|
||||
if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) {
|
||||
// get the filename from this file dialog
|
||||
std::list<std::string> list = promisedlist_.back().get();
|
||||
if (!list.empty()) {
|
||||
// selected a filenames
|
||||
pathlist_ = list;
|
||||
path_ = list.front();
|
||||
// save path location
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
|
||||
}
|
||||
else {
|
||||
pathlist_.clear();
|
||||
path_.clear();
|
||||
}
|
||||
// done with this file dialog
|
||||
promisedlist_.pop_back();
|
||||
busy_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string openPlaylistFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenPlaylistDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openPlaylistFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenMediaDialog::open()
|
||||
{
|
||||
@@ -165,6 +213,21 @@ void DialogToolkit::SaveSessionDialog::setFolder(std::string path)
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename( path );
|
||||
}
|
||||
|
||||
std::string savePlaylistFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::SavePlaylistDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, savePlaylistFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DialogToolkit::SavePlaylistDialog::setFolder(std::string path)
|
||||
{
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename( path );
|
||||
}
|
||||
|
||||
std::string openFolderDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenFolderDialog::open()
|
||||
{
|
||||
@@ -227,7 +290,7 @@ std::string saveSessionFileDialog(const std::string &label, const std::string &p
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * save_file_name;
|
||||
|
||||
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session");
|
||||
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix (MIX)");
|
||||
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
@@ -244,7 +307,7 @@ std::string saveSessionFileDialog(const std::string &label, const std::string &p
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, save_pattern, "vimix session");
|
||||
add_filter_file_dialog(dialog, 1, save_pattern, "vimix (MIX)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
@@ -288,7 +351,7 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0);
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix (MIX)", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
@@ -304,7 +367,7 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix session");
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix (MIX)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
@@ -337,6 +400,205 @@ std::string openSessionFileDialog(const std::string &label, const std::string &p
|
||||
}
|
||||
|
||||
|
||||
std::list<std::string> selectSessionsFileDialog(const std::string &label,const std::string &path)
|
||||
{
|
||||
std::list<std::string> files;
|
||||
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[1] = { VIMIX_FILE_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_names;
|
||||
open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 1, open_pattern, "vimix (MIX)", 1);
|
||||
|
||||
if (open_file_names) {
|
||||
|
||||
const std::string& str (open_file_names);
|
||||
const std::string& delimiters = "|";
|
||||
// Skip delimiters at beginning.
|
||||
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
|
||||
|
||||
// Find first non-delimiter.
|
||||
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
|
||||
|
||||
while (std::string::npos != pos || std::string::npos != lastPos) {
|
||||
// Found a token, add it to the vector.
|
||||
files.push_back(str.substr(lastPos, pos - lastPos));
|
||||
|
||||
// Skip delimiters.
|
||||
lastPos = str.find_first_not_of(delimiters, pos);
|
||||
|
||||
// Find next non-delimiter.
|
||||
pos = str.find_first_of(delimiters, lastPos);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return files;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix (MIX)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// multiple files
|
||||
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true );
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
|
||||
while (open_file_names) {
|
||||
files.push_back( (char *) open_file_names->data );
|
||||
open_file_names = open_file_names->next;
|
||||
}
|
||||
g_slist_free( open_file_names );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
std::string openPlaylistFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[1] = { VIMIX_PLAYLIST_FILE_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix playlist", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix playlist");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name) {
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string savePlaylistFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
char const * save_pattern[1] = { VIMIX_PLAYLIST_FILE_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * save_file_name;
|
||||
|
||||
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix playlist");
|
||||
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Save", GTK_RESPONSE_ACCEPT, NULL );
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, save_pattern, "vimix playlist");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (save_file_name) {
|
||||
filename = std::string(save_file_name);
|
||||
g_free( save_file_name );
|
||||
}
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
if (!filename.empty() && !SystemToolkit::has_extension(filename, VIMIX_PLAYLIST_FILE_EXT ) )
|
||||
filename += std::string(".") + VIMIX_PLAYLIST_FILE_EXT;
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
@@ -511,7 +773,7 @@ std::list<std::string> selectImagesFileDialog(const std::string &label,const std
|
||||
std::list<std::string> files;
|
||||
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif" };
|
||||
char const * open_pattern[3] = { "*.jpg", "*.png", "*.tif" };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_names;
|
||||
|
||||
@@ -30,13 +30,6 @@ public:
|
||||
static bool busy() { return busy_; }
|
||||
};
|
||||
|
||||
class OpenImageDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenImageDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
@@ -44,13 +37,6 @@ public:
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenMediaDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class SaveSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
@@ -59,6 +45,39 @@ public:
|
||||
void open();
|
||||
};
|
||||
|
||||
class MultipleSessionsDialog : public FileDialog
|
||||
{
|
||||
std::list<std::string> pathlist_;
|
||||
std::vector< std::future< std::list<std::string> > > promisedlist_;
|
||||
public:
|
||||
MultipleSessionsDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open() override;
|
||||
bool closed() override;
|
||||
inline std::list<std::string> files() const { return pathlist_; }
|
||||
};
|
||||
|
||||
class OpenPlaylistDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenPlaylistDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class SavePlaylistDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
SavePlaylistDialog(const std::string &name) : FileDialog(name) {}
|
||||
void setFolder(std::string path);
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenMediaDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenFolderDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
@@ -66,6 +85,13 @@ public:
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenImageDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenImageDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class MultipleImagesDialog : public FileDialog
|
||||
{
|
||||
std::list<std::string> pathlist_;
|
||||
|
||||
@@ -100,7 +100,7 @@ void ImGuiToolkit::ButtonDisabled(const char* label, const ImVec2 &size_arg)
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
|
||||
bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* shortcut)
|
||||
bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* tooltip, bool rightalign)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
@@ -119,7 +119,7 @@ bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* sho
|
||||
float radius = height * 0.50f;
|
||||
|
||||
// toggle action : operate on the whole area
|
||||
ImGui::InvisibleButton(label, ImVec2(frame_width - frame_height, frame_height));
|
||||
ImGui::InvisibleButton(label, ImVec2(frame_width, frame_height));
|
||||
if (ImGui::IsItemClicked()) {
|
||||
*toggle = !*toggle;
|
||||
ret = true;
|
||||
@@ -143,19 +143,19 @@ bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* sho
|
||||
col_bg = ImGui::GetColorU32(ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_TabActive], t));
|
||||
|
||||
// draw help text if present
|
||||
if (shortcut) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.9f));
|
||||
ImGui::RenderText(draw_pos, shortcut);
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
if (tooltip != nullptr && ImGui::IsItemHovered())
|
||||
ImGuiToolkit::ToolTip(tooltip);
|
||||
|
||||
// right alignment
|
||||
float alignment = rightalign ? width + g.Style.FramePadding.x : 3.5f * ImGui::GetTextLineHeightWithSpacing();
|
||||
|
||||
// draw the label right aligned
|
||||
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
|
||||
ImVec2 text_pos = draw_pos + ImVec2(frame_width -3.8f * ImGui::GetTextLineHeightWithSpacing() -label_size.x, 0.f);
|
||||
ImVec2 text_pos = draw_pos + ImVec2(frame_width -alignment - g.Style.ItemSpacing.x -label_size.x, 0.f);
|
||||
ImGui::RenderText(text_pos, label);
|
||||
|
||||
// draw switch after the text
|
||||
ImVec2 p = draw_pos + ImVec2(frame_width -3.5f * ImGui::GetTextLineHeightWithSpacing(), 0.f);
|
||||
ImVec2 p = draw_pos + ImVec2(frame_width -alignment, 0.f);
|
||||
draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f);
|
||||
draw_list->AddCircleFilled(ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 250));
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace ImGuiToolkit
|
||||
|
||||
// buttons
|
||||
bool ButtonToggle (const char* label, bool* toggle, const char *tooltip = nullptr);
|
||||
bool ButtonSwitch (const char* label, bool* toggle, const char *shortcut = nullptr);
|
||||
bool ButtonSwitch (const char* label, bool* toggle, const char *tooltip = nullptr, bool rightalign = false);
|
||||
void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0));
|
||||
void ButtonDisabled(const char* label, const ImVec2& size_arg = ImVec2(0,0));
|
||||
bool TextButton (const char* text, const char *tooltip = nullptr, const char *shortcut = nullptr);
|
||||
|
||||
@@ -526,8 +526,8 @@ void ImGuiVisitor::visit (Source& s)
|
||||
ImGuiToolkit::Indication(workspaces_[s.workspace()].second.c_str(), workspaces_[s.workspace()].first, 16);
|
||||
|
||||
// locking
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 3.f * ImGui::GetFrameHeightWithSpacing()) );
|
||||
const char *tooltip[2] = {"Unlocked", "Locked"};
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + preview_height - ImGui::GetFrameHeightWithSpacing()) );
|
||||
static const char *tooltip[2] = {"Unlocked", "Locked"};
|
||||
bool l = s.locked();
|
||||
if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) {
|
||||
s.setLocked(l);
|
||||
|
||||
162
src/Playlist.cpp
Normal file
162
src/Playlist.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
|
||||
#include "Playlist.h"
|
||||
|
||||
Playlist::Playlist() : current_index_(UINTMAX_MAX)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Playlist& Playlist::operator = (const Playlist& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
path_.clear();
|
||||
for(auto it = b.path_.cbegin(); it != b.path_.cend(); ++it)
|
||||
path_.push_back(*it);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Playlist::clear()
|
||||
{
|
||||
path_.clear();
|
||||
}
|
||||
|
||||
void Playlist::load(const std::string &filename)
|
||||
{
|
||||
// try to load playlist file
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(filename.c_str());
|
||||
|
||||
// do not warn if non existing file
|
||||
if (eResult == XML_ERROR_FILE_NOT_FOUND)
|
||||
return;
|
||||
// warn and return on other error
|
||||
else if (XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
// first element should be called by the application name
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement("vimixplaylist");
|
||||
if (pRoot == nullptr)
|
||||
return;
|
||||
|
||||
// all good, can clear previous list
|
||||
filename_ = filename;
|
||||
path_.clear();
|
||||
|
||||
// Then it should contain a list of path
|
||||
XMLElement* pathNode = pRoot->FirstChildElement("path");
|
||||
for( ; pathNode ; pathNode = pathNode->NextSiblingElement()) {
|
||||
const char *p = pathNode->GetText();
|
||||
if (p)
|
||||
add( std::string(p) );
|
||||
}
|
||||
}
|
||||
|
||||
bool Playlist::save()
|
||||
{
|
||||
if ( filename_.empty() )
|
||||
return false;
|
||||
|
||||
saveAs(filename_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Playlist::saveAs(const std::string &filename)
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.NewElement("vimixplaylist");
|
||||
xmlDoc.InsertEndChild(pRoot);
|
||||
|
||||
for(auto it = path_.cbegin(); it != path_.cend(); ++it) {
|
||||
XMLElement *pathNode = xmlDoc.NewElement("path");
|
||||
XMLText *text = xmlDoc.NewText( it->c_str() );
|
||||
pathNode->InsertEndChild( text );
|
||||
pRoot->InsertEndChild(pathNode);
|
||||
}
|
||||
|
||||
XMLError eResult = xmlDoc.SaveFile(filename.c_str());
|
||||
if ( !XMLResultError(eResult))
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
bool Playlist::add(const std::string &path)
|
||||
{
|
||||
if (has(path))
|
||||
return false;
|
||||
path_.push_back(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Playlist::add(const std::list<std::string> &list)
|
||||
{
|
||||
size_t before = path_.size();
|
||||
for (auto it = list.begin(); it != list.end(); ++it) {
|
||||
if (!has( *it ))
|
||||
path_.push_back( *it );
|
||||
}
|
||||
return path_.size()-before;
|
||||
}
|
||||
|
||||
void Playlist::remove(const std::string &path)
|
||||
{
|
||||
std::deque<std::string>::const_iterator it = std::find(path_.begin(), path_.end(), path);
|
||||
|
||||
if (it != path_.end())
|
||||
path_.erase(it);
|
||||
}
|
||||
|
||||
bool Playlist::has(const std::string &path) const
|
||||
{
|
||||
std::deque<std::string>::const_iterator it = std::find(path_.begin(), path_.end(), path);
|
||||
|
||||
return (it != path_.end());
|
||||
}
|
||||
|
||||
void Playlist::remove(size_t index)
|
||||
{
|
||||
if ( index < path_.size() ) {
|
||||
path_.erase (path_.begin()+index);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::move(size_t from_index, size_t to_index)
|
||||
{
|
||||
if ( from_index < path_.size() && to_index < path_.size() && from_index != to_index ) {
|
||||
if ( from_index < to_index ) {
|
||||
path_.insert(path_.begin() + to_index + 1, path_[from_index]);
|
||||
path_.erase(path_.begin() + from_index);
|
||||
}
|
||||
else {
|
||||
path_.insert(path_.begin() + to_index, path_[from_index]);
|
||||
path_.erase(path_.begin() + from_index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/Playlist.h
Normal file
40
src/Playlist.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef PLAYLIST_H
|
||||
#define PLAYLIST_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
class Playlist
|
||||
{
|
||||
public:
|
||||
Playlist();
|
||||
Playlist& operator = (const Playlist& b);
|
||||
|
||||
// load / save XML
|
||||
void clear ();
|
||||
void load (const std::string &filename);
|
||||
void saveAs (const std::string &filename);
|
||||
bool save ();
|
||||
|
||||
// add / remove / test by path
|
||||
bool add (const std::string &path);
|
||||
size_t add (const std::list<std::string> &list);
|
||||
void remove (const std::string &path);
|
||||
bool has (const std::string &path) const;
|
||||
|
||||
// access by index, from 0 to size()
|
||||
inline size_t size () const { return path_.size(); }
|
||||
inline std::string at (size_t index) const { return path_.at(index); }
|
||||
void remove (size_t index);
|
||||
void move (size_t from_index, size_t to_index);
|
||||
inline size_t current() const { return current_index_; }
|
||||
|
||||
private:
|
||||
|
||||
std::deque< std::string > path_;
|
||||
std::string filename_;
|
||||
size_t current_index_;
|
||||
};
|
||||
|
||||
#endif // PLAYLIST_H
|
||||
@@ -41,6 +41,7 @@ XMLElement *save_history(Settings::History &h, const char *nodename, XMLDocument
|
||||
pElement->SetAttribute("autoload", h.load_at_start);
|
||||
pElement->SetAttribute("autosave", h.save_on_exit);
|
||||
pElement->SetAttribute("valid", h.front_is_valid);
|
||||
pElement->SetAttribute("ordering", h.ordering);
|
||||
for(auto it = h.filenames.cbegin();
|
||||
it != h.filenames.cend(); ++it) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
@@ -135,6 +136,8 @@ void Settings::Save(uint64_t runtime)
|
||||
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
|
||||
applicationNode->SetAttribute("show_tooptips", application.show_tooptips);
|
||||
applicationNode->SetAttribute("accept_connections", application.accept_connections);
|
||||
applicationNode->SetAttribute("pannel_main_mode", application.pannel_main_mode);
|
||||
applicationNode->SetAttribute("pannel_playlist_mode", application.pannel_playlist_mode);
|
||||
applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode);
|
||||
applicationNode->SetAttribute("pannel_always_visible", application.pannel_always_visible);
|
||||
applicationNode->SetAttribute("stream_protocol", application.stream_protocol);
|
||||
@@ -261,6 +264,9 @@ void Settings::Save(uint64_t runtime)
|
||||
// recent session filenames
|
||||
recent->InsertEndChild( save_history(application.recentSessions, "Session", xmlDoc));
|
||||
|
||||
// recent session folders
|
||||
recent->InsertEndChild( save_history(application.recentPlaylists, "Playlist", xmlDoc));
|
||||
|
||||
// recent session folders
|
||||
recent->InsertEndChild( save_history(application.recentFolders, "Folder", xmlDoc));
|
||||
|
||||
@@ -353,6 +359,9 @@ void load_history(Settings::History &h, const char *nodename, XMLElement *root)
|
||||
pElement->QueryBoolAttribute("autoload", &h.load_at_start);
|
||||
pElement->QueryBoolAttribute("autosave", &h.save_on_exit);
|
||||
pElement->QueryBoolAttribute("valid", &h.front_is_valid);
|
||||
pElement->QueryIntAttribute("ordering", &h.ordering);
|
||||
|
||||
h.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,11 +412,11 @@ void Settings::Load()
|
||||
else if (XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
|
||||
// first element should be called by the application name
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
|
||||
if (pRoot == nullptr)
|
||||
return;
|
||||
|
||||
// runtime
|
||||
pRoot->QueryUnsigned64Attribute("runtime", &application.total_runtime);
|
||||
|
||||
@@ -422,6 +431,8 @@ void Settings::Load()
|
||||
applicationNode->QueryBoolAttribute("show_tooptips", &application.show_tooptips);
|
||||
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
|
||||
applicationNode->QueryBoolAttribute("pannel_always_visible", &application.pannel_always_visible);
|
||||
applicationNode->QueryIntAttribute("pannel_main_mode", &application.pannel_main_mode);
|
||||
applicationNode->QueryIntAttribute("pannel_playlist_mode", &application.pannel_playlist_mode);
|
||||
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode);
|
||||
applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol);
|
||||
applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port);
|
||||
@@ -618,6 +629,9 @@ void Settings::Load()
|
||||
// recent session filenames
|
||||
load_history(application.recentSessions, "Session", pElement);
|
||||
|
||||
// recent session playlist
|
||||
load_history(application.recentPlaylists, "Playlist", pElement);
|
||||
|
||||
// recent session folders
|
||||
load_history(application.recentFolders, "Folder", pElement);
|
||||
|
||||
@@ -686,6 +700,12 @@ void Settings::Load()
|
||||
|
||||
}
|
||||
|
||||
void Settings::History::assign(const string &filename)
|
||||
{
|
||||
path.assign(filename);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void Settings::History::push(const string &filename)
|
||||
{
|
||||
if (filename.empty()) {
|
||||
@@ -718,6 +738,9 @@ void Settings::History::validate()
|
||||
else
|
||||
fit = filenames.erase(fit);
|
||||
}
|
||||
if (!path.empty() && !SystemToolkit::file_exists( path )) {
|
||||
path = "";
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::KnownHosts::push(const string &ip, const string &port)
|
||||
|
||||
@@ -129,16 +129,19 @@ struct History
|
||||
bool load_at_start;
|
||||
bool save_on_exit;
|
||||
bool changed;
|
||||
int ordering;
|
||||
|
||||
History() {
|
||||
path = IMGUI_LABEL_RECENT_FILES;
|
||||
path = "";
|
||||
front_is_valid = false;
|
||||
load_at_start = true;
|
||||
save_on_exit = true;
|
||||
changed = false;
|
||||
ordering = 3;
|
||||
}
|
||||
void push(const std::string &filename);
|
||||
void remove(const std::string &filename);
|
||||
void push (const std::string &filename);
|
||||
void remove (const std::string &filename);
|
||||
void assign (const std::string &filename);
|
||||
void validate();
|
||||
};
|
||||
|
||||
@@ -278,6 +281,8 @@ struct Application
|
||||
bool action_history_follow_view;
|
||||
bool show_tooptips;
|
||||
|
||||
int pannel_main_mode;
|
||||
int pannel_playlist_mode;
|
||||
int pannel_current_session_mode;
|
||||
bool pannel_always_visible;
|
||||
|
||||
@@ -321,12 +326,11 @@ struct Application
|
||||
std::vector<WindowConfig> windows;
|
||||
|
||||
// recent files histories
|
||||
int orderingSessions;
|
||||
History recentSessions;
|
||||
History recentPlaylists;
|
||||
History recentFolders;
|
||||
History recentImport;
|
||||
History recentImportFolders;
|
||||
int orderingImportFolder;
|
||||
History recentRecordings;
|
||||
std::map< std::string, std::string > dialogRecentFolder;
|
||||
|
||||
@@ -355,6 +359,8 @@ struct Application
|
||||
loopback_camera = 0;
|
||||
shm_method = 0;
|
||||
shm_socket_path = "";
|
||||
pannel_main_mode = 0;
|
||||
pannel_playlist_mode = 0;
|
||||
pannel_current_session_mode = 0;
|
||||
current_view = 1;
|
||||
current_workspace= 3;
|
||||
@@ -363,8 +369,6 @@ struct Application
|
||||
windows = std::vector<WindowConfig>(1+MAX_OUTPUT_WINDOW);
|
||||
windows[0].w = 1600;
|
||||
windows[0].h = 900;
|
||||
orderingSessions = 3;
|
||||
orderingImportFolder = 3;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -912,7 +912,7 @@ void SourceControlWindow::RenderSelection(size_t i)
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f);
|
||||
if (ImGui::Button(ICON_FA_MINUS_CIRCLE)) {
|
||||
if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) {
|
||||
resetActiveSelection();
|
||||
Mixer::manager().session()->deleteBatch(i);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
||||
#include "TimerMetronomeWindow.h"
|
||||
#include "InputMappingWindow.h"
|
||||
#include "ShaderEditWindow.h"
|
||||
#include "Playlist.h"
|
||||
|
||||
|
||||
struct ImVec2;
|
||||
@@ -48,8 +49,8 @@ class Navigator
|
||||
float padding_width_;
|
||||
|
||||
// behavior pannel
|
||||
bool show_config_;
|
||||
bool pannel_visible_;
|
||||
int pannel_main_mode_;
|
||||
float pannel_alpha_;
|
||||
bool view_pannel_visible;
|
||||
bool selected_button[NAV_COUNT];
|
||||
@@ -63,12 +64,13 @@ class Navigator
|
||||
void applyButtonSelection(int index);
|
||||
|
||||
// side pannels
|
||||
void RenderSourcePannel(Source *s);
|
||||
void RenderMainPannel();
|
||||
void RenderMainPannelVimix();
|
||||
void RenderSourcePannel(Source *s, const ImVec2 &iconsize);
|
||||
void RenderMainPannel(const ImVec2 &iconsize);
|
||||
void RenderMainPannelSession();
|
||||
void RenderMainPannelPlaylist();
|
||||
void RenderMainPannelSettings();
|
||||
void RenderTransitionPannel();
|
||||
void RenderNewPannel();
|
||||
void RenderTransitionPannel(const ImVec2 &iconsize);
|
||||
void RenderNewPannel(const ImVec2 &iconsize);
|
||||
void RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 &size);
|
||||
void RenderMousePointerSelector(const ImVec2 &size);
|
||||
|
||||
@@ -198,6 +200,10 @@ protected:
|
||||
DialogToolkit::OpenSessionDialog *sessionimportdialog;
|
||||
DialogToolkit::SaveSessionDialog *sessionsavedialog;
|
||||
|
||||
// Favorites and playlists
|
||||
Playlist favorites;
|
||||
std::string playlists_path;
|
||||
|
||||
// objects and windows
|
||||
Navigator navigator;
|
||||
ToolBox toolbox;
|
||||
@@ -209,6 +215,7 @@ protected:
|
||||
|
||||
void showMenuFile();
|
||||
void showMenuEdit();
|
||||
void showMenuWindows();
|
||||
bool saveOrSaveAs(bool force_versioning = false);
|
||||
void selectSaveFilename();
|
||||
void selectOpenFilename();
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#define VIMIX_FILE_EXT "mix"
|
||||
#define VIMIX_FILE_PATTERN "*.mix"
|
||||
#define VIMIX_PLAYLIST_FILE_EXT "lix"
|
||||
#define VIMIX_PLAYLIST_FILE_PATTERN "*.lix"
|
||||
#define MEDIA_FILES_PATTERN "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",\
|
||||
"*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",\
|
||||
"*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",\
|
||||
@@ -140,7 +142,7 @@
|
||||
#define ALT_LOCK " ALT LOCK"
|
||||
#endif
|
||||
|
||||
#define MENU_NEW_FILE ICON_FA_FILE " New"
|
||||
#define MENU_NEW_FILE ICON_FA_FILE "+ New"
|
||||
#define SHORTCUT_NEW_FILE CTRL_MOD "W"
|
||||
#define MENU_OPEN_FILE ICON_FA_FILE_UPLOAD " Open"
|
||||
#define SHORTCUT_OPEN_FILE CTRL_MOD "O"
|
||||
@@ -186,19 +188,22 @@
|
||||
#define MENU_CLOSE ICON_FA_TIMES " Close"
|
||||
#define DIALOG_FAILED_SOURCE ICON_FA_EXCLAMATION_TRIANGLE " Source failure"
|
||||
|
||||
#define MENU_NOTE ICON_FA_STICKY_NOTE " Add sticky note"
|
||||
#define MENU_METRICS ICON_FA_TACHOMETER_ALT " Metrics"
|
||||
#define MENU_SOURCE_TOOL ICON_FA_WRENCH " Source toolbar"
|
||||
#define MENU_HELP ICON_FA_LIFE_RING " Help"
|
||||
#define SHORTCUT_HELP CTRL_MOD "H"
|
||||
#define MENU_LOGS ICON_FA_LIST_UL " Logs"
|
||||
#define SHORTCUT_LOGS CTRL_MOD "L"
|
||||
#define MENU_PLAYER ICON_FA_PLAY_CIRCLE " Player "
|
||||
#define TOOLTIP_PLAYER "Player "
|
||||
#define SHORTCUT_PLAYER CTRL_MOD "P"
|
||||
#define MENU_OUTPUT ICON_FA_DESKTOP " Display "
|
||||
#define TOOLTIP_OUTPUT "Display "
|
||||
#define SHORTCUT_OUTPUT CTRL_MOD "D"
|
||||
#define MENU_TIMER ICON_FA_CLOCK " Timer "
|
||||
#define TOOLTIP_TIMER "Timer "
|
||||
#define SHORTCUT_TIMER CTRL_MOD "T"
|
||||
#define MENU_INPUTS ICON_FA_HAND_PAPER " Inputs mapping "
|
||||
#define TOOLTIP_INPUTS "Inputs mapping "
|
||||
#define SHORTCUT_INPUTS CTRL_MOD "I"
|
||||
#define TOOLTIP_SHADEREDITOR "Shader Editor "
|
||||
|
||||
Reference in New Issue
Block a user