Hvorfor Ubuntu (Studio)

Årsaken er RAD Studio fra Embarcadero! What????

Jeg bruker RAD Studio som mitt favoritt utviklings verktøy, og programmerer i C++. RAD Studio er, etter min mening, et av de beste utviklingsverktøy på markedet. Du programmerer ett sted, og lager programmer for både Windows, Apple maskiner, Android og Linux.

Selve RAD Studio mener jeg er skrevet i (Delphi-) Pascal. Delphi kommer fra gamle Borland. Borland var opprinnelig kjent for sin gamle C++ kompiler og OWL biblioteket. De regjerte nesten fullstendig den gangen. Så prøvde de seg med C++ Builder, Java Builder og Delphi Pascal. Det er antagelig mest riktig å si bare «Delphi» og ikke «Delphi Pascal». Delphi er en objekt orientert utgave av Pascal.

Alt det som er nevnt ovenfor er mer eller mindre skrevet i Delphi, hele det som nå kalles RAD Studio. RAD Studio inneholder nå både C++ Builder og Delphi, men ikke Java Builder (vet ikke om den fortsatt eksisterer).

Ok, tilbake til Ubuntu, som er en Linux distro. Jeg nevnte du kan sitte på en Windows maskin og lage programmer for Windows , Apple maskiner, Android og Linux, men det er visse begrensninger. Det gjelder C++.

Disse gutta er noen av verdens beste Pascal og Delphi programmerere, det skal de ha. Delphi er derfor deres førstefødte, som vi sier. Alt nytt kommer først til Delphi, deretter etter en viss tid til C++ Builder. Dette må vi godta.

Det betyr at RAD Studio ikke har ferdig alle funksjonene. De har f.eks. ferdig Delphi for Linux, men ikke C++ Builder, og den Linux distroen de har valgt å starte med, er Ubuntu.

En annen og lignende begrensning er det for Android. Google forlanger nå 64-bits kode i programmet. Det har de også ferdig for Delphi, men ikke for C++ Builder.

Da ser du mitt dilemma. Jeg kjenner litt til OpenSUSE distroen, og det er først nå jeg prøver Ubuntu, men den er jeg altså nå nødt til å kjøre. I tillegg må jeg altså bruke Delphi (Pascal) som programmerings verktøy foreløpig.

Embarcadero vil ikke si når C++ Builder får 64-bits kode for Android og når vi får C++ Builder for linux. De vil heller ikke bekrefte om det kommer til å skje. Det skjærer dypt i en hardcore C++ programmerers hjerte å skulle gå over til Delphi (Pascal) 🙂 men vi får leve i håpet. Jeg ser det som en umulighet at ikke vi skal få 64-bits C++ for Android og C++ for Linux.

Open SUSE og Ubuntu

Jeg driver og ser litt på et par Linux distribusjoner, Open SUSE og Ubuntu. Da går det fort et par dager uten nye poster her.

Jeg tenkte å prøve Delphi under Linux, men da må jeg forlate min kjære C++ og gå over til Delphi Pascal. Som gammel hardcore C++ programmerer skjærer det meg i hjertet å skulle bruke Pascal 🙂 Men tiden får vise.

I tillegg må jeg forlate min favoritt-distro, Open SUSE, og starte opp med Ubuntu. Delphi supporterer Ubuntu, men ikke Open SUSE.

Kanskje blir det litt QT i stedet, hvem vet?

Stiler, Styles

Jeg har lagt ut et lite eksemplel på hvordan du kan laste opp en stil ved runtime. Egentlig hadde jeg tenkt å bruke en INI-fil, men vi får se om jeg får tid og lyst til å gjøre det (skuffelsen med Firemonkey grafikk bibliotek som jeg nevnte 🙂 )

Siden med innlegget ligger her:

Eksempler->RAD Code Samples->Stiler, Styles

Edit Undo

Jeg har lagt ut en ny side med info om min løsning på en edit-undo. Dette løste jeg ved å opprette en «Stack» i dokument-klassen og rett og slett «stacke» den originale bitmapen.

Den er foreløpig i testversjon, men prinsippet ser ut til å virke 🙂

Siden ligger her:

Nytt C++ Eksempel

Har nettopp lagt ut et nytt eksempel vedr. gråtone bilde eller monochrome som Firemonkey kaller det.

Det er mye likt det som er skrevet i bloggen, men jeg synes det blir litt bedre strukturert i meny-sidene.

