Initial commit.
This commit is contained in:
29
Resources/InsaneWidget/CMake/FindLibInsane.cmake
Normal file
29
Resources/InsaneWidget/CMake/FindLibInsane.cmake
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
|
||||
# - Try to find LibInsane
|
||||
# Once done this will define
|
||||
# LIBINSANE_FOUND - System has LibInsane
|
||||
# LIBINSANE_INCLUDE_DIRS - The LibInsane include directories
|
||||
# LIBINSANE_LIBRARIES - The libraries needed to use LibInsane
|
||||
# LIBINSANE_DEFINITIONS - Compiler switches required for using LibInsane
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_LIBINSANE QUIET libinsane.pc)
|
||||
set(LIBINSANE_DEFINITIONS ${PC_LIBINSANE_CFLAGS_OTHER})
|
||||
|
||||
find_path(LIBINSANE_INCLUDE_DIR libinsane/sane.h
|
||||
HINTS ${PC_LIBINSANE_INCLUDEDIR} ${PC_LIBINSANE_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES libinsane )
|
||||
|
||||
find_library(LIBINSANE_LIBRARY NAMES libinsane.so
|
||||
HINTS ${PC_LIBINSANE_LIBDIR} ${PC_LIBINSANE_LIBRARY_DIRS} )
|
||||
|
||||
set(LIBINSANE_LIBRARIES ${LIBINSANE_LIBRARY})
|
||||
set(LIBINSANE_INCLUDE_DIRS ${LIBINSANE_INCLUDE_DIR} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set LIBINSANE_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(LibInsane DEFAULT_MSG
|
||||
LIBINSANE_LIBRARY LIBINSANE_INCLUDE_DIR)
|
||||
mark_as_advanced(LIBINSANE_INCLUDE_DIR LIBINSANE_LIBRARY )
|
||||
23
Resources/InsaneWidget/CMake/FindPaper.cmake
Normal file
23
Resources/InsaneWidget/CMake/FindPaper.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
# - Try to find Paper
|
||||
# Once done this will define
|
||||
# PAPER_FOUND - System has Paper
|
||||
# PAPER_INCLUDE_DIRS - The Paper include directories
|
||||
# PAPER_LIBRARIES - The libraries needed to use Paper
|
||||
# PAPER_DEFINITIONS - Compiler switches required for using Paper
|
||||
|
||||
find_path(PAPER_INCLUDE_DIR paper.h
|
||||
HINTS ${PC_PAPER_INCLUDEDIR} ${PC_PAPER_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES paper )
|
||||
|
||||
find_library(PAPER_LIBRARY NAMES libpaper.so
|
||||
HINTS ${PC_PAPER_LIBDIR} ${PC_PAPER_LIBRARY_DIRS} )
|
||||
|
||||
set(PAPER_LIBRARIES ${PAPER_LIBRARY} )
|
||||
set(PAPER_INCLUDE_DIRS ${PAPER_INCLUDE_DIR} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set PAPER_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(Paper DEFAULT_MSG
|
||||
PAPER_LIBRARY PAPER_INCLUDE_DIR)
|
||||
mark_as_advanced(PAPER_INCLUDE_DIR PAPER_LIBRARY )
|
||||
27
Resources/InsaneWidget/CMake/FindPoDoFo.cmake
Normal file
27
Resources/InsaneWidget/CMake/FindPoDoFo.cmake
Normal file
@@ -0,0 +1,27 @@
|
||||
# - Try to find PoDoFo
|
||||
# Once done this will define
|
||||
# PODOFO_FOUND - System has PoDoFo
|
||||
# PODOFO_INCLUDE_DIRS - The PoDoFo include directories
|
||||
# PODOFO_LIBRARIES - The libraries needed to use PoDoFo
|
||||
# PODOFO_DEFINITIONS - Compiler switches required for using PoDoFo
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_PODOFO QUIET libpodofo.pc)
|
||||
set(PODOFO_DEFINITIONS ${PC_PODOFO_CFLAGS_OTHER})
|
||||
|
||||
find_path(PODOFO_INCLUDE_DIR podofo/podofo.h
|
||||
HINTS ${PC_PODOFO_INCLUDEDIR} ${PC_PODOFO_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES podofo )
|
||||
|
||||
find_library(PODOFO_LIBRARY NAMES libpodofo.so
|
||||
HINTS ${PC_PODOFO_LIBDIR} ${PC_PODOFO_LIBRARY_DIRS} )
|
||||
|
||||
set(PODOFO_LIBRARIES ${PODOFO_LIBRARY} )
|
||||
set(PODOFO_INCLUDE_DIRS ${PODOFO_INCLUDE_DIR} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set PODOFO_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(PoDoFo DEFAULT_MSG
|
||||
PODOFO_LIBRARY PODOFO_INCLUDE_DIR)
|
||||
mark_as_advanced(PODOFO_INCLUDE_DIR PODOFO_LIBRARY )
|
||||
33
Resources/InsaneWidget/CMakeLists.txt
Normal file
33
Resources/InsaneWidget/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(InsaneWidget)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake")
|
||||
|
||||
find_package(wxWidgets COMPONENTS base core CONFIG REQUIRED)
|
||||
find_package(LibInsane REQUIRED)
|
||||
find_package(PoDoFo REQUIRED)
|
||||
find_package(Paper REQUIRED)
|
||||
|
||||
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_LIST_DIR}/UI
|
||||
${CMAKE_CURRENT_LIST_DIR}/../Utilities)
|
||||
|
||||
add_library(insanewidget STATIC
|
||||
UI/InsaneWidget.cpp
|
||||
UI/ScannerWidget.cpp
|
||||
|
||||
Common.h
|
||||
XInsaneWidget.cpp
|
||||
XScannerWidget.cpp
|
||||
InsaneWorker.cpp
|
||||
PixelToImageWriter.cpp
|
||||
PixelToPdfWriter.cpp)
|
||||
|
||||
target_link_libraries(insanewidget minutils
|
||||
${wxWidgets_LIBRARIES}
|
||||
${LIBINSANE_LIBRARIES}
|
||||
${PODOFO_LIBRARIES}
|
||||
${NETPBM_LIBRARIES}
|
||||
${PAPER_LIBRARIES})
|
||||
30
Resources/InsaneWidget/Common.h
Normal file
30
Resources/InsaneWidget/Common.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// /*
|
||||
// * File: Common.h
|
||||
// * Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
// * License : CeCILL-C
|
||||
// * Copyright Saleem Edah-Tally - © 2025
|
||||
// *
|
||||
// * Created on 27 06 2025, 20:34
|
||||
// */
|
||||
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
enum {PDF, PNG, JPEG, TIFF, PNM};
|
||||
typedef std::map <int, std::string> ExtensionsMap;
|
||||
|
||||
static ExtensionsMap Extensions;
|
||||
|
||||
static void UpdateExtensionsMap()
|
||||
{
|
||||
Extensions[PDF] = "pdf";
|
||||
Extensions[PNG] = "png";
|
||||
Extensions[JPEG] = "jpeg";
|
||||
Extensions[TIFF] = "tiff";
|
||||
Extensions[PNM] = "pnm";
|
||||
}
|
||||
|
||||
#endif // COMMON_H
|
||||
582
Resources/InsaneWidget/InsaneWorker.cpp
Normal file
582
Resources/InsaneWidget/InsaneWorker.cpp
Normal file
@@ -0,0 +1,582 @@
|
||||
/*
|
||||
* File: InsaneWorker.cpp
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2024
|
||||
*
|
||||
* Created on 14 02 2024, 13:00
|
||||
*/
|
||||
|
||||
#include "InsaneWorker.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <paper.h>
|
||||
#include <libinsane/log.h>
|
||||
#include <libinsane/safebet.h>
|
||||
#include <libinsane/error.h>
|
||||
#include <libinsane/util.h>
|
||||
#include <libinsane/normalizers.h>
|
||||
#include <libinsane/constants.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define IERR(e) ("[" + to_string(e) + "] " + lis_strerror(e))
|
||||
static bool gs_cancelRequested = false;
|
||||
|
||||
InsaneWorker::InsaneWorker ( InsaneWorkerEvent * evh )
|
||||
{
|
||||
m_evh = evh;
|
||||
}
|
||||
|
||||
InsaneWorker::~InsaneWorker()
|
||||
{
|
||||
if (m_api)
|
||||
m_api->cleanup(m_api);
|
||||
}
|
||||
|
||||
bool InsaneWorker::Init()
|
||||
{
|
||||
// Terminate a previous one.
|
||||
if (m_api)
|
||||
{
|
||||
m_devices.clear();
|
||||
m_deviceId.clear();
|
||||
m_api->cleanup(m_api);
|
||||
m_sourceItem = nullptr;
|
||||
m_rootSourceItem = nullptr;
|
||||
}
|
||||
lis_error err;
|
||||
err = lis_safebet ( &m_api );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnInsaneError ( IERR(err) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
lis_device_descriptor ** devices = nullptr; // nullptr terminated.
|
||||
err = m_api->list_devices ( m_api, LIS_DEVICE_LOCATIONS_LOCAL_ONLY, &devices ); // Gets network scanners though.
|
||||
if ( err )
|
||||
{
|
||||
m_evh->OnInsaneError ( IERR(err) );
|
||||
return false;
|
||||
}
|
||||
|
||||
uint numberOfDevices = 0;
|
||||
for ( uint i = 0; devices[i] != nullptr; i++ )
|
||||
{
|
||||
numberOfDevices++;
|
||||
}
|
||||
// Collect available descriptions for all devices.
|
||||
for ( uint i = 0; i < numberOfDevices; i++ )
|
||||
{
|
||||
DeviceDescriptor dd;
|
||||
lis_device_descriptor * descriptor = devices[i];
|
||||
dd.id = descriptor->dev_id;
|
||||
dd.model = descriptor->model;
|
||||
dd.type = descriptor->type;
|
||||
dd.vendor = descriptor->vendor;
|
||||
m_devices.push_back(dd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine if a source item is a feeder.
|
||||
int InsaneWorker::IsADF(const std::string& deviceId, int sourceChildIndex)
|
||||
{
|
||||
if ( !m_api )
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
lis_error err;
|
||||
lis_item * rootSourceItem = nullptr;
|
||||
err = m_api->get_device ( m_api, deviceId.c_str(), &rootSourceItem );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return false;
|
||||
}
|
||||
lis_item ** children = nullptr;
|
||||
err = rootSourceItem->get_children(rootSourceItem, &children);
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool adf = (children[sourceChildIndex]->type == LIS_ITEM_ADF);
|
||||
rootSourceItem->close(rootSourceItem);
|
||||
return adf;
|
||||
}
|
||||
|
||||
// Ex: all supported resolutions.
|
||||
std::string InsaneWorker::GetOptionPossibleValues(const std::string& deviceId,
|
||||
const std::string& optionName, std::vector<std::string>& possible)
|
||||
{
|
||||
if ( !m_api )
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
lis_error err;
|
||||
lis_item * item = nullptr;
|
||||
err = m_api->get_device ( m_api, deviceId.c_str(), &item );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return "";
|
||||
}
|
||||
|
||||
lis_option_descriptor ** options = nullptr;
|
||||
err = item->get_options ( item, &options );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
item->close(item);
|
||||
return "";
|
||||
}
|
||||
uint numberOfOptions = 0;
|
||||
for ( uint i = 0; options[i] != nullptr; i++ )
|
||||
{
|
||||
numberOfOptions++;
|
||||
}
|
||||
|
||||
std::string defaultValue;
|
||||
for ( uint i = 0; i < numberOfOptions; i++ )
|
||||
{
|
||||
lis_option_descriptor * optionDescriptor = options[i];
|
||||
const std::string name = optionDescriptor->name;
|
||||
if ( ToLower(name) != optionName )
|
||||
continue;
|
||||
union lis_value value;
|
||||
err = optionDescriptor->fn.get_value ( optionDescriptor, &value );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
item->close(item);
|
||||
return "";
|
||||
}
|
||||
lis_value_type type = optionDescriptor->value.type;
|
||||
bool isString = false;
|
||||
switch ( type )
|
||||
{
|
||||
case LIS_TYPE_BOOL:
|
||||
defaultValue = std::to_string(( bool ) value.boolean);
|
||||
break;
|
||||
case LIS_TYPE_INTEGER:
|
||||
defaultValue = std::to_string(( long ) value.integer);
|
||||
break;
|
||||
case LIS_TYPE_DOUBLE:
|
||||
defaultValue = std::to_string(( double ) value.dbl);
|
||||
break;
|
||||
case LIS_TYPE_STRING:
|
||||
isString = true;
|
||||
defaultValue = value.string;
|
||||
break;
|
||||
case LIS_TYPE_IMAGE_FORMAT:
|
||||
defaultValue = "Unmanaged";
|
||||
break;
|
||||
default:
|
||||
defaultValue = "Unknown";
|
||||
break;
|
||||
}
|
||||
lis_value_list valueList = optionDescriptor->constraint.possible.list;
|
||||
int numberOfListItems = valueList.nb_values;
|
||||
if ( numberOfListItems )
|
||||
{
|
||||
for ( int j = 0; j < numberOfListItems; j++ )
|
||||
{
|
||||
if ( isString )
|
||||
possible.push_back( valueList.values[j].string );
|
||||
else
|
||||
possible.push_back( std::to_string ( valueList.values[j].integer ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item->close(item);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Open a source item and prepare for the next step, i.e, scanning.
|
||||
bool InsaneWorker::ConfigureDevice(const std::string& deviceId,
|
||||
const std::string& source, int sourceChildIndex,
|
||||
const std::string& mode, int resolution,
|
||||
const std::string& paperSize)
|
||||
{
|
||||
if ( !m_api )
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnError ( "Insane api is NULL; has Init() been called?");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_deviceId = deviceId;
|
||||
m_source = source;
|
||||
m_sourceChildIndex = (sourceChildIndex > -1) ? sourceChildIndex : 0;
|
||||
m_mode = mode;
|
||||
m_resolution = resolution;
|
||||
m_paperSize = paperSize;
|
||||
|
||||
lis_error err;
|
||||
|
||||
err = m_api->get_device ( m_api, m_deviceId.c_str(), &m_rootSourceItem );
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
lis_item ** children = nullptr;
|
||||
err = m_rootSourceItem->get_children(m_rootSourceItem, &children);
|
||||
if ( err )
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Selecting a lis_item by the source index.
|
||||
// One child item is created by libinsane if there is no source: v4l.
|
||||
m_sourceItem = children[m_sourceChildIndex];
|
||||
if (!m_sourceItem)
|
||||
{
|
||||
if (m_evh)
|
||||
m_evh->OnError("Could not determine a device item");
|
||||
m_rootSourceItem->close(m_rootSourceItem);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Error status not checked; use defaults if failure.
|
||||
lis_set_option(m_sourceItem, OPT_NAME_MODE, mode.c_str());
|
||||
/*
|
||||
* Selecting a source on a child item that has been selected by a source index.
|
||||
* Sounds weird but needed.
|
||||
*/
|
||||
lis_set_option(m_sourceItem, OPT_NAME_SOURCE, source.c_str());
|
||||
lis_set_option(m_sourceItem, OPT_NAME_RESOLUTION, to_string(resolution).c_str());
|
||||
pair<double, double> br;
|
||||
if (GetBottomRight(br))
|
||||
{
|
||||
// For v4l, these do not get applied.
|
||||
lis_set_option(m_sourceItem, "br-x", to_string(br.first).c_str());
|
||||
lis_set_option(m_sourceItem, "br-y", to_string(br.second).c_str());
|
||||
}
|
||||
|
||||
// Don't close m_rootDeviceItem, it is prepared for ::Scan().
|
||||
return true;
|
||||
}
|
||||
|
||||
// Perform scanning: create raw data files.
|
||||
bool InsaneWorker::Scan(const std::string& dir, const std::string& basename,
|
||||
int startIndex, int padwidth, int increment)
|
||||
{
|
||||
if ( !m_api || !m_sourceItem )
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnError ( "Insane api or device item is NULL, cannot scan.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (dir.empty() || basename.empty() || startIndex < 0 || padwidth < 0 || (increment == 0))
|
||||
{
|
||||
if ( m_evh )
|
||||
{
|
||||
m_evh->OnError ( "Invalid input, cannot scan.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto makeFileName = [&] (int index)
|
||||
{
|
||||
// std:format is not friendly with a variable padwidth; requires a literal format.
|
||||
stringstream ss;
|
||||
ss << setfill('0') << setw(padwidth) << index;
|
||||
return (dir + "/" + basename + "-" + ss.str());
|
||||
};
|
||||
|
||||
lis_error err;
|
||||
|
||||
lis_scan_session * session;
|
||||
// Scanning starts here on device.
|
||||
err = m_sourceItem->scan_start(m_sourceItem, &session);
|
||||
if (err || !session)
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
lis_scan_parameters parameters; // DO NOT use a POINTER here.
|
||||
err = session->get_scan_parameters(session, ¶meters);
|
||||
if (err)
|
||||
{
|
||||
if ( m_evh )
|
||||
m_evh->OnInsaneError ( IERR ( err ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* format is troublesome: always 0.
|
||||
* But for LineArt, in the logs, sane_get_parameters() returns format=1 (v4l)
|
||||
* or format=2 (remote).
|
||||
*/
|
||||
// lis_img_format format = parameters.format; // guaranteed, but always 0, even for LineArt.
|
||||
const int lineArtFactor = (m_mode == string("LineArt") ? 3 : 1); // insane always outputs RGB.
|
||||
int imageWidth = parameters.width; // pixels, guaranteed.
|
||||
int imageHeight = parameters.height; // pixels, not guaranteed.
|
||||
size_t imageSize = parameters.image_size * lineArtFactor; // bytes, not guaranteed.
|
||||
InsaneWorkerEvent::ImageAttributes imageAttributes;
|
||||
imageAttributes.size = imageSize;
|
||||
imageAttributes.width = imageWidth;
|
||||
imageAttributes.height = imageHeight;
|
||||
|
||||
// This file is valid raw pixels. It can be converted with rawtoppm and rawtopgm.
|
||||
uint pageIndex = startIndex;
|
||||
string filePath = makeFileName(pageIndex);
|
||||
const int bytesPerRow = imageSize / imageHeight;
|
||||
/*
|
||||
* Needs confirmation.
|
||||
* LineArt : 1 pixel = 1 bit - maximum resolution = 9600
|
||||
* Gray : 1 pixel = 1 byte - maximum resolution = 2400
|
||||
* Color : 1 pixel = 3 bytes - maximum resolution = 600
|
||||
*
|
||||
* A4, LineArt, 4800: 1690000620 bytes file OK (1.6 GiB).
|
||||
* Above that resolution:
|
||||
* std::bad_alloc - 1993034797 bytes (1.856 GiB) cannot be allocated
|
||||
* for one row. System with 64 GB RAM.
|
||||
*/
|
||||
|
||||
if (m_evh)
|
||||
m_evh->OnStartScanSession(startIndex, imageAttributes);
|
||||
do
|
||||
{
|
||||
ofstream outFile;
|
||||
if (m_evh)
|
||||
m_evh->OnPageStartScan(filePath, pageIndex, imageAttributes);
|
||||
do
|
||||
{
|
||||
if (gs_cancelRequested)
|
||||
{
|
||||
session->cancel(session);
|
||||
if (m_evh)
|
||||
m_evh->OnSessionCancelled(filePath);
|
||||
gs_cancelRequested = false;
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
char * readBytes = new char[bytesPerRow];
|
||||
size_t bufferSize = bytesPerRow;
|
||||
err = session->scan_read(session, readBytes, &bufferSize); // bufferSize is in/out.
|
||||
if (LIS_IS_ERROR(err))
|
||||
{
|
||||
delete[] readBytes;
|
||||
if (m_evh)
|
||||
m_evh->OnSessionReadError(filePath);
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* bufferSize == 0 does not mean end_of_page or end of file.
|
||||
* But we get here when end_of_feed is false while there's nothing more to read.
|
||||
*/
|
||||
if (bufferSize)
|
||||
{
|
||||
if (!outFile.is_open())
|
||||
outFile.open(filePath, ios::binary | ios::trunc);
|
||||
outFile.write((const char*) readBytes, bufferSize);
|
||||
}
|
||||
delete[] readBytes;
|
||||
}
|
||||
catch (std::bad_alloc& e)
|
||||
{
|
||||
m_rootSourceItem->close(m_rootSourceItem);
|
||||
cout << "ABORT: " << e.what() << " - could not allocate " << bytesPerRow << " bytes." << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError("Insufficient system RAM.");
|
||||
return false;
|
||||
}
|
||||
} while (!session->end_of_page(session));
|
||||
/*
|
||||
* end_of_feed may return false though there is no more to read.
|
||||
* If outFile is not open, nothing was read; don't call OnPageEndScan.
|
||||
*/
|
||||
bool outFileWasOpen = outFile.is_open();
|
||||
// It may not have been opened.
|
||||
outFile.flush();
|
||||
outFile.close();
|
||||
if (m_evh && outFileWasOpen)
|
||||
m_evh->OnPageEndScan(filePath, pageIndex, imageAttributes);
|
||||
pageIndex += increment;
|
||||
if (m_sourceItem->type != LIS_ITEM_ADF) // v4l also
|
||||
break;
|
||||
filePath = makeFileName(pageIndex);
|
||||
} while( !session->end_of_feed(session));
|
||||
if (m_evh)
|
||||
m_evh->OnEndScanSession(pageIndex - increment, imageAttributes);
|
||||
|
||||
m_rootSourceItem->close(m_sourceItem); // Child.
|
||||
m_rootSourceItem->close(m_rootSourceItem); // Root.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine the page extent.
|
||||
bool InsaneWorker::GetBottomRight(std::pair<double, double>& br)
|
||||
{
|
||||
int res = paperinit();
|
||||
if (res != PAPER_OK)
|
||||
{
|
||||
const string msg = "Could not initialise the paper library.";
|
||||
cerr << msg << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
const paper * p = paperinfo(m_paperSize.c_str());
|
||||
if (!p)
|
||||
{
|
||||
string msg = "Failed to find the requested paper; attempt to use a default paper size.";
|
||||
cerr << msg << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError(msg);
|
||||
p = defaultpaper();
|
||||
if (!p)
|
||||
{
|
||||
msg = "Failed to find a default paper size; using the default scanner sizes.";
|
||||
cerr << msg << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int unit = paperunit(p);
|
||||
double conversionFactor = 1.0;
|
||||
if (unit == PAPER_UNIT_MM)
|
||||
conversionFactor = 1.0;
|
||||
else if (unit == PAPER_UNIT_IN)
|
||||
conversionFactor = 25.4;
|
||||
else
|
||||
{
|
||||
const string msg = "The measurement unit of the paper size is not handled; using the default scanner sizes.";
|
||||
cerr << msg << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
br.first = paperwidth(p) * conversionFactor;
|
||||
br.second = paperheight(p) * conversionFactor;
|
||||
|
||||
res = paperdone();
|
||||
if (res != PAPER_OK)
|
||||
{
|
||||
const string msg = "Could not cleanly end the paper library.";
|
||||
cerr << msg << endl;
|
||||
if (m_evh)
|
||||
m_evh->OnError(msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string InsaneWorker::ToLower(const std::string& input)
|
||||
{
|
||||
// We are dealing with single byte characters.
|
||||
string output;
|
||||
for ( char c : input )
|
||||
output.append ( 1, std::tolower ( c ) );
|
||||
return output;
|
||||
}
|
||||
|
||||
void InsaneWorker::Cancel()
|
||||
{
|
||||
gs_cancelRequested = true;
|
||||
}
|
||||
|
||||
std::pair<int, int> InsaneWorker::UpdateStartAndIncrement(const int startPageIndex, const int increment,
|
||||
const bool adf, const bool doubleSided, const int total)
|
||||
{
|
||||
int newStartPageIndex = startPageIndex;
|
||||
int newIncrement = increment;
|
||||
int totalEven = total;
|
||||
if (total % 2)
|
||||
totalEven++;
|
||||
// Scanning forward before max.
|
||||
if ((increment > 0) && adf && doubleSided && (startPageIndex < (totalEven - 2)))
|
||||
{
|
||||
newIncrement = 2;
|
||||
newStartPageIndex += newIncrement;
|
||||
}
|
||||
// Scanning forward at max.
|
||||
else if ((increment > 0) && adf && doubleSided && (startPageIndex == (totalEven - 2)))
|
||||
{
|
||||
newIncrement = -2;
|
||||
newStartPageIndex++;
|
||||
}
|
||||
// Scanning backward.
|
||||
else if ((increment < 0) && adf && doubleSided && (startPageIndex <= (totalEven - 1)))
|
||||
{
|
||||
newIncrement = -2;
|
||||
newStartPageIndex += newIncrement;
|
||||
}
|
||||
else
|
||||
{
|
||||
newIncrement = 1;
|
||||
newStartPageIndex++;
|
||||
}
|
||||
|
||||
std::pair<int, int> result = {newStartPageIndex, newIncrement};
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
InsaneWorkerEvent::InsaneWorkerEvent()
|
||||
{}
|
||||
|
||||
InsaneWorkerEvent::~InsaneWorkerEvent()
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnInsaneError ( const std::string& message )
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnError ( const std::string& message )
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnSessionCancelled(const std::string& filePath)
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnSessionReadError(const std::string& filePath)
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnPageStartScan(const std::string& filePath,uint pageIndex, const ImageAttributes& imageAttributes)
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnPageEndScan(const std::string& filePath, uint pageIndex, const ImageAttributes& imageAttributes)
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes)
|
||||
{}
|
||||
|
||||
void InsaneWorkerEvent::OnEndScanSession(uint pageIndex, const ImageAttributes& imageAttributes)
|
||||
{}
|
||||
128
Resources/InsaneWidget/InsaneWorker.h
Normal file
128
Resources/InsaneWidget/InsaneWorker.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* File: InsaneWorker.h
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2024
|
||||
*
|
||||
* Created on 14 02 2024, 13:00
|
||||
*/
|
||||
|
||||
#ifndef INSANEWORKER_H
|
||||
#define INSANEWORKER_H
|
||||
|
||||
#include <libinsane/capi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class InsaneWorkerEvent;
|
||||
|
||||
/**
|
||||
* Performs scanning to raw data as files.\n
|
||||
* Input values are as obtained from the scanner widget:
|
||||
*
|
||||
* - device identifier - ex: sane:brother5:net1;dev0
|
||||
* - source - ex: Automatic Document Feeder(centrally aligned)
|
||||
* - source index - important for an ADF source
|
||||
* - mode - Color, LineArt, anything else is Gray
|
||||
* - resolution
|
||||
* - paper size - only those handled by PoDoFo
|
||||
*
|
||||
* Output files are numbered with a padded suffix according to an increment that
|
||||
* can be negative.
|
||||
*/
|
||||
|
||||
/*
|
||||
* All C objects must be initialised.
|
||||
* Initialise to nullptr and not to NULL.
|
||||
* Otherwise, unhealthy things are observed,
|
||||
* like 'if (err)' always evaluated to true.
|
||||
*/
|
||||
|
||||
class InsaneWorker
|
||||
{
|
||||
public:
|
||||
|
||||
InsaneWorker ( InsaneWorkerEvent* evh = nullptr );
|
||||
~InsaneWorker();
|
||||
struct DeviceDescriptor
|
||||
{
|
||||
std::string id;
|
||||
std::string model;
|
||||
std::string type;
|
||||
std::string vendor;
|
||||
std::string GetLabel()
|
||||
{
|
||||
return model + "-" + type + " [" + vendor + "]";
|
||||
}
|
||||
};
|
||||
|
||||
bool Init(); // Must be explicitly called, once only.
|
||||
std::vector<DeviceDescriptor> GetDeviceDescriptors()
|
||||
{
|
||||
return m_devices;
|
||||
}
|
||||
int IsADF(const std::string& deviceId, int sourceChildIndex);;
|
||||
std::string GetOptionPossibleValues(const std::string& deviceId,
|
||||
const std::string& optionName, std::vector<std::string>& possible);
|
||||
bool ConfigureDevice(const std::string& deviceId,
|
||||
const std::string& source = "FlatBed", int sourceChildIndex = 0,
|
||||
const std::string& mode = "Color", int resolution = 300,
|
||||
const std::string& paperSize = "A4");
|
||||
bool Scan(const std::string& dir, const std::string& basename,
|
||||
int startIndex = 0, int padwidth = 2, int increment = 1); // increment can be negative
|
||||
void Cancel();
|
||||
InsaneWorkerEvent * GetEventHandler() const
|
||||
{
|
||||
return m_evh;
|
||||
}
|
||||
/**
|
||||
* Update the start page index and increment to pass to ::Scan(), accounting
|
||||
* for ADF and double sided scanning.
|
||||
*/
|
||||
std::pair<int, int> UpdateStartAndIncrement(const int startPageIndex, const int increment,
|
||||
const bool adf, const bool doubleSided, const int total);
|
||||
|
||||
private:
|
||||
lis_api * m_api = nullptr;
|
||||
lis_item * m_rootSourceItem = nullptr; // May have child items.
|
||||
lis_item * m_sourceItem = nullptr;
|
||||
InsaneWorkerEvent * m_evh = nullptr;
|
||||
|
||||
std::vector<DeviceDescriptor> m_devices;
|
||||
std::string m_deviceId;
|
||||
std::string m_source = "FlatBed";
|
||||
int m_sourceChildIndex = 0;
|
||||
std::string m_mode = "Color";
|
||||
int m_resolution = 300;
|
||||
std::string m_paperSize = "A4";
|
||||
|
||||
bool GetBottomRight(std::pair<double, double>& br);
|
||||
std::string ToLower(const std::string& input);
|
||||
};
|
||||
|
||||
class InsaneWorkerEvent
|
||||
{
|
||||
public:
|
||||
InsaneWorkerEvent();
|
||||
virtual ~InsaneWorkerEvent();
|
||||
|
||||
struct ImageAttributes
|
||||
{
|
||||
size_t size = 0; // bytes
|
||||
uint width = 0; // pixels
|
||||
uint height = 0; // pixels
|
||||
};
|
||||
|
||||
virtual void OnInsaneError ( const std::string& message );
|
||||
virtual void OnError ( const std::string& message );
|
||||
virtual void OnSessionCancelled(const std::string& filePath);
|
||||
virtual void OnSessionReadError(const std::string& filePath);
|
||||
virtual void OnPageStartScan(const std::string& filePath, uint pageIndex,
|
||||
const ImageAttributes& imageAttributes);
|
||||
virtual void OnPageEndScan(const std::string& filePath, uint pageIndex,
|
||||
const ImageAttributes& imageAttributes);
|
||||
virtual void OnStartScanSession(uint pageIndex, const ImageAttributes& imageAttributes);
|
||||
virtual void OnEndScanSession(uint pageIndex, const ImageAttributes& imageAttributes);
|
||||
};
|
||||
|
||||
#endif // INSANEWORKER_H
|
||||
61
Resources/InsaneWidget/PixelToImageWriter.cpp
Normal file
61
Resources/InsaneWidget/PixelToImageWriter.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// /*
|
||||
// * File: PixelToImageWriter.cpp
|
||||
// * Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
// * License : CeCILL-C
|
||||
// * Copyright Saleem Edah-Tally - © 2025
|
||||
// *
|
||||
// * Created on 27 06 2025, 20:28
|
||||
// */
|
||||
|
||||
#include "PixelToImageWriter.h"
|
||||
#include <Common.h>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool PixelToImageWriter::Convert(const std::string& pixelFilePath,
|
||||
int imageWidth, int imageHeight,
|
||||
int outputFormat, wxImage * image)
|
||||
{
|
||||
UpdateExtensionsMap();
|
||||
wxImage * outImage = image;
|
||||
unique_ptr<wxImage> tmpImage;
|
||||
if (!image)
|
||||
{
|
||||
tmpImage = make_unique<wxImage>();
|
||||
outImage = tmpImage.get();
|
||||
}
|
||||
string raw;
|
||||
ifstream ifs(pixelFilePath, ios::binary);
|
||||
if (!ifs.good())
|
||||
{
|
||||
cerr << _("Failed to read raw file.") << endl;
|
||||
return false;
|
||||
}
|
||||
raw.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
|
||||
|
||||
outImage->SetData((unsigned char*) raw.data(), imageWidth, imageHeight, true); // true +++
|
||||
|
||||
switch (outputFormat)
|
||||
{
|
||||
case PNG:
|
||||
outImage->SaveFile(pixelFilePath + "." + Extensions[PNG], wxBITMAP_TYPE_PNG);
|
||||
break;
|
||||
case JPEG:
|
||||
outImage->SaveFile(pixelFilePath + "." + Extensions[JPEG], wxBITMAP_TYPE_JPEG);
|
||||
break;
|
||||
case TIFF:
|
||||
outImage->SaveFile(pixelFilePath + "." + Extensions[TIFF], wxBITMAP_TYPE_TIFF);
|
||||
break;
|
||||
case PNM:
|
||||
outImage->SaveFile(pixelFilePath + "." + Extensions[PNM], wxBITMAP_TYPE_PNM);
|
||||
break;
|
||||
case PDF:
|
||||
break;
|
||||
default:
|
||||
cerr << _("Unhandled output image format.") << endl;
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
28
Resources/InsaneWidget/PixelToImageWriter.h
Normal file
28
Resources/InsaneWidget/PixelToImageWriter.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// /*
|
||||
// * File: PixelToImageWriter.h
|
||||
// * Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
// * License : CeCILL-C
|
||||
// * Copyright Saleem Edah-Tally - © 2025
|
||||
// *
|
||||
// * Created on 27 06 2025, 20:28
|
||||
// */
|
||||
|
||||
#ifndef PIXELTOIMAGEWRITER_H
|
||||
#define PIXELTOIMAGEWRITER_H
|
||||
|
||||
#include "Common.h"
|
||||
#include <string>
|
||||
#include <wx/wx.h>
|
||||
|
||||
/**
|
||||
* Create an image file from a raw scanned file.\n
|
||||
*/
|
||||
class PixelToImageWriter
|
||||
{
|
||||
public:
|
||||
static bool Convert(const std::string& pixelFilePath,
|
||||
int imageWidth, int imageHeight, int outputFormat = PNG,
|
||||
wxImage * image = nullptr);
|
||||
};
|
||||
|
||||
#endif // PIXELTOIMAGEWRITER_H
|
||||
83
Resources/InsaneWidget/PixelToPdfWriter.cpp
Normal file
83
Resources/InsaneWidget/PixelToPdfWriter.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* File: PixelToPdfWriter.cpp
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2025
|
||||
*
|
||||
* Created on 14 06 2025, 17:15
|
||||
*/
|
||||
|
||||
#include "PixelToPdfWriter.h"
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
using namespace PoDoFo;
|
||||
|
||||
PixelToPdfWriter::PixelToPdfWriter()
|
||||
{
|
||||
m_pageSizes["A0"] = PoDoFo::PdfPageSize::A0;
|
||||
m_pageSizes["A1"] = PoDoFo::PdfPageSize::A1;
|
||||
m_pageSizes["A2"] = PoDoFo::PdfPageSize::A2;
|
||||
m_pageSizes["A3"] = PoDoFo::PdfPageSize::A3;
|
||||
m_pageSizes["A4"] = PoDoFo::PdfPageSize::A4;
|
||||
m_pageSizes["A5"] = PoDoFo::PdfPageSize::A5;
|
||||
m_pageSizes["A6"] = PoDoFo::PdfPageSize::A6;
|
||||
m_pageSizes["Letter"] = PoDoFo::PdfPageSize::Letter;
|
||||
m_pageSizes["Legal"] = PoDoFo::PdfPageSize::Legal;
|
||||
m_pageSizes["Tabloid"] = PoDoFo::PdfPageSize::Tabloid;
|
||||
}
|
||||
|
||||
|
||||
bool PixelToPdfWriter::AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
|
||||
PoDoFo::PdfPageSize pageSize, PoDoFo::PdfColorSpace)
|
||||
{
|
||||
try
|
||||
{
|
||||
Rect pageRect = PdfPage::CreateStandardPageSize(pageSize);
|
||||
PdfPage& page = m_doc.GetPages().CreatePageAt(index, pageRect);
|
||||
|
||||
PdfImageInfo info;
|
||||
info.Width = width; // Must be known beforehand and must be exact.
|
||||
info.Height = height;
|
||||
info.BitsPerComponent = 8;
|
||||
info.ColorSpace = PdfColorSpace::DeviceRGB; // Is always RGB from libinsane.
|
||||
|
||||
ifstream ifs(pixelFile, ios::binary);
|
||||
string content;
|
||||
content.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
|
||||
bufferview bv(content);
|
||||
|
||||
const uint pageNumber = m_doc.GetPages().GetCount();
|
||||
std::unique_ptr<PdfImage> image = m_doc.CreateImage("Page_" + to_string(pageNumber)); // No space.
|
||||
image->SetDataRaw(bv, info); // OK for pixel file, including LineArt
|
||||
double scale = std::min(pageRect.Width / image->GetWidth(), pageRect.Height / image->GetHeight());
|
||||
|
||||
PdfPainter painter;
|
||||
painter.SetCanvas(page);
|
||||
painter.DrawImage(*(image), 0.0, 0.0, scale, scale);
|
||||
painter.FinishDrawing();
|
||||
}
|
||||
catch (PdfError& e)
|
||||
{
|
||||
cerr << e.ErrorMessage(e.GetCode()) << endl << e.what() << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PixelToPdfWriter::Save(const std::string& pdfFile)
|
||||
{
|
||||
m_doc.Save(pdfFile);
|
||||
}
|
||||
|
||||
uint PixelToPdfWriter::GetNumberOfPages() const
|
||||
{
|
||||
return m_doc.GetPages().GetCount();
|
||||
}
|
||||
|
||||
void PixelToPdfWriter::RemovePageAt(uint index)
|
||||
{
|
||||
m_doc.GetPages().RemovePageAt(index);
|
||||
}
|
||||
49
Resources/InsaneWidget/PixelToPdfWriter.h
Normal file
49
Resources/InsaneWidget/PixelToPdfWriter.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* File: PixelToPdfWriter.h
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2025
|
||||
*
|
||||
* Created on 14 06 2025, 17:15
|
||||
*/
|
||||
|
||||
#ifndef PIXELTOPDFWRITER_H
|
||||
#define PIXELTOPDFWRITER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <podofo/podofo.h>
|
||||
|
||||
/**
|
||||
* Create a PDF document, append or insert pages from raw scanned files.\n
|
||||
* Each image is a full page scan; it is scaled in the PDF document to the full
|
||||
* page dimensions.\n
|
||||
* Account for page size.
|
||||
*/
|
||||
class PixelToPdfWriter
|
||||
{
|
||||
public:
|
||||
typedef std::map<std::string, PoDoFo::PdfPageSize> PageSizeMap;
|
||||
|
||||
PixelToPdfWriter();
|
||||
bool AddPageAt(const std::string& pixelFile, uint width, uint height, uint index,
|
||||
PoDoFo::PdfPageSize pageSize = PoDoFo::PdfPageSize::A4,
|
||||
PoDoFo::PdfColorSpace = PoDoFo::PdfColorSpace::DeviceRGB /*Unused*/);
|
||||
void Save(const std::string& pdfFile);
|
||||
uint GetNumberOfPages() const;
|
||||
void RemovePageAt(uint index);
|
||||
PoDoFo::PdfPageSize GetPageSize(const std::string& literal)
|
||||
{
|
||||
return m_pageSizes[literal]; // 0 if not found, PdfPageSize::Unknown.
|
||||
}
|
||||
PageSizeMap GetPageSizes() const
|
||||
{
|
||||
return m_pageSizes;
|
||||
}
|
||||
private:
|
||||
PoDoFo::PdfMemDocument m_doc;
|
||||
|
||||
PageSizeMap m_pageSizes;
|
||||
};
|
||||
|
||||
#endif // PIXELTOPDFWRITER_H
|
||||
180
Resources/InsaneWidget/UI/InsaneWidget.cpp
Normal file
180
Resources/InsaneWidget/UI/InsaneWidget.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: InsaneWidgetcpp.cpp
|
||||
// Purpose:
|
||||
// Author: Saleem EDAH-TALLY
|
||||
// Modified by:
|
||||
// Created: dim. 15 juin 2025 19:37:17
|
||||
// RCS-ID:
|
||||
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
|
||||
// Licence: CeCILL-C
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// For compilers that support precompilation, includes "wx/wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/wx.h"
|
||||
#endif
|
||||
|
||||
////@begin includes
|
||||
////@end includes
|
||||
|
||||
#include "InsaneWidget.h"
|
||||
|
||||
////@begin XPM images
|
||||
////@end XPM images
|
||||
|
||||
|
||||
/*
|
||||
* InsaneWidget type definition
|
||||
*/
|
||||
|
||||
IMPLEMENT_DYNAMIC_CLASS( InsaneWidget, wxPanel )
|
||||
|
||||
|
||||
/*
|
||||
* InsaneWidget event table definition
|
||||
*/
|
||||
|
||||
BEGIN_EVENT_TABLE( InsaneWidget, wxPanel )
|
||||
|
||||
////@begin InsaneWidget event table entries
|
||||
////@end InsaneWidget event table entries
|
||||
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
/*
|
||||
* InsaneWidget constructors
|
||||
*/
|
||||
|
||||
InsaneWidget::InsaneWidget()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
InsaneWidget::InsaneWidget( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
|
||||
{
|
||||
Init();
|
||||
Create(parent, id, pos, size, style);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsaneWidget creator
|
||||
*/
|
||||
|
||||
bool InsaneWidget::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
|
||||
{
|
||||
////@begin InsaneWidget creation
|
||||
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
|
||||
wxPanel::Create( parent, id, pos, size, style );
|
||||
|
||||
CreateControls();
|
||||
if (GetSizer())
|
||||
{
|
||||
GetSizer()->SetSizeHints(this);
|
||||
}
|
||||
Centre();
|
||||
////@end InsaneWidget creation
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsaneWidget destructor
|
||||
*/
|
||||
|
||||
InsaneWidget::~InsaneWidget()
|
||||
{
|
||||
////@begin InsaneWidget destruction
|
||||
////@end InsaneWidget destruction
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Member initialisation
|
||||
*/
|
||||
|
||||
void InsaneWidget::Init()
|
||||
{
|
||||
////@begin InsaneWidget member initialisation
|
||||
lblNewDoc = NULL;
|
||||
txtNewDoc = NULL;
|
||||
btnScan = NULL;
|
||||
////@end InsaneWidget member initialisation
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Control creation for InsaneWidget
|
||||
*/
|
||||
|
||||
void InsaneWidget::CreateControls()
|
||||
{
|
||||
////@begin InsaneWidget content construction
|
||||
InsaneWidget* itemPanel1 = this;
|
||||
|
||||
wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
|
||||
itemPanel1->SetSizer(itemBoxSizer2);
|
||||
|
||||
wxBoxSizer* itemBoxSizer1 = new wxBoxSizer(wxHORIZONTAL);
|
||||
itemBoxSizer2->Add(itemBoxSizer1, 0, wxGROW|wxALL, 5);
|
||||
|
||||
lblNewDoc = new wxStaticText( itemPanel1, ID_NewDoc_LBL, _("New"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
if (InsaneWidget::ShowToolTips())
|
||||
lblNewDoc->SetToolTip(_("'Right' click to define a scan project."));
|
||||
itemBoxSizer1->Add(lblNewDoc, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
txtNewDoc = new wxTextCtrl( itemPanel1, ID_NewDoc_TXT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY );
|
||||
if (InsaneWidget::ShowToolTips())
|
||||
txtNewDoc->SetToolTip(_("Full path to destination file without the extension; it is determined by the output type."));
|
||||
itemBoxSizer1->Add(txtNewDoc, 1, wxGROW|wxALL, 5);
|
||||
|
||||
btnScan = new wxButton( itemPanel1, ID_Scan_BTN, _("Scan"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
if (InsaneWidget::ShowToolTips())
|
||||
btnScan->SetToolTip(_("'Left click' to start the scan project.\n'Right click' to show the scanner widget."));
|
||||
itemBoxSizer1->Add(btnScan, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
////@end InsaneWidget content construction
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Should we show tooltips?
|
||||
*/
|
||||
|
||||
bool InsaneWidget::ShowToolTips()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get bitmap resources
|
||||
*/
|
||||
|
||||
wxBitmap InsaneWidget::GetBitmapResource( const wxString& name )
|
||||
{
|
||||
// Bitmap retrieval
|
||||
////@begin InsaneWidget bitmap retrieval
|
||||
wxUnusedVar(name);
|
||||
return wxNullBitmap;
|
||||
////@end InsaneWidget bitmap retrieval
|
||||
}
|
||||
|
||||
/*
|
||||
* Get icon resources
|
||||
*/
|
||||
|
||||
wxIcon InsaneWidget::GetIconResource( const wxString& name )
|
||||
{
|
||||
// Icon retrieval
|
||||
////@begin InsaneWidget icon retrieval
|
||||
wxUnusedVar(name);
|
||||
return wxNullIcon;
|
||||
////@end InsaneWidget icon retrieval
|
||||
}
|
||||
97
Resources/InsaneWidget/UI/InsaneWidget.h
Normal file
97
Resources/InsaneWidget/UI/InsaneWidget.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: InsaneWidget.h
|
||||
// Purpose:
|
||||
// Author: Saleem EDAH-TALLY
|
||||
// Modified by:
|
||||
// Created: dim. 15 juin 2025 19:37:17
|
||||
// RCS-ID:
|
||||
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
|
||||
// Licence: CeCILL-C
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _INSANEWIDGET_H_
|
||||
#define _INSANEWIDGET_H_
|
||||
|
||||
|
||||
/*!
|
||||
* Includes
|
||||
*/
|
||||
|
||||
////@begin includes
|
||||
////@end includes
|
||||
|
||||
/*!
|
||||
* Forward declarations
|
||||
*/
|
||||
|
||||
////@begin forward declarations
|
||||
////@end forward declarations
|
||||
|
||||
/*!
|
||||
* Control identifiers
|
||||
*/
|
||||
|
||||
////@begin control identifiers
|
||||
#define ID_INSANEWIDGET 10000
|
||||
#define ID_NewDoc_LBL 10014
|
||||
#define ID_NewDoc_TXT 10005
|
||||
#define ID_Scan_BTN 10006
|
||||
#define SYMBOL_INSANEWIDGET_STYLE wxTAB_TRAVERSAL
|
||||
#define SYMBOL_INSANEWIDGET_TITLE _("InsaneWidget")
|
||||
#define SYMBOL_INSANEWIDGET_IDNAME ID_INSANEWIDGET
|
||||
#define SYMBOL_INSANEWIDGET_SIZE wxSize(400, 300)
|
||||
#define SYMBOL_INSANEWIDGET_POSITION wxDefaultPosition
|
||||
////@end control identifiers
|
||||
|
||||
|
||||
/*!
|
||||
* InsaneWidget class declaration
|
||||
*/
|
||||
|
||||
class InsaneWidget: public wxPanel
|
||||
{
|
||||
DECLARE_DYNAMIC_CLASS( InsaneWidget )
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
public:
|
||||
/// Constructors
|
||||
InsaneWidget();
|
||||
InsaneWidget( wxWindow* parent, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
|
||||
|
||||
/// Creation
|
||||
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
|
||||
|
||||
/// Destructor
|
||||
~InsaneWidget();
|
||||
|
||||
/// Initialises member variables
|
||||
void Init();
|
||||
|
||||
/// Creates the controls and sizers
|
||||
void CreateControls();
|
||||
|
||||
////@begin InsaneWidget event handler declarations
|
||||
|
||||
////@end InsaneWidget event handler declarations
|
||||
|
||||
////@begin InsaneWidget member function declarations
|
||||
|
||||
/// Retrieves bitmap resources
|
||||
wxBitmap GetBitmapResource( const wxString& name );
|
||||
|
||||
/// Retrieves icon resources
|
||||
wxIcon GetIconResource( const wxString& name );
|
||||
////@end InsaneWidget member function declarations
|
||||
|
||||
/// Should we show tooltips?
|
||||
static bool ShowToolTips();
|
||||
|
||||
////@begin InsaneWidget member variables
|
||||
wxStaticText* lblNewDoc;
|
||||
wxTextCtrl* txtNewDoc;
|
||||
wxButton* btnScan;
|
||||
////@end InsaneWidget member variables
|
||||
};
|
||||
|
||||
#endif
|
||||
// _INSANEWIDGET_H_
|
||||
1659
Resources/InsaneWidget/UI/InsaneWidget.pjd
Normal file
1659
Resources/InsaneWidget/UI/InsaneWidget.pjd
Normal file
File diff suppressed because it is too large
Load Diff
1
Resources/InsaneWidget/UI/InsaneWidget.rc
Normal file
1
Resources/InsaneWidget/UI/InsaneWidget.rc
Normal file
@@ -0,0 +1 @@
|
||||
#include "wx/msw/wx.rc"
|
||||
237
Resources/InsaneWidget/UI/ScannerWidget.cpp
Normal file
237
Resources/InsaneWidget/UI/ScannerWidget.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: ScannerWidget.cpp
|
||||
// Purpose:
|
||||
// Author: Saleem EDAH-TALLY
|
||||
// Modified by:
|
||||
// Created: dim. 15 juin 2025 19:39:52
|
||||
// RCS-ID:
|
||||
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
|
||||
// Licence: CeCILL-C
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// For compilers that support precompilation, includes "wx/wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/wx.h"
|
||||
#endif
|
||||
|
||||
////@begin includes
|
||||
////@end includes
|
||||
|
||||
#include "ScannerWidget.h"
|
||||
|
||||
////@begin XPM images
|
||||
////@end XPM images
|
||||
|
||||
|
||||
/*
|
||||
* ScannerWidget type definition
|
||||
*/
|
||||
|
||||
IMPLEMENT_DYNAMIC_CLASS( ScannerWidget, wxPanel )
|
||||
|
||||
|
||||
/*
|
||||
* ScannerWidget event table definition
|
||||
*/
|
||||
|
||||
BEGIN_EVENT_TABLE( ScannerWidget, wxPanel )
|
||||
|
||||
////@begin ScannerWidget event table entries
|
||||
////@end ScannerWidget event table entries
|
||||
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
/*
|
||||
* ScannerWidget constructors
|
||||
*/
|
||||
|
||||
ScannerWidget::ScannerWidget()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
ScannerWidget::ScannerWidget( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
|
||||
{
|
||||
Init();
|
||||
Create(parent, id, pos, size, style);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ScannerWidget creator
|
||||
*/
|
||||
|
||||
bool ScannerWidget::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
|
||||
{
|
||||
////@begin ScannerWidget creation
|
||||
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
|
||||
wxPanel::Create( parent, id, pos, size, style );
|
||||
|
||||
CreateControls();
|
||||
if (GetSizer())
|
||||
{
|
||||
GetSizer()->SetSizeHints(this);
|
||||
}
|
||||
Centre();
|
||||
////@end ScannerWidget creation
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ScannerWidget destructor
|
||||
*/
|
||||
|
||||
ScannerWidget::~ScannerWidget()
|
||||
{
|
||||
////@begin ScannerWidget destruction
|
||||
////@end ScannerWidget destruction
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Member initialisation
|
||||
*/
|
||||
|
||||
void ScannerWidget::Init()
|
||||
{
|
||||
////@begin ScannerWidget member initialisation
|
||||
lblImageType = NULL;
|
||||
cmbOutputType = NULL;
|
||||
btnRefreshDevices = NULL;
|
||||
lblDevices = NULL;
|
||||
cmbDevices = NULL;
|
||||
lblSource = NULL;
|
||||
cmbSource = NULL;
|
||||
lblMode = NULL;
|
||||
cmbMode = NULL;
|
||||
lblResolution = NULL;
|
||||
cmbResolution = NULL;
|
||||
lblPaperSize = NULL;
|
||||
cmbPaperSize = NULL;
|
||||
////@end ScannerWidget member initialisation
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Control creation for ScannerWidget
|
||||
*/
|
||||
|
||||
void ScannerWidget::CreateControls()
|
||||
{
|
||||
////@begin ScannerWidget content construction
|
||||
ScannerWidget* itemPanel1 = this;
|
||||
|
||||
wxBoxSizer* itemBoxSizer1 = new wxBoxSizer(wxVERTICAL);
|
||||
itemPanel1->SetSizer(itemBoxSizer1);
|
||||
|
||||
wxFlexGridSizer* itemFlexGridSizer2 = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
itemBoxSizer1->Add(itemFlexGridSizer2, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
|
||||
|
||||
lblImageType = new wxStaticText( itemPanel1, wxID_STATIC_IMAGE_TYPE, _("Format:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemFlexGridSizer2->Add(lblImageType, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbOutputTypeStrings;
|
||||
cmbOutputType = new wxComboBox( itemPanel1, ID_COMBOBOX_IMAGE_TYPE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbOutputTypeStrings, wxCB_READONLY );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
cmbOutputType->SetToolTip(_("Output document format"));
|
||||
itemFlexGridSizer2->Add(cmbOutputType, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxBoxSizer* itemBoxSizer5 = new wxBoxSizer(wxHORIZONTAL);
|
||||
itemFlexGridSizer2->Add(itemBoxSizer5, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
btnRefreshDevices = new wxButton( itemPanel1, ID_BUTTON_REFRESH_DEVICES, wxGetTranslation(wxString() + (wxChar) 0x21BB), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
btnRefreshDevices->SetToolTip(_("Refresh available devices"));
|
||||
itemBoxSizer5->Add(btnRefreshDevices, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
lblDevices = new wxStaticText( itemPanel1, wxID_STATIC_DEVICES, _("Devices:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemBoxSizer5->Add(lblDevices, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbDevicesStrings;
|
||||
cmbDevices = new wxComboBox( itemPanel1, ID_COMBOBOX_Devices, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbDevicesStrings, wxCB_READONLY );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
cmbDevices->SetToolTip(_("Available devices"));
|
||||
itemFlexGridSizer2->Add(cmbDevices, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
lblSource = new wxStaticText( itemPanel1, wxID_STATIC_SOURCE, _("Source:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemFlexGridSizer2->Add(lblSource, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbSourceStrings;
|
||||
cmbSource = new wxComboBox( itemPanel1, ID_COMBOBOX_SOURCE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbSourceStrings, wxCB_READONLY );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
cmbSource->SetToolTip(_("Scan source"));
|
||||
itemFlexGridSizer2->Add(cmbSource, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
lblMode = new wxStaticText( itemPanel1, wxID_STATIC_MODE, _("Mode:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemFlexGridSizer2->Add(lblMode, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbModeStrings;
|
||||
cmbMode = new wxComboBox( itemPanel1, ID_COMBOBOX_MODE, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbModeStrings, wxCB_READONLY );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
cmbMode->SetToolTip(_("Scan mode"));
|
||||
itemFlexGridSizer2->Add(cmbMode, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
lblResolution = new wxStaticText( itemPanel1, wxID_STATIC_RESOLUTION, _("Resolution:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemFlexGridSizer2->Add(lblResolution, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbResolutionStrings;
|
||||
cmbResolution = new wxComboBox( itemPanel1, ID_COMBOBOX_RESOLUTION, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbResolutionStrings, wxCB_READONLY );
|
||||
if (ScannerWidget::ShowToolTips())
|
||||
cmbResolution->SetToolTip(_("Scan resolution"));
|
||||
itemFlexGridSizer2->Add(cmbResolution, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
lblPaperSize = new wxStaticText( itemPanel1, wxID_STATIC, _("Paper size:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
itemFlexGridSizer2->Add(lblPaperSize, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
wxArrayString cmbPaperSizeStrings;
|
||||
cmbPaperSize = new wxComboBox( itemPanel1, ID_COMBOBOX, wxEmptyString, wxDefaultPosition, wxDefaultSize, cmbPaperSizeStrings, wxCB_READONLY );
|
||||
itemFlexGridSizer2->Add(cmbPaperSize, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5);
|
||||
|
||||
itemFlexGridSizer2->AddGrowableCol(1);
|
||||
|
||||
////@end ScannerWidget content construction
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Should we show tooltips?
|
||||
*/
|
||||
|
||||
bool ScannerWidget::ShowToolTips()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get bitmap resources
|
||||
*/
|
||||
|
||||
wxBitmap ScannerWidget::GetBitmapResource( const wxString& name )
|
||||
{
|
||||
// Bitmap retrieval
|
||||
////@begin ScannerWidget bitmap retrieval
|
||||
wxUnusedVar(name);
|
||||
return wxNullBitmap;
|
||||
////@end ScannerWidget bitmap retrieval
|
||||
}
|
||||
|
||||
/*
|
||||
* Get icon resources
|
||||
*/
|
||||
|
||||
wxIcon ScannerWidget::GetIconResource( const wxString& name )
|
||||
{
|
||||
// Icon retrieval
|
||||
////@begin ScannerWidget icon retrieval
|
||||
wxUnusedVar(name);
|
||||
return wxNullIcon;
|
||||
////@end ScannerWidget icon retrieval
|
||||
}
|
||||
116
Resources/InsaneWidget/UI/ScannerWidget.h
Normal file
116
Resources/InsaneWidget/UI/ScannerWidget.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: ScannerWidget.h
|
||||
// Purpose:
|
||||
// Author: Saleem EDAH-TALLY
|
||||
// Modified by:
|
||||
// Created: dim. 15 juin 2025 19:39:52
|
||||
// RCS-ID:
|
||||
// Copyright: Copyright Saleem EDAH-TALLY. All rights reserved.
|
||||
// Licence: CeCILL-C
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _SCANNERWIDGET_H_
|
||||
#define _SCANNERWIDGET_H_
|
||||
|
||||
|
||||
/*!
|
||||
* Includes
|
||||
*/
|
||||
|
||||
////@begin includes
|
||||
////@end includes
|
||||
|
||||
/*!
|
||||
* Forward declarations
|
||||
*/
|
||||
|
||||
////@begin forward declarations
|
||||
////@end forward declarations
|
||||
|
||||
/*!
|
||||
* Control identifiers
|
||||
*/
|
||||
|
||||
////@begin control identifiers
|
||||
#define ID_SCANNERWIDGET 10000
|
||||
#define wxID_STATIC_IMAGE_TYPE 10031
|
||||
#define ID_COMBOBOX_IMAGE_TYPE 10007
|
||||
#define ID_BUTTON_REFRESH_DEVICES 10006
|
||||
#define wxID_STATIC_DEVICES 10029
|
||||
#define ID_COMBOBOX_Devices 10001
|
||||
#define wxID_STATIC_SOURCE 10028
|
||||
#define ID_COMBOBOX_SOURCE 10002
|
||||
#define wxID_STATIC_MODE 10027
|
||||
#define ID_COMBOBOX_MODE 10003
|
||||
#define wxID_STATIC_RESOLUTION 10030
|
||||
#define ID_COMBOBOX_RESOLUTION 10004
|
||||
#define ID_COMBOBOX 10005
|
||||
#define SYMBOL_SCANNERWIDGET_STYLE wxTAB_TRAVERSAL
|
||||
#define SYMBOL_SCANNERWIDGET_TITLE _("ScannerWidget")
|
||||
#define SYMBOL_SCANNERWIDGET_IDNAME ID_SCANNERWIDGET
|
||||
#define SYMBOL_SCANNERWIDGET_SIZE wxSize(400, 300)
|
||||
#define SYMBOL_SCANNERWIDGET_POSITION wxDefaultPosition
|
||||
////@end control identifiers
|
||||
|
||||
|
||||
/*!
|
||||
* ScannerWidget class declaration
|
||||
*/
|
||||
|
||||
class ScannerWidget: public wxPanel
|
||||
{
|
||||
DECLARE_DYNAMIC_CLASS( ScannerWidget )
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
public:
|
||||
/// Constructors
|
||||
ScannerWidget();
|
||||
ScannerWidget( wxWindow* parent, wxWindowID id = SYMBOL_SCANNERWIDGET_IDNAME, const wxPoint& pos = SYMBOL_SCANNERWIDGET_POSITION, const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE, long style = SYMBOL_SCANNERWIDGET_STYLE );
|
||||
|
||||
/// Creation
|
||||
bool Create( wxWindow* parent, wxWindowID id = SYMBOL_SCANNERWIDGET_IDNAME, const wxPoint& pos = SYMBOL_SCANNERWIDGET_POSITION, const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE, long style = SYMBOL_SCANNERWIDGET_STYLE );
|
||||
|
||||
/// Destructor
|
||||
~ScannerWidget();
|
||||
|
||||
/// Initialises member variables
|
||||
void Init();
|
||||
|
||||
/// Creates the controls and sizers
|
||||
void CreateControls();
|
||||
|
||||
////@begin ScannerWidget event handler declarations
|
||||
|
||||
////@end ScannerWidget event handler declarations
|
||||
|
||||
////@begin ScannerWidget member function declarations
|
||||
|
||||
/// Retrieves bitmap resources
|
||||
wxBitmap GetBitmapResource( const wxString& name );
|
||||
|
||||
/// Retrieves icon resources
|
||||
wxIcon GetIconResource( const wxString& name );
|
||||
////@end ScannerWidget member function declarations
|
||||
|
||||
/// Should we show tooltips?
|
||||
static bool ShowToolTips();
|
||||
|
||||
////@begin ScannerWidget member variables
|
||||
wxStaticText* lblImageType;
|
||||
wxComboBox* cmbOutputType;
|
||||
wxButton* btnRefreshDevices;
|
||||
wxStaticText* lblDevices;
|
||||
wxComboBox* cmbDevices;
|
||||
wxStaticText* lblSource;
|
||||
wxComboBox* cmbSource;
|
||||
wxStaticText* lblMode;
|
||||
wxComboBox* cmbMode;
|
||||
wxStaticText* lblResolution;
|
||||
wxComboBox* cmbResolution;
|
||||
wxStaticText* lblPaperSize;
|
||||
wxComboBox* cmbPaperSize;
|
||||
////@end ScannerWidget member variables
|
||||
};
|
||||
|
||||
#endif
|
||||
// _SCANNERWIDGET_H_
|
||||
412
Resources/InsaneWidget/XInsaneWidget.cpp
Normal file
412
Resources/InsaneWidget/XInsaneWidget.cpp
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* File: XInsaneWidget.cpp
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2025
|
||||
*
|
||||
* Created on 15 06 2025, 21:24
|
||||
*/
|
||||
|
||||
#include "XInsaneWidget.h"
|
||||
#include <PopupTransientWindow.h>
|
||||
#include <MiscTools.h>
|
||||
#include "PixelToImageWriter.h"
|
||||
#include "PixelToPdfWriter.h"
|
||||
#include <Common.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// When device discovery in a detached thread completes.
|
||||
// ----------------------------------------------------------------------------
|
||||
class BackgroundScannerDiscoveryEVH : public BackgroundScannerDiscoveryEvent
|
||||
{
|
||||
public:
|
||||
BackgroundScannerDiscoveryEVH(XInsaneWidget * owner)
|
||||
{
|
||||
m_owner = owner;
|
||||
}
|
||||
virtual ~BackgroundScannerDiscoveryEVH()
|
||||
{}
|
||||
void OnDone() override
|
||||
{
|
||||
if (!m_owner)
|
||||
return;
|
||||
m_owner->CallAfter(&XInsaneWidget::EnableScanButton, true);
|
||||
}
|
||||
private:
|
||||
wxWeakRef<XInsaneWidget> m_owner = nullptr;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
class ScanProjectHandler : public InsaneWorkerEvent
|
||||
{
|
||||
public:
|
||||
ScanProjectHandler(XInsaneWidget * owner, TimeredStatusBar * sb = nullptr)
|
||||
: InsaneWorkerEvent(), m_owner(owner), m_sb (sb)
|
||||
{
|
||||
}
|
||||
/*
|
||||
* mode, outputType, adf, doubleSided, total, PaperSize:
|
||||
* These are not updated here if there are pending jobs.
|
||||
* However,
|
||||
* deviceId, source, sourceIndex, mode, resolution, paperSize
|
||||
* can be changed if there are pending jobs in ConfigureDevice() since it is
|
||||
* called before these setters. They won't change on their own but by user
|
||||
* interaction.
|
||||
*/
|
||||
void SetMode(const string& mode)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_mode = mode;
|
||||
}
|
||||
void SetOutputType(int outputType)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_outputType = outputType;
|
||||
}
|
||||
void SetADF(bool adf)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_adf = adf;
|
||||
if (m_adf && m_doubleSided)
|
||||
m_startPageIndex = -2;
|
||||
else
|
||||
m_startPageIndex = -1;
|
||||
}
|
||||
void SetDoubleSided(bool doubleSided)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_doubleSided = doubleSided;
|
||||
if (m_adf && m_doubleSided)
|
||||
m_startPageIndex = -2;
|
||||
else
|
||||
m_startPageIndex = -1;
|
||||
}
|
||||
void SetTotalNumberOfSides(uint total)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_total = total;
|
||||
m_totalEven = total;
|
||||
if (total % 2)
|
||||
m_totalEven = total + 1;
|
||||
}
|
||||
void SetPaperSize(const wxString& paperSize)
|
||||
{
|
||||
if (m_pixelFiles.size())
|
||||
return;
|
||||
m_paperSize = paperSize;
|
||||
}
|
||||
std::pair<int, int> GetStartAndIncrement(InsaneWorker * insaneWorker)
|
||||
{
|
||||
wxASSERT_MSG(insaneWorker != nullptr, "insaneWorker is NULL.");
|
||||
std::pair<int, int> startAndIncrement = insaneWorker->UpdateStartAndIncrement(m_startPageIndex, m_increment,
|
||||
m_adf, m_doubleSided, m_total);
|
||||
m_startPageIndex = startAndIncrement.first;
|
||||
m_increment = startAndIncrement.second;
|
||||
return startAndIncrement;
|
||||
}
|
||||
void OnInsaneError ( const std::string& message ) override
|
||||
{
|
||||
cerr << message << endl;
|
||||
Reset();
|
||||
MiscTools::MessageBox(_("A scan library error occurred."), true);
|
||||
}
|
||||
void OnError ( const std::string& message ) override
|
||||
{
|
||||
cerr << message << endl;
|
||||
Reset();
|
||||
MiscTools::MessageBox(_("A general error occurred."), true);
|
||||
}
|
||||
void OnSessionReadError(const std::string & filePath) override
|
||||
{
|
||||
const wxString msg = _("A session read error occurred.");
|
||||
cerr << msg << endl;
|
||||
Reset();
|
||||
MiscTools::MessageBox(msg, true);
|
||||
}
|
||||
void OnSessionCancelled(const std::string & filePath) override
|
||||
{
|
||||
const wxString msg = _("Session cancelled.");
|
||||
Reset();
|
||||
MiscTools::MessageBox(msg, true);
|
||||
}
|
||||
// Every time a page is fully scanned.
|
||||
void OnPageEndScan(const std::string & filePath, uint pageIndex,
|
||||
const ImageAttributes& imageAttributes) override
|
||||
{
|
||||
m_startPageIndex = pageIndex;
|
||||
m_pixelFiles[pageIndex] = {filePath, imageAttributes};
|
||||
|
||||
auto informProgress = [&] ()
|
||||
{
|
||||
if (!m_sb || (m_total == 1))
|
||||
return;
|
||||
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
|
||||
wxString progress = to_string(m_pixelFiles.size()) + "/" + to_string(max);
|
||||
wxString info = _("Scanning: ");
|
||||
if (m_increment == 2)
|
||||
info = _("Front face: ");
|
||||
else if (m_increment == -2)
|
||||
info = _("Back face: ");
|
||||
wxString msg = info + progress;
|
||||
if (m_increment == 2 && ((max / 2) == (m_pixelFiles.size())))
|
||||
{
|
||||
wxString upperBoundMessage = _(". Turn the whole stack of pages.");
|
||||
msg += upperBoundMessage;
|
||||
}
|
||||
|
||||
m_sb->SetStatusText(msg);
|
||||
};
|
||||
|
||||
if (m_outputType != PDF)
|
||||
{
|
||||
// Convert pixel file to PNG using netpbm.
|
||||
if (!PixelToImageWriter::Convert(filePath, imageAttributes.width, imageAttributes.height, m_outputType))
|
||||
{
|
||||
const wxString msg = _("Failed to create output image.");
|
||||
cerr << msg << endl;
|
||||
if (m_sb)
|
||||
m_sb->SetTransientText(msg);
|
||||
}
|
||||
wxRemoveFile(filePath);
|
||||
informProgress();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append or insert PDF page from pixel file using PoDoFo.
|
||||
if (!m_pixelToPdfWriter.get())
|
||||
m_pixelToPdfWriter = std::unique_ptr<PixelToPdfWriter> (new PixelToPdfWriter());
|
||||
uint index = (m_increment > 0) // 1 or 2
|
||||
? m_pixelToPdfWriter->GetNumberOfPages() // Append.
|
||||
: m_totalEven - m_pixelToPdfWriter->GetNumberOfPages(); // Insert.
|
||||
PoDoFo::PdfPageSize pageSize = m_pixelToPdfWriter->GetPageSize(m_paperSize.ToStdString());
|
||||
if (pageSize == PoDoFo::PdfPageSize::Unknown)
|
||||
{
|
||||
const wxString msg = _("Wrong paper size: ") + m_paperSize + _("; using A4.");
|
||||
cerr << msg << endl;
|
||||
pageSize = PoDoFo::PdfPageSize::A4;
|
||||
}
|
||||
if (!m_pixelToPdfWriter->AddPageAt(filePath, imageAttributes.width, imageAttributes.height, index, pageSize))
|
||||
{
|
||||
const wxString msg = _("Failed to add page to PDF document.");
|
||||
cerr << msg << endl;
|
||||
if (m_sb)
|
||||
m_sb->SetTransientText(msg);
|
||||
}
|
||||
wxRemoveFile(filePath);
|
||||
informProgress();
|
||||
}
|
||||
}
|
||||
void OnEndScanSession(uint pageIndex, const ImageAttributes & imageAttributes) override
|
||||
{
|
||||
int max = (m_adf && m_doubleSided) ? m_totalEven : m_total;
|
||||
// A special case is made for (total == 1): double-sided does not have meaning.
|
||||
if (m_total == 1)
|
||||
max = m_total;
|
||||
if (m_pixelFiles.size() >= max)
|
||||
{
|
||||
// All pages have been scanned, accounting for ADF and double-sided.
|
||||
if (m_outputType != PDF)
|
||||
{
|
||||
// Remove the last image if an odd number of pages was requested.
|
||||
if (m_increment != 1 && (m_total != m_totalEven))
|
||||
{
|
||||
const string filePath = std::get<0> (m_pixelFiles.rbegin()->second) + "." + Extensions[m_outputType];
|
||||
wxRemoveFile(filePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the last page if an odd number of pages was requested.
|
||||
if (m_increment != 1 && (m_total != m_totalEven))
|
||||
{
|
||||
uint lastIndex = m_pixelFiles.end()->first;
|
||||
m_pixelToPdfWriter->RemovePageAt(lastIndex - 1);
|
||||
}
|
||||
const string filePath = m_owner->txtNewDoc->GetValue().ToStdString() + "." + Extensions[m_outputType];
|
||||
m_pixelToPdfWriter->Save(filePath);
|
||||
m_pixelToPdfWriter.reset((new PixelToPdfWriter())); // For next scan project.
|
||||
}
|
||||
Reset();
|
||||
m_owner->txtNewDoc->Clear();
|
||||
if (m_sb)
|
||||
{
|
||||
const wxString msg = _("Finished.");
|
||||
m_sb->SetTransientText(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Reset()
|
||||
{
|
||||
// Don't reset calculated variables that depend on widgets.
|
||||
m_startPageIndex = 0;
|
||||
m_increment = 1;
|
||||
m_totalEven = 0;
|
||||
m_pixelFiles.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
wxWeakRef<XInsaneWidget> m_owner = nullptr;
|
||||
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
|
||||
std::unique_ptr<PixelToPdfWriter> m_pixelToPdfWriter;
|
||||
PixelFilesMap m_pixelFiles;
|
||||
string m_mode = "Color";
|
||||
uint m_outputType = PDF;
|
||||
wxString m_paperSize = _T("A4");
|
||||
bool m_adf = false;
|
||||
bool m_doubleSided = false;
|
||||
int m_total = 0;
|
||||
int m_totalEven = 0;
|
||||
int m_startPageIndex = 0;
|
||||
int m_increment = 1;
|
||||
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
XInsaneWidget::XInsaneWidget(wxWindow* parent, TimeredStatusBar * sb, wxConfig * config, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: InsaneWidget(parent, id, pos, size, style)
|
||||
{
|
||||
UpdateExtensionsMap();
|
||||
m_config = config;
|
||||
m_sb = sb;
|
||||
lblNewDoc->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnLblNewDocRightClick, this );
|
||||
txtNewDoc->Bind ( wxEVT_KEY_UP, &XInsaneWidget::OnTxtNewDocKeyPressed, this );
|
||||
|
||||
m_ptwScannerWidget = std::unique_ptr<wxPopupTransientWindow> (new wxPopupTransientWindow ( wxApp::GetGUIInstance()->GetTopWindow() ));
|
||||
m_ptwScannerWidget->Show ( false );
|
||||
m_scanProject = std::make_unique<ScanProjectHandler>(this, m_sb);
|
||||
m_insaneWorker = std::make_unique<InsaneWorker>(m_scanProject.get());
|
||||
m_scannerWidget = std::make_unique<XScannerWidget> ( m_ptwScannerWidget.get(), m_sb, m_insaneWorker.get() );
|
||||
m_scannerWidget->SetConfig ( m_config );
|
||||
|
||||
btnScan->Enable(false);
|
||||
btnScan->Bind ( wxEVT_RIGHT_UP, &XInsaneWidget::OnBtnScanRightClick, this );
|
||||
btnScan->Bind ( wxEVT_LEFT_UP, &XInsaneWidget::OnBtnScanClick, this );
|
||||
m_backgroundScannerDiscoveryEvh = std::make_unique<BackgroundScannerDiscoveryEVH>(this);
|
||||
BackgroundScannerDiscovery * backgroundDiscovery = new BackgroundScannerDiscovery ( m_scannerWidget.get(),
|
||||
m_backgroundScannerDiscoveryEvh.get());
|
||||
backgroundDiscovery->Run();
|
||||
}
|
||||
|
||||
XInsaneWidget::~XInsaneWidget() = default; // Important for mixing unique_ptr and PIMPL.
|
||||
|
||||
// Show a popup to specifiy the number of pages to scan and back-sided scanning.
|
||||
void XInsaneWidget::OnLblNewDocRightClick ( wxMouseEvent& evt )
|
||||
{
|
||||
/*
|
||||
* The previous ConfigEditorPopup is deleted here, which commits the
|
||||
* parameters to the config file. At any time, the current parameter values
|
||||
* can be lost if a crash occurs.
|
||||
*/
|
||||
m_pageStack.reset(new ConfigEditorPopup(wxApp::GetGUIInstance()->GetTopWindow(), m_config));
|
||||
PopupTransientWindow * ptw = m_pageStack->CreatePopup();
|
||||
if ( !ptw )
|
||||
{
|
||||
evt.Skip();
|
||||
return;
|
||||
}
|
||||
wxCheckBox * cb = m_pageStack->AddCheckBox (_("Double sided:"),_T("/Scanner/DoubleSided") );
|
||||
cb->SetToolTip (_("Scan all front faces first, then all back faces in reverse order.") );
|
||||
wxSpinCtrl * spn = m_pageStack->AddSpinCtrl (_("Total:"),_T("/Scanner/Total") );
|
||||
spn->SetRange ( 1, 50 );
|
||||
spn->SetToolTip (_("Total number of sides to scan (not total number of sheets).") );
|
||||
m_pageStack->ShowPopup();
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void XInsaneWidget::OnTxtNewDocKeyPressed ( wxKeyEvent& evt )
|
||||
{
|
||||
if ( evt.GetKeyCode() == WXK_BACK )
|
||||
{
|
||||
txtNewDoc->Clear();
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
// Show the scanner widget.
|
||||
void XInsaneWidget::OnBtnScanRightClick ( wxMouseEvent& evt )
|
||||
{
|
||||
if ( m_scannerWidget->cmbDevices->GetCount() )
|
||||
{
|
||||
const wxSize current = m_scannerWidget->GetSize();
|
||||
m_scannerWidget->SetSize ( wxSize ( 500, current.GetHeight() ) );
|
||||
}
|
||||
MiscTools::ShowTransientPopup ( m_ptwScannerWidget.get(), m_scannerWidget.get() );
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
// Start scanning.
|
||||
void XInsaneWidget::OnBtnScanClick ( wxMouseEvent& evt )
|
||||
{
|
||||
const wxString dest = txtNewDoc->GetValue();
|
||||
if (dest.IsEmpty())
|
||||
{
|
||||
MiscTools::MessageBox(_("Destination file missing."), true);
|
||||
evt.Skip();
|
||||
return;
|
||||
}
|
||||
const uint outputType = m_scannerWidget->GetScannerOutputType();
|
||||
bool adf = false;
|
||||
bool doubleSided = false;
|
||||
uint total = 1;
|
||||
wxString paperSize = _T("A4");
|
||||
if (m_config)
|
||||
{
|
||||
doubleSided = m_config->ReadBool("/Scanner/DoubleSided", false);
|
||||
total = m_config->ReadLong("/Scanner/Total", 1);
|
||||
m_config->Read("/Scanner/Last/PaperSize", &paperSize, "A4");
|
||||
}
|
||||
|
||||
wxFileName destFile(dest);
|
||||
const string deviceId = m_scannerWidget->GetCurrentDeviceId().ToStdString();
|
||||
const std::pair<int, wxString> sourceAttributes = m_scannerWidget->GetScannerSource();
|
||||
const string source = sourceAttributes.second.ToStdString();
|
||||
const string mode = m_scannerWidget->GetScannerMode().ToStdString();
|
||||
int resolution = 300;
|
||||
if ( !m_scannerWidget->GetScannerResolution().IsEmpty() )
|
||||
{
|
||||
resolution = std::stoi ( m_scannerWidget->GetScannerResolution().ToStdString() );
|
||||
}
|
||||
int sourceIndex = sourceAttributes.first == wxNOT_FOUND
|
||||
? 0 : sourceAttributes.first;
|
||||
adf = m_insaneWorker->IsADF(deviceId, sourceIndex);
|
||||
if (m_insaneWorker->ConfigureDevice(deviceId, source, sourceIndex, mode, resolution, paperSize.ToStdString()))
|
||||
{
|
||||
m_scanProject->SetADF(adf);
|
||||
m_scanProject->SetMode(mode);
|
||||
m_scanProject->SetOutputType(outputType);
|
||||
m_scanProject->SetPaperSize(paperSize);
|
||||
m_scanProject->SetDoubleSided(doubleSided);
|
||||
m_scanProject->SetTotalNumberOfSides(total);
|
||||
|
||||
std::pair<int, int> startAndIncrement = m_scanProject->GetStartAndIncrement(m_insaneWorker.get());
|
||||
|
||||
const int padWidth = ( ushort ) m_config->Read (_T("/Scanner/Counter/Length"), 2 );
|
||||
bool res = m_insaneWorker->Scan(destFile.GetPath().ToStdString(),
|
||||
destFile.GetName().ToStdString(),
|
||||
startAndIncrement.first, padWidth, startAndIncrement.second);
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void XInsaneWidget::ResetScanProject()
|
||||
{
|
||||
if (m_scanProject)
|
||||
m_scanProject->Reset();
|
||||
}
|
||||
|
||||
void XInsaneWidget::CancelScanning()
|
||||
{
|
||||
if (m_insaneWorker)
|
||||
m_insaneWorker->Cancel();
|
||||
}
|
||||
|
||||
void XInsaneWidget::EnableScanButton(bool enable)
|
||||
{
|
||||
btnScan->Enable(enable);
|
||||
}
|
||||
81
Resources/InsaneWidget/XInsaneWidget.h
Normal file
81
Resources/InsaneWidget/XInsaneWidget.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* File: XInsaneWidget.h
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License : CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2025
|
||||
*
|
||||
* Created on 15 06 2025, 21:24
|
||||
*/
|
||||
|
||||
#ifndef XINSANEWIDGET_H
|
||||
#define XINSANEWIDGET_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/config.h>
|
||||
#include <wx/popupwin.h>
|
||||
#include <InsaneWidget.h>
|
||||
#include <InsaneWorker.h>
|
||||
#include <XScannerWidget.h>
|
||||
#include <TimeredStatusBar.h>
|
||||
#include <ConfigEditorPopup.h>
|
||||
#include <memory>
|
||||
|
||||
class BackgroundScannerDiscoveryEVH;
|
||||
class ScanProjectHandler; // An event handler extending InsaneWorkerEvent.
|
||||
|
||||
// Page index (not page number), {pixel file path, {pixel count, image width, image height}}.
|
||||
typedef std::map<uint, std::tuple<std::string, InsaneWorkerEvent::ImageAttributes>> PixelFilesMap;
|
||||
|
||||
/**
|
||||
* Shows a label, a disabled text box and a button.
|
||||
*
|
||||
* Label:
|
||||
* - Right click: define the number of pages to scan and double-sided scanning.
|
||||
* - Number of pages: it's the number of sides, not the number of sheets.
|
||||
* - Backface: if an automatic document feeder is used. If 5 pages are to be
|
||||
* scanned, feed 3 pages on front face, turn the whole stack including the
|
||||
* 6th page to scan the backfaces; the last page will be discarded.
|
||||
* With a feeder, the 'double-sided' option can be off if all backfaces
|
||||
* are blank.
|
||||
* With a flatbed scanner, the 'double-sided' should typically be off and
|
||||
* the pages scanned in their logical order.
|
||||
*
|
||||
* Text box:
|
||||
* - shows the path and the basename of the files to create. This must be set
|
||||
* by the application in any conveniant manner.
|
||||
* Pressing the backspace key clears the text box.
|
||||
*
|
||||
* Button:
|
||||
* - Right click: shows the scanner widget.
|
||||
* - Left click: starts scanning.
|
||||
*/
|
||||
class XInsaneWidget : public InsaneWidget
|
||||
{
|
||||
public:
|
||||
virtual ~XInsaneWidget();
|
||||
XInsaneWidget( wxWindow* parent, TimeredStatusBar * sb, wxConfig * config, wxWindowID id = SYMBOL_INSANEWIDGET_IDNAME, const wxPoint& pos = SYMBOL_INSANEWIDGET_POSITION, const wxSize& size = SYMBOL_INSANEWIDGET_SIZE, long style = SYMBOL_INSANEWIDGET_STYLE );
|
||||
|
||||
void ResetScanProject();
|
||||
void CancelScanning(); // Not tested, probably doesn't work as intended.
|
||||
void EnableScanButton(bool enable); // For CallAfter.
|
||||
private:
|
||||
wxConfig * m_config;
|
||||
wxWeakRef<TimeredStatusBar> m_sb;
|
||||
|
||||
// Contains a popup to define the number of pages and double-sided scanning.
|
||||
std::unique_ptr<ConfigEditorPopup> m_pageStack;
|
||||
// Contains the scanner widget.
|
||||
std::unique_ptr<wxPopupTransientWindow> m_ptwScannerWidget;
|
||||
// Available devices and minimal options.
|
||||
std::unique_ptr<XScannerWidget> m_scannerWidget;
|
||||
std::unique_ptr<InsaneWorker> m_insaneWorker;
|
||||
std::unique_ptr<BackgroundScannerDiscoveryEVH> m_backgroundScannerDiscoveryEvh;
|
||||
std::unique_ptr<ScanProjectHandler> m_scanProject;
|
||||
|
||||
void OnLblNewDocRightClick ( wxMouseEvent& evt );
|
||||
void OnTxtNewDocKeyPressed ( wxKeyEvent& evt );
|
||||
void OnBtnScanRightClick ( wxMouseEvent& evt );
|
||||
void OnBtnScanClick ( wxMouseEvent& evt );
|
||||
};
|
||||
|
||||
#endif // XINSANEWIDGET_H
|
||||
318
Resources/InsaneWidget/XScannerWidget.cpp
Normal file
318
Resources/InsaneWidget/XScannerWidget.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* File: XScannerWidget.cpp
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License: CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2024
|
||||
*
|
||||
* Created on ?
|
||||
*/
|
||||
|
||||
#include "XScannerWidget.h"
|
||||
#include "XClientData.hpp"
|
||||
#include "Common.h"
|
||||
#include <libinsane/constants.h>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
XScannerWidget::~XScannerWidget()
|
||||
{}
|
||||
|
||||
XScannerWidget::XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
|
||||
InsaneWorker * insaneWorker,
|
||||
wxWindowID id, const wxPoint& pos, const wxSize& size, long int style )
|
||||
: ScannerWidget ( parent, id, pos, size, style )
|
||||
{
|
||||
UpdateExtensionsMap();
|
||||
m_sb = sb;
|
||||
m_insaneWorker = insaneWorker;
|
||||
if (m_insaneWorker)
|
||||
m_insaneWorkerEvh = m_insaneWorker->GetEventHandler();
|
||||
cmbOutputType->Append (Extensions[PDF]); // Use the file extension and the enum index without client data.
|
||||
#if wxUSE_LIBPNG
|
||||
cmbOutputType->Append (Extensions[PNG]);
|
||||
#endif
|
||||
#if wxUSE_LIBJPEG
|
||||
cmbOutputType->Append (Extensions[JPEG]);
|
||||
#endif
|
||||
#if wxUSE_LIBTIFF
|
||||
cmbOutputType->Append (Extensions[TIFF]);
|
||||
#endif
|
||||
#if wxUSE_PNM
|
||||
cmbOutputType->Append (Extensions[PNM]);
|
||||
#endif
|
||||
// Paper sizes handled by podofo.
|
||||
cmbPaperSize->Append(wxArrayString({"A0", "A1", "A2", "A3", "A4", "A5", "A6",
|
||||
"Letter", "Legal", "Tabloid"}));
|
||||
|
||||
btnRefreshDevices->Bind ( wxEVT_COMMAND_BUTTON_CLICKED, &XScannerWidget::OnButtonRefreshDevices, this );
|
||||
cmbDevices->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnDeviceSelected, this );
|
||||
cmbOutputType->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnImageTypeSelected, this );
|
||||
cmbSource->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnSourceSelected, this );
|
||||
cmbMode->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnModeSelected, this );
|
||||
cmbResolution->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnResolutionSelected, this );
|
||||
cmbPaperSize->Bind ( wxEVT_COMMAND_COMBOBOX_SELECTED, &XScannerWidget::OnPaperSizeSelected, this );
|
||||
GetParent()->Bind ( wxEVT_SHOW, &XScannerWidget::OnActivated, this );
|
||||
}
|
||||
|
||||
bool XScannerWidget::FindDevices ( bool async )
|
||||
{
|
||||
if ( m_sb && !async )
|
||||
m_sb->SetTransientText (_("Searching for devices...") );
|
||||
if ( !m_insaneWorker->Init() )
|
||||
{
|
||||
const wxString msg = _("Could not initialise insane api.");
|
||||
cerr << msg << endl;
|
||||
if (m_insaneWorkerEvh)
|
||||
m_insaneWorkerEvh->OnInsaneError(msg.ToStdString());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<InsaneWorker::DeviceDescriptor> devices = m_insaneWorker->GetDeviceDescriptors();
|
||||
|
||||
CallAfter ( &XScannerWidget::ClearScannerItems );
|
||||
uint numberOfDevices = devices.size();
|
||||
|
||||
for ( uint i = 0; i < numberOfDevices; i++ )
|
||||
{
|
||||
InsaneWorker::DeviceDescriptor device = devices.at(i);
|
||||
CallAfter ( &XScannerWidget::AppendScannerItem, device.GetLabel(), device.id );
|
||||
}
|
||||
|
||||
if ( m_sb )
|
||||
{
|
||||
const wxString msg = wxVariant ( ( long ) numberOfDevices ).GetString() + _(" device(s) found.");
|
||||
// Should be but don't : it's as if the above code has not been run.
|
||||
// m_sb->CallAfter(&TimeredStatusBar::SetTransientText, msg, 3000);
|
||||
m_sb->SetTransientText(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XScannerWidget::AppendScannerItem ( const wxString& display, const wxString& clientData )
|
||||
{
|
||||
cmbDevices->Append ( display, new XClientData ( clientData ) );
|
||||
}
|
||||
|
||||
void XScannerWidget::ClearScannerItems()
|
||||
{
|
||||
cmbDevices->Clear();
|
||||
cmbSource->Clear();
|
||||
cmbMode->Clear();
|
||||
cmbResolution->Clear();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnButtonRefreshDevices ( wxCommandEvent& evt )
|
||||
{
|
||||
btnRefreshDevices->Enable ( false );
|
||||
m_insaneWorker->Init();
|
||||
FindDevices();
|
||||
evt.Skip();
|
||||
btnRefreshDevices->Enable ( true );
|
||||
}
|
||||
|
||||
void XScannerWidget::SetConfig ( wxFileConfig* config )
|
||||
{
|
||||
m_config = config;
|
||||
if ( !m_config )
|
||||
return;
|
||||
|
||||
wxString lastImageType;
|
||||
m_config->Read (_T("/Scanner/Last/OutputType"), &lastImageType );
|
||||
if ( cmbOutputType->FindString ( lastImageType, true ) != wxNOT_FOUND )
|
||||
cmbOutputType->SetStringSelection ( lastImageType );
|
||||
else
|
||||
cmbOutputType->Select(0);
|
||||
|
||||
wxString lastPaperSize;
|
||||
m_config->Read (_T("/Scanner/Last/PaperSize"), &lastPaperSize );
|
||||
if ( cmbPaperSize->FindString ( lastPaperSize, true ) != wxNOT_FOUND )
|
||||
cmbPaperSize->SetStringSelection ( lastPaperSize );
|
||||
else
|
||||
cmbPaperSize->Select(4);
|
||||
}
|
||||
|
||||
|
||||
void XScannerWidget::OnDeviceSelected ( wxCommandEvent& evt )
|
||||
{
|
||||
if ( !m_config || ( cmbDevices->GetSelection() == wxNOT_FOUND ) )
|
||||
return;
|
||||
|
||||
int currentSelection = cmbDevices->GetCurrentSelection();
|
||||
if ( currentSelection == wxNOT_FOUND )
|
||||
return;
|
||||
XClientData * lastDevice = static_cast<XClientData*> ( cmbDevices->GetClientObject ( currentSelection ) );
|
||||
if ( !lastDevice )
|
||||
return;
|
||||
m_config->Write ( "/Scanner/Last/Device", lastDevice->GetData().GetString() );
|
||||
m_config->Flush();
|
||||
UpdateScannerOptions ( lastDevice->GetData().GetString() );
|
||||
}
|
||||
|
||||
void XScannerWidget::OnImageTypeSelected ( wxCommandEvent& evt )
|
||||
{
|
||||
if ( !m_config )
|
||||
return;
|
||||
|
||||
m_config->Write ( "/Scanner/Last/OutputType", cmbOutputType->GetStringSelection() );
|
||||
m_config->Flush();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnSourceSelected ( wxCommandEvent& evt )
|
||||
{
|
||||
if ( !m_config )
|
||||
return;
|
||||
const wxString deviceId = GetCurrentDeviceId();
|
||||
if ( wxIsEmpty ( deviceId ) )
|
||||
return;
|
||||
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_SOURCE );
|
||||
m_config->Write ( key, cmbSource->GetStringSelection() );
|
||||
m_config->Flush();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnModeSelected ( wxCommandEvent& evt )
|
||||
{
|
||||
if ( !m_config )
|
||||
return;
|
||||
const wxString deviceId = GetCurrentDeviceId();
|
||||
if ( wxIsEmpty ( deviceId ) )
|
||||
return;
|
||||
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_MODE );
|
||||
m_config->Write ( key, cmbMode->GetStringSelection() );
|
||||
m_config->Flush();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnResolutionSelected ( wxCommandEvent& evt )
|
||||
{
|
||||
if ( !m_config )
|
||||
return;
|
||||
const wxString deviceId = GetCurrentDeviceId();
|
||||
if ( wxIsEmpty ( deviceId ) )
|
||||
return;
|
||||
const wxString key =_T("/Scanner/") + deviceId +_T("/") + _T ( OPT_NAME_RESOLUTION );
|
||||
m_config->Write ( key, cmbResolution->GetStringSelection() );
|
||||
m_config->Flush();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnPaperSizeSelected(wxCommandEvent& evt)
|
||||
{
|
||||
if ( !m_config )
|
||||
return;
|
||||
|
||||
m_config->Write ( "/Scanner/Last/PaperSize", cmbPaperSize->GetStringSelection() );
|
||||
m_config->Flush();
|
||||
}
|
||||
|
||||
void XScannerWidget::UpdateScannerOptions ( const wxString& deviceId )
|
||||
{
|
||||
if ( wxIsEmpty ( deviceId ) )
|
||||
return;
|
||||
|
||||
auto addListConstrainedOptions = [&] ( wxComboBox * cmb, const wxString& lastKey )
|
||||
{
|
||||
if (!cmb)
|
||||
return;
|
||||
cmb->Clear();
|
||||
vector<string> possible;
|
||||
const string defaultValue = m_insaneWorker->GetOptionPossibleValues(deviceId.ToStdString(), lastKey.ToStdString(), possible);
|
||||
for (string itemValue : possible)
|
||||
{
|
||||
cmb->Append(itemValue);
|
||||
}
|
||||
cmb->Enable(cmb->GetCount());
|
||||
if ( m_config )
|
||||
{
|
||||
wxString savedValue;
|
||||
const wxString key =_T("Scanner/") + deviceId +_T("/") + lastKey;
|
||||
m_config->Read ( key, &savedValue );
|
||||
if ( !savedValue.IsEmpty() )
|
||||
{
|
||||
cmb->SetStringSelection ( savedValue );
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmb->SetStringSelection ( defaultValue );
|
||||
};
|
||||
|
||||
/*
|
||||
* For a device with flatbed and ADF, there are multiple sources, each with a name.
|
||||
* The display name obtained here is different from the child devices of lis_item.
|
||||
* Each child item is a source. The root item must not be used for scanning
|
||||
* following the documentation. Fortunately, they are listed in the same order
|
||||
* with both enumerations.
|
||||
* Ex: Brother DCP-L8410CDW
|
||||
* 0 - FlatBed - flatbed
|
||||
* 1 - Automatic Document Feeder(left aligned) - feeder(left aligned)
|
||||
* 2 - Automatic Document Feeder(centrally aligned) - feeder(centrally aligned)
|
||||
* We must therefore select a source by its index.
|
||||
*/
|
||||
addListConstrainedOptions(cmbSource, OPT_NAME_SOURCE);
|
||||
addListConstrainedOptions(cmbMode, OPT_NAME_MODE);
|
||||
addListConstrainedOptions(cmbResolution, OPT_NAME_RESOLUTION);
|
||||
}
|
||||
|
||||
|
||||
wxString XScannerWidget::GetCurrentDeviceId() const
|
||||
{
|
||||
int currentSelection = cmbDevices->GetCurrentSelection();
|
||||
if ( currentSelection == wxNOT_FOUND )
|
||||
return wxEmptyString;
|
||||
XClientData * clientData = static_cast<XClientData*> ( cmbDevices->GetClientObject ( currentSelection ) );
|
||||
if ( !clientData )
|
||||
return wxEmptyString;
|
||||
return clientData->GetData().GetString();
|
||||
}
|
||||
|
||||
void XScannerWidget::OnActivated ( wxShowEvent& evt )
|
||||
{
|
||||
if ( evt.IsShown() && cmbDevices->GetCount() && cmbDevices->GetCurrentSelection() == wxNOT_FOUND )
|
||||
{
|
||||
if ( m_config )
|
||||
{
|
||||
wxString lastDevice;
|
||||
m_config->Read (_T("/Scanner/Last/Device"), &lastDevice );
|
||||
if ( lastDevice.IsEmpty() || !cmbDevices->GetCount() )
|
||||
{
|
||||
evt.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
for ( uint i = 0; i < cmbDevices->GetCount(); i++ )
|
||||
{
|
||||
XClientData * clientData = static_cast<XClientData*> ( cmbDevices->GetClientObject ( i ) );
|
||||
if ( !clientData )
|
||||
continue;
|
||||
if ( clientData->GetData().GetString() == lastDevice )
|
||||
{
|
||||
cmbDevices->Select ( i );
|
||||
UpdateScannerOptions ( lastDevice );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
wxThread::ExitCode BackgroundScannerDiscovery::Entry()
|
||||
{
|
||||
if (m_owner)
|
||||
{
|
||||
m_owner->FindDevices ( true );
|
||||
wxShowEvent evt;
|
||||
evt.SetShow ( true );
|
||||
m_owner->OnActivated ( evt );
|
||||
if (m_evh)
|
||||
m_evh->OnDone();
|
||||
}
|
||||
return ( wxThread::ExitCode ) 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void BackgroundScannerDiscoveryEvent::OnDone()
|
||||
{}
|
||||
|
||||
120
Resources/InsaneWidget/XScannerWidget.h
Normal file
120
Resources/InsaneWidget/XScannerWidget.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* File: XScannerWidget.h
|
||||
* Author: Saleem Edah-Tally - nmset@yandex.com
|
||||
* License: CeCILL-C
|
||||
* Copyright Saleem Edah-Tally - © 2024
|
||||
*
|
||||
* Created on ?
|
||||
*/
|
||||
|
||||
#ifndef XSCANNERWIDGET_H
|
||||
#define XSCANNERWIDGET_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/thread.h>
|
||||
#include <wx/config.h>
|
||||
#include <ScannerWidget.h>
|
||||
#include "TimeredStatusBar.h"
|
||||
#include "InsaneWorker.h"
|
||||
#include "Common.h"
|
||||
#include <map>
|
||||
|
||||
class BackgroundScannerDiscoveryEvent;
|
||||
|
||||
/**
|
||||
* Find available scanner devices, USB or network attached.\n
|
||||
* Show the devices and their minimal properties in a popup.\n
|
||||
* Make the properties available through API.\n
|
||||
* Specify output file format and page sizes.\n
|
||||
* All selections are saved and restored using wxConfig.\n
|
||||
* Devices can be searched in a detached thread, typically on application
|
||||
* startup.\n
|
||||
*/
|
||||
|
||||
class XScannerWidget : public ScannerWidget
|
||||
{
|
||||
friend class BackgroundScannerDiscovery;
|
||||
public:
|
||||
XScannerWidget() {};
|
||||
~XScannerWidget();
|
||||
|
||||
|
||||
XScannerWidget ( wxWindow* parent, TimeredStatusBar * sb,
|
||||
InsaneWorker * insaneWorker,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = SYMBOL_SCANNERWIDGET_SIZE,
|
||||
long int style = SYMBOL_SCANNERWIDGET_STYLE );
|
||||
|
||||
void SetConfig ( wxConfig * config );
|
||||
bool FindDevices ( bool async = false );
|
||||
wxString GetCurrentDeviceId() const;
|
||||
wxString GetScannerMode() const
|
||||
{
|
||||
return cmbMode->GetValue();
|
||||
}
|
||||
std::pair<int, wxString> GetScannerSource() const
|
||||
{
|
||||
return {cmbSource->GetCurrentSelection(), cmbSource->GetValue()};
|
||||
}
|
||||
wxString GetScannerResolution() const
|
||||
{
|
||||
return cmbResolution->GetValue();
|
||||
}
|
||||
uint GetScannerOutputType()
|
||||
{
|
||||
return cmbOutputType->GetCurrentSelection();
|
||||
}
|
||||
wxString GetPaperSize() const
|
||||
{
|
||||
return cmbPaperSize->GetValue();
|
||||
}
|
||||
|
||||
private:
|
||||
wxWeakRef<TimeredStatusBar> m_sb = nullptr;
|
||||
wxConfig * m_config = nullptr;
|
||||
|
||||
InsaneWorker * m_insaneWorker;
|
||||
InsaneWorkerEvent * m_insaneWorkerEvh;
|
||||
|
||||
void OnButtonRefreshDevices ( wxCommandEvent& evt );
|
||||
/*
|
||||
* For synchronous and asynchronous calls/
|
||||
*/
|
||||
void AppendScannerItem ( const wxString& display, const wxString& clientData );
|
||||
void ClearScannerItems();
|
||||
|
||||
void OnActivated ( wxShowEvent& evt );
|
||||
void OnImageTypeSelected ( wxCommandEvent& evt );
|
||||
void OnDeviceSelected ( wxCommandEvent& evt );
|
||||
void OnSourceSelected ( wxCommandEvent& evt );
|
||||
void OnModeSelected ( wxCommandEvent& evt );
|
||||
void OnResolutionSelected ( wxCommandEvent& evt );
|
||||
void OnPaperSizeSelected ( wxCommandEvent& evt );
|
||||
void UpdateScannerOptions ( const wxString& deviceId );
|
||||
};
|
||||
|
||||
// Asynchronous device discovery.
|
||||
class BackgroundScannerDiscovery : public wxThread
|
||||
{
|
||||
public:
|
||||
BackgroundScannerDiscovery ( XScannerWidget * owner, BackgroundScannerDiscoveryEvent * evh )
|
||||
{
|
||||
m_owner = owner;
|
||||
m_evh = evh;
|
||||
}
|
||||
virtual ~BackgroundScannerDiscovery() {};
|
||||
virtual ExitCode Entry();
|
||||
private:
|
||||
XScannerWidget * m_owner = nullptr;
|
||||
BackgroundScannerDiscoveryEvent * m_evh = nullptr;
|
||||
};
|
||||
|
||||
class BackgroundScannerDiscoveryEvent
|
||||
{
|
||||
public:
|
||||
virtual void OnDone();
|
||||
|
||||
};
|
||||
|
||||
#endif // XSCANNERWIDGET_H
|
||||
Reference in New Issue
Block a user