rawTherapee/rtgui/main.cc
Pandagrapher 320408306d
Build improvements + fix for window size/position (macOS) (#5771)
* Allow using relative path in MacOS for development builds
* [macOS] Fixes RT "cache"/"config" folder not located in the right place
In actual "dev" branch, RT "cache"/"config" folder is located in
"~/Application Support/RawTherapee" folder instead of
"~/Library/Application Support/RawTherapee" folder (as in v5.8 release
and discribed in RawPedia). Moreover, "CACHE_NAME_SUFFIX" cMake option
was not anymore considered.
* [macOS] Remember RT window position correctly, fixes #3209

As described in GTK documentation, some OS Windows Manager do not
consider setting window size and position before it is completely
created (which seems to be the case for macOS). In this commit,
restoring window size/position is now done after its creation.
Morevover, macOS menu bar height is now considered.
2020-05-28 12:26:14 +02:00

600 lines
18 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* RawTherapee 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.
*
* RawTherapee 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 RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef __GNUC__
#if defined(__FAST_MATH__)
#error Using the -ffast-math CFLAG is known to lead to problems. Disable it to compile RawTherapee.
#endif
#endif
#include "config.h"
#include <gtkmm.h>
#include <giomm.h>
#include <iostream>
#include <tiffio.h>
#include "rtwindow.h"
#include <cstring>
#include <cstdlib>
#include <locale.h>
#include <lensfun.h>
#include "cachemanager.h"
#include "editorpanel.h"
#include "filecatalog.h"
#include "filepanel.h"
#include "options.h"
#include "soundman.h"
#include "rtimage.h"
#include "version.h"
#include "extprog.h"
#include "../rtengine/dynamicprofile.h"
#include "../rtengine/procparams.h"
#ifndef WIN32
#include <glibmm/fileutils.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glibmm/threads.h>
#else
#include <glibmm/thread.h>
#include "conio.h"
#include "windows.h"
#endif
// Set this to 1 to make RT work when started with Eclipse and arguments, at least on Windows platform
#define ECLIPSE_ARGS 0
// stores path to data files
Glib::ustring argv0;
Glib::ustring creditsPath;
Glib::ustring licensePath;
Glib::ustring argv1;
Glib::ustring argv2;
bool simpleEditor = false;
bool gimpPlugin = false;
bool remote = false;
unsigned char initialGdkScale = 1;
//Glib::Threads::Thread* mainThread;
namespace
{
// For an unknown reason, Glib::filename_to_utf8 doesn't work on reliably Windows,
// so we're using Glib::filename_to_utf8 for Linux/Apple and Glib::locale_to_utf8 for Windows.
Glib::ustring fname_to_utf8 (const char* fname)
{
#ifdef WIN32
try {
return Glib::locale_to_utf8 (fname);
} catch (Glib::Error&) {
return Glib::convert_with_fallback (fname, "UTF-8", "ISO-8859-1", "?");
}
#else
return Glib::filename_to_utf8 (fname);
#endif
}
// This recursive mutex will be used by gdk_threads_enter/leave instead of a simple mutex
static Glib::Threads::RecMutex myGdkRecMutex;
static void myGdkLockEnter()
{
myGdkRecMutex.lock();
}
static void myGdkLockLeave()
{
// Automatic gdk_flush for non main thread
#if AUTO_GDK_FLUSH
//if (Glib::Thread::self() != mainThread) {
// gdk_flush();
//}
#endif
myGdkRecMutex.unlock();
}
/* Process line command options
* Returns
* 0 if process in batch has executed
* 1 to start GUI (with a dir or file option)
* 2 to start GUI because no files found
* -1 if there is an error in parameters
* -2 if an error occurred during processing
* -3 if at least one required procparam file was not found */
int processLineParams ( int argc, char **argv )
{
int ret = 1;
for ( int iArg = 1; iArg < argc; iArg++) {
Glib::ustring currParam (argv[iArg]);
if ( currParam.empty() ) {
continue;
}
#if ECLIPSE_ARGS
currParam = currParam.substr (1, currParam.length() - 2);
#endif
if ( currParam.at (0) == '-' && currParam.size() > 1 ) {
switch ( currParam.at (1) ) {
case '-':
// GTK --argument, we're skipping it
break;
#ifdef WIN32
case 'w': // This case is handled outside this function
break;
#endif
case 'v':
printf("RawTherapee, version %s\n", RTVERSION);
ret = 0;
break;
#ifndef __APPLE__ // TODO agriggio - there seems to be already some "single instance app" support for OSX in rtwindow. Disabling it here until I understand how to merge the two
case 'R':
if (!gimpPlugin) {
remote = true;
}
break;
#endif
case 'g':
if (currParam == "-gimp") {
gimpPlugin = true;
simpleEditor = true;
remote = false;
break;
}
// no break here on purpose
case 'h':
case '?':
default: {
printf(" An advanced, cross-platform program for developing raw photos.\n\n");
printf(" Website: http://www.rawtherapee.com/\n");
printf(" Documentation: http://rawpedia.rawtherapee.com/\n");
printf(" Forum: https://discuss.pixls.us/c/software/rawtherapee\n");
printf(" Code and bug reports: https://github.com/Beep6581/RawTherapee\n\n");
printf("Symbols:\n");
printf(" <Chevrons> indicate parameters you can change.\n\n");
printf("Usage:\n");
printf(" %s <folder> Start File Browser inside folder.\n",Glib::path_get_basename (argv[0]).c_str());
printf(" %s <file> Start Image Editor with file.\n\n",Glib::path_get_basename (argv[0]).c_str());
std::cout << std::endl;
printf("Options:\n");
#ifdef WIN32
printf(" -w Do not open the Windows console\n");
#endif
printf(" -v Print RawTherapee version number and exit\n");
#ifndef __APPLE__
printf(" -R Raise an already running RawTherapee instance (if available)\n");
#endif
printf(" -h -? Display this help message\n");
ret = -1;
break;
}
}
} else {
if (argv1.empty()) {
argv1 = Glib::ustring (fname_to_utf8 (argv[iArg]));
#if ECLIPSE_ARGS
argv1 = argv1.substr (1, argv1.length() - 2);
#endif
} else if (gimpPlugin) {
argv2 = Glib::ustring (fname_to_utf8 (argv[iArg]));
break;
}
if (!gimpPlugin) {
break;
}
}
}
return ret;
}
bool init_rt()
{
extProgStore->init();
SoundManager::init();
if (!rtengine::settings->verbose) {
TIFFSetWarningHandler (nullptr); // avoid annoying message boxes
}
#ifndef WIN32
// Move the old path to the new one if the new does not exist
if (Glib::file_test (Glib::build_filename (options.rtdir, "cache"), Glib::FILE_TEST_IS_DIR) && !Glib::file_test (options.cacheBaseDir, Glib::FILE_TEST_IS_DIR)) {
g_rename (Glib::build_filename (options.rtdir, "cache").c_str (), options.cacheBaseDir.c_str ());
}
#endif
return true;
}
void cleanup_rt()
{
rtengine::cleanup();
}
RTWindow *create_rt_window()
{
Glib::ustring icon_path = Glib::build_filename (argv0, "images");
Glib::RefPtr<Gtk::IconTheme> defaultIconTheme = Gtk::IconTheme::get_default();
defaultIconTheme->append_search_path (icon_path);
//gdk_threads_enter ();
RTWindow *rtWindow = new RTWindow();
rtWindow->setWindowSize(); // Need to be called after RTWindow creation to work with all OS Windows Manager
return rtWindow;
}
class RTApplication: public Gtk::Application
{
public:
RTApplication():
Gtk::Application ("com.rawtherapee.application",
Gio::APPLICATION_HANDLES_OPEN),
rtWindow (nullptr)
{
}
~RTApplication() override
{
if (rtWindow) {
delete rtWindow;
}
cleanup_rt();
}
private:
bool create_window()
{
if (rtWindow) {
return true;
}
if (!init_rt()) {
Gtk::MessageDialog msgd ("Fatal error!\nThe RT_SETTINGS and/or RT_PATH environment variables are set, but use a relative path. The path must be absolute!", true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
add_window (msgd);
msgd.run ();
return false;
} else {
rtWindow = create_rt_window();
add_window (*rtWindow);
return true;
}
}
// Override default signal handlers:
void on_activate() override
{
if (create_window()) {
rtWindow->present();
}
}
void on_open (const Gio::Application::type_vec_files& files,
const Glib::ustring& hint) override
{
if (create_window()) {
struct Data {
std::vector<Thumbnail *> entries;
Glib::ustring lastfilename;
FileCatalog *filecatalog;
};
Data *d = new Data;
d->filecatalog = rtWindow->fpanel->fileCatalog;
for (const auto &f : files) {
Thumbnail *thm = cacheMgr->getEntry (f->get_path());
if (thm) {
d->entries.push_back (thm);
d->lastfilename = f->get_path();
}
}
if (!d->entries.empty()) {
const auto doit =
[] (gpointer data) -> gboolean {
Data *d = static_cast<Data *> (data);
d->filecatalog->openRequested (d->entries);
d->filecatalog->selectImage (d->lastfilename, true);
delete d;
return FALSE;
};
gdk_threads_add_idle (doit, d);
} else {
delete d;
}
rtWindow->present();
}
}
private:
RTWindow *rtWindow;
};
void show_gimp_plugin_info_dialog(Gtk::Window *parent)
{
if (options.gimpPluginShowInfoDialog) {
Gtk::MessageDialog info(*parent, M("GIMP_PLUGIN_INFO"), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, true);
Gtk::Box *box = info.get_message_area();
Gtk::CheckButton dontshowagain(M("DONT_SHOW_AGAIN"));
dontshowagain.show();
box->pack_start(dontshowagain);
info.run();
options.gimpPluginShowInfoDialog = !dontshowagain.get_active();
}
}
} // namespace
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
setlocale (LC_NUMERIC, "C"); // to set decimal point to "."
simpleEditor = false;
gimpPlugin = false;
remote = false;
argv0 = "";
argv1 = "";
argv2 = "";
Glib::init(); // called by Gtk::Main, but this may be important for thread handling, so we call it ourselves now
Gio::init ();
#ifdef WIN32
if (GetFileType (GetStdHandle (STD_OUTPUT_HANDLE)) == 0x0003) {
// started from msys2 console => do not buffer stdout
setbuf(stdout, NULL);
}
#endif
#ifdef BUILD_BUNDLE
char exname[512] = {0};
Glib::ustring exePath;
// get the path where the rawtherapee executable is stored
#ifdef WIN32
WCHAR exnameU[512] = {0};
GetModuleFileNameW (NULL, exnameU, 511);
WideCharToMultiByte (CP_UTF8, 0, exnameU, -1, exname, 511, 0, 0 );
#else
if (readlink ("/proc/self/exe", exname, 511) < 0) {
strncpy (exname, argv[0], 511);
}
#endif
exePath = Glib::path_get_dirname (exname);
// set paths
if (Glib::path_is_absolute (DATA_SEARCH_PATH)) {
argv0 = DATA_SEARCH_PATH;
} else {
argv0 = Glib::build_filename (exePath, DATA_SEARCH_PATH);
}
if (Glib::path_is_absolute (CREDITS_SEARCH_PATH)) {
creditsPath = CREDITS_SEARCH_PATH;
} else {
creditsPath = Glib::build_filename (exePath, CREDITS_SEARCH_PATH);
}
if (Glib::path_is_absolute (LICENCE_SEARCH_PATH)) {
licensePath = LICENCE_SEARCH_PATH;
} else {
licensePath = Glib::build_filename (exePath, LICENCE_SEARCH_PATH);
}
options.rtSettings.lensfunDbDirectory = LENSFUN_DB_PATH;
#else
argv0 = DATA_SEARCH_PATH;
creditsPath = CREDITS_SEARCH_PATH;
licensePath = LICENCE_SEARCH_PATH;
options.rtSettings.lensfunDbDirectory = LENSFUN_DB_PATH;
#endif
#ifdef WIN32
bool consoleOpened = false;
// suppression of annoying error boxes
SetErrorMode (SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
if (argc > 1) {
if (!remote && !Glib::file_test (argv1, Glib::FILE_TEST_EXISTS ) && !Glib::file_test (argv1, Glib::FILE_TEST_IS_DIR)) {
bool stdoutRedirecttoConsole = (GetFileType (GetStdHandle (STD_OUTPUT_HANDLE)) == 0x0000);
// open console, if stdout is invalid
if (stdoutRedirecttoConsole) {
// check if parameter -w was passed.
// We have to do that in this step, because it controls whether to open a console to show the output of following steps
bool Console = true;
for (int i = 1; i < argc; i++)
if (!strcmp (argv[i], "-w") || !strcmp (argv[i], "-R") || !strcmp (argv[i], "-gimp")) {
Console = false;
break;
}
if (Console && AllocConsole()) {
AttachConsole ( GetCurrentProcessId() ) ;
// Don't allow CTRL-C in console to terminate RT
SetConsoleCtrlHandler ( NULL, true );
// Set title of console
char consoletitle[128];
sprintf (consoletitle, "RawTherapee %s Console", RTVERSION);
SetConsoleTitle (consoletitle);
// increase size of screen buffer
COORD c;
c.X = 200;
c.Y = 1000;
SetConsoleScreenBufferSize ( GetStdHandle ( STD_OUTPUT_HANDLE ), c );
// Disable console-Cursor
CONSOLE_CURSOR_INFO cursorInfo;
cursorInfo.dwSize = 100;
cursorInfo.bVisible = false;
SetConsoleCursorInfo ( GetStdHandle ( STD_OUTPUT_HANDLE ), &cursorInfo );
if (stdoutRedirecttoConsole) { // if stdout is Redirect to console, we also redirect stderr to console
freopen ( "CON", "w", stdout ) ;
freopen ( "CON", "w", stderr ) ;
}
freopen ( "CON", "r", stdin ) ;
consoleOpened = true;
}
}
}
int ret = processLineParams ( argc, argv);
if ( ret <= 0 ) {
fflush(stdout);
if (consoleOpened) {
printf ("Press any key to exit RawTherapee\n");
FlushConsoleInputBuffer (GetStdHandle (STD_INPUT_HANDLE));
getch();
}
return ret;
}
}
#else
if (argc > 1) {
int ret = processLineParams ( argc, argv);
if ( ret <= 0 ) {
return ret;
}
}
#endif
Glib::ustring fatalError;
try {
Options::load();
} catch (Options::Error &e) {
fatalError = e.get_msg();
}
if (gimpPlugin) {
if (!Glib::file_test (argv1, Glib::FILE_TEST_EXISTS) || Glib::file_test (argv1, Glib::FILE_TEST_IS_DIR)) {
printf ("Error: argv1 doesn't exist\n");
return 1;
}
if (argv2.empty()) {
printf ("Error: -gimp requires two arguments\n");
return 1;
}
} else if (!remote && Glib::file_test(argv1, Glib::FILE_TEST_EXISTS) && !Glib::file_test(argv1, Glib::FILE_TEST_IS_DIR)) {
simpleEditor = true;
}
int ret = 0;
if (options.pseudoHiDPISupport) {
// Reading/updating GDK_SCALE early if it exists
const gchar *gscale = g_getenv("GDK_SCALE");
if (gscale && gscale[0] == '2') {
initialGdkScale = 2;
}
// HOMBRE: On Windows, if resolution is set to 200%, Gtk internal variables are SCALE=2 and DPI=96
g_setenv("GDK_SCALE", "1", true);
}
gdk_threads_set_lock_functions (G_CALLBACK (myGdkLockEnter), (G_CALLBACK (myGdkLockLeave)));
gdk_threads_init();
gtk_init (&argc, &argv); // use the "--g-fatal-warnings" command line flag to make warnings fatal
if (fatalError.empty() && remote) {
char *app_argv[2] = { const_cast<char *> (argv0.c_str()) };
int app_argc = 1;
if (!argv1.empty()) {
app_argc = 2;
app_argv[1] = const_cast<char *> (argv1.c_str());
}
RTApplication app;
ret = app.run (app_argc, app_argv);
} else {
if (fatalError.empty() && init_rt()) {
Gtk::Main m (&argc, &argv);
gdk_threads_enter();
const std::unique_ptr<RTWindow> rtWindow (create_rt_window());
if (gimpPlugin) {
show_gimp_plugin_info_dialog(rtWindow.get());
}
m.run (*rtWindow);
gdk_threads_leave();
if (gimpPlugin && rtWindow->epanel && rtWindow->epanel->isRealized()) {
if (!rtWindow->epanel->saveImmediately(argv2, SaveFormat())) {
ret = -2;
}
}
cleanup_rt();
} else {
Gtk::Main m (&argc, &argv);
Gtk::MessageDialog msgd (Glib::ustring::compose("FATAL ERROR!\n\n%1", fatalError), true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
msgd.run ();
ret = -2;
}
}
#ifdef WIN32
if (consoleOpened) {
printf ("Press any key to exit RawTherapee\n");
fflush(stdout);
FlushConsoleInputBuffer (GetStdHandle (STD_INPUT_HANDLE));
getch();
}
#endif
return ret;
}