En annen forskjell (jeg KAN ta feil her) er at i sideoppsettet er det intet oppsett for f.eks. stikkord. Tar jeg feil her? Derfor nevner jeg noen av de nye sidene i bloggpost slik som her 🙂

Den nye siden finnes her:

Eksempler->RAD CodeSamples->Gråskala(Monochrome)

Firemonkey Gråskala

Som en slags avslutning på klagen på Firemonkey ImageLibrary kan jeg jo vise hvordan vi gjør om et RGB til Gråskala i Firemonkey. Det begynner på vanlig måte i hoveddelen med å kaste om på en adresse:

void __fastcall TForm1::menu_IMAGE_GRAYSCALE_Click(TObject *Sender)
{
	if (TabControl1->TabCount < 1 ) {
		return;
	}
	 TTabItem   *myTabItemPtr;

	 // Get a pointer to active tab

	 myTabItemPtr = TabControl1->ActiveTab;

	 // Type cast to class DocPage and
	 // let DocPage do the job
	 ((DocPage *) myTabItemPtr)->Monochrome();

}

Ikke mye hokus pokus i funksjonen ovenfor, men så fortsetter det i dokumentklassen:

// --------------------------------------------------------------------------
//  				Monochrome
// --------------------------------------------------------------------------

void __fastcall DocPage::Monochrome()
{
	try
	{
		TForm14 *dlgMonochrome =  new TForm14(Application);

		dlgMonochrome->Image1->Bitmap = Image1->Bitmap;
		dlgMonochrome->Caption = _T("Monochrome");

		dlgMonochrome->myMonochrome->Input = dlgMonochrome->Image1->Bitmap;
		//dlgBoxBlur->myBoxBlur->BlurAmount = dlgBoxBlur->TrackBar1->Value / 100;
		dlgMonochrome->Image2->Bitmap = dlgMonochrome->myMonochrome->Output;


		dlgMonochrome->ShowModal();
		if( dlgMonochrome -> iretState == 1 ) {

			// Don't forget the undo-stack

			UndoItem = new TUndoItem();
			UndoItem->UndoName = new String("Monochrome");
			UndoItem->UndoBitmap = new TBitmap();

			// Have to use "Assign()" because everything are pointers!!!

			UndoItem->UndoBitmap->Assign(Image1->Bitmap);
			mystack.push(UndoItem);

			Image1->Bitmap->Assign(dlgMonochrome->Image2->Bitmap);
			//Image1->Bitmap->Monochrome = true;

		}

		dlgMonochrome->DisposeOf();
	}
	catch(...)
	{
		ShowMessage("Monochrome did not succeed");
	}

}

Koden ovenfor viser min måte å lage en dialogboks på, en TForm klasse og så kjøres det en «dlgMonochrome->ShowModal()».

I de fleste av mine filteroperasjoner bruker jeg en dialogboks med to «TImage»-klasser, en for originalen og en for sluttresultatet. Bildet nedenfor viser det ved designtidspunkt i RAD-Studio

Koden for selve TForm-klassen er vist nedenfor.

//---------------------------------------------------------------------------

#ifndef MonochromeH
#define MonochromeH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.StdCtrls.hpp>
#include <FMX.Types.hpp>
#include <FMX.Effects.hpp>
#include <FMX.Filter.Effects.hpp>
#include <FMX.Objects.hpp>
//---------------------------------------------------------------------------
class TForm14 : public TForm
{
__published:	// IDE-managed Components
	TImage *Image1;
	TImage *Image2;
	TButton *Cancel;
	TButton *OK;
	void __fastcall OnCreate(TObject *Sender);
	void __fastcall CancelClick(TObject *Sender);
	void __fastcall OKClick(TObject *Sender);
private:	// User declarations
public:		// User declarations
	int iretState;
	TFilterMonochrome * myMonochrome;
	__fastcall TForm14(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm14 *Form14;
//---------------------------------------------------------------------------
#endif

De enkelte funksjonene i klassen ovenfor er det heller ikke mye å si om, de vises nedenfor:

//---------------------------------------------------------------------------

#include <fmx.h>
#pragma hdrstop

#include "Monochrome.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm14 *Form14;
//---------------------------------------------------------------------------
__fastcall TForm14::TForm14(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm14::OnCreate(TObject *Sender)
{
	myMonochrome = new TFilterMonochrome(this);

	 myMonochrome->Input = Image1->Bitmap;
	 //TrackBar1->Value = 25;
	 //myPixelate->BlockCount = TrackBar1->Value;
	 Image2->Bitmap = myMonochrome->Output;

}
//---------------------------------------------------------------------------
void __fastcall TForm14::CancelClick(TObject *Sender)
{
	iretState = 0;
	Close();

}
//---------------------------------------------------------------------------
void __fastcall TForm14::OKClick(TObject *Sender)
{
	iretState = 1;
	Close();

}
//---------------------------------------------------------------------------

Jeg konstruerer et monokrom filter ved inngangen, og så kan du se jeg kobler outputen fra filteret til inputen i Image2. til sist setter jeg en returstatus ved exit tidspunkt.

Kjøring av programmet ser slik ut:

Firemonkey Image Library 2

Først: Det hele starter i Hovedformen, «TForm1», alle menyfunksjoner gjør det, og så jumper programmet i de fleste tilfelle til en funksjon i «DocPage» klassen. Det eneste som gjøres her er å opprette en adressepeker til neste funksjon:

// --------------------- Display Image Properties ---------------

	void __fastcall TForm1::menu_PROPERTIES_Click(TObject *Sender)         // 19
{
	 TTabItem   *myTabItemPtr;

	 // Get a pointer to active tab
	 myTabItemPtr = TabControl1->ActiveTab;

	 // Type cast to class DocPage and
	 // let DocPage do the job
	 ((DocPage *) myTabItemPtr)->ImageProperties();
}

De meste av info finnes i bitmap’en, «TBitmap», og hvordan du henter opp denne er egentlig forklart i dokumentasjonen fra Embarcadero.

// --------------------------------------------------------------------------
//  				Image Properties
// --------------------------------------------------------------------------
void __fastcall DocPage::ImageProperties()
{
	try
	{
		TForm6 *dlgProperties =  new TForm6(Application);

		dlgProperties->Image1->Bitmap = Image1->Bitmap;
		dlgProperties->Caption = _T("Image Properties");

		// Pixelformat
		TPixelFormat myPixelformat = Image1->Bitmap->PixelFormat;
		UnicodeString strPixelformat(PixelFormatToString(myPixelformat));
		dlgProperties->Edit1->Text = strPixelformat;

		// Bitmap Scale
		float BitmapScale = Image1->Bitmap->BitmapScale;
		UnicodeString strBitmapScale(BitmapScale);
		dlgProperties->Edit2->Text = strBitmapScale;

		// Bytes per line
		int BytesPerLine = Image1->Bitmap->BytesPerLine;
		UnicodeString strBytesPerLine(BytesPerLine);
		dlgProperties->Edit3->Text = strBytesPerLine;

		// Bytes per pixel
		int BytesPerPixel = Image1->Bitmap->BytesPerPixel;
		UnicodeString strBytesPerPixel(BytesPerPixel);
		dlgProperties->Edit4->Text = strBytesPerPixel;

		// Bitmap Height
		int Height = Image1->Bitmap->Height;
		UnicodeString strHeight(Height);
		dlgProperties->Edit5->Text = strHeight;

		// Bitmap Width
		int Width = Image1->Bitmap->Width;
		UnicodeString strWidth(Width);
		dlgProperties->Edit6->Text = strWidth;
		// -----------------------------------------------------------------
		// DisplayCount
		iDisplayCount = Screen->DisplayCount;
		UnicodeString strDisplayCount(iDisplayCount);
		dlgProperties->Edit7->Text = strDisplayCount;

		// Desktop Width i pixels (might be for instance dual screen)
		iDesktopWidthInPixels = Screen->DesktopWidth;
		UnicodeString strDesktopWidth(iDesktopWidthInPixels);
		dlgProperties->Edit8->Text = strDesktopWidth;

		// Desktop Height i pixels
		iDesktopHeightInPixels = Screen->DesktopHeight;
		UnicodeString strDesktopHeight(iDesktopHeightInPixels);
		dlgProperties->Edit9->Text = strDesktopHeight;

		// Screen Width in pixels
		iScreenWidthInPixels = Screen->Width;
		UnicodeString strScreenWidth(iScreenWidthInPixels);
		dlgProperties->Edit10->Text = strScreenWidth;

		// Screen Height in pixels
		iScreenHeightInPixels = Screen->Height;
		UnicodeString strScreenHeight(iScreenHeightInPixels);
		dlgProperties->Edit11->Text = strScreenHeight;

		// Work area
		iWorkAreaWidthInPixels = Screen->WorkAreaWidth;
		UnicodeString strWorkAreaWidth(iWorkAreaWidthInPixels);
		dlgProperties->Edit12->Text = strWorkAreaWidth;

		iWorkAreaHeightInPixels = Screen->WorkAreaHeight;
		UnicodeString strWorkAreaHeight(iWorkAreaHeightInPixels);
		dlgProperties->Edit13->Text = strWorkAreaHeight;

		// Junior school math: a**2 + b**2 = c**2
		iScreenSizeInPixels = sqrt(iScreenWidthInPixels*iScreenWidthInPixels + iScreenHeightInPixels*iScreenHeightInPixels);
		UnicodeString strpixsize(iScreenSizeInPixels);
		dlgProperties->Edit14->Text = strpixsize;

        // Guessing 27" monitor as a starter
		iScreenPPI = iScreenSizeInPixels / 27;
		dlgProperties->Edit15->Text = iScreenPPI;

		//UnicodeString strii(17);

		dlgProperties->ComboBox1->Items->Add( 15);
		dlgProperties->ComboBox1->Items->Add( 17);
		dlgProperties->ComboBox1->Items->Add( 19);
		dlgProperties->ComboBox1->Items->Add( 20);
		dlgProperties->ComboBox1->Items->Add( 22);
		dlgProperties->ComboBox1->Items->Add( 24);
		dlgProperties->ComboBox1->Items->Add( 27);
		dlgProperties->ComboBox1->Items->Add( 28);
		dlgProperties->ComboBox1->Items->Add( 32);
		dlgProperties->ComboBox1->Items->Add( 34);
		dlgProperties->ComboBox1->Items->Add( 35);
		dlgProperties->ComboBox1->Items->Add( 43);
		dlgProperties->ComboBox1->Items->Add( 49);

		dlgProperties->ComboBox1->ItemIndex = 6;

		dlgProperties->ShowModal();
		if( dlgProperties -> iretState == 1 ) {

			//
		}

		dlgProperties->DisposeOf();
	}
	catch(...)
	{
		ShowMessage("Image properties did not succeed");
	}

}

Det som ligger av kode i selve dialogboksen, er å foreta en beregning hver gang du endrer skjermstørrelse. Da beregnes verdien av pixels/inch.

//---------------------------------------------------------------------------

void __fastcall TForm6::OnComboBoxChange(TObject *Sender)
{

	UnicodeString strSelected = ComboBox1->Selected->Text;
	int isize = strSelected.ToInt();
	UnicodeString scrSize = Edit14->Text;
	int iscrSize = scrSize.ToInt();
	int iPixPerInch = round(iscrSize / isize);

	Edit15->Text = iPixPerInch;
}

Dynamisk Meny

Jeg har valgt å generere alle menyene ved runtime, da har jeg full kontroll. Tidligere fiklet jeg litt med C++Builder’s egen internasjonalisering, men den slo ikke helt an hos meg. Jeg bruker i stedet en variant av noe jeg har laget og brukt tidligere.

Der ligger alle menyteksten i en egen fil, og har du behov for oversettelse til et annet språk, sender du bare denne filen til en translatør.

Slik systemet er her nå, lastes det en DLL og ferdig med det, det er greit nok, men det skulle ikke være alt for vanskelig å skrive om dette til et antall DLL-er og angi f.eks. i en ini-fil hvilken som skal lastes.

Systemet er som følger:

Det hele starter i OnFormCreate:

void __fastcall TForm1::OnFormCreate(TObject *Sender)
{
   ---
   ---
   hLibHandle = LoadLibrary( L"DynDllProject.dll" ); // Menu strings

   if( hLibHandle )
	{

	iret = CreateDynMenu( hLibHandle );
        ---
        ---

Ikke noe hokus-pokus i denne funksjonen altså. Alle menyfunksjonene er lagt i «menu1.cpp», du vil finne den kildefilen i prosjektlisten til høyre i RAD Studio.

Hovedfunksjonen, «CreateDynMenu», burde nok kanskje vært skrevet om litt, ingen vanskelighet det heller, men jeg bruker fortsatt gamle ting som jeg vet funker og for å slippe og tenke for mye 🙂 ( Jeg har utelatt detaljer vedr. testing for oversiktens skyld)

int  __fastcall TForm1::CreateDynMenu(HINSTANCE hLibHandle)
{
         ---	
         ---
	CreateFileMenu( hLibHandle );
	CreateEditMenu( hLibHandle );
	CreateSearchMenu( hLibHandle );
	CreateImageMenu( hLibHandle );
         ---
         ---
         ---

Vi kan da gå videre til funksjonene for hver enkelt meny. De er i prinsippet helt like, omtrent som følgende fra File-menyen:

//---------------------------------------------------------------------------
// ------------------------ CreateFileMenu ----------------------------------
//---------------------------------------------------------------------------

int  __fastcall TForm1::CreateFileMenu(HINSTANCE hLibHandle)
{
	TMenuItem *ParentItem;
	TMenuItem *ParentItem2;
	TMenuItem *ChildItem;

	if( hLibHandle != 0)
	{
		charReturnFunc myFunc;

		// GetProcAddress returns a pointer to the loaded method
		myFunc = (charReturnFunc)GetProcAddress(hLibHandle, "GetFileMenuString");

		if( myFunc != 0)
		{
			ParentItem = new TMenuItem(MenuBar1);
			ParentItem->Text = myFunc(0, 256);          // 0 = "File"
			ParentItem->Name = ParentItem->Text;
			MenuBar1->AddObject(ParentItem );

			ChildItem = new TMenuItem(ParentItem);
			ChildItem->Text = myFunc(1, 256);           // 1 = "New"
			ChildItem->Name = ChildItem->Text;
			ChildItem->OnClick = menu_FILE_NEW_Click;
			ParentItem->AddObject(ChildItem);

Først må du hente opp adressen til funksjonen (det er standard bruk av en DLL) og selve menyteksten hentes opp med «ParentItem->Text = myFunc(0, 256)» hvor første parameter er nummeret til tekststringen og det andre tallet er en maks-verdi.

Komplisert? egentlig ikke, men vi er ikke ferdig ennu. Vi skal videre se på de enkelte menyfunksjoner i DLL-en her vist ved parameter 2 i «myfunc», nemlig «GetFileMenuString».

///////////////////////////////////////////////////////////////////////
//
//   wchar_t* GetFileMenuString(UINT uID, int maxlen)
//
//
wchar_t* GetFileMenuString(UINT uID, int maxlen)
{

	switch (uID) {
		case 0:
			return menu_FILE.c_str();                   // index    0
		case 1:
			return menu_FILE_NEW.c_str();               //  1
		case 2:
			return menu_FILE_OPEN.c_str();              //  2
		case 3:
			return menu_FILE_OPEN_RECENT.c_str();       //  3

OK, nå gjenstår det vel bare selve tekststringene som alle ligger i samme fil:

///////////////////////////////////////////////////////////////////////////////
//
// Lots and lots of string defs
//
// FireMonkey does not like resourcefiles, so
// this is my first solution to that problem.
//
// $Rev: 19001 $
//
// Copyright(c) 2019- Gamlisen Norway
//              All rights reserved
//
// ------------------------------------------------------------------
//  File menu textstrings
//
const String menu_FILE = _T("File");                    	// index 	0
const String menu_FILE_NEW = _T("New");                 	//          1
const String menu_FILE_OPEN = _T("Open");               	//          2
const String menu_FILE_OPEN_RECENT = _T("OpenRecent"); 		//          3
const String menu_FILE_CLOSE = _T("CloseFile");        		//          4
const String menu_FILE_CLOSE_ALL = _T("CloseAll");     		//          5
const String menu_FILE_SAVE = _T("SaveFile");          		//          6
const String menu_FILE_SAVE_AS = _T("SaveFileAs");     		//          7
const String menu_FILE_SAVE_PROJECT = _T("SaveProject"); 	//        	8
const String menu_FILE_SAVE_PROJECT_AS = _T("SaveProjectAs"); //  		9

Nå er vi ferdig med alle de enkelte delene som inngår i min form for dynamisk meny.

Komplisert?

Egentlig ikke når du først har forstått systemet 🙂 Samtlige funksjoner blir forklart i detalj på de enkelte sidene.

Gamlisens Fotobox

Planen var først å lage et program for enkel bilderedigering, for så ved neste utgave å legge til et dokumentformat, dvs. tilsvarende et skrivebord, et A4 ark (dokument redigering).

Status Jan. 2020:
Fotobox 1 kan anses ferdig med bl.a. copy/paste, crop og filter funksjoner.

Fotobox 2 ikke påbegynt p.g.a misnøye med Firemonkeys grafikkbibliotek. Dette gjør om alle formater til 32 bits RGB internt. Det betyr at om du åpner ett eller annet bildeformat og så lagrer det tilbake, vil det lagres som 32 bits RGB uansett hva det var på forhånd.

Jeg begynner likevel å legge ut eksempler fra kodingen, så får tid og humør bestemme den videre utvikling.