From 8a955d8180c15274f6b6d9659f0e445b85508279 Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@c028cbd2-c16b-5b4b-a496-9718f37d4682> Date: Wed, 1 Sep 2010 15:47:38 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'R_10_00'. git-svn-id: svn://10.65.10.50/branches/R_10_00@20818 c028cbd2-c16b-5b4b-a496-9718f37d4682 --- ba/bainst46.cpp | 20 + ha/ha0.cpp | 30 + ha/ha0.h | 4 + ha/ha01.gif | Bin 0 -> 11015 bytes ha/ha0200.cpp | 98 + ha/ha0200a.h | 25 + ha/ha0200a.uml | 299 +++ ha/ha0300.cpp | 393 ++++ ha/ha0300a.h | 54 + ha/ha0300a.uml | 405 ++++ ha/ha0500.cpp | 780 ++++++ ha/ha0500a.h | 15 + ha/ha0500a.uml | 167 ++ ha/halib.h | 20 + ha/hamenu.men | 10 + ha/hatabcom.txt | 18 + projects/ha.sln | 20 + res/moka.ico | Bin 0 -> 372526 bytes xvaga/treelistctrl.cpp | 5077 ++++++++++++++++++++++++++++++++++++++++ xvaga/treelistctrl.h | 552 +++++ 20 files changed, 7987 insertions(+) create mode 100755 ba/bainst46.cpp create mode 100755 ha/ha0.cpp create mode 100755 ha/ha0.h create mode 100755 ha/ha01.gif create mode 100755 ha/ha0200.cpp create mode 100755 ha/ha0200a.h create mode 100755 ha/ha0200a.uml create mode 100755 ha/ha0300.cpp create mode 100755 ha/ha0300a.h create mode 100755 ha/ha0300a.uml create mode 100755 ha/ha0500.cpp create mode 100755 ha/ha0500a.h create mode 100755 ha/ha0500a.uml create mode 100755 ha/halib.h create mode 100755 ha/hamenu.men create mode 100755 ha/hatabcom.txt create mode 100755 projects/ha.sln create mode 100755 res/moka.ico create mode 100755 xvaga/treelistctrl.cpp create mode 100755 xvaga/treelistctrl.h diff --git a/ba/bainst46.cpp b/ba/bainst46.cpp new file mode 100755 index 000000000..c320b3987 --- /dev/null +++ b/ba/bainst46.cpp @@ -0,0 +1,20 @@ +#include + +#include "bainstlib.h" + +class TInstall_HA : public TInstallmodule_app +{ + +protected: + virtual int module_number() const { return HAAUT; } + +public: + virtual ~TInstall_HA () {} +}; + +int bainst46(int argc, char** argv) +{ + TInstall_HA app; + app.run(argc, argv); + return 0; +} diff --git a/ha/ha0.cpp b/ha/ha0.cpp new file mode 100755 index 000000000..867514175 --- /dev/null +++ b/ha/ha0.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "ha0.h" + +#define usage "Error - usage : %s -{1|2|3|4}" + +int main(int argc,char** argv) + +{ + int rt = -1 ; + const int r = (argc > 1) ? atoi(&argv[1][1]) : -1; + + switch (r) + { + case 1: + rt = ha0200(argc, argv) ; break; //configurazione modulo Hardy + break; + case 2: + rt = ha0300(argc, argv) ; break; //gestione documenti premio (contratti premio) + break; + //case 3: + //rt = ha0400(argc,argv) ; break; //contabilizzazione contratti premio Hardy + case 4: + rt = ha0500(argc,argv) ; break; //elaborazione documenti Hardy + default: + error_box(usage, argv[0]) ; break; + } + return rt; +} diff --git a/ha/ha0.h b/ha/ha0.h new file mode 100755 index 000000000..3cfeea74a --- /dev/null +++ b/ha/ha0.h @@ -0,0 +1,4 @@ +int ha0200(int argc, char* argv[]); +int ha0300(int argc, char* argv[]); +//int ha0400(int argc, char* argv[]); +int ha0500(int argc, char* argv[]); diff --git a/ha/ha01.gif b/ha/ha01.gif new file mode 100755 index 0000000000000000000000000000000000000000..dd8f8fbc66fc7ae474299c06ebe8a31586df93a2 GIT binary patch literal 11015 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 A_5c6? literal 0 HcmV?d00001 diff --git a/ha/ha0200.cpp b/ha/ha0200.cpp new file mode 100755 index 000000000..ffdb9d526 --- /dev/null +++ b/ha/ha0200.cpp @@ -0,0 +1,98 @@ +#include +#include + +#include "ha0200a.h" + +/////////////////////////////////////////////////// +// Maschera +/////////////////////////////////////////////////// +class TConf_Hardy_mask : public TAutomask +{ +protected: + virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly); + +public: + void config_loader(TSheet_field& sf, const char* paragrafo); + void config_setter(TSheet_field& sf, const char* paragrafo); + TConf_Hardy_mask(const TFilename& f); + + virtual ~TConf_Hardy_mask(){}; +}; + +bool TConf_Hardy_mask::on_field_event(TOperable_field& o, TField_event e, long jolly) +{ + switch (o.dlg()) + { + default: + break; + } + return true; +} + +TConf_Hardy_mask::TConf_Hardy_mask(const TFilename& f) : TAutomask (f) +{ +} + +/////////////////////////////////////////////////// +// Applicazione +/////////////////////////////////////////////////// +class TConf_Hardy : public TConfig_application +{ + TMask* _ch; + +protected: + virtual TMask* create_mask(const TFilename& f); + virtual TMask* get_mask(); + +public: + virtual bool preprocess_config (TMask& mask, TConfig& config); + virtual bool postprocess_config (TMask& mask, TConfig& config); + virtual bool user_create( ); + virtual bool user_destroy( ); + + TConf_Hardy() : TConfig_application(CONFIG_DITTA), _ch(NULL) { } + virtual ~TConf_Hardy() { } +}; + +TMask* TConf_Hardy::get_mask() +{ + return _ch; +} + +TMask* TConf_Hardy::create_mask(const TFilename& f) +{ + if (_ch == NULL) + _ch = new TConf_Hardy_mask(f); + return _ch; +} + +bool TConf_Hardy::preprocess_config (TMask& mask, TConfig& config) +{ + return true; +} + +bool TConf_Hardy::postprocess_config (TMask& mask, TConfig& config) +{ + return true; +} + +bool TConf_Hardy::user_create( ) +{ + TConfig cfg(CONFIG_DITTA, "ha"); + cfg.set("EdMask", "ha0200a"); + return true; +} + +bool TConf_Hardy::user_destroy( ) +{ + if (_ch != NULL) + delete _ch; + return true; +} + +int ha0200(int argc, char* argv[]) +{ + TConf_Hardy app; + app.run(argc, argv, TR("Configurazione Hardy")); + return 0; +} \ No newline at end of file diff --git a/ha/ha0200a.h b/ha/ha0200a.h new file mode 100755 index 000000000..d354c52d7 --- /dev/null +++ b/ha/ha0200a.h @@ -0,0 +1,25 @@ +#define F_CODTIPO_FAT 201 +#define F_DESCRTIPO_FAT 202 +#define F_STATO_INI_FAT 203 +#define F_STATO_FIN_FAT 204 +//------------------------------ +#define F_CO_ANT_NUM 205 +#define F_CO_ANT_TIP 207 + +#define F_NA_ANT_NUM 210 +#define F_NA_ANT_TIP 212 +#define F_NA_ANT_SPE 213 +//------------------------------ +#define F_CO_POST_NUM 215 +#define F_CO_POST_TIP 217 + +#define F_NA_POST_NUM 220 +#define F_NA_POST_TIP 222 +#define F_NA_POST_SPE 223 +//------------------------------ +#define F_CO_RIFA_NUM 225 +#define F_CO_RIFA_TIP 227 + +#define F_NA_RIFA_NUM 230 +#define F_NA_RIFA_TIP 232 +#define F_NA_RIFA_SPE 233 diff --git a/ha/ha0200a.uml b/ha/ha0200a.uml new file mode 100755 index 000000000..21daa2c60 --- /dev/null +++ b/ha/ha0200a.uml @@ -0,0 +1,299 @@ +#include "ha0200a.h" + +TOOLBAR "topbar" 0 0 0 2 +#include +ENDPAGE + +PAGE "Configurazione Hardy" -1 -1 78 23 + +GROUPBOX DLG_NULL 76 4 +BEGIN + PROMPT 1 1 "@bTipo documento per fatture" +END + +STRING F_CODTIPO_FAT 4 +BEGIN + PROMPT 2 2 "Tipo " + USE %TIP + INPUT CODTAB F_CODTIPO_FAT + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CODTIPO_FAT CODTAB + OUTPUT F_DESCRTIPO_FAT S0 + CHECKTYPE REQUIRED + FLAG "UP" + FIELD TipoFatt +END + +STRING F_DESCRTIPO_FAT 50 +BEGIN + PROMPT 15 2 "" + USE %TIP KEY 2 + INPUT S0 F_DESCRTIPO_FAT + DISPLAY "Descrizione@60" S0 + DISPLAY "Codice" CODTAB + COPY OUTPUT F_CODTIPO_FAT + CHECKTYPE NORMAL +END + +STRING F_STATO_INI_FAT 1 +BEGIN + PROMPT 2 3 "Stato iniziale " + USE %STD + INPUT CODTAB F_STATO_INI_FAT + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_STATO_INI_FAT CODTAB + FLAGS "U" + CHECKTYPE REQUIRED + FIELD StatoIniFatt +END + +STRING F_STATO_FIN_FAT 1 +BEGIN + PROMPT 25 3 "Stato finale " + USE %STD + INPUT CODTAB F_STATO_FIN_FAT + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_STATO_FIN_FAT CODTAB + FLAGS "U" + CHECKTYPE REQUIRED + FIELD StatoFinFatt +END + +//--Anticipi------------- + +GROUPBOX DLG_NULL 76 4 +BEGIN + PROMPT 1 5 "@bAnticipi" +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 6 "@bContratti premi" +END + +STRING F_CO_ANT_NUM 4 +BEGIN + PROMPT 20 6 "Numerazione " + /*USE %NUM + INPUT CODTAB F_CO_ANT_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_ANT_NUM CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoAntNum +END + +STRING F_CO_ANT_TIP 4 +BEGIN + PROMPT 42 6 "Tipo " + /*USE %TIP + INPUT CODTAB F_CO_ANT_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_ANT_TIP CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoAntTip +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 7 "@bNote di Accredito" +END + +STRING F_NA_ANT_NUM 4 +BEGIN + PROMPT 20 7 "Numerazione " + USE %NUM + INPUT CODTAB F_NA_ANT_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_ANT_NUM CODTAB + CHECKTYPE REQUIRED + FIELD NaAntNum +END + +STRING F_NA_ANT_TIP 4 +BEGIN + PROMPT 42 7 "Tipo " + USE %TIP + INPUT CODTAB F_NA_ANT_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_ANT_TIP CODTAB + CHECKTYPE REQUIRED + FIELD NaAntTip +END + +STRING F_NA_ANT_SPE 8 +BEGIN + PROMPT 58 7 "Riga " + USE SPP + INPUT CODTAB F_NA_ANT_SPE + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_ANT_SPE CODTAB + CHECKTYPE REQUIRED + FIELD NaAntSpe +END + + +//--Posticipi------------- + +GROUPBOX DLG_NULL 76 4 +BEGIN + PROMPT 1 9 "@bPosticipi" +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 10 "@bContratti premi" +END + +STRING F_CO_POST_NUM 4 +BEGIN + PROMPT 20 10 "Numerazione " + /*USE %NUM + INPUT CODTAB F_CO_POST_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_POST_NUM CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoPostNum +END + +STRING F_CO_POST_TIP 4 +BEGIN + PROMPT 42 10 "Tipo " + /*USE %TIP + INPUT CODTAB F_CO_POST_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_POST_TIP CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoPostTip +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 11 "@bNote di Accredito" +END + +STRING F_NA_POST_NUM 4 +BEGIN + PROMPT 20 11 "Numerazione " + USE %NUM + INPUT CODTAB F_NA_POST_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_POST_NUM CODTAB + CHECKTYPE REQUIRED + FIELD NaPostNum +END + +STRING F_NA_POST_TIP 4 +BEGIN + PROMPT 42 11 "Tipo " + USE %TIP + INPUT CODTAB F_NA_POST_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_POST_TIP CODTAB + CHECKTYPE REQUIRED + FIELD NaPostTip +END + +STRING F_NA_POST_SPE 8 +BEGIN + PROMPT 58 11 "Riga " + USE SPP + INPUT CODTAB F_NA_POST_SPE + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_POST_SPE CODTAB + CHECKTYPE REQUIRED + FIELD NaPostSpe +END + +//--Rifatturazione------------- + +GROUPBOX DLG_NULL 76 4 +BEGIN + PROMPT 1 13 "@bRifatturazione" +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 14 "@bContratti premi" +END + +STRING F_CO_RIFA_NUM 4 +BEGIN + PROMPT 20 14 "Numerazione " + /*USE %NUM + INPUT CODTAB F_CO_RIFA_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_RIFA_NUM CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoRifaNum +END + +STRING F_CO_RIFA_TIP 4 +BEGIN + PROMPT 42 14 "Tipo " + /*USE %TIP + INPUT CODTAB F_CO_RIFA_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CO_RIFA_TIP CODTAB + CHECKTYPE REQUIRED*/ + FIELD CoRifaTip +END + +TEXT DLG_NULL +BEGIN + PROMPT 2 15 "@bNote di Accredito" +END + +STRING F_NA_RIFA_NUM 4 +BEGIN + PROMPT 20 15 "Numerazione " + USE %NUM + INPUT CODTAB F_NA_RIFA_NUM + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_RIFA_NUM CODTAB + CHECKTYPE REQUIRED + FIELD NaRifaNum +END + +STRING F_NA_RIFA_TIP 4 +BEGIN + PROMPT 42 15 "Tipo " + USE %TIP + INPUT CODTAB F_NA_RIFA_TIP + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_RIFA_TIP CODTAB + CHECKTYPE REQUIRED + FIELD NaRifaTip +END + +STRING F_NA_RIFA_SPE 8 +BEGIN + PROMPT 58 15 "Riga " + USE SPP + INPUT CODTAB F_NA_RIFA_SPE + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_NA_RIFA_SPE CODTAB + CHECKTYPE REQUIRED + FIELD NaRifaSpe +END + +ENDPAGE + +ENDMASK \ No newline at end of file diff --git a/ha/ha0300.cpp b/ha/ha0300.cpp new file mode 100755 index 000000000..f84167f27 --- /dev/null +++ b/ha/ha0300.cpp @@ -0,0 +1,393 @@ +#include +#include +#include + +#include +#include +#include + +#include "../mg/umart.h" +#include "../ve/condv.h" +#include "../ve/rcondv.h" + +#include "halib.h" +#include "ha0.h" +#include "ha0300a.h" + +////////////////////////////////////////////// +// Maschera +////////////////////////////////////////////// +class TDocumenti_premio_msk : public TAutomask +{ +protected: + bool find_prezzo_articolo(const TString& codart, real& prezzo, TString& um) const; + virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly); + +public: + TDocumenti_premio_msk(); +}; + + +TDocumenti_premio_msk::TDocumenti_premio_msk() : TAutomask("ha0300a") +{ +} + +bool TDocumenti_premio_msk::find_prezzo_articolo(const TString& codart, real& prezzo, TString& um) const +{ + //1) contratto + const long codcf = get_long(F_CODCF); + const TString& codcontr = get(F_CODCONTR); + + TToken_string key_umart; + key_umart.add(codart); + key_umart.add(1); + const TRectype& rec_umart = cache().get(LF_UMART, key_umart); + um = rec_umart.get(UMART_UM); + const real umart_prezzo = rec_umart.get_real(UMART_PREZZO); + + prezzo = umart_prezzo; //mal che vada sarà il prezzo di umart + TToken_string key; + + //CONTRATTI: tipo=C|catven=|tipocf=C|codcf=codcf|cod=codcontr|tiporiga=A|codriga=codart|um=um + key.add("C"); + key.add(""); + key.add("C"); + key.add(codcf); + key.add(codcontr); + + //per um è necessario se il contratto scelto ha la gestione delle um accesa (tanto per complicarsi la vita) + const bool gestum_contr = cache().get(LF_CONDV, key, CONDV_GESTUM) == "X"; + + key.add("A"); + key.add(codart); + if (gestum_contr) + key.add(um); + + const TRectype& rec_contratto = cache().get(LF_RCONDV, key); + const real contratto_prezzo = rec_contratto.get(RCONDV_PREZZO); + + //2) non c'è un prezzo sul contratto, prova con il listino standard + if (!contratto_prezzo.is_zero()) + prezzo = contratto_prezzo; + else + { + key.cut(0); + //LISTINI: tipo=L|catven=catven|tipocf=|codcf=|cod=codlis|tiporiga=A|codriga=codart|um=um + key.add("L"); + //la catven se c'è è del cliente + TToken_string key_cfven; + key_cfven.add("C"); + key_cfven.add(codcf); + const TString& catven = cache().get(LF_CFVEN, key_cfven, CFV_CATVEN); + key.add(catven); + key.add(""); + key.add(""); + const TString& codlis = get(F_CODLIS); + key.add(codlis); + + //per um è necessario se il listino scelto ha la gestione delle um accesa (tanto per complicarsi la vita) + const bool gestum_list = cache().get(LF_CONDV, key, CONDV_GESTUM) == "X"; + + key.add("A"); + key.add(codart); + if (gestum_list) + key.add(um); + + const TRectype& rec_listino = cache().get(LF_RCONDV, key); + const real listino_prezzo = rec_listino.get(RCONDV_PREZZO); + if (!listino_prezzo.is_zero()) + prezzo = listino_prezzo; + } + + return !prezzo.is_zero(); +} + +bool TDocumenti_premio_msk::on_field_event(TOperable_field& o, TField_event e, long jolly) +{ + switch (o.dlg()) + { + case F_TIPOCONTR: + if (e == fe_init || e == fe_modify) + { + //all'inizio deve proporre una lista dei possibili numerazioni e tipi che stanno in configurazione + TConfig config(CONFIG_DITTA, "ha"); + TString4 codnum, tipodoc; + + switch (o.get()[0]) + { + case 'A': + codnum = config.get("CoAntNum"); + tipodoc = config.get("CoAntTip"); + break; + case 'R': + codnum = config.get("CoRifaNum"); + tipodoc = config.get("CoRifaTip"); + break; + default: + codnum = config.get("CoPostNum"); + tipodoc = config.get("CoPostTip"); + break; + } + + set(F_CODNUM, codnum); + set(F_TIPODOC, tipodoc); + } + break; + case S_CODART: + if (e == fe_modify) + { + //caricamento del prezzo in fase modifica codart: sequenza contratto->listino->umart + //non è possibile mettere un prezzo a mano alla cazzo! + real prezzo; + TString4 um; + //se il prezzo l'ha trovato lo mette nel relativo campo + if (find_prezzo_articolo(o.get(), prezzo, um)) + { + TMask& row_mask = o.mask(); + row_mask.set(S_PREZZO, prezzo); + row_mask.set(S_UMQTA, um); + } + else + { + TString msg; + msg.format("Non esiste il prezzo per l'articolo %s nei listini selezionati!", (const char*)o.get()); + return error_box(msg); + } + } + break; + default: + break; + }; + return true; +} + +////////////////////////////////////////////// +// Applicazione +////////////////////////////////////////////// +class TDocumenti_premio : public TRelation_application +{ + TRelation* _rel; + TDocumenti_premio_msk* _msk; + +protected: + virtual bool user_create(); + virtual bool user_destroy(); + virtual TMask* get_mask(int) {return _msk;} + virtual TRelation* get_relation() const {return _rel;} + + void write_rows(const TMask& m); + void read_rows(TMask& m); + + virtual bool get_next_key(TToken_string& key); + virtual int write(const TMask& m); + virtual int rewrite(const TMask& m); + virtual int read(TMask& m); + + virtual void init_query_mode(TMask& m); + virtual void init_insert_mode(TMask& m); + virtual void init_modify_mode(TMask& m); +}; + + +//cerca il primo numero valido per NDOC +bool TDocumenti_premio::get_next_key(TToken_string& key) +{ + long n = 0; + + TLocalisamfile doc(LF_DOC); + TRectype& curr = doc.curr(); + const char provv = _msk->get(F_PROVV)[0]; + const int anno = _msk->get_int(F_ANNO); + const TString4 codnum = _msk->get(F_CODNUM); + + curr.put(DOC_PROVV, provv); + curr.put(DOC_ANNO, anno); + curr.put(DOC_CODNUM, codnum); + curr.put(DOC_NDOC, 9999999L); + + const int err = doc.read(_isgreat); + + if (err != _isemptyfile) + { + if (err == NOERR) + doc.prev(); + if (curr.get_char(DOC_PROVV) == provv && + curr.get_int(DOC_ANNO) == anno && + curr.get(DOC_CODNUM) == codnum) + n = curr.get_long(DOC_NDOC); + } + + n++; + + key.cut(0); + key.add(F_PROVV); key.add(provv); + key.add(F_ANNO); key.add(anno); + key.add(F_CODNUM); key.add(codnum); + key.add(F_NDOC); key.add(n); + + return n > 0; +} + +void TDocumenti_premio::read_rows(TMask& m) +{ + //chiave delle righe (escluso nriga) basata sulla testata + TToken_string rdoc_key; + + const char provv = m.get(F_PROVV)[0]; + const int anno = m.get_int(F_ANNO); + const TString& codnum = m.get(F_CODNUM); + const long ndoc = m.get_long(F_NDOC); + + rdoc_key.add(codnum); + rdoc_key.add(anno); + rdoc_key.add(provv); + rdoc_key.add(ndoc); + + //array con le righe che rispondono alla chiave di testata + TRecord_array righedoc(rdoc_key, LF_RIGHEDOC); + + //sheet e maschera di riga dello sheet + TSheet_field& sheet = m.sfield(F_RIGHE); + TMask& sm = sheet.sheet_mask(); + sheet.destroy(); + + //giro sulle righe documento + for (int i = 1; i <= righedoc.rows(); i++) + { + const TRectype& rec = righedoc.row(i); //record con l'elemento riga dell'array + const TString& tipo = rec.get(RDOC_TIPORIGA); //in base al tiporiga si devono fare operazioni diverse + //se è un tipo riga merce -> va aggiunta allo sheet + if (tipo == HARDY_TIPORIGA_MERCE || tipo.blank()) + { + TToken_string& row = sheet.row(-1); //aggiunge una riga vuota + for (int i = sm.fields()-1; i >= 0; i--) //giro su tutti i campi della maschera di riga... + { + TMask_field& mf = sm.fld(i); //aggiunge solo quelli che hanno un field + if ((mf.field() != NULL) && (mf.dlg() > 100)) //> 100 per evitare errori sui campi dlg_null + { + const int idx = sheet.cid2index(mf.dlg()); + row.add(mf.field()->read(rec), idx); + } + } + } + else if (tipo == HARDY_TIPORIGA_SOMMA)//se invece è la riga con le somme anticipate/maturate (solo 1 per contratto!) -> va messa in testata + { + const real anticipato = rec.get(RCA_2_ANTICIPATO); + const real maturato = rec.get(RCA_2_RESTITUITO); + m.set(F_ANTICIPATO, anticipato); + m.set(F_RESTITUITO, maturato); + } + + } +} + + +void TDocumenti_premio::write_rows(const TMask& m) +{ + //chiave delle righe basata sui campi di testata + const char provv = m.get(F_PROVV)[0]; + const int anno = m.get_int(F_ANNO); + const TString& codnum = m.get(F_CODNUM); + const long ndoc = m.get_long(F_NDOC); + + TRectype* key_rec = new TRectype(LF_RIGHEDOC); + + key_rec->put(RDOC_PROVV, provv); + key_rec->put(RDOC_ANNO, anno); + key_rec->put(RDOC_CODNUM, codnum); + key_rec->put(RDOC_NDOC, ndoc); + + //recordarray con le righe che rispondono alla chiave di testata key_rec + TRecord_array righedoc(LF_RIGHEDOC, RDOC_NRIGA); + righedoc.set_key(key_rec); + + //sheet e maschera di riga dello sheet + TSheet_field& sheet = m.sfield(F_RIGHE); + TMask& sm = sheet.sheet_mask(); + + //giro sulle righe dello sheet (righe di tipo merce) + FOR_EACH_SHEET_ROW(sheet, i, row) + { + TRectype& rec = righedoc.row(i+1, true); //record con l'elemento riga dell'array + for (int i = sm.fields()-1; i >= 0; i--) //giro su tutti i campi della maschera di riga... + { + TMask_field& mf = sm.fld(i); //aggiunge solo quelli che hanno un field + if ((mf.field() != NULL) && (mf.dlg() > 100)) //> 100 per evitare errori sui campi dlg_null + { + const int idx = sheet.cid2index(mf.dlg()); + mf.field()->write(row->get(idx), rec); + } + } + rec.put(RDOC_TIPORIGA, HARDY_TIPORIGA_MERCE); + } + //salva la riga di tipo somme anticipate/rimborsate (H02) che in realtà è in testata + const int righedoc_items = righedoc.rows(); + TRectype& last_rec = righedoc.row(righedoc_items + 1, true); + const real anticipato = m.get_real(F_ANTICIPATO); + const real maturato = m.get_real(F_RESTITUITO); + last_rec.put(RDOC_QTAGG4, anticipato); + last_rec.put(RDOC_QTAGG5, maturato); + last_rec.put(RDOC_TIPORIGA, HARDY_TIPORIGA_SOMMA); + + //e alla fine della fiera scrive tutto ufficialmente + righedoc.rewrite(); +} + + +void TDocumenti_premio::init_query_mode(TMask& m) +{ +} + +void TDocumenti_premio::init_insert_mode(TMask& m) +{ +} + +void TDocumenti_premio::init_modify_mode(TMask& m) +{ + m.disable(F_TIPOCONTR); //non si può cambiare il tipo contratto una volta stabilito sennò non funziona + un cazzo +} + +int TDocumenti_premio::write(const TMask& m) +{ + const int err = TRelation_application::write(m); + if (err == NOERR) + write_rows(m); + return err; +} + +int TDocumenti_premio::rewrite(const TMask& m) +{ + const int err = TRelation_application::rewrite(m); + if (err == NOERR) + write_rows(m); + return err; +} + +int TDocumenti_premio::read(TMask& m) +{ + const int err = TRelation_application::read(m); + if (err == NOERR) + read_rows(m); + return err; +} + +bool TDocumenti_premio::user_create() +{ + _rel = new TRelation(LF_DOC); + _msk = new TDocumenti_premio_msk; + return true; +} + +bool TDocumenti_premio::user_destroy() +{ + delete _rel; + delete _msk; + return true; +} + +int ha0300(int argc, char* argv[]) +{ + TDocumenti_premio a; + a.run(argc, argv, TR("Documenti premio Hardy")); + return 0; +} diff --git a/ha/ha0300a.h b/ha/ha0300a.h new file mode 100755 index 000000000..171efeab4 --- /dev/null +++ b/ha/ha0300a.h @@ -0,0 +1,54 @@ +//campi maschera ha0300a.uml + +#define F_CODNUM 201 +#define F_TIPODOC 202 +#define F_PROVV 203 +#define F_ANNO 204 + +#define F_TIPOCF 205 +#define F_CODCF 206 +#define F_DESCF 207 + +#define F_CODCONTR 210 +#define F_DESCONTR 211 +#define F_CODLIS 212 +#define F_DESLIS 213 + +#define F_NDOC 215 +#define F_DATADOC 216 +#define F_STATO 217 + +#define F_CODAG 220 +#define F_DESCRAG 221 +#define F_CODPAG 222 +#define F_DESCRPAG 223 +#define F_TIPOCONTR 224 +#define F_DATACOMP 225 +#define F_DATAFCOMP 226 +#define F_ANTICIPATO 227 +#define F_RESTITUITO 228 + +#define F_RIGHE 500 //questo va messo 500 sennò ve0 si incazza e non funziona più + +//campi della maschera riga sheet +#define S_CODART 101 +#define S_DESCR 102 +#define S_UMQTA 103 +#define S_PREZZO 104 +#define S_PREMIO 105 +#define S_RICARICO 106 +#define S_MATURATO 107 + +//vadetecum per il sagace programmatore sui campi dei CONTRATTI PREMI HARDY + +//campo msk/sheet campo file tipo riga +//S_PREMIO QTAGG1 H01 +//S_RICARICO QTAGG2 H01 +//S_MATURATO QTAGG5 H01 + +// QTAGG3 H01 - H02 -> usato come campo di appoggio per gli importi in elaborazione ha0500 +//F_ANTICIPATO QTAGG4 H02 +//F_RESTITUITO QTAGG5 H02 + +//ATTENZIONE! QTA H01 - H02 -> usato come campo di appoggio per umqta in elaborazione ha0500 +//ATTANZIONE! UMQTA H01 - H02 -> usato come campo di appoggio per qta in elaborazione ha0500 \ No newline at end of file diff --git a/ha/ha0300a.uml b/ha/ha0300a.uml new file mode 100755 index 000000000..35e9801dd --- /dev/null +++ b/ha/ha0300a.uml @@ -0,0 +1,405 @@ +#include "ha0300a.h" + +TOOLBAR "" 0 0 0 2 + +#include + +ENDPAGE + +PAGE "Contratto premi Hardy" -1 -1 78 23 + +GROUPBOX DLG_NULL 78 13 +BEGIN + PROMPT 1 0 "" +END + +RADIOBUTTON F_TIPOCONTR 1 76 +BEGIN + PROMPT 2 0 "@bTipo contratto" + ITEM "A|Anticipo" MESSAGE CLEAR,F_DATAFCOMP|ENABLE,1@ + ITEM "P|Posticipo" MESSAGE ENABLE,F_DATAFCOMP|CLEAR,1@ + ITEM "R|Rifatturazione" MESSAGE CLEAR,F_DATAFCOMP|ENABLE,1@ + FIELD TIPOCFFATT + FLAGS "GZ" + KEY 1 +END + +STRING F_CODNUM 4 +BEGIN + PROMPT 102 101 "Cod. num. " + FIELD CODNUM + USE %NUM KEY 1 + INPUT CODTAB F_CODNUM + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + CHECKTYPE NORMAL + FLAGS "GDU" + KEY 1 +END + +STRING F_TIPODOC 4 +BEGIN + FIELD TIPODOC + PROMPT 120 101 "Tipo doc. " + USE %TIP KEY 1 + INPUT CODTAB F_TIPODOC + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + CHECKTYPE NORMAL + FLAGS "GDU" +END + +LIST F_TIPOCF 9 +BEGIN + PROMPT 130 101 "" + FIELD TIPOCF + IT "C|Cliente" + FLAGS "D" +END + +STRING F_CODCF 6 +BEGIN + PROMPT 2 3 "Cliente " + WARNING "Cliente assente" + HELP "Codice del cliente del documento" + FLAGS "R" + FIELD CODCF + USE LF_CLIFO KEY 1 + INPUT TIPOCF "C" + INPUT CODCF F_CODCF + DISPLAY "Codice" CODCF + DISPLAY "Ragione Sociale@50" RAGSOC + DISPLAY "Partita IVA@12" PAIV + DISPLAY "Sospeso" SOSPESO + OUTPUT F_CODCF CODCF + OUTPUT F_DESCF RAGSOC + CHECKTYPE REQUIRED + ADD RUN cg0 -1 C +END + +STRING F_DESCF 50 +BEGIN + WARNING "Cliente assente" + HELP "Ragione sociale del cliente del documento" + PROMPT 24 3 "" + USE LF_CLIFO KEY 2 + INPUT TIPOCF "C" + INPUT RAGSOC F_DESCF + DISPLAY "Ragione Sociale@50" RAGSOC + DISPLAY "Partita IVA@12" PAIV + DISPLAY "Codice" CODCF + COPY OUTPUT F_CODCF + CHECKTYPE REQUIRED + ADD RUN cg0 -1 C +END + +LIST F_PROVV 1 +BEGIN + PROMPT 140 101 "" + ITEM "D|D" + FIELD PROVV + FLAGS "D" + KEY 1 +END + +NUMBER F_ANNO 4 +BEGIN + PROMPT 2 4 "Esercizio " + USE ESC + INPUT CODTAB F_ANNO + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_ANNO CODTAB + FIELD ANNO + CHECKTYPE REQUIRED + FLAGS "A" + KEY 1 +END + +NUMBER F_NDOC 6 +BEGIN + PROMPT 24 4 "N. contr. premi " + USE LF_DOC SELECT BETWEEN(CODCF,#F_CODCF,#F_CODCF) + INPUT CODNUM F_CODNUM SELECT + INPUT PROVV F_PROVV SELECT + INPUT ANNO F_ANNO SELECT + INPUT NDOC F_NDOC + DISPLAY "Numero" NDOC + DISPLAY "Data doc." DATADOC + DISPLAY "Inizio validita" DATACOMP + DISPLAY "Fine validita" DATAFCOMP + DISPLAY "Tipo" TIPOCFFATT + DISPLAY "Agente" CODAG + OUPUT F_NDOC NDOC + FIELD NDOC + KEY 1 + CHECKTYPE REQUIRED +END + +DATE F_DATADOC +BEGIN + PROMPT 50 4 "Data " + FIELD DATADOC +END + +STRING F_STATO 1 +BEGIN + PROMPT 69 4 "Stato " + FIELD STATO + USE %STD KEY 1 + INPUT CODTAB F_STATO + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_STATO CODTAB + CHECKTYPE NORMAL + FLAGS "DG" +END + +STRING F_CODCONTR 3 +BEGIN + PROMPT 2 5 "Listino cliente " + USE CONDV + INPUT TIPO "C" + INPUT TIPOCF F_TIPOCF SELECT + INPUT CODCF F_CODCF SELECT + INPUT COD F_CODCONTR + DISPLAY "Codice" COD + DISPLAY "Descrizione@50" DESCR + DISPLAY "Inizio validita'" VALIN + DISPLAY "Fine validità" VALFIN + OUTPUT F_CODCONTR COD + OUTPUT F_DESCONTR DESCR + FIELD CODCONT + CHECKTYPE NORMAL +END + +STRING F_DESCONTR 50 +BEGIN + PROMPT 26 5 "" + FLAGS "DG" +END + +STRING F_CODLIS 3 +BEGIN + PROMPT 2 6 "Listino standard " + USE CONDV + INPUT TIPO "L" + INPUT COD F_CODLIS + DISPLAY "Codice" COD + DISPLAY "Descrizione@50" DESCR + DISPLAY "Inizio validita'" VALIN + DISPLAY "Fine validità" VALFIN + OUTPUT F_CODLIS COD + OUTPUT F_DESLIS DESCR + FIELD CODLIST + CHECKTYPE NORMAL +END + +STRING F_DESLIS 50 +BEGIN + PROMPT 26 6 "" + FLAGS "DG" +END + +STRING F_CODAG 5 +BEGIN + PROMPT 2 7 "Agente " + FIELD CODAG + USE LF_AGENTI + INPUT CODAGE F_CODAG + DISPLAY "Codice@8R" CODAGE + DISPLAY "Descrizione@50" RAGSOC + OUTPUT F_CODAG CODAGE + OUTPUT F_DESCRAG RAGSOC + CHECKTYPE NORMAL + FLAGS "UZ" +END + +STRING F_DESCRAG 50 +BEGIN + PROMPT 24 7 "" + USE LF_AGENTI KEY 2 + INPUT RAGSOC F_DESCRAG + DISPLAY "Descrizione@50" RAGSOC + DISPLAY "Codice@8R" CODAGE + COPY OUTPUT F_CODAG + CHECKTYPE NORMAL +END + +STRING F_CODPAG 4 +BEGIN + PROMPT 2 8 "Cond. pag. " + FIELD CODPAG + USE %CPG + INPUT CODTAB F_CODPAG + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CODPAG CODTAB + OUTPUT F_DESCRPAG S0 + CHECKTYPE NORMAL + FLAGS "U" + HE "Inserisci il codice del tipo di pagamento" + WA "Codice tipo pagamento non trovato" + ADD RUN ba3 -6 +END + +STRING F_DESCRPAG 50 +BEGIN + PROMPT 24 8 "" + USE %CPG KEY 2 + INPUT S0 F_DESCRPAG + DISPLAY "Descrizione@50" S0 + DISPLAY "Codice" CODTAB + COPY OUTPUT F_CODPAG + CHECKTYPE NORMAL + HE "Inserisci il codice del tipo di pagamento" + WA "Codice tipo pagamento non trovato" + ADD RUN ba3 -6 +END + +DATE F_DATACOMP +BEGIN + PROMPT 2 9 "Inizio validita' " + FIELD DATACOMP + CHECKTYPE REQUIRED +END + +DATE F_DATAFCOMP +BEGIN + PROMPT 35 9 "Fine validita' " + FIELD DATAFCOMP +END + +GROUPBOX DLG_NULL 76 3 +BEGIN + PROMPT 2 10 "@bSomme anticipate/restituite" +END + +CURRENCY F_ANTICIPATO +BEGIN + PROMPT 3 11 "Anticipato " + GROUP 1 +END + +CURRENCY F_RESTITUITO +BEGIN + PROMPT 38 11 "Restituito " + GROUP 1 +END + +SPREADSHEET F_RIGHE +BEGIN +PROMPT 2 13 "" +ITEM "Codice Articolo@20" +ITEM "Descrizione@40" +ITEM "UM@2" +ITEM "Prezzo listino" +ITEM "Premio@10" +ITEM "Ns. carico" +ITEM "Bonus@10" +END + +ENDPAGE + +ENDMASK + +///////////////////////////////////////// +//maschera di riga +TOOLBAR "topbar" 0 0 0 2 + +BUTTON DLG_OK 2 2 +BEGIN + PROMPT 1 1 "" +END + +BUTTON DLG_DELREC 2 2 +BEGIN + PROMPT 2 1 "" +END + +BUTTON DLG_CANCEL 2 2 +BEGIN + PROMPT 3 1 "" +END + +ENDPAGE + +PAGE "Riga contratto premi Hardy" -1 -1 76 12 + +STRING S_CODART 20 +BEGIN + PROMPT 1 1 "Articolo " + USE LF_ANAMAG KEY 1 + INPUT CODART S_CODART + DISPLAY "Codice@20" CODART + DISPLAY "Descrizione@50" DESCR + OUTPUT S_CODART CODART + OUTPUT S_DESCR DESCR + WARNING "Articolo assente" + FLAGS "U" + FIELD CODART + ADD RUN ve2 -3 +END + +STRING S_DESCR 50 +BEGIN + PROMPT 1 2 "Descrizione " + USE 47 KEY 2 + INPUT DESCR S_DESCR + DISPLAY "Descrizione@50" DESCR + DISPLAY "Codice@20" CODART + COPY OUTPUT S_CODART + FIELD DESCR +END + +STRING S_UMQTA 2 +BEGIN + PROMPT 1 3 "U.M. " + USE LF_UMART KEY 2 + JOIN %UMS INTO CODTAB=UM + INPUT CODART S_CODART SELECT + INPUT UM S_UMQTA + DISPLAY "Codice@20" UM + DISPLAY "Descrizione@50" %UMS->S0 + OUTPUT S_UMQTA UM + FIELD UMQTA + FLAGS "U" + CHECKTYPE REQUIRED +END + +GROUPBOX DLG_NULL 74 6 +BEGIN + PROMPT 1 4 "@bValori" +END + +CURRENCY S_PREZZO +BEGIN + PROMPT 2 5 "Prezzo listino " + FLAGS "UDG" + FIELD PREZZO +END + +CURRENCY S_PREMIO +BEGIN + PROMPT 2 6 "Premio " + FLAGS "U" + FIELD QTAGG1 +END + +CURRENCY S_RICARICO +BEGIN + PROMPT 2 7 "A Ns. carico " + FLAGS "U" + FIELD QTAGG2 +END + +CURRENCY S_MATURATO +BEGIN + PROMPT 2 8 "Bonus maturato " + FLAGS "DU" + FIELD QTAGG5 +END + +ENDPAGE + +ENDMASK \ No newline at end of file diff --git a/ha/ha0500.cpp b/ha/ha0500.cpp new file mode 100755 index 000000000..cd29b853e --- /dev/null +++ b/ha/ha0500.cpp @@ -0,0 +1,780 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../cg/cglib01.h" +#include "../ve/velib.h" + +#include "halib.h" +#include "ha0.h" +#include "ha0500a.h" + +/////////////////////////////////////////////////////////// +// TAutomask +/////////////////////////////////////////////////////////// +class THardy_elab_docs_mask : public TAutomask +{ +protected: + virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly); + +public: + THardy_elab_docs_mask(); + ~THardy_elab_docs_mask(); +}; + + +THardy_elab_docs_mask::THardy_elab_docs_mask() : TAutomask ("ha0500a") +{ +} + +THardy_elab_docs_mask::~THardy_elab_docs_mask() +{ +} + +bool THardy_elab_docs_mask::on_field_event(TOperable_field& o, TField_event e, long jolly) +{ + switch (o.dlg()) + { + case F_ADATA: + if (e == fe_close || e == fe_modify) + { + //se la data iniziale è piena -> l'anno deve essere lo stesso nelle 2 date; + //se invece è vuota -> la data iniziale viene presa come la data iniziale dell'esercizio della data finale... + //..ma questo qui non serve e viene rinviato alla query principale del recordset + const TDate adata = get_date(F_ADATA); + TEsercizi_contabili esc; + const int adata_esc = esc.date2esc(adata); + + TDate dadata = o.get(); + if (dadata.ok()) + { + const int dadata_esc = esc.date2esc(dadata); + if (adata_esc != dadata_esc) + return error_box("Le date devono appartenere allo stesso esercizio!"); + } + } + break; + /*case F_TIPOCONTR: + if (e == fe_modify) + { + //in base alla tipologia di contratti da elaborare decide numerazione e tipo delle NAC da generare + TConfig config(CONFIG_DITTA, "ha"); + TString4 codnum, tipodoc; + + switch (o.get()[0]) + { + case 'A': + codnum = config.get("NaAntNum"); + tipodoc = config.get("NaAntTip"); + break; + case 'R': + codnum = config.get("NaRifaNum"); + tipodoc = config.get("NaRifaTip"); + break; + default: + codnum = config.get("NaPostNum"); + tipodoc = config.get("NaPostTip"); + break; + } + + set(F_CODNUM_NAC, codnum); + set(F_CODTIPO_NAC, tipodoc); + } + break;*/ + //in caso di elaborazione definitiva è obbligatorio.. + //..eliminare tutte le NAC provvisorie generate in precedenza + case F_DEFINITIVO: + if (e == fe_modify) + { + if (o.get() == "X") + { + set(F_KILLPROVV, "X"); + disable(F_KILLPROVV); + } + else + { + enable(F_KILLPROVV); + set(F_KILLPROVV, " "); + } + } + break; + default: + break; + } + return true; +} + + + +/////////////////////////////////////// +// TSkeleton_application +/////////////////////////////////////// +class THardy_elab_docs : public TSkeleton_application +{ + +protected: + //metodi alto livello + void elabora(const TMask& mask); + int kill_provv_nac(const TMask& mask); + long genera_recordset(const TMask& mask, TISAM_recordset& recset); + void elabora_documenti(const TMask& mask, TISAM_recordset& recset, TLog_report& log); + + //metodi medio livello + bool aggiorna_contratti_anticipo(const TString& rdoc_codart, const real& rdoc_qta, TDocumento& contratto); + bool aggiorna_contratti_posticipo(const TString& rdoc_codart, const real& rdoc_qta, TDocumento& contratto); + bool aggiorna_contratti(TDocumento& curr_doc, TArray& contratti_cliente); + bool genera_nac(const TMask& mask, TArray& contratti_cliente, TArray& documenti_cliente, TLog_report& log); + + //metodi basso livello + int find_contratti_cliente(const long codcf, const TMask& mask, TArray& contratti_cliente); + void check_date(const TDate& datafine, TDate& dataini); + int find_numerazioni(const TString& tipo_to_elab, TString_array& num_doc); + +public: + virtual void main_loop(); + virtual bool create(); + +}; + +//metodo per ricavare la data iniziale di elaborazione qualora l'utonto non la metta e l'esercizio da usare +void THardy_elab_docs::check_date(const TDate& datafine, TDate& dataini) +{ + TEsercizi_contabili esc; + TDate datafine_tmp = datafine; + const int esercizio = esc.date2esc(datafine); + esc.code2range(esercizio, dataini, datafine_tmp); +} + +int THardy_elab_docs::find_numerazioni(const TString& tipo_to_elab, TString_array& num_doc) +{ + TISAM_recordset num_recset("USE %NUM"); + for (bool ok = num_recset.move_first(); ok; ok = num_recset.move_next()) //giro sui vari tipi numerazione + { + const TString4 codtab = num_recset.get("CODTAB").as_string(); + + const TCodice_numerazione numerazione(codtab); + for (int t = numerazione.ntipi_doc() - 1; t >= 0; t--) + { + const TString& tipo_doc = numerazione.tipo_doc(t); + if (tipo_doc == tipo_to_elab) + { + if (num_doc.find(codtab) < 0) // Evito aggiunta di doppioni + num_doc.add(codtab); + break; + } + } //for (int t = codnum.. + } //for (bool ok = num_recset... + + return num_doc.items(); +} + + +//metodo per accoppare tutte le NAC provvisorie generate in precedenza +int THardy_elab_docs::kill_provv_nac(const TMask& mask) +{ + int nac_killed = 0; + const TDate& adata = mask.get_date(F_ADATA); + int anno = adata.year(); + + TConfig config(CONFIG_DITTA, "ha"); + + TToken_string numerazioni; + numerazioni.add(config.get("NaAntNum")); + numerazioni.add(config.get("NaRifaNum")); + numerazioni.add(config.get("NaPostNum")); + + FOR_EACH_TOKEN(numerazioni, codnum) + { + TRelation rel_doc(LF_DOC); + + TRectype& rec = rel_doc.curr(); + rec.put(DOC_PROVV, "P"); + rec.put(DOC_ANNO, anno); + rec.put(DOC_CODNUM, codnum); + + TCursor cur_doc (&rel_doc, "", 1, &rec, &rec); + const long items = cur_doc.items(); + cur_doc.freeze(); + TProgind progind(items, "Eliminazione NAC provvisorie in corso...", false, true); + + for (cur_doc = 0; cur_doc.pos() < items; ++cur_doc) + { + progind.addstatus(1); + TDocumento doc(rec); + int err = doc.remove(); + if (err == NOERR) + nac_killed++; + } + } + return nac_killed; +} + +//metodo che filtra tutti i documenti in base ai parametri della maschera +long THardy_elab_docs::genera_recordset(const TMask& mask, TISAM_recordset& recset) +{ + //parametri di elaborazione + //per prima cosa controlla se il cliente è stato specificato; questo influisce sulla scelta della chiave di ricerca sul file + int key = 3; + const long codcf = mask.get_long(F_CODCF); + if (codcf > 0) + key = 2; + + //e costruiamola 'sta query! + //usa i documenti con chiave variabile (vedi sopra) + TString query; + query << "USE DOC KEY " << key; + //lo stato dipende da quanto sta scritto sulla elaborazione differita (stato iniziale dei docs da considerare) + //viene messo CODNUM nella SELECT perchè, essendoci un range di date nelle chiavi, la numerazione verrebbe ignorata! (provato!) + query << "\nSELECT (STATO=#STATOINI)"; + + //in base al tipo documento che si deve elaborare (settato in configurazione), ci possono essere più numerazioni da considerare! + TConfig config(CONFIG_DITTA, "ha"); + const TString& tipo_to_elab = config.get("TipoFatt"); + //e adesso cerca le numerazioni che contengono il tipo preso dalla configurazione + TString_array num_doc; + const int numerazioni_valide = find_numerazioni(tipo_to_elab, num_doc); + if (numerazioni_valide > 0) + { + query << "&&("; + + for (int i = 0; i < numerazioni_valide; i++) + { + if (i > 0) + query << "||"; + + query << "(CODNUM='" << num_doc[i] << "')"; + } + + query << ")"; + } + //se c'è l'agente specificato... + const TString& agente = mask.get(F_CODAGE); + const bool has_007 = agente.full(); + if (has_007) + query << "&&(CODAG=#CODAG)"; + //ordinamento agente-codcf-numdoc + query << "\nBY "; + if (has_007) + query << DOC_CODAG << " "; + + query << DOC_CODCF << " " << DOC_NDOC; + + //from-to dipendente da chiave + switch (key) + { + case 2: //chiave per tipocf-codcf-provv-anno-datadoc-codnum + { + query << "\nFROM TIPOCF=C CODCF=#CODCF PROVV=D ANNO=#ANNO DATADOC=#DADATA"; + query << "\nTO TIPOCF=C CODCF=#CODCF PROVV=D ANNO=#ANNO DATADOC=#ADATA"; + } + break; + + case 3: //chiave per datadoc-provv-anno-codnum + { + query << "\nFROM DATADOC=#DADATA PROVV=D ANNO=#ANNO"; + query << "\nTO DATADOC=#ADATA PROVV=D ANNO=#ANNO"; + } + break; + + default: + break; + } + + //setta la nuova query al recordset (inizialmente nato dequeryzzato) + recset.set(query); + + //settaggio delle variabili + const TDate adata = mask.get_date(F_ADATA); + TDate dadata = mask.get_date(F_DADATA); + //se la data iniziale è vuota deve coincidere con l'inizio dell'esercizio della data finale (obbligatoria!) + int esc = adata.year(); + if (!dadata.ok()) + check_date(adata, dadata); + + //lo stato dei documenti da considerare va a raccatarlo nel config + const TString& stato_ini = config.get("StatoIniFatt"); + recset.set_var("#STATOINI", stato_ini); + + if (agente.full()) + recset.set_var("#CODAG", agente); + if (codcf > 0) + recset.set_var("#CODCF", codcf); + recset.set_var("#ANNO", long(esc)); + recset.set_var("#DADATA", dadata); + recset.set_var("#ADATA", adata); + + return recset.items(); +} + +//metodo che riempie un array con tutti i contratti del cliente passatogli (in base alla tipologia di contratti da elaborare) +int THardy_elab_docs::find_contratti_cliente(const long codcf, const TMask& mask, TArray& contratti_cliente) +{ + contratti_cliente.destroy(); + //deve cercare tutti i contratti del cliente e metterli nell'array + TString query; + query << "USE DOC KEY 4"; + query << "\nSELECT ((CODNUM=#A_CODNUM)||(CODNUM=#R_CODNUM)||(CODNUM=#P_CODNUM))"; + query << "\nFROM TIPOCF=C CODCF=#CODCF"; + query << "\nTO TIPOCF=C CODCF=#CODCF"; + + TISAM_recordset recset(query); + + //settaggio delle variabili + //il codice numerazione lo trova nella configurazione Hardy, e lo deve scegliere in base alla tipologia di contratti che sta esaminando! + TConfig config(CONFIG_DITTA, "ha"); + + const TString& num_ant = config.get("CoAntNum"); + const TString& num_rifa = config.get("CoRifaNum"); + const TString& num_post = config.get("CoPostNum"); + + recset.set_var("#A_CODNUM", num_ant); + recset.set_var("#R_CODNUM", num_rifa); + recset.set_var("#P_CODNUM", num_post); + + recset.set_var("#CODCF", codcf); + + const long n_contratti = recset.items(); //questo serve solo al sagace programmatore + + //aggiunge i contratti all'array: solo quelli in auge nel periodo di calcolo selezionato sulla maschera! + for (bool ok = recset.move_first(); ok; ok = recset.move_next()) + { + //controlla il tipo di contratto + const char tipo_contr = recset.get(DOC_TIPOCFFATT).as_string()[0]; + + //contratti anticipo 'A': datainizio esiste sempre, datafine non esiste (va ad esaurimento) + //contratti posticipo 'P': datainizio esiste sempre, datafine può non esistere + //contratti rifatturazione 'R': come contratti anticipo + + //controlla validità del contratto con le date scelte per l'elaborazione dei documenti + const TDate data_ini_contratto = recset.get(DOC_DATACOMP).as_date(); + + TDate data_ini_elab = mask.get_date(F_DADATA); + const TDate data_fine_elab = mask.get_date(F_ADATA); + if (!data_ini_elab.ok()) + check_date(data_fine_elab, data_ini_elab); + + //quindi la datainizio vale per tutti allo stesso modo (è obbligatoria nei contratti) + //se l'elaborazione finisce prima che cominci il contratto -> il contratto non serve a nulla + if (data_ini_contratto > data_fine_elab) + continue; + //la data fine vale invece solo per i contratti 'P' e potrebbe non esserci (contratti senza scadenza) + TDate data_fine_contratto; + if (tipo_contr == 'P') + { + data_fine_contratto = recset.get(DOC_DATAFCOMP).as_date(); + if (data_fine_contratto.ok()) + { + if (data_fine_contratto < data_ini_elab) + continue; + } + } + + TDocumento* curr_contratto = new TDocumento(recset.get(DOC_PROVV).as_string()[0], recset.get(DOC_ANNO).as_int(), + recset.get(DOC_CODNUM).as_string(), recset.get(DOC_NDOC).as_int()); + contratti_cliente.add(curr_contratto); + } + + return contratti_cliente.items(); +} + + +bool THardy_elab_docs::aggiorna_contratti_anticipo(const TString& rdoc_codart, const real& rdoc_qta, TDocumento& contratto) +{ + bool elaborato = false; + + FOR_EACH_PHYSICAL_RDOC(contratto, rm, rigamerce) + { + const TString& rigamerce_codart = rigamerce->get(RDOC_CODART); + //se trova il codart in una delle righe di contratto... + if (rdoc_codart == rigamerce_codart) + { + const real rigamerce_premio = rigamerce->get_real(RC_1_PREMIO); + //se il premio non è nullo procede all'aggiornamento del restituito + if (rigamerce_premio != ZERO) + { + //aggiornamento delle righe di tipo spesa (verigH02) per aggiornare le somme restituite + FOR_EACH_PHYSICAL_RDOC(contratto, ra, rigacontratto) + { + //cerca una riga anticipo da evadere sul contratto per aggiornare la somma restituita sull'anticipo + if (rigacontratto->is_spese()) + { + //usa qtagg3 come campo di appoggio per il calcolo della somma restituita dovuta al contratto (parte da zero per ogni elaborazione di NAC) + real somma_restituita = rigacontratto->get_real(RCA_2_RESTITUITO); + //la somma restituita deve essere incrementata per la qta di merce (caffè) che c'è sulla riga della fattura in esame... + //..moltiplicata per il premio che c'è nella riga di tipo merce del contratto in esame + somma_restituita += rdoc_qta * rigamerce_premio; + rigacontratto->put(RCA_2_RESTITUITO, somma_restituita); + elaborato = true; + } + } //FOR_EACH_PHYSICAL.. fine casino sulla riga di tipo spese + + if (rigamerce->is_merce()) + { + real somma_bonus = rigamerce->get_real(RC_1_BONUS); + somma_bonus += rdoc_qta * rigamerce_premio; + rigamerce->put(RC_1_BONUS, somma_bonus); + elaborato = true; + } + } + + //aggiornamento delle quantità per le righe di tipo merce (verigH01) + if (rigamerce->is_merce()) + { + real qta_tot = rigamerce->get_real(RDOC_QTA); //prende la qta tot dal contratto (è usato un campo di appoggio RDOC_QTA) + qta_tot += rdoc_qta; //aggiunge la qta della riga documento + rigamerce->put(RDOC_QTA, qta_tot); //riscrive sul campo di appoggio del contratto + } + + } //if(rdoc_codart... + } //FOR_EACH_PHYSICAL.. + return elaborato; +} + + +bool THardy_elab_docs::aggiorna_contratti_posticipo(const TString& rdoc_codart, const real& rdoc_qta, TDocumento& contratto) +{ + bool elaborato = false; + + FOR_EACH_PHYSICAL_RDOC(contratto, rm, rigamerce) + { + const TString& rigamerce_codart = rigamerce->get(RDOC_CODART); + //se trova il codart in una delle righe di contratto... + if (rdoc_codart == rigamerce_codart) + { + const real rigamerce_premio = rigamerce->get_real(RC_1_PREMIO); + //..aggiorna direttamente il bonus totale per l'articolo presente sulla riga + if (rigamerce_premio != ZERO) + { + //usa QTAGG3 come campo di appoggio per il calcolo della somma restituita dovuta al contratto (parte da zero per ogni elaborazione di NAC) + real somma_bonus = rigamerce->get_real(RC_1_BONUS); + somma_bonus += rdoc_qta * rigamerce_premio; + rigamerce->put(RC_1_BONUS, somma_bonus); + elaborato = true; + } //if(rigamerce... + + //aggiornamento delle righe di tipo merce (verigH01) + if (rigamerce->is_merce()) + { + real qta_tot = rigamerce->get_real(RDOC_QTA); + qta_tot += rdoc_qta; + rigamerce->put(RDOC_QTA, qta_tot); + } + + } //if(rdoc_codart... + } //FOR_EACH_PHYSICAL.. + return elaborato; +} + +//aggiorna, in base al documento in esame curr_doc, le somme restituite nelle righe di tipo verigh02 nei contratti validi.. +//..del cliente +bool THardy_elab_docs::aggiorna_contratti(TDocumento& curr_doc, TArray& contratti_cliente) +{ + bool elaborato = false; + + FOR_EACH_PHYSICAL_RDOC(curr_doc, r, rdoc) if (rdoc->is_merce()) //giro su tutte le righe merce delle fatture + { + TString80 rdoc_codart = rdoc->get(RDOC_CODART); //non si riesce ad usare un TString& perchè lo perde dopo un pò + rdoc_codart.trim(); + const real rdoc_qta = rdoc->quantita(); + + //controlla se il codart della riga esiste in uno dei contratti validi + for (int i = 0; i < contratti_cliente.items(); i++) + { + TDocumento& contratto = (TDocumento&)contratti_cliente[i]; + const TString& tipo_contratto = contratto.get(DOC_TIPOCFFATT); + + //in base al tipo di contratto (Anticipo/Posticipo/Rifatturazione) decide cosa fare + if (tipo_contratto == "A" || tipo_contratto == "R") + { + elaborato |= aggiorna_contratti_anticipo(rdoc_codart, rdoc_qta, contratto); + } //if(tipo_contratto... + else if (tipo_contratto == "P") + { + elaborato |= aggiorna_contratti_posticipo(rdoc_codart, rdoc_qta, contratto); + } + } //for(int i.. + } //FOR_EACH... + + return elaborato; +} + + +bool THardy_elab_docs::genera_nac(const TMask& mask, TArray& contratti_cliente, TArray& documenti_cliente, TLog_report& log) +{ + //si informa se l'elaborazione è definitiva o meno + const bool definitivo = mask.get_bool(F_DEFINITIVO); + + //giro su tutti i contratti del cliente che stanno nell'array (ogni contratto genera una NAC) + FOR_EACH_ARRAY_ITEM(contratti_cliente, r, riga) + { + TDocumento& contratto = *(TDocumento*)riga; + const long ndoc = contratto.numero(); //il numdoc del contratto serve nelle segnalazioni + const long codcf = contratto.codcf(); //il codice cliente ci serve nella generazione della NAC.. + const char tipo_contratto = contratto.get(DOC_TIPOCFFATT)[0]; //..e pure il tipo di contratto in esame! + + //segnaliamo l'elaborazione del contratto sul log + TString msg; + msg << "Elaborato contratto premi n." << ndoc << " del cliente " << codcf; + log.log(0, msg); + + + // generazione del documento NAC dal contratto + // ------------------------------------------- + + // TESTATA + + //alcuni parametri delle righe vanno presi dalla configurazione + TConfig config(CONFIG_DITTA, "ha"); + TString4 nac_codnum, nac_tipo; + TString8 cod_riga; + switch (tipo_contratto) + { + case 'A': + nac_codnum = config.get("NaAntNum"); + nac_tipo = config.get("NaAntTip"); + cod_riga = config.get("NaAntSpe"); + break; + case 'R': + nac_codnum = config.get("NaRifaNum"); + nac_tipo = config.get("NaRifaTip"); + cod_riga = config.get("NaRifaSpe"); + break; + default: + nac_codnum = config.get("NaPostNum"); + nac_tipo = config.get("NaPostTip"); + cod_riga = config.get("NaPostSpe"); + break; + } + + const int anno = mask.get_date(F_ADATA).year(); + //solo in caso di elaborazione definitiva si scrivono NAC di tipo D; sennò di tipo P, che sono uccidibili.. + //..all'inizio di ogni nuova elaborazione + char provv = 'P'; + if (definitivo) + provv = 'D'; + + TDocumento nac(provv, anno, nac_codnum, 0); //num_doc = 0 perchè viene aggiornato in fase di registrazione + nac.set_tipo(nac_tipo); + nac.put(DOC_STATO, 1); + nac.put(DOC_DATADOC, mask.get(F_DATAELAB)); + nac.put(DOC_TIPOCF, 'C'); + nac.put(DOC_CODCF, codcf); + + + // RIGHE + + //ogni riga di tipo merce (verigh01) del contratto origina una riga della NAC + //noto il codice di riga spesa (che farà le veci del codart), troviamo il tipo riga dalla tabella SPP e tutte le features che servono + const TRectype& rec_spp = cache().get("SPP", cod_riga); + const TString4 tipo_riga = rec_spp.get("S8"); + const TString80 descr_riga_spp = rec_spp.get("S0"); + const TString4 codiva = rec_spp.get("S3"); + + //giro sulle righe del contratto, che originano le righe NAC + for (int i = 1; i <= contratto.rows(); i++) + { + const TRiga_documento& riga_contratto = contratto[i]; //le righe di un documento partono da 1! (standard del mercoledì) + const TString& tiporiga = riga_contratto.get(RDOC_TIPORIGA); + const TString& tipo_riga_contratto = cache().get("%TRI", tiporiga, "S7"); + + //solo le righe di tipo merce (verigh01) dei contratti devono comparire nelle NAC + if (tipo_riga_contratto == "M") + { + TString80 riga_contratto_codart = riga_contratto.get(RDOC_CODART); + riga_contratto_codart.trim(); + const real riga_contratto_bonus = riga_contratto.get_real(RDOC_QTAGG3); + if (riga_contratto_bonus != ZERO) + int cazzone = 1; + const real riga_contratto_qta = riga_contratto.get_real(RDOC_QTA); + const TString4 riga_contratto_um = riga_contratto.get(RDOC_UMQTA); + const real riga_contratto_premio = riga_contratto.get_real(RC_1_PREMIO); + + // riga (dovrebbe essere un'unica riga per NAC, il cui valore sta in QTAGG3 ed è stato calcolato nella aggiorna_contratti()) + TRiga_documento& nac_row = nac.new_row(tipo_riga); + nac_row.put(RDOC_NRIGA, i); + nac_row.put(RDOC_CODART, cod_riga); + + //panegirico della descrizione + nac_row.put(RDOC_DESCR, descr_riga_spp); + + msg.cut(0); //risparmiamo sulle stringhe, mica sono gratis + const TDate adata = mask.get_date(F_ADATA); + TDate dadata = mask.get_date(F_DADATA); + if (!dadata.ok()) + check_date(adata, dadata); + + msg << " " << dadata << " -- " << adata << " --(Art." << riga_contratto_codart << ")"; + nac_row.put(RDOC_DESCEST, msg); + nac_row.put(RDOC_DESCLUNGA, "X"); + + //importi, qta, umqta + nac_row.put(RDOC_UMQTA, riga_contratto_um); + nac_row.put(RDOC_QTA, riga_contratto_qta); + nac_row.put(RDOC_PREZZO, riga_contratto_premio); + + //iva + nac_row.put(RDOC_CODIVA, codiva); + } + } + + // salvataggi vari + // --------------- + //la NAC viene scritta comunque, con provv='D' se elaborazione definitiva + int err = nac.write(); + + msg.cut(0); //la fase di risparmio stringhe continua + if (err == NOERR) + msg << "Generata NAC "; + else + msg << "Impossibile generare NAC "; + msg << " - provv:" << provv << " - num:" << nac_codnum << " - tipo:" << nac_tipo << " - anno:" << anno << " |" << r; + log.log(0, msg); + + // se non ci sono errori -> in caso di elaborazione definitiva procede alla registrazione.. + //.. del contratto (ricordiamo che in memoria il contratto ha già le righe aggiornate + if (definitivo && err == NOERR) + { + //prima di registrare il contratto vanno svuotati i campi appoggio sulle righe che sono stati usati.. + //..per qta e premi vari e riempiti nella aggiorna_contratti() + for (int i = 1; i <= contratto.rows(); i++) + { + TRiga_documento& riga_contratto = contratto[i]; + riga_contratto.put(RDOC_QTAGG3, ZERO); + riga_contratto.put(RDOC_QTA, ZERO); + } + + //alla fine della fiera aggiorna il contratto + err = contratto.rewrite(); + msg.cut(0); + if (err == NOERR) + msg << "Aggiornato contratto premi n. "; + else + msg << "Impossibile aggiornare il contratto premi n. "; + + msg << ndoc << " del cliente " << codcf; + log.log(0, msg); + } + + log.log(0, ""); + + } //FOR_EACH_ARRAY_ITEM(... giro sui contratti cliente + + + //il metodo ritornerà il successo o meno della registrazione + return true; +} + + +void THardy_elab_docs::elabora_documenti(const TMask& mask, TISAM_recordset& recset, TLog_report& log) +{ + TProgind pi(recset.items(), TR("Elaborazione documenti in corso..."), true, true); + + //inizializza variabili da usare nella scansione del recordset + long old_codcf = 0L; + //array con l'insieme dei contratti e dei documenti elaborati per un singolo cliente! + TArray contratti_cliente, documenti_cliente; + + //giro sulle fatture (è il giro di più alto livello che viene esteso all'interno delle aggiorna_contratti) + for (bool ok = recset.move_first(); ok; ok = recset.move_next()) + { + if (!pi.addstatus(1)) + break; + + const long codcf = recset.get(DOC_CODCF).as_int(); + //al cambio cliente deve controllare i contratti di quel cliente nel periodo di elaborazione!! + if (codcf != old_codcf) + { + //aggiorna old_codcf in modo da poter controllare i contratti solo al cambio codcf + old_codcf = codcf; + + const int n_contratti = find_contratti_cliente(codcf, mask, contratti_cliente); + if (n_contratti == 0) + { + TString msg; + msg << "Il cliente " << codcf << " non ha un contratto premi valido nel periodo di elaborazione selezionato ma ha fatture."; + log.log_error(msg); + } + } + + if (contratti_cliente.items() > 0) + { + //se ha trovato uno o più contratti validi nel periodo passa alla elaborazione dei documenti del cliente + TDocumento* curr_doc = new TDocumento(recset.cursor()->curr()); + //elabora il documento corrente aggiornando le somme restituite sui contratti validi + if (aggiorna_contratti(*curr_doc, contratti_cliente)) + documenti_cliente.add(curr_doc); + else + delete(curr_doc); + } + } //for (bool ok = recset.move_first()... + + //generazione NAC (una per contratto cliente) + genera_nac(mask, contratti_cliente, documenti_cliente, log); +} + + + +//metodo di alto livello con i punti principali del programma (come da analisi...) +void THardy_elab_docs::elabora(const TMask& mask) +{ + //1) eventuale accoppamento di tutti i documenti provvisori creati con precedenti elaborazioni precedenti + int nac_killed = 0; + if (mask.get_bool(F_KILLPROVV)) + nac_killed = kill_provv_nac(mask); + + //log report con segnalazioni sui clienti trattati (bene o male) + TLog_report log("Sintesi elaborazione"); + log.kill_duplicates(); + log.log(0, ""); + + //2) recordset ordinato codag-codcf-numdoc con tutti i docs che soddisfano i parametri dell'utente + // --------------------------------------------------------------------------------------------- + TISAM_recordset recset(""); + const long items = genera_recordset(mask, recset); + if (items == 0) + { + log.log(1, "Non esistono documenti di vendita che soddisfino i parametri selezionati! Ritenta sarai più fortunato!"); + } + + //3) elaborazione documenti e contratti, generazione NAC, salvataggi + // --------------------------------------------------------------- + elabora_documenti(mask, recset, log); + + //3) scrittura log + // ------------- + log.print_or_preview(); +} + +void THardy_elab_docs::main_loop() +{ + THardy_elab_docs_mask mask; + while (mask.run() == K_ENTER) + { + elabora(mask); + } +} + +bool THardy_elab_docs::create() +{ + //controlla se la chiave ha l'autorizzazione a questo programma (solo per hardy!) + Tdninst dninst; + if (!dninst.can_I_run(true)) + return error_box(TR("Programma non autorizzato!")); + + open_files(LF_DOC, LF_RIGHEDOC, 0); + + return TSkeleton_application::create(); +} + +int ha0500 (int argc, char* argv[]) +{ + THardy_elab_docs main_app; + main_app.run(argc, argv, TR("Elaborazione documenti Hardy")); + return true; +} \ No newline at end of file diff --git a/ha/ha0500a.h b/ha/ha0500a.h new file mode 100755 index 000000000..443542d11 --- /dev/null +++ b/ha/ha0500a.h @@ -0,0 +1,15 @@ +#define F_CODAGE 201 +#define F_DESCRAGE 202 +#define F_TIPOCF 203 +#define F_CODCF 204 +#define F_RAGSOC 205 +#define F_DADATA 206 +#define F_ADATA 207 +//#define F_TIPOCONTR 208 +//#define F_CODNUM_NAC 210 +//#define F_DESCRNUM_NAC 211 +//#define F_CODTIPO_NAC 212 +//#define F_DESCRTIPO_NAC 213 +#define F_DATAELAB 214 +#define F_DEFINITIVO 215 +#define F_KILLPROVV 216 diff --git a/ha/ha0500a.uml b/ha/ha0500a.uml new file mode 100755 index 000000000..c97019837 --- /dev/null +++ b/ha/ha0500a.uml @@ -0,0 +1,167 @@ +#include "ha0500a.h" + +PAGE "Elaborazione documenti Hardy" -1 -1 78 11 + +GROUPBOX DLG_NULL 76 5 +BEGIN + PROMPT 1 1 "@bParametri documenti" +END + +STRING F_CODAGE 6 +BEGIN + PROMPT 2 2 "Agente " + FLAGS "U" + USE LF_AGENTI + INPUT CODAGE F_CODAGE + DISPLAY "Codice" CODAGE + DISPLAY "Descrizione@50" RAGSOC + OUTPUT F_CODAGE CODAGE + OUTPUT F_DESCRAGE RAGSOC + CHECKTYPE NORMAL +END + +STRING F_DESCRAGE 50 +BEGIN + PROMPT 22 2 "" + USE LF_AGENTI KEY 2 + INPUT RAGSOC F_DESCRAGE + DISPLAY "Descrizione@50" RAGSOC + DISPLAY "Codice" CODAGE + COPY OUTPUT F_CODAGE + CHECKTYPE NORMAL +END + +STRING F_CODCF 6 +BEGIN + PROMPT 2 3 "Cliente " + USE LF_CLIFO + INPUT TIPOCF "C" + INPUT CODCF F_CODCF + DISPLAY "Codice" CODCF + DISPLAY "Ragione sociale@50" RAGSOC + OUTPUT F_CODCF CODCF + OUTPUT F_RAGSOC RAGSOC + CHECKTYPE NORMAL + FLAGS "U" +END + +STRING F_RAGSOC 50 +BEGIN + PROMPT 22 3 "" + USE LF_CLIFO KEY 2 + INPUT TIPOCF "C" + INPUT RAGSOC F_RAGSOC + DISPLAY "Ragione sociale@50" RAGSOC + DISPLAY "Codice" CODCF + COPY OUTPUT F_CODCF + CHECKTYPE NORMAL +END + +DATE F_DADATA +BEGIN + PROMPT 2 4 "Da data " +END + +DATE F_ADATA +BEGIN + PROMPT 22 4 "A data " + CHECKTYPE REQUIRED + VALIDATE DATE_CMP_FUNC >= F_DADATA +END + +GROUPBOX DLG_NULL 76 3 +BEGIN + PROMPT 1 6 "@bParametri elaborazione" +END + +DATE F_DATAELAB +BEGIN + PROMPT 2 7 "Data elaborazione " + FLAGS "A" + CHECKTYPE REQUIRED +END + +BOOLEAN F_DEFINITIVO +BEGIN + PROMPT 40 7 "Definitivo" +END + +/*RADIOBUTTON F_TIPOCONTR 1 72 +BEGIN + PROMPT 2 8 "@bTipi contratto da elaborare" + ITEM "A|Anticipo" + ITEM "P|Posticipo" + ITEM "R|Rifatturazione" + FLAGS "Z" +END + +STRING F_CODNUM_NAC 4 +BEGIN + PROMPT 2 11 "Num. doc. NAC " + USE %NUM + INPUT CODTAB F_CODNUM_NAC + DISPLAY "Codice@8" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CODNUM_NAC CODTAB + OUTPUT F_DESCRNUM_NAC S0 + FLAGS "DG" + CHECKTYPE REQUIRED +END + +STRING F_DESCRNUM_NAC 50 43 +BEGIN + PROMPT 29 11 "" + USE %NUM KEY 2 + INPUT S0 F_DESCRNUM_NAC + DISPLAY "Descrizione@50" S0 + DISPLAY "Codice@8" CODTAB + COPY OUTPUT F_CODNUM_NAC + FLAGS "DG" + CHECKTYPE NORMAL +END + +STRING F_CODTIPO_NAC 4 +BEGIN + PROMPT 2 12 "Tipo doc. NAC " + USE %TIP + INPUT CODTAB F_CODTIPO_NAC + DISPLAY "Codice" CODTAB + DISPLAY "Descrizione@50" S0 + OUTPUT F_CODTIPO_NAC CODTAB + OUTPUT F_DESCRTIPO_NAC S0 + FLAGS "DG" + CHECKTYPE REQUIRED +END + +STRING F_DESCRTIPO_NAC 50 43 +BEGIN + PROMPT 29 12 "" + USE %TIP KEY 2 + INPUT S0 F_DESCRTIPO_NAC + DISPLAY "Descrizione@60" S0 + DISPLAY "Codice" CODTAB + COPY OUTPUT F_CODTIPO_NAC + FLAGS "DG" + CHECKTYPE NORMAL +END*/ + +BOOLEAN F_KILLPROVV +BEGIN + PROMPT 1 9 "Eliminare tutte le note di accredito provvisorie" +END + +STRING DLG_PROFILE 50 +BEGIN + PROMPT 1 -1 "Profilo " + PSELECT +END + +ENDPAGE + +TOOLBAR "" 0 0 0 2 + +#include + +ENDPAGE + +ENDMASK diff --git a/ha/halib.h b/ha/halib.h new file mode 100755 index 000000000..dd981abb9 --- /dev/null +++ b/ha/halib.h @@ -0,0 +1,20 @@ +//definizioni delle righe dei contratti premio Hardy (da includere nei .cpp) +#define HARDY_TIPORIGA_MERCE "H01" +#define HARDY_TIPORIGA_SOMMA "H02" + +//ridefinizione dei campi per rendere chiare le elaborazioni +//legenda per il sagace programmatore: +//RC = riga contratto standard, tipo 1, merce +//RCA = riga contratto anticipo, tipo 2, spese (1 per ogni contratto anticipo) +// 1 = riga tipo 1, merce +// 2 = riga tipo 2, spese +// segue il vero significato del campo (vero... verosimile, che è meglio!) +#define RC_1_PREMIO RDOC_QTAGG1 +#define RC_1_NSCARICO RDOC_QTAGG2 +#define RC_1_BONUS RDOC_QTAGG5 + +#define RCA_2_ANTICIPATO RDOC_QTAGG4 +#define RCA_2_RESTITUITO RDOC_QTAGG5 + +//#define RC_1_BONUS_NAC RDOC_QTAGG3 +//#define RC_2_RESO_NAC RDOC_QTAGG3 diff --git a/ha/hamenu.men b/ha/hamenu.men new file mode 100755 index 000000000..a789c6bcc --- /dev/null +++ b/ha/hamenu.men @@ -0,0 +1,10 @@ +[HAMENU_001] +Caption = "Hardy caffe'" +Picture = +Module = 46 +Flags = "F" +Item_01 = "Configurazione Hardy", "ha0 -1", "" +Item_02 = "Gestione contratti premio", "ha0 -2", "" +Item_03 = "Contabilizzazione contratti premio", "ha0 -3", "" +Item_04 = "Generazione NAC", "ha0 -4", "" +Item_05 = "Gestione listini", "ve2 -4", "" diff --git a/ha/hatabcom.txt b/ha/hatabcom.txt new file mode 100755 index 000000000..ba373ca59 --- /dev/null +++ b/ha/hatabcom.txt @@ -0,0 +1,18 @@ +[Header] +Version=199519 +File=4 +Fields=COD,3|CODTAB,25|S0,70|S1,70|S2,70|S3,70|S4,25|S5,25|S6,5|S7,5 +Fields=S8,5|S9,5|S10,5|S11,5|I0,7|I1,7|I2,7|I3,7|I4,7|I5,7 +Fields=I6,7|I7,7|I8,7|I9,7|I10,7|I11,7|I12,7|I13,7|I14,7|R0,18 +Fields=R1,18|R2,18|R3,18|R4,18|R5,18|R6,18|R7,18|R8,18|R9,18|R10,18 +Fields=R11,18|R12,18|R13,18|R14,18|R15,18|R16,18|R17,18|R18,18|R19,18|R20,18 +Fields=R21,18|R22,18|R23,18|R24,18|R25,18|R26,18|R27,18|R28,18|R29,18|D0,10 +Fields=D1,10|D2,10|D3,10|D4,10|B0,1|B1,1|B2,1|B3,1|B4,1|B5,1 +Fields=B6,1|B7,1|B8,1|B9,1|B10,1|B11,1|B12,1|B13,1|B14,1|B15,1 +Fields=FPC,1 + +[Data] +NUM|HCO|Contratto||HCA HCP||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||X||||||||||||||| +TIP|HCO|Contratto Hardy||1259||hacontr|hacontr|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +TRI|H01|Merce|||||||M|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +TRI|H02|Somma anticipata|||||||S|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| diff --git a/projects/ha.sln b/projects/ha.sln new file mode 100755 index 000000000..94d6259ed --- /dev/null +++ b/projects/ha.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ha0", "ha0.vcxproj", "{2DB7F8EF-BABB-4A27-BC7D-8821B0D285C6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DB7F8EF-BABB-4A27-BC7D-8821B0D285C6}.Debug|Win32.ActiveCfg = Debug|Win32 + {2DB7F8EF-BABB-4A27-BC7D-8821B0D285C6}.Debug|Win32.Build.0 = Debug|Win32 + {2DB7F8EF-BABB-4A27-BC7D-8821B0D285C6}.Release|Win32.ActiveCfg = Release|Win32 + {2DB7F8EF-BABB-4A27-BC7D-8821B0D285C6}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/res/moka.ico b/res/moka.ico new file mode 100755 index 0000000000000000000000000000000000000000..7eeba30061dbd6ec015cae18699486604e1ff7b3 GIT binary patch literal 372526 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if wxCHECK_VERSION(2, 7, 0) +#include +#endif +#include +#include +#include + +#ifdef __WXMAC__ +#include "wx/mac/private.h" +#endif + +#include // only required for debugging purpose + +#undef WXDLLEXPORT +#define WXDLLEXPORT +#include "treelistctrl.h" + +// --------------------------------------------------------------------------- +// array types +// --------------------------------------------------------------------------- + +class wxTreeListItem; + +#if !wxCHECK_VERSION(2, 5, 0) +2WX_DEFINE_ARRAY(wxTreeListItem *, wxArrayTreeListItems); +#else +WX_DEFINE_ARRAY_PTR(wxTreeListItem *, wxArrayTreeListItems); +#endif + +#include +WX_DECLARE_OBJARRAY(wxTreeListColumnInfo, wxArrayTreeListColumnInfo); +#include +WX_DEFINE_OBJARRAY(wxArrayTreeListColumnInfo); + + +// -------------------------------------------------------------------------- +// constants +// -------------------------------------------------------------------------- + +static const int NO_IMAGE = -1; + +static const int LINEHEIGHT = 10; +static const int LINEATROOT = 5; +static const int MARGIN = 2; +static const int MININDENT = 16; +static const int BTNWIDTH = 9; +static const int BTNHEIGHT = 9; +static const int EXTRA_WIDTH = 4; +static const int EXTRA_HEIGHT = 4; +static const int HEADER_OFFSET_X = 0; // changed from 1 to 0 on 2009.03.10 for Windows (other OS untested) +static const int HEADER_OFFSET_Y = 1; + +static const int DRAG_TIMER_TICKS = 250; // minimum drag wait time in ms +static const int FIND_TIMER_TICKS = 500; // minimum find wait time in ms +static const int RENAME_TIMER_TICKS = 250; // minimum rename wait time in ms + +const wxChar* wxTreeListCtrlNameStr = _T("treelistctrl"); + +static wxTreeListColumnInfo wxInvalidTreeListColumnInfo; + + +// --------------------------------------------------------------------------- +// private classes +// --------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// wxTreeListHeaderWindow (internal) +//----------------------------------------------------------------------------- + +class wxTreeListHeaderWindow : public wxWindow +{ +protected: + wxTreeListMainWindow *m_owner; + const wxCursor *m_currentCursor; + const wxCursor *m_resizeCursor; + bool m_isDragging; + + // column being resized + int m_column; + + // divider line position in logical (unscrolled) coords + int m_currentX; + + // minimal position beyond which the divider line can't be dragged in + // logical coords + int m_minX; + + wxArrayTreeListColumnInfo m_columns; + + // total width of the columns + int m_total_col_width; + +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) + // which col header is currently highlighted with mouse-over + int m_hotTrackCol; + int XToCol(int x); + void RefreshColLabel(int col); +#endif + +public: + wxTreeListHeaderWindow(); + + wxTreeListHeaderWindow( wxWindow *win, + wxWindowID id, + wxTreeListMainWindow *owner, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + long style = 0, + const wxString &name = _T("wxtreelistctrlcolumntitles") ); + + virtual ~wxTreeListHeaderWindow(); + + void DoDrawRect( wxDC *dc, int x, int y, int w, int h ); + void DrawCurrent(); + void AdjustDC(wxDC& dc); + + void OnPaint( wxPaintEvent &event ); + void OnEraseBackground(wxEraseEvent& WXUNUSED(event)) { ;; } // reduce flicker + void OnMouse( wxMouseEvent &event ); + void OnSetFocus( wxFocusEvent &event ); + + // total width of all columns + int GetWidth() const { return m_total_col_width; } + + // column manipulation + int GetColumnCount() const { return (int)m_columns.GetCount(); } + + void AddColumn (const wxTreeListColumnInfo& colInfo); + + void InsertColumn (int before, const wxTreeListColumnInfo& colInfo); + + void RemoveColumn (int column); + + // column information manipulation + const wxTreeListColumnInfo& GetColumn (int column) const{ + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + wxInvalidTreeListColumnInfo, _T("Invalid column")); + return m_columns[column]; + } + wxTreeListColumnInfo& GetColumn (int column) { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + wxInvalidTreeListColumnInfo, _T("Invalid column")); + return m_columns[column]; + } + void SetColumn (int column, const wxTreeListColumnInfo& info); + + wxString GetColumnText (int column) const { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + wxEmptyString, _T("Invalid column")); + return m_columns[column].GetText(); + } + void SetColumnText (int column, const wxString& text) { + wxCHECK_RET ((column >= 0) && (column < GetColumnCount()), + _T("Invalid column")); + m_columns[column].SetText (text); + } + + int GetColumnAlignment (int column) const { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + wxALIGN_LEFT, _T("Invalid column")); + return m_columns[column].GetAlignment(); + } + void SetColumnAlignment (int column, int flag) { + wxCHECK_RET ((column >= 0) && (column < GetColumnCount()), + _T("Invalid column")); + m_columns[column].SetAlignment (flag); + } + + int GetColumnWidth (int column) const { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + -1, _T("Invalid column")); + return m_columns[column].GetWidth(); + } + void SetColumnWidth (int column, int width); + + bool IsColumnEditable (int column) const { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + false, _T("Invalid column")); + return m_columns[column].IsEditable(); + } + + bool IsColumnShown (int column) const { + wxCHECK_MSG ((column >= 0) && (column < GetColumnCount()), + true, _T("Invalid column")); + return m_columns[column].IsShown(); + } + + // needs refresh + bool m_dirty; + +private: + // common part of all ctors + void Init(); + + void SendListEvent(wxEventType type, wxPoint pos); + + DECLARE_DYNAMIC_CLASS(wxTreeListHeaderWindow) + DECLARE_EVENT_TABLE() +}; + + +//----------------------------------------------------------------------------- +// wxTreeListMainWindow (internal) +//----------------------------------------------------------------------------- + +class wxEditTextCtrl; + + +// this is the "true" control +class wxTreeListMainWindow: public wxScrolledWindow +{ +public: + // creation + // -------- + wxTreeListMainWindow() { Init(); } + + wxTreeListMainWindow (wxTreeListCtrl *parent, wxWindowID id = -1, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxTR_DEFAULT_STYLE, + const wxValidator &validator = wxDefaultValidator, + const wxString& name = _T("wxtreelistmainwindow")) + { + Init(); + Create (parent, id, pos, size, style, validator, name); + } + + virtual ~wxTreeListMainWindow(); + + bool Create(wxTreeListCtrl *parent, wxWindowID id = -1, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxTR_DEFAULT_STYLE, + const wxValidator &validator = wxDefaultValidator, + const wxString& name = _T("wxtreelistctrl")); + + // accessors + // --------- + + // return true if this is a virtual list control + bool IsVirtual() const { return HasFlag(wxTR_VIRTUAL); } + + // get the total number of items in the control + size_t GetCount() const; + + // indent is the number of pixels the children are indented relative to + // the parents position. SetIndent() also redraws the control + // immediately. + unsigned int GetIndent() const { return m_indent; } + void SetIndent(unsigned int indent); + + // see wxTreeListCtrl for the meaning + unsigned int GetLineSpacing() const { return m_linespacing; } + void SetLineSpacing(unsigned int spacing); + + // image list: these functions allow to associate an image list with + // the control and retrieve it. Note that when assigned with + // SetImageList, the control does _not_ delete + // the associated image list when it's deleted in order to allow image + // lists to be shared between different controls. If you use + // AssignImageList, the control _does_ delete the image list. + + // The normal image list is for the icons which correspond to the + // normal tree item state (whether it is selected or not). + // Additionally, the application might choose to show a state icon + // which corresponds to an app-defined item state (for example, + // checked/unchecked) which are taken from the state image list. + wxImageList *GetImageList() const { return m_imageListNormal; } + wxImageList *GetStateImageList() const { return m_imageListState; } + wxImageList *GetButtonsImageList() const { return m_imageListButtons; } + + void SetImageList(wxImageList *imageList); + void SetStateImageList(wxImageList *imageList); + void SetButtonsImageList(wxImageList *imageList); + void AssignImageList(wxImageList *imageList); + void AssignStateImageList(wxImageList *imageList); + void AssignButtonsImageList(wxImageList *imageList); + + void SetToolTip(const wxString& tip); + void SetToolTip(wxToolTip *tip); + void SetItemToolTip(const wxTreeItemId& item, const wxString &tip); + + // Functions to work with tree ctrl items. + + // accessors + // --------- + + // retrieve item's label + wxString GetItemText (const wxTreeItemId& item) const + { return GetItemText (item, GetMainColumn()); } + wxString GetItemText (const wxTreeItemId& item, int column) const; + wxString GetItemText (wxTreeItemData* item, int column) const; + + // get one of the images associated with the item (normal by default) + int GetItemImage (const wxTreeItemId& item, + wxTreeItemIcon which = wxTreeItemIcon_Normal) const + { return GetItemImage (item, GetMainColumn(), which); } + int GetItemImage (const wxTreeItemId& item, int column, + wxTreeItemIcon which = wxTreeItemIcon_Normal) const; + + // get the data associated with the item + wxTreeItemData *GetItemData(const wxTreeItemId& item) const; + + bool GetItemBold(const wxTreeItemId& item) const; + wxColour GetItemTextColour(const wxTreeItemId& item) const; + wxColour GetItemBackgroundColour(const wxTreeItemId& item) const; + wxFont GetItemFont(const wxTreeItemId& item) const; + + // modifiers + // --------- + + // set item's label + void SetItemText (const wxTreeItemId& item, const wxString& text) + { SetItemText (item, GetMainColumn(), text); } + void SetItemText (const wxTreeItemId& item, int column, const wxString& text); + + // get one of the images associated with the item (normal by default) + void SetItemImage (const wxTreeItemId& item, int image, + wxTreeItemIcon which = wxTreeItemIcon_Normal) + { SetItemImage (item, GetMainColumn(), image, which); } + void SetItemImage (const wxTreeItemId& item, int column, int image, + wxTreeItemIcon which = wxTreeItemIcon_Normal); + + // associate some data with the item + void SetItemData(const wxTreeItemId& item, wxTreeItemData *data); + + // force appearance of [+] button near the item. This is useful to + // allow the user to expand the items which don't have any children now + // - but instead add them only when needed, thus minimizing memory + // usage and loading time. + void SetItemHasChildren(const wxTreeItemId& item, bool has = true); + + // the item will be shown in bold + void SetItemBold(const wxTreeItemId& item, bool bold = true); + + // set the item's text colour + void SetItemTextColour(const wxTreeItemId& item, const wxColour& colour); + + // set the item's background colour + void SetItemBackgroundColour(const wxTreeItemId& item, const wxColour& colour); + + // set the item's font (should be of the same height for all items) + void SetItemFont(const wxTreeItemId& item, const wxFont& font); + + // set the window font + virtual bool SetFont( const wxFont &font ); + + // set the styles. No need to specify a GetWindowStyle here since + // the base wxWindow member function will do it for us + void SetWindowStyle(const long styles); + + // item status inquiries + // --------------------- + + // is the item visible (it might be outside the view or not expanded)? + bool IsVisible(const wxTreeItemId& item, bool fullRow, bool within = true) const; + // does the item has any children? + bool HasChildren(const wxTreeItemId& item) const; + // is the item expanded (only makes sense if HasChildren())? + bool IsExpanded(const wxTreeItemId& item) const; + // is this item currently selected (the same as has focus)? + bool IsSelected(const wxTreeItemId& item) const; + // is item text in bold font? + bool IsBold(const wxTreeItemId& item) const; + // does the layout include space for a button? + + // number of children + // ------------------ + + // if 'recursively' is false, only immediate children count, otherwise + // the returned number is the number of all items in this branch + size_t GetChildrenCount(const wxTreeItemId& item, bool recursively = true); + + // navigation + // ---------- + + // wxTreeItemId.IsOk() will return false if there is no such item + + // get the root tree item + wxTreeItemId GetRootItem() const { return m_rootItem; } // implict cast from wxTreeListItem * + + // get the item currently selected, only if a single item is selected + wxTreeItemId GetSelection() const { return m_selectItem; } + + // get all the items currently selected, return count of items + size_t GetSelections(wxArrayTreeItemIds&) const; + + // get the parent of this item (may return NULL if root) + wxTreeItemId GetItemParent(const wxTreeItemId& item) const; + + // for this enumeration function you must pass in a "cookie" parameter + // which is opaque for the application but is necessary for the library + // to make these functions reentrant (i.e. allow more than one + // enumeration on one and the same object simultaneously). Of course, + // the "cookie" passed to GetFirstChild() and GetNextChild() should be + // the same! + + // get child of this item +#if !wxCHECK_VERSION(2, 5, 0) + wxTreeItemId GetFirstChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetNextChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetPrevChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetLastChild(const wxTreeItemId& item, long& cookie) const; +#else + wxTreeItemId GetFirstChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetNextChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetPrevChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetLastChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; +#endif + + // get sibling of this item + wxTreeItemId GetNextSibling(const wxTreeItemId& item) const; + wxTreeItemId GetPrevSibling(const wxTreeItemId& item) const; + + // get item in the full tree (currently only for internal use) + wxTreeItemId GetNext(const wxTreeItemId& item, bool fulltree = true) const; + wxTreeItemId GetPrev(const wxTreeItemId& item, bool fulltree = true) const; + + // get expanded item, see IsExpanded() + wxTreeItemId GetFirstExpandedItem() const; + wxTreeItemId GetNextExpanded(const wxTreeItemId& item) const; + wxTreeItemId GetPrevExpanded(const wxTreeItemId& item) const; + + // get visible item, see IsVisible() + wxTreeItemId GetFirstVisible( bool fullRow, bool within) const; + wxTreeItemId GetNextVisible (const wxTreeItemId& item, bool fullRow, bool within) const; + wxTreeItemId GetPrevVisible (const wxTreeItemId& item, bool fullRow, bool within) const; + wxTreeItemId GetLastVisible ( bool fullRow, bool within) const; + + // operations + // ---------- + + // add the root node to the tree + wxTreeItemId AddRoot (const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item in as the first child of the parent + wxTreeItemId PrependItem(const wxTreeItemId& parent, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item after a given one + wxTreeItemId InsertItem(const wxTreeItemId& parent, + const wxTreeItemId& idPrevious, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item before the one with the given index + wxTreeItemId InsertItem(const wxTreeItemId& parent, + size_t index, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item in as the last child of the parent + wxTreeItemId AppendItem(const wxTreeItemId& parent, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // delete this item and associated data if any + void Delete(const wxTreeItemId& item); + // delete all children (but don't delete the item itself) + // NB: this won't send wxEVT_COMMAND_TREE_ITEM_DELETED events + void DeleteChildren(const wxTreeItemId& item); + // delete the root and all its children from the tree + // NB: this won't send wxEVT_COMMAND_TREE_ITEM_DELETED events + void DeleteRoot(); + + // expand this item + void Expand(const wxTreeItemId& item); + // expand this item and all subitems recursively + void ExpandAll(const wxTreeItemId& item); + // collapse the item without removing its children + void Collapse(const wxTreeItemId& item); + // collapse the item and remove all children + void CollapseAndReset(const wxTreeItemId& item); + // toggles the current state + void Toggle(const wxTreeItemId& item); + + // remove the selection from currently selected item (if any) + void Unselect(); + void UnselectAll(); + // select this item + bool SelectItem(const wxTreeItemId& item, const wxTreeItemId& prev = (wxTreeItemId*)NULL, + bool unselect_others = true); + void SelectAll(); + // make sure this item is visible (expanding the parent item and/or + // scrolling to this item if necessary) + void EnsureVisible(const wxTreeItemId& item); + // scroll to this item (but don't expand its parent) + void ScrollTo(const wxTreeItemId& item); + void AdjustMyScrollbars(); + + // The first function is more portable (because easier to implement + // on other platforms), but the second one returns some extra info. + wxTreeItemId HitTest (const wxPoint& point) + { int flags; int column; return HitTest (point, flags, column); } + wxTreeItemId HitTest (const wxPoint& point, int& flags) + { int column; return HitTest (point, flags, column); } + wxTreeItemId HitTest (const wxPoint& point, int& flags, int& column); + + + // get the bounding rectangle of the item (or of its label only) + bool GetBoundingRect(const wxTreeItemId& item, + wxRect& rect, + bool textOnly = false) const; + + // Start editing the item label: this (temporarily) replaces the item + // with a one line edit control. The item will be selected if it hadn't + // been before. + void EditLabel (const wxTreeItemId& item, int column); + + // sorting + // this function is called to compare 2 items and should return -1, 0 + // or +1 if the first item is less than, equal to or greater than the + // second one. The base class version performs alphabetic comparaison + // of item labels (GetText) + virtual int OnCompareItems(const wxTreeItemId& item1, + const wxTreeItemId& item2); + // sort the children of this item using OnCompareItems + // + // NB: this function is not reentrant and not MT-safe (FIXME)! + void SortChildren(const wxTreeItemId& item); + + // searching + wxTreeItemId FindItem (const wxTreeItemId& item, const wxString& str, int mode = 0); + + // implementation only from now on + + // overridden base class virtuals + virtual bool SetBackgroundColour(const wxColour& colour); + virtual bool SetForegroundColour(const wxColour& colour); + + // drop over item + void SetDragItem (const wxTreeItemId& item = (wxTreeItemId*)NULL); + + // callbacks + void OnPaint( wxPaintEvent &event ); + void OnEraseBackground(wxEraseEvent& WXUNUSED(event)) { ;; } // to reduce flicker + void OnSetFocus( wxFocusEvent &event ); + void OnKillFocus( wxFocusEvent &event ); + void OnChar( wxKeyEvent &event ); + void OnMouse( wxMouseEvent &event ); + void OnIdle( wxIdleEvent &event ); + void OnScroll(wxScrollWinEvent& event); + void OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) { ;; } + + // implementation helpers + int GetColumnCount() const + { return m_owner->GetHeaderWindow()->GetColumnCount(); } + + void SetMainColumn (int column) + { if ((column >= 0) && (column < GetColumnCount())) m_main_column = column; } + + int GetMainColumn() const { return m_main_column; } + + int GetBestColumnWidth (int column, wxTreeItemId parent = wxTreeItemId()); + int GetItemWidth (int column, wxTreeListItem *item); + wxFont GetItemFont (wxTreeListItem *item); + + void SetFocus(); + +protected: + wxTreeListCtrl* m_owner; + + int m_main_column; + + friend class wxTreeListItem; + friend class wxTreeListRenameTimer; + friend class wxEditTextCtrl; + + wxFont m_normalFont; + wxFont m_boldFont; + + wxTreeListItem *m_rootItem; // root item + wxTreeListItem *m_curItem; // current item, either selected or marked + wxTreeListItem *m_shiftItem; // item, where the shift key was pressed + wxTreeListItem *m_selectItem; // current selected item, not with wxTR_MULTIPLE + + int m_curColumn; + + int m_btnWidth, m_btnWidth2; + int m_btnHeight, m_btnHeight2; + int m_imgWidth, m_imgWidth2; + int m_imgHeight, m_imgHeight2; + unsigned short m_indent; + int m_lineHeight; + unsigned short m_linespacing; + wxPen m_dottedPen; + wxBrush *m_hilightBrush, + *m_hilightUnfocusedBrush; + bool m_hasFocus; +public: + bool m_dirty; +protected: + bool m_ownsImageListNormal, + m_ownsImageListState, + m_ownsImageListButtons; + bool m_lastOnSame; // last click on the same item as prev + bool m_left_down_selection; + + wxImageList *m_imageListNormal, + *m_imageListState, + *m_imageListButtons; + + bool m_isDragStarted; // set at the very beginning of dragging + bool m_isDragging; // set once a drag begin event was fired + wxPoint m_dragStartPos; // set whenever m_isDragStarted is set to true + wxTreeListItem *m_dragItem; + int m_dragCol; + + wxTreeListItem *m_editItem; // item, which is currently edited + wxTimer *m_editTimer; + bool m_editAccept; // currently unused, OnRenameAccept() argument makes it redundant + wxString m_editRes; + int m_editCol; + wxEditTextCtrl *m_editControl; + + // char navigation + wxTimer *m_findTimer; + wxString m_findStr; + + bool m_isItemToolTip; // true if individual item tooltips were set (disable global tooltip) + wxString m_toolTip; // global tooltip + wxTreeListItem *m_toolTipItem; // item whose tip is currently shown (NULL==global, -1==not displayed) + + // the common part of all ctors + void Init(); + + // misc helpers + wxTreeItemId DoInsertItem(const wxTreeItemId& parent, + size_t previous, + const wxString& text, + int image, int selectedImage, + wxTreeItemData *data); + void DoDeleteItem (wxTreeListItem *item); + void SetCurrentItem(wxTreeListItem *item); + bool HasButtons(void) const + { return (m_imageListButtons) || HasFlag (wxTR_TWIST_BUTTONS|wxTR_HAS_BUTTONS); } + + void CalculateLineHeight(); + int GetLineHeight(wxTreeListItem *item) const; + void PaintLevel( wxTreeListItem *item, wxDC& dc, int level, int &y, + int x_maincol); + void PaintItem( wxTreeListItem *item, wxDC& dc); + + void CalculateLevel( wxTreeListItem *item, wxDC &dc, int level, int &y, + int x_maincol); + void CalculatePositions(); + void CalculateSize( wxTreeListItem *item, wxDC &dc ); + + void RefreshSubtree (wxTreeListItem *item); + void RefreshLine (wxTreeListItem *item); + // redraw all selected items + void RefreshSelected(); + // RefreshSelected() recursive helper + void RefreshSelectedUnder (wxTreeListItem *item); + + void OnRenameTimer(); + void OnRenameAccept(bool isCancelled); + + void FillArray(wxTreeListItem*, wxArrayTreeItemIds&) const; + bool TagAllChildrenUntilLast (wxTreeListItem *crt_item, wxTreeListItem *last_item); + bool TagNextChildren (wxTreeListItem *crt_item, wxTreeListItem *last_item); + void UnselectAllChildren (wxTreeListItem *item ); + bool SendEvent(wxEventType event_type, wxTreeListItem *item = NULL, wxTreeEvent *event = NULL); // returns true if processed + +private: + DECLARE_EVENT_TABLE() + DECLARE_DYNAMIC_CLASS(wxTreeListMainWindow) +}; + + +// timer used for enabling in-place edit +class wxTreeListRenameTimer: public wxTimer +{ +public: + wxTreeListRenameTimer( wxTreeListMainWindow *owner ); + + void Notify(); + +private: + wxTreeListMainWindow *m_owner; +}; + +// control used for in-place edit +class wxEditTextCtrl: public wxTextCtrl +{ +public: + wxEditTextCtrl (wxWindow *parent, + const wxWindowID id, + bool *accept, + wxString *res, + wxTreeListMainWindow *owner, + const wxString &value = wxEmptyString, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + int style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString &name = wxTextCtrlNameStr ); + ~wxEditTextCtrl(); + + virtual bool Destroy(); // wxWindow override + void EndEdit(bool isCancelled); + void SetOwner(wxTreeListMainWindow *owner) { m_owner = owner; } + + void OnChar( wxKeyEvent &event ); + void OnKeyUp( wxKeyEvent &event ); + void OnKillFocus( wxFocusEvent &event ); + + +private: + wxTreeListMainWindow *m_owner; + bool *m_accept; + wxString *m_res; + wxString m_startValue; + bool m_finished; // true==deleting, don't process events anymore + + DECLARE_EVENT_TABLE() +}; + + +// a tree item (NOTE: this class is storage only, does not generate events) +class wxTreeListItem +{ +public: + // ctors & dtor + wxTreeListItem() { m_data = NULL; m_toolTip = NULL; } + wxTreeListItem( wxTreeListMainWindow *owner, + wxTreeListItem *parent, + const wxArrayString& text, + int image, + int selImage, + wxTreeItemData *data ); + + ~wxTreeListItem(); + + // trivial accessors + wxArrayTreeListItems& GetChildren() { return m_children; } + + const wxString GetText() const + { + return GetText(0); + } + const wxString GetText (int column) const + { + if(m_text.GetCount() > 0) + { + if( IsVirtual() ) return m_owner->GetItemText( m_data, column ); + else return m_text[column]; + } + return wxEmptyString; + } + + int GetImage (wxTreeItemIcon which = wxTreeItemIcon_Normal) const + { return m_images[which]; } + int GetImage (int column, wxTreeItemIcon which=wxTreeItemIcon_Normal) const + { + if(column == m_owner->GetMainColumn()) return m_images[which]; + if(column < (int)m_col_images.GetCount()) return m_col_images[column]; + return NO_IMAGE; + } + + wxTreeItemData *GetData() const { return m_data; } + + const wxString * GetToolTip() const { return m_toolTip; } + + // returns the current image for the item (depending on its + // selected/expanded/whatever state) + int GetCurrentImage() const; + + void SetText (const wxString &text ); + void SetText (int column, const wxString& text) + { + if (column < (int)m_text.GetCount()) { + m_text[column] = text; + }else if (column < m_owner->GetColumnCount()) { + int howmany = m_owner->GetColumnCount(); + for (int i = (int)m_text.GetCount(); i < howmany; ++i) m_text.Add (wxEmptyString); + m_text[column] = text; + } + } + void SetImage (int image, wxTreeItemIcon which) { m_images[which] = image; } + void SetImage (int column, int image, wxTreeItemIcon which) + { + if (column == m_owner->GetMainColumn()) { + m_images[which] = image; + }else if (column < (int)m_col_images.GetCount()) { + m_col_images[column] = image; + }else if (column < m_owner->GetColumnCount()) { + int howmany = m_owner->GetColumnCount(); + for (int i = (int)m_col_images.GetCount(); i < howmany; ++i) m_col_images.Add (NO_IMAGE); + m_col_images[column] = image; + } + } + + void SetData(wxTreeItemData *data) { m_data = data; } + + void SetToolTip(const wxString &tip) { + if (m_toolTip) { + delete m_toolTip; m_toolTip = NULL; + } + if (tip.length() > 0) { + m_toolTip = new wxString(tip); + } + } + + void SetHasPlus(bool has = true) { m_hasPlus = has; } + + void SetBold(bool bold) { m_isBold = bold; } + + int GetX() const { return m_x; } + int GetY() const { return m_y; } + + void SetX (int x) { m_x = x; } + void SetY (int y) { m_y = y; } + + int GetHeight() const { return m_height; } + int GetWidth() const { return m_width; } + + void SetHeight (int height) { m_height = height; } + void SetWidth (int width) { m_width = width; } + + int GetTextX() const { return m_text_x; } + void SetTextX (int text_x) { m_text_x = text_x; } + + wxTreeListItem *GetItemParent() const { return m_parent; } + + // operations + // deletes all children + void DeleteChildren(); + + // get count of all children (and grand children if 'recursively') + size_t GetChildrenCount(bool recursively = true) const; + + void Insert(wxTreeListItem *child, size_t index) + { m_children.Insert(child, index); } + + void GetSize( int &x, int &y, const wxTreeListMainWindow* ); + + // return the item at given position (or NULL if no item), onButton is + // true if the point belongs to the item's button, otherwise it lies + // on the button's label + wxTreeListItem *HitTest (const wxPoint& point, + const wxTreeListMainWindow *, + int &flags, int& column, int level); + + void Expand() { m_isCollapsed = false; } + void Collapse() { m_isCollapsed = true; } + + void SetHilight( bool set = true ) { m_hasHilight = set; } + + // status inquiries + bool HasChildren() const { return !m_children.IsEmpty(); } + bool IsSelected() const { return m_hasHilight != 0; } + bool IsExpanded() const { return !m_isCollapsed; } + bool HasPlus() const { return m_hasPlus || HasChildren(); } + bool IsBold() const { return m_isBold != 0; } + bool IsVirtual() const { return m_owner->IsVirtual(); } + + // attributes + // get them - may be NULL + wxTreeItemAttr *GetAttributes() const { return m_attr; } + // get them ensuring that the pointer is not NULL + wxTreeItemAttr& Attr() + { + if ( !m_attr ) + { + m_attr = new wxTreeItemAttr; + m_ownsAttr = true; + } + return *m_attr; + } + // set them + void SetAttributes(wxTreeItemAttr *attr) + { + if ( m_ownsAttr ) delete m_attr; + m_attr = attr; + m_ownsAttr = false; + } + // set them and delete when done + void AssignAttributes(wxTreeItemAttr *attr) + { + SetAttributes(attr); + m_ownsAttr = true; + } + +private: + wxTreeListMainWindow *m_owner; // control the item belongs to + + // since there can be very many of these, we save size by chosing + // the smallest representation for the elements and by ordering + // the members to avoid padding. + wxArrayString m_text; // labels to be rendered for item + + wxTreeItemData *m_data; // user-provided data + + wxString *m_toolTip; + + wxArrayTreeListItems m_children; // list of children + wxTreeListItem *m_parent; // parent of this item + + wxTreeItemAttr *m_attr; // attributes??? + + // tree ctrl images for the normal, selected, expanded and + // expanded+selected states + short m_images[wxTreeItemIcon_Max]; + wxArrayShort m_col_images; // images for the various columns (!= main) + + // main column item positions + wxCoord m_x; // (virtual) offset from left (vertical line) + wxCoord m_y; // (virtual) offset from top + wxCoord m_text_x; // item offset from left + short m_width; // width of this item + unsigned char m_height; // height of this item + + // use bitfields to save size + int m_isCollapsed :1; + int m_hasHilight :1; // same as focused + int m_hasPlus :1; // used for item which doesn't have + // children but has a [+] button + int m_isBold :1; // render the label in bold font + int m_ownsAttr :1; // delete attribute when done +}; + +// =========================================================================== +// implementation +// =========================================================================== + +// --------------------------------------------------------------------------- +// wxTreeListRenameTimer (internal) +// --------------------------------------------------------------------------- + +wxTreeListRenameTimer::wxTreeListRenameTimer( wxTreeListMainWindow *owner ) +{ + m_owner = owner; +} + +void wxTreeListRenameTimer::Notify() +{ + m_owner->OnRenameTimer(); +} + +//----------------------------------------------------------------------------- +// wxEditTextCtrl (internal) +//----------------------------------------------------------------------------- + +BEGIN_EVENT_TABLE (wxEditTextCtrl,wxTextCtrl) + EVT_CHAR (wxEditTextCtrl::OnChar) + EVT_KEY_UP (wxEditTextCtrl::OnKeyUp) + EVT_KILL_FOCUS (wxEditTextCtrl::OnKillFocus) +END_EVENT_TABLE() + +wxEditTextCtrl::wxEditTextCtrl (wxWindow *parent, + const wxWindowID id, + bool *accept, + wxString *res, + wxTreeListMainWindow *owner, + const wxString &value, + const wxPoint &pos, + const wxSize &size, + int style, + const wxValidator& validator, + const wxString &name) + : wxTextCtrl (parent, id, value, pos, size, style | wxSIMPLE_BORDER, validator, name) +{ + m_res = res; + m_accept = accept; + m_owner = owner; + (*m_accept) = false; + (*m_res) = wxEmptyString; + m_startValue = value; + m_finished = false; +} + +wxEditTextCtrl::~wxEditTextCtrl() { + EndEdit(true); // cancelled +} + +void wxEditTextCtrl::EndEdit(bool isCancelled) { + if (m_finished) return; + m_finished = true; + + if (m_owner) { + (*m_accept) = ! isCancelled; + (*m_res) = isCancelled ? m_startValue : GetValue(); + m_owner->OnRenameAccept(*m_res == m_startValue); + m_owner->m_editControl = NULL; + m_owner->m_editItem = NULL; + m_owner->SetFocus(); // This doesn't work. TODO. + m_owner = NULL; + } + + Destroy(); +} + +bool wxEditTextCtrl::Destroy() { + Hide(); + wxTheApp->GetTraits()->ScheduleForDestroy(this); + return true; +} + +void wxEditTextCtrl::OnChar( wxKeyEvent &event ) +{ + if (m_finished) + { + event.Skip(); + return; + } + if (event.GetKeyCode() == WXK_RETURN) + { + EndEdit(false); // not cancelled + return; + } + if (event.GetKeyCode() == WXK_ESCAPE) + { + EndEdit(true); // cancelled + return; + } + event.Skip(); +} + +void wxEditTextCtrl::OnKeyUp( wxKeyEvent &event ) +{ + if (m_finished) + { + event.Skip(); + return; + } + + // auto-grow the textctrl: + wxSize parentSize = m_owner->GetSize(); + wxPoint myPos = GetPosition(); + wxSize mySize = GetSize(); + int sx, sy; + GetTextExtent(GetValue() + _T("M"), &sx, &sy); + if (myPos.x + sx > parentSize.x) sx = parentSize.x - myPos.x; + if (mySize.x > sx) sx = mySize.x; + SetSize(sx, -1); + + event.Skip(); +} + +void wxEditTextCtrl::OnKillFocus( wxFocusEvent &event ) +{ + if (m_finished) + { + event.Skip(); + return; + } + + EndEdit(false); // not cancelled +} + +//----------------------------------------------------------------------------- +// wxTreeListHeaderWindow +//----------------------------------------------------------------------------- + +IMPLEMENT_DYNAMIC_CLASS(wxTreeListHeaderWindow,wxWindow); + +BEGIN_EVENT_TABLE(wxTreeListHeaderWindow,wxWindow) + EVT_PAINT (wxTreeListHeaderWindow::OnPaint) + EVT_ERASE_BACKGROUND(wxTreeListHeaderWindow::OnEraseBackground) // reduce flicker + EVT_MOUSE_EVENTS (wxTreeListHeaderWindow::OnMouse) + EVT_SET_FOCUS (wxTreeListHeaderWindow::OnSetFocus) +END_EVENT_TABLE() + + +void wxTreeListHeaderWindow::Init() +{ + m_currentCursor = (wxCursor *) NULL; + m_isDragging = false; + m_dirty = false; + m_total_col_width = 0; +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) + m_hotTrackCol = -1; +#endif + + // prevent any background repaint in order to reducing flicker + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +} + +wxTreeListHeaderWindow::wxTreeListHeaderWindow() +{ + Init(); + + m_owner = (wxTreeListMainWindow *) NULL; + m_resizeCursor = (wxCursor *) NULL; +} + +wxTreeListHeaderWindow::wxTreeListHeaderWindow( wxWindow *win, + wxWindowID id, + wxTreeListMainWindow *owner, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString &name ) + : wxWindow( win, id, pos, size, style, name ) +{ + Init(); + + m_owner = owner; + m_resizeCursor = new wxCursor(wxCURSOR_SIZEWE); + +#if !wxCHECK_VERSION(2, 5, 0) + SetBackgroundColour (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_BTNFACE)); +#else + SetBackgroundColour (wxSystemSettings::GetColour (wxSYS_COLOUR_BTNFACE)); +#endif +} + +wxTreeListHeaderWindow::~wxTreeListHeaderWindow() +{ + delete m_resizeCursor; +} + +void wxTreeListHeaderWindow::DoDrawRect( wxDC *dc, int x, int y, int w, int h ) +{ +#if !wxCHECK_VERSION(2, 5, 0) + wxPen pen (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_BTNSHADOW ), 1, wxSOLID); +#else + wxPen pen (wxSystemSettings::GetColour (wxSYS_COLOUR_BTNSHADOW ), 1, wxSOLID); +#endif + + const int m_corner = 1; + + dc->SetBrush( *wxTRANSPARENT_BRUSH ); +#if defined( __WXMAC__ ) + dc->SetPen (pen); +#else // !GTK, !Mac + dc->SetPen( *wxBLACK_PEN ); +#endif + dc->DrawLine( x+w-m_corner+1, y, x+w, y+h ); // right (outer) + dc->DrawRectangle( x, y+h, w+1, 1 ); // bottom (outer) + +#if defined( __WXMAC__ ) + pen = wxPen( wxColour( 0x88 , 0x88 , 0x88 ), 1, wxSOLID ); +#endif + dc->SetPen( pen ); + dc->DrawLine( x+w-m_corner, y, x+w-1, y+h ); // right (inner) + dc->DrawRectangle( x+1, y+h-1, w-2, 1 ); // bottom (inner) + + dc->SetPen( *wxWHITE_PEN ); + dc->DrawRectangle( x, y, w-m_corner+1, 1 ); // top (outer) + dc->DrawRectangle( x, y, 1, h ); // left (outer) + dc->DrawLine( x, y+h-1, x+1, y+h-1 ); + dc->DrawLine( x+w-1, y, x+w-1, y+1 ); +} + +// shift the DC origin to match the position of the main window horz +// scrollbar: this allows us to always use logical coords +void wxTreeListHeaderWindow::AdjustDC(wxDC& dc) +{ + int xpix; + m_owner->GetScrollPixelsPerUnit( &xpix, NULL ); + int x; + m_owner->GetViewStart( &x, NULL ); + + // account for the horz scrollbar offset + dc.SetDeviceOrigin( -x * xpix, 0 ); +} + +void wxTreeListHeaderWindow::OnPaint( wxPaintEvent &WXUNUSED(event) ) +{ + wxAutoBufferedPaintDC dc( this ); + AdjustDC( dc ); + + int x = HEADER_OFFSET_X; + + // width and height of the entire header window + int w, h; + GetClientSize( &w, &h ); + m_owner->CalcUnscrolledPosition(w, 0, &w, NULL); + dc.SetBackgroundMode(wxTRANSPARENT); + +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) + int numColumns = GetColumnCount(); + for ( int i = 0; i < numColumns && x < w; i++ ) + { + if (!IsColumnShown (i)) continue; // do next column if not shown + + wxHeaderButtonParams params; + + // TODO: columnInfo should have label colours... + params.m_labelColour = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT ); + params.m_labelFont = GetFont(); + + wxTreeListColumnInfo& column = GetColumn(i); + int wCol = column.GetWidth(); + int flags = 0; + wxRect rect(x, 0, wCol, h); + x += wCol; + + if ( i == m_hotTrackCol) + flags |= wxCONTROL_CURRENT; + + params.m_labelText = column.GetText(); + params.m_labelAlignment = column.GetAlignment(); + + int image = column.GetImage(); + wxImageList* imageList = m_owner->GetImageList(); + if ((image != -1) && imageList) + params.m_labelBitmap = imageList->GetBitmap(image); + + wxRendererNative::Get().DrawHeaderButton(this, dc, rect, flags, wxHDR_SORT_ICON_NONE, ¶ms); + } + + if (x < w) { + wxRect rect(x, 0, w-x, h); + wxRendererNative::Get().DrawHeaderButton(this, dc, rect); + } + +#else // not 2.7.0.1+ + + dc.SetFont( GetFont() ); + + // do *not* use the listctrl colour for headers - one day we will have a + // function to set it separately + //dc.SetTextForeground( *wxBLACK ); +#if !wxCHECK_VERSION(2, 5, 0) + dc.SetTextForeground (wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOWTEXT )); +#else + dc.SetTextForeground (wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT )); +#endif + + int numColumns = GetColumnCount(); + for ( int i = 0; i < numColumns && x < w; i++ ) + { + if (!IsColumnShown (i)) continue; // do next column if not shown + + wxTreeListColumnInfo& column = GetColumn(i); + int wCol = column.GetWidth(); + + // the width of the rect to draw: make it smaller to fit entirely + // inside the column rect + int cw = wCol - 2; + +#if !wxCHECK_VERSION(2, 7, 0) + dc.SetPen( *wxWHITE_PEN ); + DoDrawRect( &dc, x, HEADER_OFFSET_Y, cw, h-2 ); +#else + wxRect rect(x, HEADER_OFFSET_Y, cw, h-2); + wxRendererNative::GetDefault().DrawHeaderButton (this, dc, rect); +#endif + + // if we have an image, draw it on the right of the label + int image = column.GetImage(); //item.m_image; + int ix = -2, iy = 0; + wxImageList* imageList = m_owner->GetImageList(); + if ((image != -1) && imageList) { + imageList->GetSize (image, ix, iy); + } + + // extra margins around the text label + int text_width = 0; + int text_x = x; + int image_offset = cw - ix - 1; + + switch(column.GetAlignment()) { + case wxALIGN_LEFT: + text_x += EXTRA_WIDTH; + cw -= ix + 2; + break; + case wxALIGN_RIGHT: + dc.GetTextExtent (column.GetText(), &text_width, NULL); + text_x += cw - text_width - EXTRA_WIDTH - MARGIN; + image_offset = 0; + break; + case wxALIGN_CENTER: + dc.GetTextExtent(column.GetText(), &text_width, NULL); + text_x += (cw - text_width)/2 + ix + 2; + image_offset = (cw - text_width - ix - 2)/2 - MARGIN; + break; + } + + // draw the image + if ((image != -1) && imageList) { + imageList->Draw (image, dc, x + image_offset/*cw - ix - 1*/, + HEADER_OFFSET_Y + (h - 4 - iy)/2, + wxIMAGELIST_DRAW_TRANSPARENT); + } + + // draw the text clipping it so that it doesn't overwrite the column boundary + wxDCClipper clipper(dc, x, HEADER_OFFSET_Y, cw, h - 4 ); + dc.DrawText (column.GetText(), text_x, HEADER_OFFSET_Y + EXTRA_HEIGHT ); + + // next column + x += wCol; + } + + int more_w = m_owner->GetSize().x - x - HEADER_OFFSET_X; + if (more_w > 0) { +#if !wxCHECK_VERSION(2, 7, 0) + DoDrawRect (&dc, x, HEADER_OFFSET_Y, more_w, h-2 ); +#else + wxRect rect (x, HEADER_OFFSET_Y, more_w, h-2); + wxRendererNative::GetDefault().DrawHeaderButton (this, dc, rect); +#endif + } + +#endif // 2.7.0.1 +} + +void wxTreeListHeaderWindow::DrawCurrent() +{ + int x1 = m_currentX; + int y1 = 0; + ClientToScreen (&x1, &y1); + + int x2 = m_currentX-1; +#ifdef __WXMSW__ + ++x2; // but why ???? +#endif + int y2 = 0; + m_owner->GetClientSize( NULL, &y2 ); + m_owner->ClientToScreen( &x2, &y2 ); + + wxScreenDC dc; + dc.SetLogicalFunction (wxINVERT); + dc.SetPen (wxPen (*wxBLACK, 2, wxSOLID)); + dc.SetBrush (*wxTRANSPARENT_BRUSH); + + AdjustDC(dc); + dc.DrawLine (x1, y1, x2, y2); + dc.SetLogicalFunction (wxCOPY); + dc.SetPen (wxNullPen); + dc.SetBrush (wxNullBrush); +} + +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) +int wxTreeListHeaderWindow::XToCol(int x) +{ + int colLeft = 0; + int numColumns = GetColumnCount(); + for ( int col = 0; col < numColumns; col++ ) + { + if (!IsColumnShown(col)) continue; + wxTreeListColumnInfo& column = GetColumn(col); + + if ( x < (colLeft + column.GetWidth()) ) + return col; + + colLeft += column.GetWidth(); + } + return -1; +} + +void wxTreeListHeaderWindow::RefreshColLabel(int col) +{ + if ( col > GetColumnCount() ) + return; + + int x = 0; + int width = 0; + int idx = 0; + do { + if (!IsColumnShown(idx)) continue; + wxTreeListColumnInfo& column = GetColumn(idx); + x += width; + width = column.GetWidth(); + } while (++idx <= col); + + m_owner->CalcScrolledPosition(x, 0, &x, NULL); + RefreshRect(wxRect(x, 0, width, GetSize().GetHeight())); +} +#endif + +void wxTreeListHeaderWindow::OnMouse (wxMouseEvent &event) { + + // we want to work with logical coords + int x; + m_owner->CalcUnscrolledPosition(event.GetX(), 0, &x, NULL); + +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) + if ( event.Moving() ) + { + int col = XToCol(x); + if ( col != m_hotTrackCol ) + { + // Refresh the col header so it will be painted with hot tracking + // (if supported by the native renderer.) + RefreshColLabel(col); + + // Also refresh the old hot header + if ( m_hotTrackCol >= 0 ) + RefreshColLabel(m_hotTrackCol); + + m_hotTrackCol = col; + } + } + + if ( event.Leaving() && m_hotTrackCol >= 0 ) + { + // Leaving the window so clear any hot tracking indicator that may be present + RefreshColLabel(m_hotTrackCol); + m_hotTrackCol = -1; + } +#endif + + if (m_isDragging) { + + SendListEvent (wxEVT_COMMAND_LIST_COL_DRAGGING, event.GetPosition()); + + // we don't draw the line beyond our window, but we allow dragging it + // there + int w = 0; + GetClientSize( &w, NULL ); + m_owner->CalcUnscrolledPosition(w, 0, &w, NULL); + w -= 6; + + // erase the line if it was drawn + if (m_currentX < w) DrawCurrent(); + + if (event.ButtonUp()) { + m_isDragging = false; + if (HasCapture()) ReleaseMouse(); + m_dirty = true; + SetColumnWidth (m_column, m_currentX - m_minX); + Refresh(); + SendListEvent (wxEVT_COMMAND_LIST_COL_END_DRAG, event.GetPosition()); + }else{ + m_currentX = wxMax (m_minX + 7, x); + + // draw in the new location + if (m_currentX < w) DrawCurrent(); + } + + }else{ // not dragging + + m_minX = 0; + bool hit_border = false; + + // end of the current column + int xpos = 0; + + // find the column where this event occured + int countCol = GetColumnCount(); + for (int column = 0; column < countCol; column++) { + if (!IsColumnShown (column)) continue; // do next if not shown + + xpos += GetColumnWidth (column); + m_column = column; + if (abs (x-xpos) < 3) { + // near the column border + hit_border = true; + break; + } + + if (x < xpos) { + // inside the column + break; + } + + m_minX = xpos; + } + + if (event.LeftDown() || event.RightUp()) { + if (hit_border && event.LeftDown()) { + m_isDragging = true; + CaptureMouse(); + m_currentX = x; + DrawCurrent(); + SendListEvent (wxEVT_COMMAND_LIST_COL_BEGIN_DRAG, event.GetPosition()); + }else{ // click on a column + wxEventType evt = event.LeftDown()? wxEVT_COMMAND_LIST_COL_CLICK: + wxEVT_COMMAND_LIST_COL_RIGHT_CLICK; + SendListEvent (evt, event.GetPosition()); + } + }else if (event.LeftDClick() && hit_border) { + SetColumnWidth (m_column, m_owner->GetBestColumnWidth (m_column)); + Refresh(); + + }else if (event.Moving()) { + bool setCursor; + if (hit_border) { + setCursor = m_currentCursor == wxSTANDARD_CURSOR; + m_currentCursor = m_resizeCursor; + }else{ + setCursor = m_currentCursor != wxSTANDARD_CURSOR; + m_currentCursor = wxSTANDARD_CURSOR; + } + if (setCursor) SetCursor (*m_currentCursor); + } + + } +} + +void wxTreeListHeaderWindow::OnSetFocus (wxFocusEvent &WXUNUSED(event)) { + m_owner->SetFocus(); +} + +void wxTreeListHeaderWindow::SendListEvent (wxEventType type, wxPoint pos) { + wxWindow *parent = GetParent(); + wxListEvent le (type, parent->GetId()); + le.SetEventObject (parent); + le.m_pointDrag = pos; + + // the position should be relative to the parent window, not + // this one for compatibility with MSW and common sense: the + // user code doesn't know anything at all about this header + // window, so why should it get positions relative to it? + le.m_pointDrag.y -= GetSize().y; + le.m_col = m_column; + parent->GetEventHandler()->ProcessEvent (le); +} + +void wxTreeListHeaderWindow::AddColumn (const wxTreeListColumnInfo& colInfo) { + m_columns.Add (colInfo); + m_total_col_width += colInfo.GetWidth(); + m_owner->AdjustMyScrollbars(); + m_owner->m_dirty = true; +} + +void wxTreeListHeaderWindow::SetColumnWidth (int column, int width) { + wxCHECK_RET ((column >= 0) && (column < GetColumnCount()), _T("Invalid column")); + m_total_col_width -= m_columns[column].GetWidth(); + m_columns[column].SetWidth(width); + m_total_col_width += width; + m_owner->AdjustMyScrollbars(); + m_owner->m_dirty = true; +} + +void wxTreeListHeaderWindow::InsertColumn (int before, const wxTreeListColumnInfo& colInfo) { + wxCHECK_RET ((before >= 0) && (before < GetColumnCount()), _T("Invalid column")); + m_columns.Insert (colInfo, before); + m_total_col_width += colInfo.GetWidth(); + m_owner->AdjustMyScrollbars(); + m_owner->m_dirty = true; +} + +void wxTreeListHeaderWindow::RemoveColumn (int column) { + wxCHECK_RET ((column >= 0) && (column < GetColumnCount()), _T("Invalid column")); + m_total_col_width -= m_columns[column].GetWidth(); + m_columns.RemoveAt (column); + m_owner->AdjustMyScrollbars(); + m_owner->m_dirty = true; +} + +void wxTreeListHeaderWindow::SetColumn (int column, const wxTreeListColumnInfo& info) { + wxCHECK_RET ((column >= 0) && (column < GetColumnCount()), _T("Invalid column")); + int w = m_columns[column].GetWidth(); + m_columns[column] = info; + if (w != info.GetWidth()) { + m_total_col_width += info.GetWidth() - w; + m_owner->AdjustMyScrollbars(); + } + m_owner->m_dirty = true; +} + +// --------------------------------------------------------------------------- +// wxTreeListItem +// --------------------------------------------------------------------------- + +wxTreeListItem::wxTreeListItem (wxTreeListMainWindow *owner, + wxTreeListItem *parent, + const wxArrayString& text, + int image, int selImage, + wxTreeItemData *data) + : m_text (text) { + + m_images[wxTreeItemIcon_Normal] = image; + m_images[wxTreeItemIcon_Selected] = selImage; + m_images[wxTreeItemIcon_Expanded] = NO_IMAGE; + m_images[wxTreeItemIcon_SelectedExpanded] = NO_IMAGE; + + m_data = data; + m_toolTip = NULL; + m_x = 0; + m_y = 0; + m_text_x = 0; + + m_isCollapsed = true; + m_hasHilight = false; + m_hasPlus = false; + m_isBold = false; + + m_owner = owner; + m_parent = parent; + + m_attr = (wxTreeItemAttr *)NULL; + m_ownsAttr = false; + + // We don't know the height here yet. + m_width = 0; + m_height = 0; +} + +wxTreeListItem::~wxTreeListItem() { + delete m_data; + if (m_toolTip) delete m_toolTip; + if (m_ownsAttr) delete m_attr; + + wxASSERT_MSG( m_children.IsEmpty(), _T("please call DeleteChildren() before destructor")); +} + +void wxTreeListItem::DeleteChildren () { + m_children.Empty(); +} + +void wxTreeListItem::SetText (const wxString &text) { + if (m_text.GetCount() > 0) { + m_text[0] = text; + }else{ + m_text.Add (text); + } +} + +size_t wxTreeListItem::GetChildrenCount (bool recursively) const { + size_t count = m_children.Count(); + if (!recursively) return count; + + size_t total = count; + for (size_t n = 0; n < count; ++n) { + total += m_children[n]->GetChildrenCount(); + } + return total; +} + +void wxTreeListItem::GetSize (int &x, int &y, const wxTreeListMainWindow *theButton) { + int bottomY = m_y + theButton->GetLineHeight (this); + if (y < bottomY) y = bottomY; + int width = m_x + m_width; + if ( x < width ) x = width; + + if (IsExpanded()) { + size_t count = m_children.Count(); + for (size_t n = 0; n < count; ++n ) { + m_children[n]->GetSize (x, y, theButton); + } + } +} + +wxTreeListItem *wxTreeListItem::HitTest (const wxPoint& point, + const wxTreeListMainWindow *theCtrl, + int &flags, int& column, int level) { + + // reset any previous hit infos + flags = 0; + column = -1; + + // for a hidden root node, don't evaluate it, but do evaluate children + if (!theCtrl->HasFlag(wxTR_HIDE_ROOT) || (level > 0)) { + + wxTreeListHeaderWindow* header_win = theCtrl->m_owner->GetHeaderWindow(); + + // check for right of all columns (outside) + if (point.x > header_win->GetWidth()) return (wxTreeListItem*) NULL; + // else find column + for (int x = 0, j = 0; j < theCtrl->GetColumnCount(); ++j) { + if (!header_win->IsColumnShown(j)) continue; + int w = header_win->GetColumnWidth (j); + if (point.x >= x && point.x < x+w) { + column = j; + break; + } + x += w; + } + + // evaluate if y-pos is okay + int h = theCtrl->GetLineHeight (this); + if ((point.y >= m_y) && (point.y <= m_y + h)) { + + // check for above/below middle + int y_mid = m_y + h/2; + if (point.y < y_mid) { + flags |= wxTREE_HITTEST_ONITEMUPPERPART; + }else{ + flags |= wxTREE_HITTEST_ONITEMLOWERPART; + } + + // check for button hit + if (HasPlus() && theCtrl->HasButtons()) { + int bntX = m_x - theCtrl->m_btnWidth2; + int bntY = y_mid - theCtrl->m_btnHeight2; + if ((point.x >= bntX) && (point.x <= (bntX + theCtrl->m_btnWidth)) && + (point.y >= bntY) && (point.y <= (bntY + theCtrl->m_btnHeight))) { + flags |= wxTREE_HITTEST_ONITEMBUTTON; + return this; + } + } + + // check for image hit + if (theCtrl->m_imgWidth > 0) { + int imgX = m_text_x - theCtrl->m_imgWidth - MARGIN; + int imgY = y_mid - theCtrl->m_imgHeight2; + if ((point.x >= imgX) && (point.x <= (imgX + theCtrl->m_imgWidth)) && + (point.y >= imgY) && (point.y <= (imgY + theCtrl->m_imgHeight))) { + flags |= wxTREE_HITTEST_ONITEMICON; + return this; + } + } + + // check for label hit + if ((point.x >= m_text_x) && (point.x <= (m_text_x + m_width))) { + flags |= wxTREE_HITTEST_ONITEMLABEL; + return this; + } + + // check for indent hit after button and image hit + if (point.x < m_x) { + flags |= wxTREE_HITTEST_ONITEMINDENT; +// Ronan, 2008.07.17: removed, not consistent column = -1; // considered not belonging to main column + return this; + } + + // check for right of label + int end = 0; + for (int i = 0; i <= theCtrl->GetMainColumn(); ++i) end += header_win->GetColumnWidth (i); + if ((point.x > (m_text_x + m_width)) && (point.x <= end)) { + flags |= wxTREE_HITTEST_ONITEMRIGHT; +// Ronan, 2008.07.17: removed, not consistent column = -1; // considered not belonging to main column + return this; + } + + // else check for each column except main + if (column >= 0 && column != theCtrl->GetMainColumn()) { + flags |= wxTREE_HITTEST_ONITEMCOLUMN; + return this; + } + + // no special flag or column found + return this; + + } + + // if children not expanded, return no item + if (!IsExpanded()) return (wxTreeListItem*) NULL; + } + + // in any case evaluate children + wxTreeListItem *child; + size_t count = m_children.Count(); + for (size_t n = 0; n < count; n++) { + child = m_children[n]->HitTest (point, theCtrl, flags, column, level+1); + if (child) return child; + } + + // not found + return (wxTreeListItem*) NULL; +} + +int wxTreeListItem::GetCurrentImage() const { + int image = NO_IMAGE; + if (IsExpanded()) { + if (IsSelected()) { + image = GetImage (wxTreeItemIcon_SelectedExpanded); + }else{ + image = GetImage (wxTreeItemIcon_Expanded); + } + }else{ // not expanded + if (IsSelected()) { + image = GetImage (wxTreeItemIcon_Selected); + }else{ + image = GetImage (wxTreeItemIcon_Normal); + } + } + + // maybe it doesn't have the specific image, try the default one instead + if (image == NO_IMAGE) image = GetImage(); + + return image; +} + +// --------------------------------------------------------------------------- +// wxTreeListMainWindow implementation +// --------------------------------------------------------------------------- + +IMPLEMENT_DYNAMIC_CLASS(wxTreeListMainWindow, wxScrolledWindow) + +BEGIN_EVENT_TABLE(wxTreeListMainWindow, wxScrolledWindow) + EVT_PAINT (wxTreeListMainWindow::OnPaint) + EVT_ERASE_BACKGROUND(wxTreeListMainWindow::OnEraseBackground) // to reduce flicker + EVT_MOUSE_EVENTS (wxTreeListMainWindow::OnMouse) + EVT_CHAR (wxTreeListMainWindow::OnChar) + EVT_SET_FOCUS (wxTreeListMainWindow::OnSetFocus) + EVT_KILL_FOCUS (wxTreeListMainWindow::OnKillFocus) + EVT_IDLE (wxTreeListMainWindow::OnIdle) + EVT_SCROLLWIN (wxTreeListMainWindow::OnScroll) + EVT_MOUSE_CAPTURE_LOST(wxTreeListMainWindow::OnCaptureLost) +END_EVENT_TABLE() + + +// --------------------------------------------------------------------------- +// construction/destruction +// --------------------------------------------------------------------------- + + +void wxTreeListMainWindow::Init() { + + m_rootItem = (wxTreeListItem*)NULL; + m_curItem = (wxTreeListItem*)NULL; + m_shiftItem = (wxTreeListItem*)NULL; + m_editItem = (wxTreeListItem*)NULL; + m_selectItem = (wxTreeListItem*)NULL; + + m_curColumn = -1; // no current column + + m_hasFocus = false; + m_dirty = false; + + m_lineHeight = LINEHEIGHT; + m_indent = MININDENT; // min. indent + m_linespacing = 4; + +#if !wxCHECK_VERSION(2, 5, 0) + m_hilightBrush = new wxBrush (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_HIGHLIGHT), wxSOLID); + m_hilightUnfocusedBrush = new wxBrush (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_BTNSHADOW), wxSOLID); +#else + m_hilightBrush = new wxBrush (wxSystemSettings::GetColour (wxSYS_COLOUR_HIGHLIGHT), wxSOLID); + m_hilightUnfocusedBrush = new wxBrush (wxSystemSettings::GetColour (wxSYS_COLOUR_BTNSHADOW), wxSOLID); +#endif + + m_imageListNormal = (wxImageList *) NULL; + m_imageListButtons = (wxImageList *) NULL; + m_imageListState = (wxImageList *) NULL; + m_ownsImageListNormal = m_ownsImageListButtons = + m_ownsImageListState = false; + + m_imgWidth = 0, m_imgWidth2 = 0; + m_imgHeight = 0, m_imgHeight2 = 0; + m_btnWidth = 0, m_btnWidth2 = 0; + m_btnHeight = 0, m_btnHeight2 = 0; + + m_isDragStarted = m_isDragging = false; + m_dragItem = NULL; + m_dragCol = -1; + + m_editTimer = new wxTreeListRenameTimer (this); + m_editControl = NULL; + + m_lastOnSame = false; + m_left_down_selection = false; + + m_findTimer = new wxTimer (this, -1); + +#if defined( __WXMAC__ ) && defined(__WXMAC_CARBON__) + m_normalFont.MacCreateThemeFont (kThemeViewsFont); +#else + m_normalFont = wxSystemSettings::GetFont (wxSYS_DEFAULT_GUI_FONT); +#endif + m_boldFont = wxFont( m_normalFont.GetPointSize(), + m_normalFont.GetFamily(), + m_normalFont.GetStyle(), + wxBOLD, + m_normalFont.GetUnderlined(), + m_normalFont.GetFaceName(), + m_normalFont.GetEncoding()); + + m_toolTip.clear(); + m_toolTipItem = (wxTreeListItem *)-1; // no tooltip displayed + m_isItemToolTip = false; // so far no item-specific tooltip +} + +bool wxTreeListMainWindow::Create (wxTreeListCtrl *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator &validator, + const wxString& name) { + +#ifdef __WXMAC__ + if (style & wxTR_HAS_BUTTONS) style |= wxTR_MAC_BUTTONS; + if (style & wxTR_HAS_BUTTONS) style &= ~wxTR_HAS_BUTTONS; + style &= ~wxTR_LINES_AT_ROOT; + style |= wxTR_NO_LINES; + + int major,minor; + wxGetOsVersion( &major, &minor ); + if (major < 10) style |= wxTR_ROW_LINES; +#endif + + wxScrolledWindow::Create (parent, id, pos, size, style|wxHSCROLL|wxVSCROLL, name); + +#if wxUSE_VALIDATORS + SetValidator(validator); +#endif + +#if !wxCHECK_VERSION(2, 5, 0) + SetBackgroundColour (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_LISTBOX)); +#else + SetBackgroundColour (wxSystemSettings::GetColour (wxSYS_COLOUR_LISTBOX)); +#endif + // prevent any background repaint in order to reducing flicker + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + +#ifdef __WXMSW__ + { + int i, j; + wxBitmap bmp(8, 8); + wxMemoryDC bdc; + bdc.SelectObject(bmp); + bdc.SetPen(*wxGREY_PEN); + bdc.DrawRectangle(-1, -1, 10, 10); + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) { + if (!((i + j) & 1)) { + bdc.DrawPoint(i, j); + } + } + } + + m_dottedPen = wxPen(bmp, 1); + } +#else +//? m_dottedPen = wxPen( *wxGREY_PEN, 1, wxDOT ); // too slow under XFree86 + m_dottedPen = wxPen( _T("grey"), 0, 0 ); // Bitmap based pen is not supported by GTK! +#endif + + m_owner = parent; + m_main_column = 0; + + return true; +} + +wxTreeListMainWindow::~wxTreeListMainWindow() { + delete m_hilightBrush; + delete m_hilightUnfocusedBrush; + + delete m_editTimer; + delete m_findTimer; + if (m_ownsImageListNormal) delete m_imageListNormal; + if (m_ownsImageListState) delete m_imageListState; + if (m_ownsImageListButtons) delete m_imageListButtons; + + if (m_editControl) { + m_editControl->SetOwner(NULL); // prevent control from calling us during delete + delete m_editControl; + } + + DeleteRoot(); +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- + +size_t wxTreeListMainWindow::GetCount() const { + return m_rootItem == NULL? 0: m_rootItem->GetChildrenCount(); +} + +void wxTreeListMainWindow::SetIndent (unsigned int indent) { + m_indent = wxMax ((unsigned)MININDENT, indent); + m_dirty = true; +} + +void wxTreeListMainWindow::SetLineSpacing (unsigned int spacing) { + m_linespacing = spacing; + m_dirty = true; + CalculateLineHeight(); +} + +size_t wxTreeListMainWindow::GetChildrenCount (const wxTreeItemId& item, + bool recursively) { + wxCHECK_MSG (item.IsOk(), 0u, _T("invalid tree item")); + return ((wxTreeListItem*)item.m_pItem)->GetChildrenCount (recursively); +} + +void wxTreeListMainWindow::SetWindowStyle (const long styles) { + // change to selection mode, reset selection + if ((styles ^ m_windowStyle) & wxTR_MULTIPLE) { UnselectAll(); } + // right now, just sets the styles. Eventually, we may + // want to update the inherited styles, but right now + // none of the parents has updatable styles + m_windowStyle = styles; + m_dirty = true; +} + +void wxTreeListMainWindow::SetToolTip(const wxString& tip) { + m_isItemToolTip = false; + m_toolTip = tip; + m_toolTipItem = (wxTreeListItem *)-1; // no tooltip displayed (force refresh) +} +void wxTreeListMainWindow::SetToolTip(wxToolTip *tip) { + m_isItemToolTip = false; + m_toolTip = (tip == NULL) ? wxString() : tip->GetTip(); + m_toolTipItem = (wxTreeListItem *)-1; // no tooltip displayed (force refresh) +} + +void wxTreeListMainWindow::SetItemToolTip(const wxTreeItemId& item, const wxString &tip) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + m_isItemToolTip = true; + ((wxTreeListItem*) item.m_pItem)->SetToolTip(tip); + m_toolTipItem = (wxTreeListItem *)-1; // no tooltip displayed (force refresh) +} + + +//----------------------------------------------------------------------------- +// functions to work with tree items +//----------------------------------------------------------------------------- + +int wxTreeListMainWindow::GetItemImage (const wxTreeItemId& item, int column, + wxTreeItemIcon which) const { + wxCHECK_MSG (item.IsOk(), -1, _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->GetImage (column, which); +} + +wxTreeItemData *wxTreeListMainWindow::GetItemData (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), NULL, _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->GetData(); +} + +bool wxTreeListMainWindow::GetItemBold (const wxTreeItemId& item) const { + wxCHECK_MSG(item.IsOk(), false, _T("invalid tree item")); + return ((wxTreeListItem *)item.m_pItem)->IsBold(); +} + +wxColour wxTreeListMainWindow::GetItemTextColour (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxNullColour, _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + return pItem->Attr().GetTextColour(); +} + +wxColour wxTreeListMainWindow::GetItemBackgroundColour (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxNullColour, _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + return pItem->Attr().GetBackgroundColour(); +} + +wxFont wxTreeListMainWindow::GetItemFont (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxNullFont, _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + return pItem->Attr().GetFont(); +} + +void wxTreeListMainWindow::SetItemImage (const wxTreeItemId& item, int column, + int image, wxTreeItemIcon which) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + pItem->SetImage (column, image, which); + wxClientDC dc (this); + CalculateSize (pItem, dc); + RefreshLine (pItem); +} + +void wxTreeListMainWindow::SetItemData (const wxTreeItemId& item, + wxTreeItemData *data) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + ((wxTreeListItem*) item.m_pItem)->SetData(data); +} + +void wxTreeListMainWindow::SetItemHasChildren (const wxTreeItemId& item, + bool has) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + pItem->SetHasPlus (has); + RefreshLine (pItem); +} + +void wxTreeListMainWindow::SetItemBold (const wxTreeItemId& item, bool bold) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + if (pItem->IsBold() != bold) { // avoid redrawing if no real change + pItem->SetBold (bold); + RefreshLine (pItem); + } +} + +void wxTreeListMainWindow::SetItemTextColour (const wxTreeItemId& item, + const wxColour& colour) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + pItem->Attr().SetTextColour (colour); + RefreshLine (pItem); +} + +void wxTreeListMainWindow::SetItemBackgroundColour (const wxTreeItemId& item, + const wxColour& colour) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + pItem->Attr().SetBackgroundColour (colour); + RefreshLine (pItem); +} + +void wxTreeListMainWindow::SetItemFont (const wxTreeItemId& item, + const wxFont& font) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + pItem->Attr().SetFont (font); + RefreshLine (pItem); +} + +bool wxTreeListMainWindow::SetFont (const wxFont &font) { + wxScrolledWindow::SetFont (font); + m_normalFont = font; + m_boldFont = wxFont (m_normalFont.GetPointSize(), + m_normalFont.GetFamily(), + m_normalFont.GetStyle(), + wxBOLD, + m_normalFont.GetUnderlined(), + m_normalFont.GetFaceName()); + CalculateLineHeight(); + return true; +} + + +// ---------------------------------------------------------------------------- +// item status inquiries +// ---------------------------------------------------------------------------- + +bool wxTreeListMainWindow::IsVisible (const wxTreeItemId& item, bool fullRow, bool within) const { + wxCHECK_MSG (item.IsOk(), false, _T("invalid tree item")); + + // An item is only visible if it's not a descendant of a collapsed item + wxTreeListItem *pItem = (wxTreeListItem*) item.m_pItem; + wxTreeListItem* parent = pItem->GetItemParent(); + while (parent) { + if (parent == m_rootItem && HasFlag(wxTR_HIDE_ROOT)) break; + if (!parent->IsExpanded()) return false; + parent = parent->GetItemParent(); + } + + // and the item is only visible if it is currently (fully) within the view + if (within) { + wxSize clientSize = GetClientSize(); + wxRect rect; + if ((!GetBoundingRect (item, rect)) || + ((!fullRow && rect.GetWidth() == 0) || rect.GetHeight() == 0) || + (rect.GetTop() < 0 || rect.GetBottom() >= clientSize.y) || + (!fullRow && (rect.GetLeft() < 0 || rect.GetRight() >= clientSize.x))) return false; + } + + return true; +} + +bool wxTreeListMainWindow::HasChildren (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), false, _T("invalid tree item")); + + // consider that the item does have children if it has the "+" button: it + // might not have them (if it had never been expanded yet) but then it + // could have them as well and it's better to err on this side rather than + // disabling some operations which are restricted to the items with + // children for an item which does have them + return ((wxTreeListItem*) item.m_pItem)->HasPlus(); +} + +bool wxTreeListMainWindow::IsExpanded (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), false, _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->IsExpanded(); +} + +bool wxTreeListMainWindow::IsSelected (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), false, _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->IsSelected(); +} + +bool wxTreeListMainWindow::IsBold (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), false, _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->IsBold(); +} + +// ---------------------------------------------------------------------------- +// navigation +// ---------------------------------------------------------------------------- + +wxTreeItemId wxTreeListMainWindow::GetItemParent (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + return ((wxTreeListItem*) item.m_pItem)->GetItemParent(); +} + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListMainWindow::GetFirstChild (const wxTreeItemId& item, + long& cookie) const { +#else +wxTreeItemId wxTreeListMainWindow::GetFirstChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { +#endif + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxArrayTreeListItems& children = ((wxTreeListItem*) item.m_pItem)->GetChildren(); + cookie = 0; + return (!children.IsEmpty())? wxTreeItemId(children.Item(0)): wxTreeItemId(); +} + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListMainWindow::GetNextChild (const wxTreeItemId& item, + long& cookie) const { +#else +wxTreeItemId wxTreeListMainWindow::GetNextChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { +#endif + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxArrayTreeListItems& children = ((wxTreeListItem*) item.m_pItem)->GetChildren(); + // it's ok to cast cookie to long, we never have indices which overflow "void*" + long *pIndex = ((long*)&cookie); + return ((*pIndex)+1 < (long)children.Count())? wxTreeItemId(children.Item(++(*pIndex))): wxTreeItemId(); +} + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListMainWindow::GetPrevChild (const wxTreeItemId& item, + long& cookie) const { +#else +wxTreeItemId wxTreeListMainWindow::GetPrevChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { +#endif + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxArrayTreeListItems& children = ((wxTreeListItem*) item.m_pItem)->GetChildren(); + // it's ok to cast cookie to long, we never have indices which overflow "void*" + long *pIndex = (long*)&cookie; + return ((*pIndex)-1 >= 0)? wxTreeItemId(children.Item(--(*pIndex))): wxTreeItemId(); +} + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListMainWindow::GetLastChild (const wxTreeItemId& item, + long& cookie) const { +#else +wxTreeItemId wxTreeListMainWindow::GetLastChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const { +#endif + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxArrayTreeListItems& children = ((wxTreeListItem*) item.m_pItem)->GetChildren(); + // it's ok to cast cookie to long, we never have indices which overflow "void*" + long *pIndex = ((long*)&cookie); + (*pIndex) = (long)children.Count(); + return (!children.IsEmpty())? wxTreeItemId(children.Last()): wxTreeItemId(); +} + +wxTreeItemId wxTreeListMainWindow::GetNextSibling (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + + // get parent + wxTreeListItem *i = (wxTreeListItem*) item.m_pItem; + wxTreeListItem *parent = i->GetItemParent(); + if (!parent) return wxTreeItemId(); // root item doesn't have any siblings + + // get index + wxArrayTreeListItems& siblings = parent->GetChildren(); + size_t index = siblings.Index (i); + wxASSERT (index != (size_t)wxNOT_FOUND); // I'm not a child of my parent? + return (index < siblings.Count()-1)? wxTreeItemId(siblings[index+1]): wxTreeItemId(); +} + +wxTreeItemId wxTreeListMainWindow::GetPrevSibling (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + + // get parent + wxTreeListItem *i = (wxTreeListItem*) item.m_pItem; + wxTreeListItem *parent = i->GetItemParent(); + if (!parent) return wxTreeItemId(); // root item doesn't have any siblings + + // get index + wxArrayTreeListItems& siblings = parent->GetChildren(); + size_t index = siblings.Index(i); + wxASSERT (index != (size_t)wxNOT_FOUND); // I'm not a child of my parent? + return (index >= 1)? wxTreeItemId(siblings[index-1]): wxTreeItemId(); +} + +// Only for internal use right now, but should probably be public +wxTreeItemId wxTreeListMainWindow::GetNext (const wxTreeItemId& item, bool fulltree) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + + // if there are any children, return first child + if (fulltree || ((wxTreeListItem*)item.m_pItem)->IsExpanded()) { + wxArrayTreeListItems& children = ((wxTreeListItem*)item.m_pItem)->GetChildren(); + if (children.GetCount() > 0) return children.Item (0); + } + + // get sibling of this item or of the ancestors instead + wxTreeItemId next; + wxTreeItemId parent = item; + do { + next = GetNextSibling (parent); + parent = GetItemParent (parent); + } while (!next.IsOk() && parent.IsOk()); + return next; +} + +// Only for internal use right now, but should probably be public +wxTreeItemId wxTreeListMainWindow::GetPrev (const wxTreeItemId& item, bool fulltree) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + + // if there are no previous sibling get parent + wxTreeItemId prev = GetPrevSibling (item); + if (! prev.IsOk()) return GetItemParent (item); + + // while previous sibling has children, return last + while (fulltree || ((wxTreeListItem*)prev.m_pItem)->IsExpanded()) { + wxArrayTreeListItems& children = ((wxTreeListItem*)prev.m_pItem)->GetChildren(); + if (children.GetCount() == 0) break; + prev = children.Item (children.GetCount() - 1); + } + + return prev; +} + +wxTreeItemId wxTreeListMainWindow::GetFirstExpandedItem() const { + return GetNextExpanded (GetRootItem()); +} + +wxTreeItemId wxTreeListMainWindow::GetNextExpanded (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + return GetNext (item, false); +} + +wxTreeItemId wxTreeListMainWindow::GetPrevExpanded (const wxTreeItemId& item) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + return GetPrev (item, false); +} + +wxTreeItemId wxTreeListMainWindow::GetFirstVisible(bool fullRow, bool within) const { + if (HasFlag(wxTR_HIDE_ROOT) || ! IsVisible(GetRootItem(), fullRow, within)) { + return GetNextVisible (GetRootItem(), fullRow, within); + } else { + return GetRootItem(); + } +} + +wxTreeItemId wxTreeListMainWindow::GetNextVisible (const wxTreeItemId& item, bool fullRow, bool within) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxTreeItemId id = GetNext (item, false); + while (id.IsOk()) { + if (IsVisible (id, fullRow, within)) return id; + id = GetNext (id, false); + } + return wxTreeItemId(); +} + +wxTreeItemId wxTreeListMainWindow::GetLastVisible ( bool fullRow, bool within) const { + wxCHECK_MSG (GetRootItem().IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxTreeItemId id = GetRootItem(); + wxTreeItemId res = id; + while ((id = GetNext (id, false)).IsOk()) { + if (IsVisible (id, fullRow, within)) res = id; + } + return res; +} + +wxTreeItemId wxTreeListMainWindow::GetPrevVisible (const wxTreeItemId& item, bool fullRow, bool within) const { + wxCHECK_MSG (item.IsOk(), wxTreeItemId(), _T("invalid tree item")); + wxTreeItemId id = GetPrev (item, true); + while (id.IsOk()) { + if (IsVisible (id, fullRow, within)) return id; + id = GetPrev(id, true); + } + return wxTreeItemId(); +} + +// ---------------------------------------------------------------------------- +// operations +// ---------------------------------------------------------------------------- + +// ---------------------------- ADD OPERATION ------------------------------- + +wxTreeItemId wxTreeListMainWindow::DoInsertItem (const wxTreeItemId& parentId, + size_t previous, + const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + wxTreeListItem *parent = (wxTreeListItem*)parentId.m_pItem; + wxCHECK_MSG (parent, wxTreeItemId(), _T("item must have a parent, at least root!") ); + m_dirty = true; // do this first so stuff below doesn't cause flicker + + wxArrayString arr; + arr.Alloc (GetColumnCount()); + for (int i = 0; i < (int)GetColumnCount(); ++i) arr.Add (wxEmptyString); + arr[m_main_column] = text; + wxTreeListItem *item = new wxTreeListItem (this, parent, arr, image, selImage, data); + if (data != NULL) { +#if !wxCHECK_VERSION(2, 5, 0) + data->SetId ((long)item); +#else + data->SetId (item); +#endif + } + parent->Insert (item, previous); + + return item; +} + +wxTreeItemId wxTreeListMainWindow::AddRoot (const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + wxCHECK_MSG(!m_rootItem, wxTreeItemId(), _T("tree can have only one root")); + wxCHECK_MSG(GetColumnCount(), wxTreeItemId(), _T("Add column(s) before adding the root item")); + m_dirty = true; // do this first so stuff below doesn't cause flicker + + wxArrayString arr; + arr.Alloc (GetColumnCount()); + for (int i = 0; i < (int)GetColumnCount(); ++i) arr.Add (wxEmptyString); + arr[m_main_column] = text; + m_rootItem = new wxTreeListItem (this, (wxTreeListItem *)NULL, arr, image, selImage, data); + if (data != NULL) { +#if !wxCHECK_VERSION(2, 5, 0) + data->SetId((long)m_rootItem); +#else + data->SetId(m_rootItem); +#endif + } + if (HasFlag(wxTR_HIDE_ROOT)) { + // if we will hide the root, make sure children are visible + m_rootItem->SetHasPlus(); + m_rootItem->Expand(); +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + // TODO: suspect that deleting and recreating a root can leave a number of members dangling + // (here m_curItem should actually be set via SetCurrentItem() ) + m_curItem = (wxTreeListItem*)GetFirstChild (m_rootItem, cookie).m_pItem; + } + return m_rootItem; +} + +wxTreeItemId wxTreeListMainWindow::PrependItem (const wxTreeItemId& parent, + const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + return DoInsertItem (parent, 0u, text, image, selImage, data); +} + +wxTreeItemId wxTreeListMainWindow::InsertItem (const wxTreeItemId& parentId, + const wxTreeItemId& idPrevious, + const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + wxTreeListItem *parent = (wxTreeListItem*)parentId.m_pItem; + wxCHECK_MSG (parent, wxTreeItemId(), _T("item must have a parent, at least root!") ); + + int index = parent->GetChildren().Index((wxTreeListItem*) idPrevious.m_pItem); + wxASSERT_MSG( index != wxNOT_FOUND, + _T("previous item in wxTreeListMainWindow::InsertItem() is not a sibling") ); + + return DoInsertItem (parentId, ++index, text, image, selImage, data); +} + +wxTreeItemId wxTreeListMainWindow::InsertItem (const wxTreeItemId& parentId, + size_t before, + const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + wxTreeListItem *parent = (wxTreeListItem*)parentId.m_pItem; + wxCHECK_MSG (parent, wxTreeItemId(), _T("item must have a parent, at least root!") ); + + return DoInsertItem (parentId, before, text, image, selImage, data); +} + +wxTreeItemId wxTreeListMainWindow::AppendItem (const wxTreeItemId& parentId, + const wxString& text, + int image, int selImage, + wxTreeItemData *data) { + wxTreeListItem *parent = (wxTreeListItem*) parentId.m_pItem; + wxCHECK_MSG (parent, wxTreeItemId(), _T("item must have a parent, at least root!") ); + + return DoInsertItem (parent, parent->GetChildren().Count(), text, image, selImage, data); +} + + +// -------------------------- DELETE OPERATION ------------------------------ + +void wxTreeListMainWindow::Delete (const wxTreeItemId& itemId) { + if (! itemId.IsOk()) return; + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + wxTreeListItem *parent = item->GetItemParent(); + wxCHECK_RET (item != m_rootItem, _T("invalid item, root may not be deleted this way!")); + + // recursive delete + DoDeleteItem(item); + + // update parent --CAUTION: must come after delete itself, so that item's + // siblings may be found + if (parent) { + parent->GetChildren().Remove (item); // remove by value + } +} + + +void wxTreeListMainWindow::DeleteRoot() { + if (! m_rootItem) return; + + SetCurrentItem((wxTreeListItem*)NULL); + m_selectItem = (wxTreeListItem*)NULL; + m_shiftItem = (wxTreeListItem*)NULL; + + DeleteChildren (m_rootItem); + SendEvent(wxEVT_COMMAND_TREE_DELETE_ITEM, m_rootItem); + delete m_rootItem; m_rootItem = NULL; +} + + +void wxTreeListMainWindow::DeleteChildren (const wxTreeItemId& itemId) { + if (! itemId.IsOk()) return; + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + + // recursive delete on all children, starting from the right to prevent + // multiple selection changes (see m_curItem handling in DoDeleteItem() ) + wxArrayTreeListItems& children = item->GetChildren(); + for (size_t n = children.GetCount(); n>0; n--) { + DoDeleteItem(children[n-1]); + // immediately remove child from array, otherwise it might get selected + // as current item (see m_curItem handling in DoDeleteItem() ) + children.RemoveAt(n-1); + } +} + + +void wxTreeListMainWindow::DoDeleteItem(wxTreeListItem *item) { + wxCHECK_RET (item, _T("invalid item for delete!")); + + m_dirty = true; // do this first so stuff below doesn't cause flicker + + // cancel any editing + if (m_editControl) { + m_editControl->EndEdit(true); // cancelled + } + + // cancel any dragging + if (item == m_dragItem) { + // stop dragging + m_isDragStarted = m_isDragging = false; + if (HasCapture()) ReleaseMouse(); + } + + // don't stay with invalid m_curItem: take next sibling or reset to NULL + // NOTE: this might be slighty inefficient when deleting a whole tree + // but has the advantage that all deletion side-effects are handled here + if (item == m_curItem) { + SetCurrentItem(item->GetItemParent()); + if (m_curItem) { + wxArrayTreeListItems& siblings = m_curItem->GetChildren(); + size_t index = siblings.Index (item); + wxASSERT (index != (size_t)wxNOT_FOUND); // I'm not a child of my parent? + SetCurrentItem(index < siblings.Count()-1 ? siblings[index+1]: (wxTreeListItem*)NULL); + } + } + // don't stay with invalid m_shiftItem: reset it to NULL + if (item == m_shiftItem) m_shiftItem = (wxTreeListItem*)NULL; + // don't stay with invalid m_selectItem: default to current item + if (item == m_selectItem) { + m_selectItem = m_curItem; + SelectItem(m_selectItem, (wxTreeItemId*)NULL, true); // unselect others + } + + // recurse children, starting from the right to prevent multiple selection + // changes (see m_curItem handling above) + wxArrayTreeListItems& children = item->GetChildren(); + for (size_t n = children.GetCount(); n>0; n--) { + DoDeleteItem(children[n-1]); + // immediately remove child from array, otherwise it might get selected + // as current item (see m_curItem handling above) + children.RemoveAt(n-1); + } + + // delete item itself + SendEvent(wxEVT_COMMAND_TREE_DELETE_ITEM, item); + delete item; +} + + +// ---------------------------------------------------------------------------- + +void wxTreeListMainWindow::SetCurrentItem(wxTreeListItem *item) { +wxTreeListItem *old_item; + + old_item = m_curItem; m_curItem = item; + + // change of item, redraw previous + if (old_item != NULL && old_item != item) { + RefreshLine(old_item); + } + +} + +// ---------------------------------------------------------------------------- + +void wxTreeListMainWindow::Expand (const wxTreeItemId& itemId) { + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + wxCHECK_RET (item, _T("invalid item in wxTreeListMainWindow::Expand") ); + + if (!item->HasPlus() || item->IsExpanded()) return; + + // send event to user code + wxTreeEvent event(wxEVT_COMMAND_TREE_ITEM_EXPANDING, 0); + event.SetInt(m_curColumn); + if (SendEvent(0, item, &event) && !event.IsAllowed()) return; // expand canceled + + item->Expand(); + m_dirty = true; + + // send event to user code + event.SetEventType (wxEVT_COMMAND_TREE_ITEM_EXPANDED); + SendEvent(0, NULL, &event); +} + +void wxTreeListMainWindow::ExpandAll (const wxTreeItemId& itemId) { + wxCHECK_RET (itemId.IsOk(), _T("invalid tree item")); + + Expand (itemId); + if (!IsExpanded (itemId)) return; +#if !wxCHECK_VERSION(2, 5, 0) + long cookie; +#else + wxTreeItemIdValue cookie; +#endif + wxTreeItemId child = GetFirstChild (itemId, cookie); + while (child.IsOk()) { + ExpandAll (child); + child = GetNextChild (itemId, cookie); + } +} + +void wxTreeListMainWindow::Collapse (const wxTreeItemId& itemId) { + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + wxCHECK_RET (item, _T("invalid item in wxTreeListMainWindow::Collapse") ); + + if (!item->HasPlus() || !item->IsExpanded()) return; + + // send event to user code + wxTreeEvent event (wxEVT_COMMAND_TREE_ITEM_COLLAPSING, 0 ); + event.SetInt(m_curColumn); + if (SendEvent(0, item, &event) && !event.IsAllowed()) return; // collapse canceled + + item->Collapse(); + m_dirty = true; + + // send event to user code + event.SetEventType (wxEVT_COMMAND_TREE_ITEM_COLLAPSED); + SendEvent(0, NULL, &event); +} + +void wxTreeListMainWindow::CollapseAndReset (const wxTreeItemId& item) { + wxCHECK_RET (item.IsOk(), _T("invalid tree item")); + + Collapse (item); + DeleteChildren (item); +} + +void wxTreeListMainWindow::Toggle (const wxTreeItemId& itemId) { + wxCHECK_RET (itemId.IsOk(), _T("invalid tree item")); + + if (IsExpanded (itemId)) { + Collapse (itemId); + }else{ + Expand (itemId); + } +} + +void wxTreeListMainWindow::Unselect() { + if (m_selectItem) { + m_selectItem->SetHilight (false); + RefreshLine (m_selectItem); + m_selectItem = (wxTreeListItem*)NULL; + } +} + +void wxTreeListMainWindow::UnselectAllChildren (wxTreeListItem *item) { + wxCHECK_RET (item, _T("invalid tree item")); + + if (item->IsSelected()) { + item->SetHilight (false); + RefreshLine (item); + if (item == m_selectItem) m_selectItem = (wxTreeListItem*)NULL; + if (item != m_curItem) m_lastOnSame = false; // selection change, so reset edit marker + } + if (item->HasChildren()) { + wxArrayTreeListItems& children = item->GetChildren(); + size_t count = children.Count(); + for (size_t n = 0; n < count; ++n) { + UnselectAllChildren (children[n]); + } + } +} + +void wxTreeListMainWindow::UnselectAll() { + UnselectAllChildren ((wxTreeListItem*)GetRootItem().m_pItem); +} + +// Recursive function ! +// To stop we must have crt_itemGetItemParent(); + + if (!parent) {// This is root item + return TagAllChildrenUntilLast (crt_item, last_item); + } + + wxArrayTreeListItems& children = parent->GetChildren(); + int index = children.Index(crt_item); + wxASSERT (index != wxNOT_FOUND); // I'm not a child of my parent? + + if ((parent->HasChildren() && parent->IsExpanded()) || + ((parent == (wxTreeListItem*)GetRootItem().m_pItem) && HasFlag(wxTR_HIDE_ROOT))) { + size_t count = children.Count(); + for (size_t n = (index+1); n < count; ++n) { + if (TagAllChildrenUntilLast (children[n], last_item)) return true; + } + } + + return TagNextChildren (parent, last_item); +} + +bool wxTreeListMainWindow::TagAllChildrenUntilLast (wxTreeListItem *crt_item, + wxTreeListItem *last_item) { + crt_item->SetHilight (true); + RefreshLine(crt_item); + + if (crt_item==last_item) return true; + + if (crt_item->HasChildren() && crt_item->IsExpanded()) { + wxArrayTreeListItems& children = crt_item->GetChildren(); + size_t count = children.Count(); + for (size_t n = 0; n < count; ++n) { + if (TagAllChildrenUntilLast (children[n], last_item)) return true; + } + } + + return false; +} + +bool wxTreeListMainWindow::SelectItem (const wxTreeItemId& itemId, + const wxTreeItemId& lastId, + bool unselect_others) { + + wxTreeListItem *item = itemId.IsOk() ? (wxTreeListItem*) itemId.m_pItem : NULL; + + // send selecting event to the user code + wxTreeEvent event( wxEVT_COMMAND_TREE_SEL_CHANGING, 0); + event.SetInt(m_curColumn); +#if !wxCHECK_VERSION(2, 5, 0) + event.SetOldItem ((long)m_curItem); +#else + event.SetOldItem (m_curItem); +#endif + if (SendEvent(0, item, &event) && !event.IsAllowed()) return false; // veto on selection change + + // unselect all if unselect other items + bool bUnselectedAll = false; // see that UnselectAll is done only once + if (unselect_others) { + if (HasFlag(wxTR_MULTIPLE)) { + UnselectAll(); bUnselectedAll = true; + }else{ + Unselect(); // to speed up thing + } + } + + // select item range + if (lastId.IsOk() && itemId.IsOk() && (itemId != lastId)) { + + if (! bUnselectedAll) UnselectAll(); + wxTreeListItem *last = (wxTreeListItem*) lastId.m_pItem; + + // ensure that the position of the item it calculated in any case + if (m_dirty) CalculatePositions(); + + // select item range according Y-position + if (last->GetY() < item->GetY()) { + if (!TagAllChildrenUntilLast (last, item)) { + TagNextChildren (last, item); + } + }else{ + if (!TagAllChildrenUntilLast (item, last)) { + TagNextChildren (item, last); + } + } + + // or select single item + }else if (itemId.IsOk()) { + + // select item according its old selection + item->SetHilight (!item->IsSelected()); + RefreshLine (item); + if (unselect_others) { + m_selectItem = (item->IsSelected())? item: (wxTreeListItem*)NULL; + } + + // or select nothing + } else { + if (! bUnselectedAll) UnselectAll(); + } + + // send event to user code + event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED); + SendEvent(0, NULL, &event); + + return true; +} + +void wxTreeListMainWindow::SelectAll() { + wxTreeItemId root = GetRootItem(); + wxCHECK_RET (HasFlag(wxTR_MULTIPLE), _T("invalid tree style")); + wxCHECK_RET (root.IsOk(), _T("no tree")); + + // send event to user code + wxTreeEvent event (wxEVT_COMMAND_TREE_SEL_CHANGING, 0); +#if !wxCHECK_VERSION(2, 5, 0) + event.SetOldItem ((long)m_curItem); +#else + event.SetOldItem (m_curItem); +#endif + event.SetInt (-1); // no colum clicked + if (SendEvent(0, m_rootItem, &event) && !event.IsAllowed()) return; // selection change vetoed + +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + wxTreeListItem *first = (wxTreeListItem *)GetFirstChild (root, cookie).m_pItem; + wxTreeListItem *last = (wxTreeListItem *)GetLastChild (root, cookie).m_pItem; + if (!TagAllChildrenUntilLast (first, last)) { + TagNextChildren (first, last); + } + + // send event to user code + event.SetEventType (wxEVT_COMMAND_TREE_SEL_CHANGED); + SendEvent(0, NULL, &event); +} + +void wxTreeListMainWindow::FillArray (wxTreeListItem *item, + wxArrayTreeItemIds &array) const { + if (item->IsSelected()) array.Add (wxTreeItemId(item)); + + if (item->HasChildren()) { + wxArrayTreeListItems& children = item->GetChildren(); + size_t count = children.GetCount(); + for (size_t n = 0; n < count; ++n) FillArray (children[n], array); + } +} + +size_t wxTreeListMainWindow::GetSelections (wxArrayTreeItemIds &array) const { + array.Empty(); + wxTreeItemId idRoot = GetRootItem(); + if (idRoot.IsOk()) FillArray ((wxTreeListItem*) idRoot.m_pItem, array); + return array.Count(); +} + +void wxTreeListMainWindow::EnsureVisible (const wxTreeItemId& item) { + if (!item.IsOk()) return; // do nothing if no item + + // first expand all parent branches + wxTreeListItem *gitem = (wxTreeListItem*) item.m_pItem; + wxTreeListItem *parent = gitem->GetItemParent(); + while (parent) { + Expand (parent); + parent = parent->GetItemParent(); + } + + ScrollTo (item); + RefreshLine (gitem); +} + +void wxTreeListMainWindow::ScrollTo (const wxTreeItemId &item) { + if (!item.IsOk()) return; // do nothing if no item + + // ensure that the position of the item it calculated in any case + if (m_dirty) CalculatePositions(); + + wxTreeListItem *gitem = (wxTreeListItem*) item.m_pItem; + + // now scroll to the item + int item_y = gitem->GetY(); + + int xUnit, yUnit; + GetScrollPixelsPerUnit (&xUnit, &yUnit); + int start_x = 0; + int start_y = 0; + GetViewStart (&start_x, &start_y); + start_y *= yUnit; + + int client_h = 0; + int client_w = 0; + GetClientSize (&client_w, &client_h); + + int x = 0; + int y = 0; + m_rootItem->GetSize (x, y, this); + x = m_owner->GetHeaderWindow()->GetWidth(); + y += yUnit + 2; // one more scrollbar unit + 2 pixels + int x_pos = GetScrollPos( wxHORIZONTAL ); + + if (item_y < start_y+3) { + // going down, item should appear at top + SetScrollbars (xUnit, yUnit, xUnit ? x/xUnit : 0, yUnit ? y/yUnit : 0, x_pos, yUnit ? item_y/yUnit : 0); + }else if (item_y+GetLineHeight(gitem) > start_y+client_h) { + // going up, item should appear at bottom + item_y += yUnit + 2; + SetScrollbars (xUnit, yUnit, xUnit ? x/xUnit : 0, yUnit ? y/yUnit : 0, x_pos, yUnit ? (item_y+GetLineHeight(gitem)-client_h)/yUnit : 0 ); + } +} + +// FIXME: tree sorting functions are not reentrant and not MT-safe! +static wxTreeListMainWindow *s_treeBeingSorted = NULL; + +static int LINKAGEMODE tree_ctrl_compare_func(wxTreeListItem **item1, + wxTreeListItem **item2) +{ + wxCHECK_MSG (s_treeBeingSorted, 0, _T("bug in wxTreeListMainWindow::SortChildren()") ); + + return s_treeBeingSorted->OnCompareItems(*item1, *item2); +} + +int wxTreeListMainWindow::OnCompareItems(const wxTreeItemId& item1, + const wxTreeItemId& item2) +{ + return m_owner->OnCompareItems (item1, item2); +} + +void wxTreeListMainWindow::SortChildren (const wxTreeItemId& itemId) { + wxCHECK_RET (itemId.IsOk(), _T("invalid tree item")); + + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + + wxCHECK_RET (!s_treeBeingSorted, + _T("wxTreeListMainWindow::SortChildren is not reentrant") ); + + wxArrayTreeListItems& children = item->GetChildren(); + if ( children.Count() > 1 ) { + m_dirty = true; + s_treeBeingSorted = this; + children.Sort(tree_ctrl_compare_func); + s_treeBeingSorted = NULL; + } +} + +wxTreeItemId wxTreeListMainWindow::FindItem (const wxTreeItemId& item, const wxString& str, int mode) { + wxString itemText; + // determine start item + wxTreeItemId next = item; + if (next.IsOk()) { + if (mode & wxTL_MODE_NAV_LEVEL) { + next = GetNextSibling (next); + }else if (mode & wxTL_MODE_NAV_VISIBLE) { // + next = GetNextVisible (next, false, true); + }else if (mode & wxTL_MODE_NAV_EXPANDED) { + next = GetNextExpanded (next); + }else{ // (mode & wxTL_MODE_NAV_FULLTREE) default + next = GetNext (next, true); + } + } + +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + if (!next.IsOk()) { + next = GetRootItem(); + if (next.IsOk() && HasFlag(wxTR_HIDE_ROOT)) { + next = GetFirstChild (GetRootItem(), cookie); + } + } + if (!next.IsOk()) return (wxTreeItemId*)NULL; + + // start checking the next items + while (next.IsOk() && (next != item)) { + if (mode & wxTL_MODE_FIND_PARTIAL) { + itemText = GetItemText (next).Mid (0, str.Length()); + }else{ + itemText = GetItemText (next); + } + if (mode & wxTL_MODE_FIND_NOCASE) { + if (itemText.CmpNoCase (str) == 0) return next; + }else{ + if (itemText.Cmp (str) == 0) return next; + } + if (mode & wxTL_MODE_NAV_LEVEL) { + next = GetNextSibling (next); + }else if (mode & wxTL_MODE_NAV_VISIBLE) { // + next = GetNextVisible (next, false, true); + }else if (mode & wxTL_MODE_NAV_EXPANDED) { + next = GetNextExpanded (next); + }else{ // (mode & wxTL_MODE_NAV_FULLTREE) default + next = GetNext (next, true); + } + if (!next.IsOk() && item.IsOk()) { + next = (wxTreeListItem*)GetRootItem().m_pItem; + if (HasFlag(wxTR_HIDE_ROOT)) { + next = (wxTreeListItem*)GetNextChild (GetRootItem().m_pItem, cookie).m_pItem; + } + } + } + return (wxTreeItemId*)NULL; +} + +void wxTreeListMainWindow::SetDragItem (const wxTreeItemId& item) { + wxTreeListItem *prevItem = m_dragItem; + m_dragItem = (wxTreeListItem*) item.m_pItem; + if (prevItem) RefreshLine (prevItem); + if (m_dragItem) RefreshLine (m_dragItem); +} + +void wxTreeListMainWindow::CalculateLineHeight() { + wxClientDC dc (this); + dc.SetFont (m_normalFont); + m_lineHeight = (int)(dc.GetCharHeight() + m_linespacing); + + if (m_imageListNormal) { + // Calculate a m_lineHeight value from the normal Image sizes. + // May be toggle off. Then wxTreeListMainWindow will spread when + // necessary (which might look ugly). + int n = m_imageListNormal->GetImageCount(); + for (int i = 0; i < n ; i++) { + int width = 0, height = 0; + m_imageListNormal->GetSize(i, width, height); + if (height > m_lineHeight) m_lineHeight = height + m_linespacing; + } + } + + if (m_imageListButtons) { + // Calculate a m_lineHeight value from the Button image sizes. + // May be toggle off. Then wxTreeListMainWindow will spread when + // necessary (which might look ugly). + int n = m_imageListButtons->GetImageCount(); + for (int i = 0; i < n ; i++) { + int width = 0, height = 0; + m_imageListButtons->GetSize(i, width, height); + if (height > m_lineHeight) m_lineHeight = height + m_linespacing; + } + } + + if (m_lineHeight < 30) { // add 10% space if greater than 30 pixels + m_lineHeight += 2; // minimal 2 pixel space + }else{ + m_lineHeight += m_lineHeight / 10; // otherwise 10% space + } +} + +void wxTreeListMainWindow::SetImageList (wxImageList *imageList) { + if (m_ownsImageListNormal) delete m_imageListNormal; + m_imageListNormal = imageList; + m_ownsImageListNormal = false; + m_dirty = true; + CalculateLineHeight(); +} + +void wxTreeListMainWindow::SetStateImageList (wxImageList *imageList) { + if (m_ownsImageListState) delete m_imageListState; + m_imageListState = imageList; + m_ownsImageListState = false; +} + +void wxTreeListMainWindow::SetButtonsImageList (wxImageList *imageList) { + if (m_ownsImageListButtons) delete m_imageListButtons; + m_imageListButtons = imageList; + m_ownsImageListButtons = false; + m_dirty = true; + CalculateLineHeight(); +} + +void wxTreeListMainWindow::AssignImageList (wxImageList *imageList) { + SetImageList(imageList); + m_ownsImageListNormal = true; +} + +void wxTreeListMainWindow::AssignStateImageList (wxImageList *imageList) { + SetStateImageList(imageList); + m_ownsImageListState = true; +} + +void wxTreeListMainWindow::AssignButtonsImageList (wxImageList *imageList) { + SetButtonsImageList(imageList); + m_ownsImageListButtons = true; +} + +// ---------------------------------------------------------------------------- +// helpers +// ---------------------------------------------------------------------------- + +void wxTreeListMainWindow::AdjustMyScrollbars() { + if (m_rootItem) { + int xUnit, yUnit; + GetScrollPixelsPerUnit (&xUnit, &yUnit); + if (xUnit == 0) xUnit = GetCharWidth(); + if (yUnit == 0) yUnit = m_lineHeight; + int x = 0, y = 0; + m_rootItem->GetSize (x, y, this); + y += yUnit + 2; // one more scrollbar unit + 2 pixels + int x_pos = GetScrollPos (wxHORIZONTAL); + int y_pos = GetScrollPos (wxVERTICAL); + x = m_owner->GetHeaderWindow()->GetWidth() + 2; + if (x < GetClientSize().GetWidth()) x_pos = 0; + SetScrollbars (xUnit, yUnit, x/xUnit, y/yUnit, x_pos, y_pos); + }else{ + SetScrollbars (0, 0, 0, 0); + } +} + +int wxTreeListMainWindow::GetLineHeight (wxTreeListItem *item) const { + if (GetWindowStyleFlag() & wxTR_HAS_VARIABLE_ROW_HEIGHT) { + return item->GetHeight(); + }else{ + return m_lineHeight; + } +} + +void wxTreeListMainWindow::PaintItem (wxTreeListItem *item, wxDC& dc) { + + wxTreeItemAttr *attr = item->GetAttributes(); + + dc.SetFont (GetItemFont (item)); + + wxColour colText; + if (attr && attr->HasTextColour()) { + colText = attr->GetTextColour(); + }else{ + colText = GetForegroundColour(); + } +#if !wxCHECK_VERSION(2, 5, 0) + wxColour colTextHilight = wxSystemSettings::GetSystemColour (wxSYS_COLOUR_HIGHLIGHTTEXT); +#else + wxColour colTextHilight = wxSystemSettings::GetColour (wxSYS_COLOUR_HIGHLIGHTTEXT); +#endif + + int total_w = m_owner->GetHeaderWindow()->GetWidth(); + int total_h = GetLineHeight(item); + int off_h = HasFlag(wxTR_ROW_LINES) ? 1 : 0; + int off_w = HasFlag(wxTR_COLUMN_LINES) ? 1 : 0; + wxDCClipper clipper (dc, 0, item->GetY(), total_w, total_h); // only within line + + int text_w = 0, text_h = 0; + dc.GetTextExtent( item->GetText(GetMainColumn()).size() > 0 + ? item->GetText(GetMainColumn()) + : _T(" "), // dummy text to avoid zero height and no highlight width + &text_w, &text_h ); + + // determine background and show it + wxColour colBg; + if (attr && attr->HasBackgroundColour()) { + colBg = attr->GetBackgroundColour(); + }else{ + colBg = m_backgroundColour; + } + dc.SetBrush (wxBrush (colBg, wxSOLID)); + dc.SetPen (*wxTRANSPARENT_PEN); + if (HasFlag (wxTR_FULL_ROW_HIGHLIGHT)) { + if (item->IsSelected()) { + if (! m_isDragging && m_hasFocus) { + dc.SetBrush (*m_hilightBrush); +#ifndef __WXMAC__ // don't draw rect outline if we already have the background color + dc.SetPen (*wxBLACK_PEN); +#endif // !__WXMAC__ + }else{ + dc.SetBrush (*m_hilightUnfocusedBrush); +#ifndef __WXMAC__ // don't draw rect outline if we already have the background color + dc.SetPen (*wxTRANSPARENT_PEN); +#endif // !__WXMAC__ + } + dc.SetTextForeground (colTextHilight); + }else if (item == m_curItem) { + dc.SetPen (m_hasFocus? *wxBLACK_PEN: *wxTRANSPARENT_PEN); + }else{ + dc.SetTextForeground (colText); + } + dc.DrawRectangle (0, item->GetY() + off_h, total_w, total_h - off_h); + }else{ + dc.SetTextForeground (colText); + } + + int text_extraH = (total_h > text_h) ? (total_h - text_h)/2 : 0; + int img_extraH = (total_h > m_imgHeight)? (total_h-m_imgHeight)/2: 0; + int x_colstart = 0; + for (int i = 0; i < GetColumnCount(); ++i ) { + if (!m_owner->GetHeaderWindow()->IsColumnShown(i)) continue; + + int col_w = m_owner->GetHeaderWindow()->GetColumnWidth(i); + wxDCClipper clipper (dc, x_colstart, item->GetY(), col_w, total_h); // only within column + + int x = 0; + int image = NO_IMAGE; + int image_w = 0; + if(i == GetMainColumn()) { + x = item->GetX() + MARGIN; + if (HasButtons()) { + x += (m_btnWidth-m_btnWidth2) + LINEATROOT; + }else{ + x -= m_indent/2; + } + if (m_imageListNormal) image = item->GetCurrentImage(); + }else{ + x = x_colstart + MARGIN; + image = item->GetImage(i); + } + if (image != NO_IMAGE) image_w = m_imgWidth + MARGIN; + + // honor text alignment + wxString text = item->GetText(i); + int w = 0; + switch ( m_owner->GetHeaderWindow()->GetColumn(i).GetAlignment() ) { + case wxALIGN_LEFT: + // nothing to do, already left aligned + break; + case wxALIGN_RIGHT: + dc.GetTextExtent (text, &text_w, NULL); + w = col_w - (image_w + text_w + off_w + MARGIN); + x += (w > 0)? w: 0; + break; + case wxALIGN_CENTER: + dc.GetTextExtent(text, &text_w, NULL); + w = (col_w - (image_w + text_w + off_w + MARGIN))/2; + x += (w > 0)? w: 0; + break; + } + int text_x = x + image_w; + if (i == GetMainColumn()) item->SetTextX (text_x); + + if (!HasFlag (wxTR_FULL_ROW_HIGHLIGHT)) { + if (i == GetMainColumn()) { + if (item->IsSelected()) { + if (!m_isDragging && m_hasFocus) { + dc.SetBrush (*m_hilightBrush); +#ifndef __WXMAC__ // don't draw rect outline if we already have the background color + dc.SetPen (*wxBLACK_PEN); +#endif // !__WXMAC__ + }else{ + dc.SetBrush (*m_hilightUnfocusedBrush); +#ifndef __WXMAC__ // don't draw rect outline if we already have the background color + dc.SetPen (*wxTRANSPARENT_PEN); +#endif // !__WXMAC__ + } + dc.SetTextForeground (colTextHilight); + }else if (item == m_curItem) { + dc.SetPen (m_hasFocus? *wxBLACK_PEN: *wxTRANSPARENT_PEN); + }else{ + dc.SetTextForeground (colText); + } + dc.DrawRectangle (text_x, item->GetY() + off_h, text_w, total_h - off_h); + }else{ + dc.SetTextForeground (colText); + } + } + + if (HasFlag(wxTR_COLUMN_LINES)) { // vertical lines between columns +#if !wxCHECK_VERSION(2, 5, 0) + wxPen pen (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_3DLIGHT ), 1, wxSOLID); +#else + wxPen pen (wxSystemSettings::GetColour (wxSYS_COLOUR_3DLIGHT ), 1, wxSOLID); +#endif + dc.SetPen ((GetBackgroundColour() == *wxWHITE)? pen: *wxWHITE_PEN); + dc.DrawLine (x_colstart+col_w-1, item->GetY(), x_colstart+col_w-1, item->GetY()+total_h); + } + + dc.SetBackgroundMode (wxTRANSPARENT); + + if (image != NO_IMAGE) { + int y = item->GetY() + img_extraH; + m_imageListNormal->Draw (image, dc, x, y, wxIMAGELIST_DRAW_TRANSPARENT ); + } + int text_y = item->GetY() + text_extraH; + dc.DrawText (text, (wxCoord)text_x, (wxCoord)text_y); + + x_colstart += col_w; + } + + // restore normal font + dc.SetFont( m_normalFont ); +} + +// Now y stands for the top of the item, whereas it used to stand for middle ! +void wxTreeListMainWindow::PaintLevel (wxTreeListItem *item, wxDC &dc, + int level, int &y, int x_maincol) { + + // Handle hide root (only level 0) + if (HasFlag(wxTR_HIDE_ROOT) && (level == 0)) { + wxArrayTreeListItems& children = item->GetChildren(); + for (size_t n = 0; n < children.Count(); n++) { + PaintLevel (children[n], dc, 1, y, x_maincol); + } + // end after expanding root + return; + } + + // calculate position of vertical lines + int x = x_maincol + MARGIN; // start of column + if (HasFlag(wxTR_LINES_AT_ROOT)) x += LINEATROOT; // space for lines at root + if (HasButtons()) { + x += (m_btnWidth-m_btnWidth2); // half button space + }else{ + x += (m_indent-m_indent/2); + } + if (HasFlag(wxTR_HIDE_ROOT)) { + x += m_indent * (level-1); // indent but not level 1 + }else{ + x += m_indent * level; // indent according to level + } + + // set position of vertical line + item->SetX (x); + item->SetY (y); + + int h = GetLineHeight (item); + int y_top = y; + int y_mid = y_top + (h/2); + y += h; + + int exposed_x = dc.LogicalToDeviceX(0); + int exposed_y = dc.LogicalToDeviceY(y_top); + + if (IsExposed(exposed_x, exposed_y, 10000, h)) { // 10000 = very much + + if (HasFlag(wxTR_ROW_LINES)) { // horizontal lines between rows + //dc.DestroyClippingRegion(); + int total_width = m_owner->GetHeaderWindow()->GetWidth(); + // if the background colour is white, choose a + // contrasting color for the lines +#if !wxCHECK_VERSION(2, 5, 0) + wxPen pen (wxSystemSettings::GetSystemColour (wxSYS_COLOUR_3DLIGHT ), 1, wxSOLID); +#else + wxPen pen (wxSystemSettings::GetColour (wxSYS_COLOUR_3DLIGHT ), 1, wxSOLID); +#endif + dc.SetPen ((GetBackgroundColour() == *wxWHITE)? pen: *wxWHITE_PEN); + dc.DrawLine (0, y_top, total_width, y_top); + dc.DrawLine (0, y_top+h, total_width, y_top+h); + } + + // draw item + PaintItem (item, dc); + + // restore DC objects + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(m_dottedPen); + + // clip to the column width + int clip_width = m_owner->GetHeaderWindow()-> + GetColumn(m_main_column).GetWidth(); + wxDCClipper clipper(dc, x_maincol, y_top, clip_width, 10000); + + if (!HasFlag(wxTR_NO_LINES)) { // connection lines + + // draw the horizontal line here + dc.SetPen(m_dottedPen); + int x2 = x - m_indent; + if (x2 < (x_maincol + MARGIN)) x2 = x_maincol + MARGIN; + int x3 = x + (m_btnWidth-m_btnWidth2); + if (HasButtons()) { + if (item->HasPlus()) { + dc.DrawLine (x2, y_mid, x - m_btnWidth2, y_mid); + dc.DrawLine (x3, y_mid, x3 + LINEATROOT, y_mid); + }else{ + dc.DrawLine (x2, y_mid, x3 + LINEATROOT, y_mid); + } + }else{ + dc.DrawLine (x2, y_mid, x - m_indent/2, y_mid); + } + } + + if (item->HasPlus() && HasButtons()) { // should the item show a button? + + if (m_imageListButtons) { + + // draw the image button here + int image = wxTreeItemIcon_Normal; + if (item->IsExpanded()) image = wxTreeItemIcon_Expanded; + if (item->IsSelected()) image += wxTreeItemIcon_Selected - wxTreeItemIcon_Normal; + int xx = x - m_btnWidth2 + MARGIN; + int yy = y_mid - m_btnHeight2; + dc.SetClippingRegion(xx, yy, m_btnWidth, m_btnHeight); + m_imageListButtons->Draw (image, dc, xx, yy, wxIMAGELIST_DRAW_TRANSPARENT); + dc.DestroyClippingRegion(); + + }else if (HasFlag (wxTR_TWIST_BUTTONS)) { + + // draw the twisty button here + dc.SetPen(*wxBLACK_PEN); + dc.SetBrush(*m_hilightBrush); + wxPoint button[3]; + if (item->IsExpanded()) { + button[0].x = x - (m_btnWidth2+1); + button[0].y = y_mid - (m_btnHeight/3); + button[1].x = x + (m_btnWidth2+1); + button[1].y = button[0].y; + button[2].x = x; + button[2].y = button[0].y + (m_btnHeight2+1); + }else{ + button[0].x = x - (m_btnWidth/3); + button[0].y = y_mid - (m_btnHeight2+1); + button[1].x = button[0].x; + button[1].y = y_mid + (m_btnHeight2+1); + button[2].x = button[0].x + (m_btnWidth2+1); + button[2].y = y_mid; + } + dc.DrawPolygon(3, button); + + }else{ // if (HasFlag(wxTR_HAS_BUTTONS)) + + // draw the plus sign here +#if !wxCHECK_VERSION(2, 7, 0) + dc.SetPen(*wxGREY_PEN); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle (x-m_btnWidth2, y_mid-m_btnHeight2, m_btnWidth, m_btnHeight); + dc.SetPen(*wxBLACK_PEN); + dc.DrawLine (x-(m_btnWidth2-2), y_mid, x+(m_btnWidth2-1), y_mid); + if (!item->IsExpanded()) { // change "-" to "+" + dc.DrawLine (x, y_mid-(m_btnHeight2-2), x, y_mid+(m_btnHeight2-1)); + } +#else + wxRect rect (x-m_btnWidth2, y_mid-m_btnHeight2, m_btnWidth, m_btnHeight); + int flag = item->IsExpanded()? wxCONTROL_EXPANDED: 0; + wxRendererNative::GetDefault().DrawTreeItemButton (this, dc, rect, flag); +#endif + + } + + } + + } + + // restore DC objects + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(m_dottedPen); + dc.SetTextForeground(*wxBLACK); + + if (item->IsExpanded()) + { + wxArrayTreeListItems& children = item->GetChildren(); + + // clip to the column width + int clip_width = m_owner->GetHeaderWindow()-> + GetColumn(m_main_column).GetWidth(); + + // process lower levels + int oldY; + if (m_imgWidth > 0) { + oldY = y_mid + m_imgHeight2; + }else{ + oldY = y_mid + h/2; + } + int y2; + for (size_t n = 0; n < children.Count(); ++n) { + + y2 = y + h/2; + PaintLevel (children[n], dc, level+1, y, x_maincol); + + // draw vertical line + wxDCClipper clipper(dc, x_maincol, y_top, clip_width, 10000); + if (!HasFlag (wxTR_NO_LINES)) { + x = item->GetX(); + dc.DrawLine (x, oldY, x, y2); + oldY = y2; + } + } + } +} + + +// ---------------------------------------------------------------------------- +// wxWindows callbacks +// ---------------------------------------------------------------------------- + +void wxTreeListMainWindow::OnPaint (wxPaintEvent &WXUNUSED(event)) { + + // init device context, clear background (BEFORE changing DC origin...) + wxAutoBufferedPaintDC dc (this); + wxBrush brush(GetBackgroundColour(), wxSOLID); + dc.SetBackground(brush); + dc.Clear(); + DoPrepareDC (dc); + + if (!m_rootItem || (GetColumnCount() <= 0)) return; + + // calculate button size + if (m_imageListButtons) { + m_imageListButtons->GetSize (0, m_btnWidth, m_btnHeight); + }else if (HasButtons()) { + m_btnWidth = BTNWIDTH; + m_btnHeight = BTNHEIGHT; + } + m_btnWidth2 = m_btnWidth/2; + m_btnHeight2 = m_btnHeight/2; + + // calculate image size + if (m_imageListNormal) { + m_imageListNormal->GetSize (0, m_imgWidth, m_imgHeight); + } + m_imgWidth2 = m_imgWidth/2; + m_imgHeight2 = m_imgHeight/2; + + // calculate indent size + if (m_imageListButtons) { + m_indent = wxMax (MININDENT, m_btnWidth + MARGIN); + }else if (HasButtons()) { + m_indent = wxMax (MININDENT, m_btnWidth + LINEATROOT); + } + + // set default values + dc.SetFont( m_normalFont ); + dc.SetPen( m_dottedPen ); + + // calculate column start and paint + int x_maincol = 0; + int i = 0; + for (i = 0; i < (int)GetMainColumn(); ++i) { + if (!m_owner->GetHeaderWindow()->IsColumnShown(i)) continue; + x_maincol += m_owner->GetHeaderWindow()->GetColumnWidth (i); + } + int y = 0; + PaintLevel (m_rootItem, dc, 0, y, x_maincol); +} + +void wxTreeListMainWindow::OnSetFocus (wxFocusEvent &event) { + m_hasFocus = true; + RefreshSelected(); + if (m_curItem) RefreshLine (m_curItem); + event.Skip(); +} + +void wxTreeListMainWindow::OnKillFocus( wxFocusEvent &event ) +{ + m_hasFocus = false; + RefreshSelected(); + if (m_curItem) RefreshLine (m_curItem); + event.Skip(); +} + +void wxTreeListMainWindow::OnChar (wxKeyEvent &event) { + // send event to user code + wxTreeEvent nevent (wxEVT_COMMAND_TREE_KEY_DOWN, 0 ); + nevent.SetInt(m_curColumn); + nevent.SetKeyEvent (event); + if (SendEvent(0, NULL, &nevent)) return; // char event handled in user code + + // if no item current, select root + bool curItemSet = false; + if (!m_curItem) { + if (! GetRootItem().IsOk()) return; + SetCurrentItem((wxTreeListItem*)GetRootItem().m_pItem); + if (HasFlag(wxTR_HIDE_ROOT)) { +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + SetCurrentItem((wxTreeListItem*)GetFirstChild (m_curItem, cookie).m_pItem); + } + SelectItem(m_curItem, (wxTreeItemId*)NULL, true); // unselect others + curItemSet = true; + } + + // remember item at shift down + if (HasFlag(wxTR_MULTIPLE) && event.ShiftDown()) { + if (!m_shiftItem) m_shiftItem = m_curItem; + }else{ + m_shiftItem = (wxTreeListItem*)NULL; + } + + if (curItemSet) return; // if no item was current until now, do nothing more + + // process all cases + wxTreeItemId newItem = (wxTreeItemId*)NULL; + switch (event.GetKeyCode()) { + + // '+': Expand subtree + case '+': + case WXK_ADD: { + if (m_curItem->HasPlus() && !IsExpanded (m_curItem)) Expand (m_curItem); + }break; + + // '-': collapse subtree + case '-': + case WXK_SUBTRACT: { + if (m_curItem->HasPlus() && IsExpanded (m_curItem)) Collapse (m_curItem); + }break; + + // '*': expand/collapse all subtrees // TODO: Mak it more useful + case '*': + case WXK_MULTIPLY: { + if (m_curItem->HasPlus() && !IsExpanded (m_curItem)) { + ExpandAll (m_curItem); + }else if (m_curItem->HasPlus()) { + Collapse (m_curItem); // TODO: CollapseAll + } + }break; + + // ' ': toggle current item + case ' ': { + SelectItem (m_curItem, (wxTreeListItem*)NULL, false); + }break; + + // : activate current item + case WXK_RETURN: { + if (! SendEvent(wxEVT_COMMAND_TREE_ITEM_ACTIVATED, m_curItem)) { + + // if the user code didn't process the activate event, + // handle it ourselves by toggling the item when it is + // double clicked + if (m_curItem && m_curItem->HasPlus()) Toggle(m_curItem); + } + }break; + + // : go to the parent without collapsing + case WXK_BACK: { + newItem = GetItemParent (m_curItem); + if ((newItem == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT)) { + newItem = GetPrevSibling (m_curItem); // get sibling instead of root + } + }break; + + // : go to first visible + case WXK_HOME: { + newItem = GetFirstVisible(false, false); + }break; + + // : go to the top of the page, or if we already are then one page back + case WXK_PAGEUP: { + int flags = 0; + int col = 0; + wxPoint abs_p = CalcUnscrolledPosition (wxPoint(1,1)); + // PAGE-UP: first go the the first visible row + newItem = m_rootItem->HitTest(abs_p, this, flags, col, 0); + newItem = GetFirstVisible(false, true); + // if we are already there then scroll back one page + if (newItem == m_curItem) { + abs_p.y -= GetClientSize().GetHeight() - m_curItem->GetHeight(); + if (abs_p.y < 0) abs_p.y = 0; + newItem = m_rootItem->HitTest(abs_p, this, flags, col, 0); + } + // newItem should never be NULL + } break; + + // : go to the previous sibling or for the last of its children, to the parent + case WXK_UP: { + newItem = GetPrevSibling (m_curItem); + if (newItem) { +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + while (IsExpanded (newItem) && HasChildren (newItem)) { + newItem = GetLastChild (newItem, cookie); + } + }else { + newItem = GetItemParent (m_curItem); + if ((newItem == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT)) { + newItem = (wxTreeItemId*)NULL; // don't go to root if it is hidden + } + } + }break; + + // : if expanded collapse subtree, else go to the parent + case WXK_LEFT: { + if (IsExpanded (m_curItem)) { + Collapse (m_curItem); + }else{ + newItem = GetItemParent (m_curItem); + if ((newItem == GetRootItem()) && HasFlag(wxTR_HIDE_ROOT)) { + newItem = GetPrevSibling (m_curItem); // go to sibling if it is hidden + } + } + }break; + + // : if possible expand subtree, else go go to the first child + case WXK_RIGHT: { + if (m_curItem->HasPlus() && !IsExpanded (m_curItem)) { + Expand (m_curItem); + }else{ + if (IsExpanded (m_curItem) && HasChildren (m_curItem)) { +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + newItem = GetFirstChild (m_curItem, cookie); + } + } + }break; + + // : if expanded go to the first child, else to the next sibling, ect + case WXK_DOWN: { + if (IsExpanded (m_curItem) && HasChildren (m_curItem)) { +#if !wxCHECK_VERSION(2, 5, 0) + long cookie = 0; +#else + wxTreeItemIdValue cookie = 0; +#endif + newItem = GetFirstChild( m_curItem, cookie ); + } + if (!newItem) { + wxTreeItemId parent = m_curItem; + do { + newItem = GetNextSibling (parent); + parent = GetItemParent (parent); + } while (!newItem && parent); + } + }break; + + // : go to the bottom of the page, or if we already are then one page further + case WXK_PAGEDOWN: { + int flags = 0; + int col = 0; + wxPoint abs_p = CalcUnscrolledPosition (wxPoint(1,GetClientSize().GetHeight() - m_curItem->GetHeight())); + // PAGE-UP: first go the the first visible row + newItem = m_rootItem->HitTest(abs_p, this, flags, col, 0); + newItem = GetLastVisible(false, true); + // if we are already there then scroll down one page + if (newItem == m_curItem) { + abs_p.y += GetClientSize().GetHeight() - m_curItem->GetHeight(); +// if (abs_p.y >= GetVirtualSize().GetHeight()) abs_p.y = GetVirtualSize().GetHeight() - 1; + newItem = m_rootItem->HitTest(abs_p, this, flags, col, 0); + } + // if we reached the empty area below the rows, return last item instead + if (! newItem) newItem = GetLastVisible(false, false); + } break; + + // : go to last item of the root + case WXK_END: { + newItem = GetLastVisible (false, false); + }break; + + // any char: go to the next matching string + default: + if (event.GetKeyCode() >= (int)' ') { + if (!m_findTimer->IsRunning()) m_findStr.Clear(); + m_findStr.Append (event.GetKeyCode()); + m_findTimer->Start (FIND_TIMER_TICKS, wxTIMER_ONE_SHOT); + wxTreeItemId prev = m_curItem? (wxTreeItemId*)m_curItem: (wxTreeItemId*)NULL; + while (true) { + newItem = FindItem (prev, m_findStr, wxTL_MODE_NAV_EXPANDED | + wxTL_MODE_FIND_PARTIAL | + wxTL_MODE_FIND_NOCASE); + if (newItem || (m_findStr.Length() <= 1)) break; + m_findStr.RemoveLast(); + }; + } + event.Skip(); + + } + + // select and show the new item + if (newItem) { + if (!event.ControlDown()) { + bool unselect_others = !((event.ShiftDown() || event.ControlDown()) && + HasFlag(wxTR_MULTIPLE)); + SelectItem (newItem, m_shiftItem, unselect_others); + } + EnsureVisible (newItem); + wxTreeListItem *oldItem = m_curItem; + SetCurrentItem((wxTreeListItem*)newItem.m_pItem); // make the new item the current item + RefreshLine (oldItem); + } + +} + +wxTreeItemId wxTreeListMainWindow::HitTest (const wxPoint& point, int& flags, int& column) { + + int w, h; + GetSize(&w, &h); + flags=0; + column = -1; + if (point.x<0) flags |= wxTREE_HITTEST_TOLEFT; + if (point.x>w) flags |= wxTREE_HITTEST_TORIGHT; + if (point.y<0) flags |= wxTREE_HITTEST_ABOVE; + if (point.y>h) flags |= wxTREE_HITTEST_BELOW; + if (flags) return wxTreeItemId(); + + if (!m_rootItem) { + flags = wxTREE_HITTEST_NOWHERE; + column = -1; + return wxTreeItemId(); + } + + wxTreeListItem *hit = m_rootItem->HitTest (CalcUnscrolledPosition(point), + this, flags, column, 0); + if (!hit) { + flags = wxTREE_HITTEST_NOWHERE; + column = -1; + return wxTreeItemId(); + } + return hit; +} + +// get the bounding rectangle of the item (or of its label only) +bool wxTreeListMainWindow::GetBoundingRect (const wxTreeItemId& itemId, wxRect& rect, + bool WXUNUSED(textOnly)) const { + wxCHECK_MSG (itemId.IsOk(), false, _T("invalid item in wxTreeListMainWindow::GetBoundingRect") ); + + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + + int xUnit, yUnit; + GetScrollPixelsPerUnit (&xUnit, &yUnit); + int startX, startY; + GetViewStart(& startX, & startY); + + rect.x = item->GetX() - startX * xUnit; + rect.y = item->GetY() - startY * yUnit; + rect.width = item->GetWidth(); + rect.height = GetLineHeight (item); + + return true; +} + +/* **** */ + +void wxTreeListMainWindow::EditLabel (const wxTreeItemId& item, int column) { + +// validate + if (!item.IsOk()) return; + if (!((column >= 0) && (column < GetColumnCount()))) return; + +// cancel any editing + if (m_editControl) { + m_editControl->EndEdit(true); // cancelled + } + +// prepare edit (position) + m_editItem = (wxTreeListItem*) item.m_pItem; + + wxTreeEvent te( wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT, 0 ); + te.SetInt (column); + SendEvent(0, m_editItem, &te); if (!te.IsAllowed()) return; + + // ensure that the position of the item it calculated in any case + if (m_dirty) CalculatePositions(); + + wxTreeListHeaderWindow* header_win = m_owner->GetHeaderWindow(); + + // position & size are rather unpredictable (tsssk, tssssk) so were + // set by trial & error (on Win 2003 pre-XP style) + int x = 0; + int w = +4; // +4 is necessary, don't know why (simple border erronously counted somewhere ?) + int y = m_editItem->GetY() + 1; // this is cell, not text + int h = m_editItem->GetHeight() - 1; // consequence from above + long style = 0; + if (column == GetMainColumn()) { + x += m_editItem->GetTextX() - 2; // wrong by 2, don't know why + w += m_editItem->GetWidth(); + } else { + for (int i = 0; i < column; ++i) x += header_win->GetColumnWidth (i); // start of column + w += header_win->GetColumnWidth (column); // currently non-main column width not pre-computed + } + switch (header_win->GetColumnAlignment (column)) { + case wxALIGN_LEFT: {style = wxTE_LEFT; x -= 1; break;} + case wxALIGN_CENTER: {style = wxTE_CENTER; x -= 1; break;} + case wxALIGN_RIGHT: {style = wxTE_RIGHT; x += 0; break;} // yes, strange but that's the way it is + } + // wxTextCtrl simple border style requires 2 extra pixels before and after + // (measured by changing to style wxNO_BORDER in wxEditTextCtrl::wxEditTextCtrl() ) + y -= 2; x -= 2; + w += 4; h += 4; + + wxClientDC dc (this); + PrepareDC (dc); + x = dc.LogicalToDeviceX (x); + y = dc.LogicalToDeviceY (y); + +// now do edit (change state, show control) + m_editCol = column; // only used in OnRenameAccept() + m_editControl = new wxEditTextCtrl (this, -1, &m_editAccept, &m_editRes, + this, m_editItem->GetText (column), + wxPoint (x, y), wxSize (w, h), style); + m_editControl->SetFocus(); +} + +void wxTreeListMainWindow::OnRenameTimer() { + EditLabel (m_curItem, m_curColumn); +} + +void wxTreeListMainWindow::OnRenameAccept(bool isCancelled) { + + // TODO if the validator fails this causes a crash + wxTreeEvent le( wxEVT_COMMAND_TREE_END_LABEL_EDIT, 0 ); + le.SetLabel( m_editRes ); + le.SetEditCanceled(isCancelled); + le.SetInt(m_editCol); + SendEvent(0, m_editItem, &le); if (! isCancelled && le.IsAllowed()) + { + SetItemText (m_editItem, le.GetInt(), le.GetLabel()); + } +} + +void wxTreeListMainWindow::OnMouse (wxMouseEvent &event) { +bool mayDrag = true; +bool maySelect = true; // may change selection +bool mayClick = true; // may process DOWN clicks to expand, send click events +bool mayDoubleClick = true; // implies mayClick +bool bSkip = true; + + // send event to user code + if (m_owner->GetEventHandler()->ProcessEvent(event)) return; // handled (and not skipped) in user code + if (!m_rootItem) return; + + +// ---------- DETERMINE EVENT ---------- +/* +wxLogMessage("OnMouse: LMR down=<%d, %d, %d> up=<%d, %d, %d> LDblClick=<%d> dragging=<%d>", + event.LeftDown(), event.MiddleDown(), event.RightDown(), + event.LeftUp(), event.MiddleUp(), event.RightUp(), + event.LeftDClick(), event.Dragging()); +*/ + wxPoint p = wxPoint (event.GetX(), event.GetY()); + int flags = 0; + wxTreeListItem *item = m_rootItem->HitTest (CalcUnscrolledPosition (p), + this, flags, m_curColumn, 0); + bool bCrosshair = (item && item->HasPlus() && (flags & wxTREE_HITTEST_ONITEMBUTTON)); + // we were dragging + if (m_isDragging) { + maySelect = mayDoubleClick = false; + } + // we are starting or continuing to drag + if (event.Dragging()) { + maySelect = mayDoubleClick = mayClick = false; + } + // crosshair area is special + if (bCrosshair) { + // left click does not select + if (event.LeftDown()) maySelect = false; + // double click is ignored + mayDoubleClick = false; + } + // double click only if simple click + if (mayDoubleClick) mayDoubleClick = mayClick; + // selection conditions --remember also that selection exludes editing + if (maySelect) maySelect = mayClick; // yes, select/unselect requires a click + if (maySelect) { + + // multiple selection mode complicates things, sometimes we + // select on button-up instead of down: + if (HasFlag(wxTR_MULTIPLE)) { + + // CONTROL/SHIFT key used, don't care about anything else, will + // toggle on key down + if (event.ControlDown() || event.ShiftDown()) { + maySelect = maySelect && (event.LeftDown() || event.RightDown()); + m_lastOnSame = false; // prevent editing when keys are used + + // already selected item: to allow drag or contextual menu for multiple + // items, we only select/unselect on click-up --and only on LEFT + // click, right is reserved for contextual menu + } else if ((item != NULL && item->IsSelected())) { + maySelect = maySelect && event.LeftUp(); + + // non-selected items: select on click-down like simple select (so + // that a right-click contextual menu may be chained) + } else { + maySelect = maySelect && (event.LeftDown() || event.RightDown()); + } + + // single-select is simply on left or right click-down + } else { + maySelect = maySelect && (event.LeftDown() || event.RightDown()); + } + } + + +// ---------- GENERAL ACTIONS ---------- + + // set focus if window clicked + if (event.LeftDown() || event.MiddleDown() || event.RightDown()) SetFocus(); + + // tooltip change ? + if (item != m_toolTipItem) { + + // not over an item, use global tip + if (item == NULL) { + m_toolTipItem = NULL; + wxScrolledWindow::SetToolTip(m_toolTip); + + // over an item + } else { + const wxString *tip = item->GetToolTip(); + + // is there an item-specific tip ? + if (tip) { + m_toolTipItem = item; + wxScrolledWindow::SetToolTip(*tip); + + // no item tip, but we are in item-specific mode (SetItemToolTip() + // was called after SetToolTip() ) + } else if (m_isItemToolTip) { + m_toolTipItem = item; + wxScrolledWindow::SetToolTip(wxString()); + + // no item tip, display global tip instead; item change ignored + } else if (m_toolTipItem != NULL) { + m_toolTipItem = NULL; + wxScrolledWindow::SetToolTip(m_toolTip); + } + } + } + + +// ---------- HANDLE SIMPLE-CLICKS (selection change, contextual menu) ---------- + if (mayClick) { + + // 2nd left-click on an item might trigger edit + if (event.LeftDown()) m_lastOnSame = (item == m_curItem); + + // left-click on haircross is expand (and no select) + if (bCrosshair && event.LeftDown()) { + + bSkip = false; + + // note that we only toggle the item for a single click, double + // click on the button doesn't do anything + Toggle (item); + } + + if (maySelect) { + bSkip = false; + + // set / remember item at shift down before current item gets changed + if (event.LeftDown() && HasFlag(wxTR_MULTIPLE) && event.ShiftDown()) { + if (!m_shiftItem) m_shiftItem = m_curItem; + }else{ + m_shiftItem = (wxTreeListItem*)NULL; + } + + // how is selection altered + // keep or discard already selected ? + bool unselect_others = ! (HasFlag(wxTR_MULTIPLE) && ( + event.ShiftDown() + || event.ControlDown() + )); + + // check is selection change is not vetoed + if (SelectItem(item, m_shiftItem, unselect_others)) { + // make the new item the current item + EnsureVisible (item); + SetCurrentItem(item); + } + } + + // generate click & menu events + if (event.MiddleDown()) { + bSkip = false; + SendEvent(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK, item); + } + if (event.RightDown()) { + bSkip = false; + SendEvent(wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK, item); + } + if (event.RightUp()) { + wxTreeEvent nevent(wxEVT_COMMAND_TREE_ITEM_MENU, 0); + nevent.SetPoint(p); + nevent.SetInt(m_curColumn); + SendEvent(0, item, &nevent); + } + + // if 2nd left click finishes on same item, will edit it + if (m_lastOnSame && event.LeftUp()) { + if ((item == m_curItem) && (m_curColumn != -1) && + (m_owner->GetHeaderWindow()->IsColumnEditable (m_curColumn)) && + (flags & (wxTREE_HITTEST_ONITEMLABEL | wxTREE_HITTEST_ONITEMCOLUMN)) + ){ + m_editTimer->Start (RENAME_TIMER_TICKS, wxTIMER_ONE_SHOT); + bSkip = false; + } + m_lastOnSame = false; + } + } + + +// ---------- HANDLE DOUBLE-CLICKS ---------- + if (mayDoubleClick && event.LeftDClick()) { + + bSkip = false; + + // double clicking should not start editing the item label + m_editTimer->Stop(); + m_lastOnSame = false; + + // selection reset to that single item which was double-clicked + if (SelectItem(item, (wxTreeItemId*)NULL, true)) { // unselect others --return false if vetoed + + // selection change not vetoed, send activate event + if (! SendEvent(wxEVT_COMMAND_TREE_ITEM_ACTIVATED, item)) { + + // if the user code didn't process the activate event, + // handle it ourselves by toggling the item when it is + // double clicked + if (item && item->HasPlus()) Toggle(item); + } + } + } + + +// ---------- HANDLE DRAGGING ---------- +// NOTE: drag itself makes no change to selection + if (mayDrag) { // actually this is always true + + // CASE 1: we were dragging => continue, end, abort + if (m_isDragging) { + + // CASE 1.1: click aborts drag: + if (event.LeftDown() || event.MiddleDown() || event.RightDown()) { + + bSkip = false; + + // stop dragging + m_isDragStarted = m_isDragging = false; + if (HasCapture()) ReleaseMouse(); + RefreshSelected(); + + // CASE 1.2: still dragging + } else if (event.Dragging()) { + + ;; // nothing to do + + // CASE 1.3: dragging now ends normally + } else { + + bSkip = false; + + // stop dragging + m_isDragStarted = m_isDragging = false; + if (HasCapture()) ReleaseMouse(); + RefreshSelected(); + + // send drag end event + wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, 0); + event.SetPoint(p); + event.SetInt(m_curColumn); + SendEvent(0, item, &event); + } + + // CASE 2: not were not dragging => continue, start + } else if (event.Dragging()) { + + // We will really start dragging if we've moved beyond a few pixels + if (m_isDragStarted) { + const int tolerance = 3; + int dx = abs(p.x - m_dragStartPos.x); + int dy = abs(p.y - m_dragStartPos.y); + if (dx <= tolerance && dy <= tolerance) + return; + // determine drag start + } else { + m_dragStartPos = p; + m_dragCol = m_curColumn; + m_dragItem = item; + m_isDragStarted = true; + return; + } + + bSkip = false; + + // we are now dragging + m_isDragging = true; + RefreshSelected(); + CaptureMouse(); // TODO: usefulness unclear + + wxTreeEvent nevent(event.LeftIsDown() + ? wxEVT_COMMAND_TREE_BEGIN_DRAG + : wxEVT_COMMAND_TREE_BEGIN_RDRAG, 0); + nevent.SetPoint(p); + nevent.SetInt(m_dragCol); + nevent.Veto(); + SendEvent(0, m_dragItem, &nevent); + } + } + + + if (bSkip) event.Skip(); +} + + +void wxTreeListMainWindow::OnIdle (wxIdleEvent &WXUNUSED(event)) { + /* after all changes have been done to the tree control, + * we actually redraw the tree when everything is over */ + + if (!m_dirty) return; + + m_dirty = false; + + CalculatePositions(); + Refresh(); + AdjustMyScrollbars(); +} + +void wxTreeListMainWindow::OnScroll (wxScrollWinEvent& event) { + // FIXME +#if defined(__WXGTK__) && !defined(__WXUNIVERSAL__) + wxScrolledWindow::OnScroll(event); +#else + HandleOnScroll( event ); +#endif + + if(event.GetOrientation() == wxHORIZONTAL) { + m_owner->GetHeaderWindow()->Refresh(); + m_owner->GetHeaderWindow()->Update(); + } +} + +void wxTreeListMainWindow::CalculateSize (wxTreeListItem *item, wxDC &dc) { + wxCoord text_w = 0; + wxCoord text_h = 0; + + dc.SetFont (GetItemFont (item)); + dc.GetTextExtent (item->GetText(m_main_column).size() > 0 + ? item->GetText (m_main_column) + : _T(" "), // blank to avoid zero height and no highlight width + &text_w, &text_h); + // restore normal font + dc.SetFont (m_normalFont); + + int max_h = (m_imgHeight > text_h) ? m_imgHeight : text_h; + if (max_h < 30) { // add 10% space if greater than 30 pixels + max_h += 2; // minimal 2 pixel space + }else{ + max_h += max_h / 10; // otherwise 10% space + } + + item->SetHeight (max_h); + if (max_h > m_lineHeight) m_lineHeight = max_h; + item->SetWidth(m_imgWidth + text_w+2); +} + +// ----------------------------------------------------------------------------- +void wxTreeListMainWindow::CalculateLevel (wxTreeListItem *item, wxDC &dc, + int level, int &y, int x_colstart) { + + // calculate position of vertical lines + int x = x_colstart + MARGIN; // start of column + if (HasFlag(wxTR_LINES_AT_ROOT)) x += LINEATROOT; // space for lines at root + if (HasButtons()) { + x += (m_btnWidth-m_btnWidth2); // half button space + }else{ + x += (m_indent-m_indent/2); + } + if (HasFlag(wxTR_HIDE_ROOT)) { + x += m_indent * (level-1); // indent but not level 1 + }else{ + x += m_indent * level; // indent according to level + } + + // a hidden root is not evaluated, but its children are always + if (HasFlag(wxTR_HIDE_ROOT) && (level == 0)) goto Recurse; + + CalculateSize( item, dc ); + + // set its position + item->SetX (x); + item->SetY (y); + y += GetLineHeight(item); + + // we don't need to calculate collapsed branches + if ( !item->IsExpanded() ) return; + +Recurse: + wxArrayTreeListItems& children = item->GetChildren(); + long n, count = (long)children.Count(); + ++level; + for (n = 0; n < count; ++n) { + CalculateLevel( children[n], dc, level, y, x_colstart ); // recurse + } +} + +void wxTreeListMainWindow::CalculatePositions() { + if ( !m_rootItem ) return; + + wxClientDC dc(this); + PrepareDC( dc ); + + dc.SetFont( m_normalFont ); + + dc.SetPen( m_dottedPen ); + //if(GetImageList() == NULL) + // m_lineHeight = (int)(dc.GetCharHeight() + 4); + + int y = 2; + int x_colstart = 0; + for (int i = 0; i < (int)GetMainColumn(); ++i) { + if (!m_owner->GetHeaderWindow()->IsColumnShown(i)) continue; + x_colstart += m_owner->GetHeaderWindow()->GetColumnWidth(i); + } + CalculateLevel( m_rootItem, dc, 0, y, x_colstart ); // start recursion +} + +void wxTreeListMainWindow::RefreshSubtree (wxTreeListItem *item) { + if (m_dirty) return; + + wxClientDC dc(this); + PrepareDC(dc); + + int cw = 0; + int ch = 0; + GetVirtualSize( &cw, &ch ); + + wxRect rect; + rect.x = dc.LogicalToDeviceX( 0 ); + rect.width = cw; + rect.y = dc.LogicalToDeviceY( item->GetY() - 2 ); + rect.height = ch; + + Refresh (true, &rect ); + AdjustMyScrollbars(); +} + +void wxTreeListMainWindow::RefreshLine (wxTreeListItem *item) { + if (m_dirty) return; + + wxClientDC dc(this); + PrepareDC( dc ); + + int cw = 0; + int ch = 0; + GetVirtualSize( &cw, &ch ); + + wxRect rect; + rect.x = dc.LogicalToDeviceX( 0 ); + rect.y = dc.LogicalToDeviceY( item->GetY() ); + rect.width = cw; + rect.height = GetLineHeight(item); //dc.GetCharHeight() + 6; + + Refresh (true, &rect); +} + +void wxTreeListMainWindow::RefreshSelected() { + // TODO: this is awfully inefficient, we should keep the list of all + // selected items internally, should be much faster + if (m_rootItem) { + RefreshSelectedUnder (m_rootItem); + } +} + +void wxTreeListMainWindow::RefreshSelectedUnder (wxTreeListItem *item) { + if (item->IsSelected()) { + RefreshLine (item); + } + + const wxArrayTreeListItems& children = item->GetChildren(); + long count = (long)children.GetCount(); + for (long n = 0; n < count; n++ ) { + RefreshSelectedUnder (children[n]); + } +} + +// ---------------------------------------------------------------------------- +// changing colours: we need to refresh the tree control +// ---------------------------------------------------------------------------- + +bool wxTreeListMainWindow::SetBackgroundColour (const wxColour& colour) { + if (!wxWindow::SetBackgroundColour(colour)) return false; + + Refresh(); + return true; +} + +bool wxTreeListMainWindow::SetForegroundColour (const wxColour& colour) { + if (!wxWindow::SetForegroundColour(colour)) return false; + + Refresh(); + return true; +} + +void wxTreeListMainWindow::SetItemText (const wxTreeItemId& itemId, int column, + const wxString& text) { + wxCHECK_RET (itemId.IsOk(), _T("invalid tree item")); + + wxClientDC dc (this); + wxTreeListItem *item = (wxTreeListItem*) itemId.m_pItem; + item->SetText (column, text); + CalculateSize (item, dc); + RefreshLine (item); +} + +wxString wxTreeListMainWindow::GetItemText (const wxTreeItemId& itemId, + int column) const { + wxCHECK_MSG (itemId.IsOk(), _T(""), _T("invalid tree item") ); + + if( IsVirtual() ) return m_owner->OnGetItemText(((wxTreeListItem*) itemId.m_pItem)->GetData(),column); + else return ((wxTreeListItem*) itemId.m_pItem)->GetText (column); +} + +wxString wxTreeListMainWindow::GetItemText (wxTreeItemData* item, +int column) const { + wxASSERT_MSG( IsVirtual(), _T("can be used only with virtual control") ); + return m_owner->OnGetItemText(item,column); +} + +void wxTreeListMainWindow::SetFocus() { + wxWindow::SetFocus(); +} + +wxFont wxTreeListMainWindow::GetItemFont (wxTreeListItem *item) { + wxTreeItemAttr *attr = item->GetAttributes(); + + if (attr && attr->HasFont()) { + return attr->GetFont(); + }else if (item->IsBold()) { + return m_boldFont; + }else{ + return m_normalFont; + } +} + +int wxTreeListMainWindow::GetItemWidth (int column, wxTreeListItem *item) { + if (!item) return 0; + + // determine item width + int w = 0, h = 0; + wxFont font = GetItemFont (item); + GetTextExtent (item->GetText (column), &w, &h, NULL, NULL, font.Ok()? &font: NULL); + w += 2*MARGIN; + + // calculate width + int width = w + 2*MARGIN; + if (column == GetMainColumn()) { + width += MARGIN; + if (HasFlag(wxTR_LINES_AT_ROOT)) width += LINEATROOT; + if (HasButtons()) width += m_btnWidth + LINEATROOT; + if (item->GetCurrentImage() != NO_IMAGE) width += m_imgWidth; + + // count indent level + int level = 0; + wxTreeListItem *parent = item->GetItemParent(); + wxTreeListItem *root = (wxTreeListItem*)GetRootItem().m_pItem; + while (parent && (!HasFlag(wxTR_HIDE_ROOT) || (parent != root))) { + level++; + parent = parent->GetItemParent(); + } + if (level) width += level * GetIndent(); + } + + return width; +} + +int wxTreeListMainWindow::GetBestColumnWidth (int column, wxTreeItemId parent) { + int maxWidth, h; + GetClientSize (&maxWidth, &h); + int width = 0; + + // get root if on item + if (!parent.IsOk()) parent = GetRootItem(); + + // add root width + if (!HasFlag(wxTR_HIDE_ROOT)) { + int w = GetItemWidth (column, (wxTreeListItem*)parent.m_pItem); + if (width < w) width = w; + if (width > maxWidth) return maxWidth; + } + + wxTreeItemIdValue cookie = 0; + wxTreeItemId item = GetFirstChild (parent, cookie); + while (item.IsOk()) { + int w = GetItemWidth (column, (wxTreeListItem*)item.m_pItem); + if (width < w) width = w; + if (width > maxWidth) return maxWidth; + + // check the children of this item + if (((wxTreeListItem*)item.m_pItem)->IsExpanded()) { + int w = GetBestColumnWidth (column, item); + if (width < w) width = w; + if (width > maxWidth) return maxWidth; + } + + // next sibling + item = GetNextChild (parent, cookie); + } + + return width; +} + + +bool wxTreeListMainWindow::SendEvent(wxEventType event_type, wxTreeListItem *item, wxTreeEvent *event) { +wxTreeEvent nevent (event_type, 0); + + if (event == NULL) { + event = &nevent; + event->SetInt (m_curColumn); // the mouse colum + } + + event->SetEventObject (m_owner); + event->SetId(m_owner->GetId()); + if (item) { +#if !wxCHECK_VERSION(2, 5, 0) + event->SetItem ((long)item); +#else + event->SetItem (item); +#endif + } + + return m_owner->GetEventHandler()->ProcessEvent (*event); +} + + +//----------------------------------------------------------------------------- +// wxTreeListCtrl +//----------------------------------------------------------------------------- + +IMPLEMENT_DYNAMIC_CLASS(wxTreeListCtrl, wxControl); + +BEGIN_EVENT_TABLE(wxTreeListCtrl, wxControl) + EVT_SIZE(wxTreeListCtrl::OnSize) +END_EVENT_TABLE(); + +bool wxTreeListCtrl::Create(wxWindow *parent, wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, const wxValidator &validator, + const wxString& name) +{ + long main_style = style & ~(wxSIMPLE_BORDER|wxSUNKEN_BORDER|wxDOUBLE_BORDER| + wxRAISED_BORDER|wxSTATIC_BORDER); + main_style |= wxWANTS_CHARS ; + long ctrl_style = style & ~(wxVSCROLL|wxHSCROLL); + + if (!wxControl::Create(parent, id, pos, size, ctrl_style, validator, name)) { + return false; + } + m_main_win = new wxTreeListMainWindow (this, -1, wxPoint(0, 0), size, + main_style, validator); + m_header_win = new wxTreeListHeaderWindow (this, -1, m_main_win, + wxPoint(0, 0), wxDefaultSize, + wxTAB_TRAVERSAL); + CalculateAndSetHeaderHeight(); + return true; +} + +void wxTreeListCtrl::CalculateAndSetHeaderHeight() +{ + if (m_header_win) { + + // we use 'g' to get the descent, too + int h; +#if wxCHECK_VERSION_FULL(2, 7, 0, 1) +#ifdef __WXMSW__ + h = (int)(wxRendererNative::Get().GetHeaderButtonHeight(m_header_win) * 0.8) + 2; +#else + h = wxRendererNative::Get().GetHeaderButtonHeight(m_header_win); +#endif +#else + int w, d; + m_header_win->GetTextExtent(_T("Hg"), &w, &h, &d); + h += d + 2 * HEADER_OFFSET_Y + EXTRA_HEIGHT; +#endif + + // only update if changed + if (h != m_headerHeight) { + m_headerHeight = h; + DoHeaderLayout(); + } + } +} + +void wxTreeListCtrl::DoHeaderLayout() +{ + int w, h; + GetClientSize(&w, &h); + if (m_header_win) { + m_header_win->SetSize (0, 0, w, m_headerHeight); + m_header_win->Refresh(); + } + if (m_main_win) { + m_main_win->SetSize (0, m_headerHeight, w, h - m_headerHeight); + } +} + +void wxTreeListCtrl::OnSize(wxSizeEvent& WXUNUSED(event)) +{ + DoHeaderLayout(); +} + +size_t wxTreeListCtrl::GetCount() const { return m_main_win->GetCount(); } + +unsigned int wxTreeListCtrl::GetIndent() const +{ return m_main_win->GetIndent(); } + +void wxTreeListCtrl::SetIndent(unsigned int indent) +{ m_main_win->SetIndent(indent); } + +unsigned int wxTreeListCtrl::GetLineSpacing() const +{ return m_main_win->GetLineSpacing(); } + +void wxTreeListCtrl::SetLineSpacing(unsigned int spacing) +{ m_main_win->SetLineSpacing(spacing); } + +wxImageList* wxTreeListCtrl::GetImageList() const +{ return m_main_win->GetImageList(); } + +wxImageList* wxTreeListCtrl::GetStateImageList() const +{ return m_main_win->GetStateImageList(); } + +wxImageList* wxTreeListCtrl::GetButtonsImageList() const +{ return m_main_win->GetButtonsImageList(); } + +void wxTreeListCtrl::SetImageList(wxImageList* imageList) +{ m_main_win->SetImageList(imageList); } + +void wxTreeListCtrl::SetStateImageList(wxImageList* imageList) +{ m_main_win->SetStateImageList(imageList); } + +void wxTreeListCtrl::SetButtonsImageList(wxImageList* imageList) +{ m_main_win->SetButtonsImageList(imageList); } + +void wxTreeListCtrl::AssignImageList(wxImageList* imageList) +{ m_main_win->AssignImageList(imageList); } + +void wxTreeListCtrl::AssignStateImageList(wxImageList* imageList) +{ m_main_win->AssignStateImageList(imageList); } + +void wxTreeListCtrl::AssignButtonsImageList(wxImageList* imageList) +{ m_main_win->AssignButtonsImageList(imageList); } + +wxString wxTreeListCtrl::GetItemText(const wxTreeItemId& item, int column) const +{ return m_main_win->GetItemText (item, column); } + +int wxTreeListCtrl::GetItemImage(const wxTreeItemId& item, int column, + wxTreeItemIcon which) const +{ return m_main_win->GetItemImage(item, column, which); } + +wxTreeItemData* wxTreeListCtrl::GetItemData(const wxTreeItemId& item) const +{ return m_main_win->GetItemData(item); } + +bool wxTreeListCtrl::GetItemBold(const wxTreeItemId& item) const +{ return m_main_win->GetItemBold(item); } + +wxColour wxTreeListCtrl::GetItemTextColour(const wxTreeItemId& item) const +{ return m_main_win->GetItemTextColour(item); } + +wxColour wxTreeListCtrl::GetItemBackgroundColour(const wxTreeItemId& item) + const +{ return m_main_win->GetItemBackgroundColour(item); } + +wxFont wxTreeListCtrl::GetItemFont(const wxTreeItemId& item) const +{ return m_main_win->GetItemFont(item); } + + +void wxTreeListCtrl::SetItemText(const wxTreeItemId& item, int column, + const wxString& text) +{ m_main_win->SetItemText (item, column, text); } + +void wxTreeListCtrl::SetItemImage(const wxTreeItemId& item, + int column, + int image, + wxTreeItemIcon which) +{ m_main_win->SetItemImage(item, column, image, which); } + +void wxTreeListCtrl::SetItemData(const wxTreeItemId& item, + wxTreeItemData* data) +{ m_main_win->SetItemData(item, data); } + +void wxTreeListCtrl::SetItemHasChildren(const wxTreeItemId& item, bool has) +{ m_main_win->SetItemHasChildren(item, has); } + +void wxTreeListCtrl::SetItemBold(const wxTreeItemId& item, bool bold) +{ m_main_win->SetItemBold(item, bold); } + +void wxTreeListCtrl::SetItemTextColour(const wxTreeItemId& item, + const wxColour& colour) +{ m_main_win->SetItemTextColour(item, colour); } + +void wxTreeListCtrl::SetItemBackgroundColour(const wxTreeItemId& item, + const wxColour& colour) +{ m_main_win->SetItemBackgroundColour(item, colour); } + +void wxTreeListCtrl::SetItemFont(const wxTreeItemId& item, + const wxFont& font) +{ m_main_win->SetItemFont(item, font); } + +bool wxTreeListCtrl::SetFont(const wxFont& font) +{ + if (m_header_win) { + m_header_win->SetFont(font); + CalculateAndSetHeaderHeight(); + m_header_win->Refresh(); + } + if (m_main_win) { + return m_main_win->SetFont(font); + }else{ + return false; + } +} + +void wxTreeListCtrl::SetWindowStyle(const long style) +{ + if(m_main_win) + m_main_win->SetWindowStyle(style); + m_windowStyle = style; + // TODO: provide something like wxTL_NO_HEADERS to hide m_header_win +} + +long wxTreeListCtrl::GetWindowStyle() const +{ + long style = m_windowStyle; + if(m_main_win) + style |= m_main_win->GetWindowStyle(); + return style; +} + +bool wxTreeListCtrl::IsVisible(const wxTreeItemId& item, bool fullRow, bool within) const +{ return m_main_win->IsVisible(item, fullRow, within); } + +bool wxTreeListCtrl::HasChildren(const wxTreeItemId& item) const +{ return m_main_win->HasChildren(item); } + +bool wxTreeListCtrl::IsExpanded(const wxTreeItemId& item) const +{ return m_main_win->IsExpanded(item); } + +bool wxTreeListCtrl::IsSelected(const wxTreeItemId& item) const +{ return m_main_win->IsSelected(item); } + +bool wxTreeListCtrl::IsBold(const wxTreeItemId& item) const +{ return m_main_win->IsBold(item); } + +size_t wxTreeListCtrl::GetChildrenCount(const wxTreeItemId& item, bool rec) +{ return m_main_win->GetChildrenCount(item, rec); } + +wxTreeItemId wxTreeListCtrl::GetRootItem() const +{ return m_main_win->GetRootItem(); } + +wxTreeItemId wxTreeListCtrl::GetSelection() const +{ return m_main_win->GetSelection(); } + +size_t wxTreeListCtrl::GetSelections(wxArrayTreeItemIds& arr) const +{ return m_main_win->GetSelections(arr); } + +wxTreeItemId wxTreeListCtrl::GetItemParent(const wxTreeItemId& item) const +{ return m_main_win->GetItemParent(item); } + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListCtrl::GetFirstChild (const wxTreeItemId& item, + long& cookie) const +#else +wxTreeItemId wxTreeListCtrl::GetFirstChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const +#endif +{ return m_main_win->GetFirstChild(item, cookie); } + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListCtrl::GetNextChild (const wxTreeItemId& item, + long& cookie) const +#else +wxTreeItemId wxTreeListCtrl::GetNextChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const +#endif +{ return m_main_win->GetNextChild(item, cookie); } + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListCtrl::GetPrevChild (const wxTreeItemId& item, + long& cookie) const +#else +wxTreeItemId wxTreeListCtrl::GetPrevChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const +#endif +{ return m_main_win->GetPrevChild(item, cookie); } + +#if !wxCHECK_VERSION(2, 5, 0) +wxTreeItemId wxTreeListCtrl::GetLastChild (const wxTreeItemId& item, + long& cookie) const +#else +wxTreeItemId wxTreeListCtrl::GetLastChild (const wxTreeItemId& item, + wxTreeItemIdValue& cookie) const +#endif +{ return m_main_win->GetLastChild(item, cookie); } + + +wxTreeItemId wxTreeListCtrl::GetNextSibling(const wxTreeItemId& item) const +{ return m_main_win->GetNextSibling(item); } + +wxTreeItemId wxTreeListCtrl::GetPrevSibling(const wxTreeItemId& item) const +{ return m_main_win->GetPrevSibling(item); } + +wxTreeItemId wxTreeListCtrl::GetNext(const wxTreeItemId& item) const +{ return m_main_win->GetNext(item, true); } + +wxTreeItemId wxTreeListCtrl::GetPrev(const wxTreeItemId& item) const +{ return m_main_win->GetPrev(item, true); } + +wxTreeItemId wxTreeListCtrl::GetFirstExpandedItem() const +{ return m_main_win->GetFirstExpandedItem(); } + +wxTreeItemId wxTreeListCtrl::GetNextExpanded(const wxTreeItemId& item) const +{ return m_main_win->GetNextExpanded(item); } + +wxTreeItemId wxTreeListCtrl::GetPrevExpanded(const wxTreeItemId& item) const +{ return m_main_win->GetPrevExpanded(item); } + +wxTreeItemId wxTreeListCtrl::GetFirstVisibleItem(bool fullRow) const +{ return GetFirstVisible(fullRow); } +wxTreeItemId wxTreeListCtrl::GetFirstVisible(bool fullRow, bool within) const +{ return m_main_win->GetFirstVisible(fullRow, within); } + +wxTreeItemId wxTreeListCtrl::GetLastVisible(bool fullRow, bool within) const +{ return m_main_win->GetLastVisible(fullRow, within); } + +wxTreeItemId wxTreeListCtrl::GetNextVisible(const wxTreeItemId& item, bool fullRow, bool within) const +{ return m_main_win->GetNextVisible(item, fullRow, within); } + +wxTreeItemId wxTreeListCtrl::GetPrevVisible(const wxTreeItemId& item, bool fullRow, bool within) const +{ return m_main_win->GetPrevVisible(item, fullRow, within); } + +wxTreeItemId wxTreeListCtrl::AddRoot (const wxString& text, int image, + int selectedImage, wxTreeItemData* data) +{ return m_main_win->AddRoot (text, image, selectedImage, data); } + +wxTreeItemId wxTreeListCtrl::PrependItem(const wxTreeItemId& parent, + const wxString& text, int image, + int selectedImage, + wxTreeItemData* data) +{ return m_main_win->PrependItem(parent, text, image, selectedImage, data); } + +wxTreeItemId wxTreeListCtrl::InsertItem(const wxTreeItemId& parent, + const wxTreeItemId& previous, + const wxString& text, int image, + int selectedImage, + wxTreeItemData* data) +{ + return m_main_win->InsertItem(parent, previous, text, image, + selectedImage, data); +} + +wxTreeItemId wxTreeListCtrl::InsertItem(const wxTreeItemId& parent, + size_t index, + const wxString& text, int image, + int selectedImage, + wxTreeItemData* data) +{ + return m_main_win->InsertItem(parent, index, text, image, + selectedImage, data); +} + +wxTreeItemId wxTreeListCtrl::AppendItem(const wxTreeItemId& parent, + const wxString& text, int image, + int selectedImage, + wxTreeItemData* data) +{ return m_main_win->AppendItem(parent, text, image, selectedImage, data); } + +void wxTreeListCtrl::Delete(const wxTreeItemId& item) +{ m_main_win->Delete(item); } + +void wxTreeListCtrl::DeleteChildren(const wxTreeItemId& item) +{ m_main_win->DeleteChildren(item); } + +void wxTreeListCtrl::DeleteRoot() +{ m_main_win->DeleteRoot(); } + +void wxTreeListCtrl::Expand(const wxTreeItemId& item) +{ m_main_win->Expand(item); } + +void wxTreeListCtrl::ExpandAll(const wxTreeItemId& item) +{ m_main_win->ExpandAll(item); } + +void wxTreeListCtrl::Collapse(const wxTreeItemId& item) +{ m_main_win->Collapse(item); } + +void wxTreeListCtrl::CollapseAndReset(const wxTreeItemId& item) +{ m_main_win->CollapseAndReset(item); } + +void wxTreeListCtrl::Toggle(const wxTreeItemId& item) +{ m_main_win->Toggle(item); } + +void wxTreeListCtrl::Unselect() +{ m_main_win->Unselect(); } + +void wxTreeListCtrl::UnselectAll() +{ m_main_win->UnselectAll(); } + +bool wxTreeListCtrl::SelectItem(const wxTreeItemId& item, const wxTreeItemId& last, + bool unselect_others) +{ return m_main_win->SelectItem (item, last, unselect_others); } + +void wxTreeListCtrl::SelectAll() +{ m_main_win->SelectAll(); } + +void wxTreeListCtrl::EnsureVisible(const wxTreeItemId& item) +{ m_main_win->EnsureVisible(item); } + +void wxTreeListCtrl::ScrollTo(const wxTreeItemId& item) +{ m_main_win->ScrollTo(item); } + +wxTreeItemId wxTreeListCtrl::HitTest(const wxPoint& pos, int& flags, int& column) +{ + wxPoint p = m_main_win->ScreenToClient (ClientToScreen (pos)); + return m_main_win->HitTest (p, flags, column); +} + +bool wxTreeListCtrl::GetBoundingRect(const wxTreeItemId& item, wxRect& rect, + bool textOnly) const +{ return m_main_win->GetBoundingRect(item, rect, textOnly); } + +void wxTreeListCtrl::EditLabel (const wxTreeItemId& item, int column) +{ m_main_win->EditLabel (item, column); } + +int wxTreeListCtrl::OnCompareItems(const wxTreeItemId& item1, + const wxTreeItemId& item2) +{ + // do the comparison here, and not delegate to m_main_win, in order + // to let the user override it + //return m_main_win->OnCompareItems(item1, item2); + return wxStrcmp(GetItemText(item1), GetItemText(item2)); +} + +void wxTreeListCtrl::SortChildren(const wxTreeItemId& item) +{ m_main_win->SortChildren(item); } + +wxTreeItemId wxTreeListCtrl::FindItem (const wxTreeItemId& item, const wxString& str, int mode) +{ return m_main_win->FindItem (item, str, mode); } + +void wxTreeListCtrl::SetDragItem (const wxTreeItemId& item) +{ m_main_win->SetDragItem (item); } + +bool wxTreeListCtrl::SetBackgroundColour(const wxColour& colour) +{ + if (!m_main_win) return false; + return m_main_win->SetBackgroundColour(colour); +} + +bool wxTreeListCtrl::SetForegroundColour(const wxColour& colour) +{ + if (!m_main_win) return false; + return m_main_win->SetForegroundColour(colour); +} + +int wxTreeListCtrl::GetColumnCount() const +{ return m_main_win->GetColumnCount(); } + +void wxTreeListCtrl::SetColumnWidth(int column, int width) +{ + m_header_win->SetColumnWidth (column, width); + m_header_win->Refresh(); +} + +int wxTreeListCtrl::GetColumnWidth(int column) const +{ return m_header_win->GetColumnWidth(column); } + +void wxTreeListCtrl::SetMainColumn(int column) +{ m_main_win->SetMainColumn(column); } + +int wxTreeListCtrl::GetMainColumn() const +{ return m_main_win->GetMainColumn(); } + +void wxTreeListCtrl::SetColumnText(int column, const wxString& text) +{ + m_header_win->SetColumnText (column, text); + m_header_win->Refresh(); +} + +wxString wxTreeListCtrl::GetColumnText(int column) const +{ return m_header_win->GetColumnText(column); } + +void wxTreeListCtrl::AddColumn(const wxTreeListColumnInfo& colInfo) +{ + m_header_win->AddColumn (colInfo); + DoHeaderLayout(); +} + +void wxTreeListCtrl::InsertColumn(int before, const wxTreeListColumnInfo& colInfo) +{ + m_header_win->InsertColumn (before, colInfo); + m_header_win->Refresh(); +} + +void wxTreeListCtrl::RemoveColumn(int column) +{ + m_header_win->RemoveColumn (column); + m_header_win->Refresh(); +} + +void wxTreeListCtrl::SetColumn(int column, const wxTreeListColumnInfo& colInfo) +{ + m_header_win->SetColumn (column, colInfo); + m_header_win->Refresh(); +} + +const wxTreeListColumnInfo& wxTreeListCtrl::GetColumn(int column) const +{ return m_header_win->GetColumn(column); } + +wxTreeListColumnInfo& wxTreeListCtrl::GetColumn(int column) +{ return m_header_win->GetColumn(column); } + +void wxTreeListCtrl::SetColumnImage(int column, int image) +{ + m_header_win->SetColumn (column, GetColumn(column).SetImage(image)); + m_header_win->Refresh(); +} + +int wxTreeListCtrl::GetColumnImage(int column) const +{ + return m_header_win->GetColumn(column).GetImage(); +} + +void wxTreeListCtrl::SetColumnEditable(int column, bool shown) +{ + m_header_win->SetColumn (column, GetColumn(column).SetEditable(shown)); +} + +void wxTreeListCtrl::SetColumnShown(int column, bool shown) +{ + wxASSERT_MSG (column != GetMainColumn(), _T("The main column may not be hidden") ); + m_header_win->SetColumn (column, GetColumn(column).SetShown(GetMainColumn()==column? true: shown)); + m_header_win->Refresh(); +} + +bool wxTreeListCtrl::IsColumnEditable(int column) const +{ + return m_header_win->GetColumn(column).IsEditable(); +} + +bool wxTreeListCtrl::IsColumnShown(int column) const +{ + return m_header_win->GetColumn(column).IsShown(); +} + +void wxTreeListCtrl::SetColumnAlignment (int column, int flag) +{ + m_header_win->SetColumn(column, GetColumn(column).SetAlignment(flag)); + m_header_win->Refresh(); +} + +int wxTreeListCtrl::GetColumnAlignment(int column) const +{ + return m_header_win->GetColumn(column).GetAlignment(); +} + +void wxTreeListCtrl::Refresh(bool erase, const wxRect* rect) +{ + m_main_win->Refresh (erase, rect); + m_header_win->Refresh (erase, rect); +} + +void wxTreeListCtrl::SetFocus() +{ m_main_win->SetFocus(); } + +wxSize wxTreeListCtrl::DoGetBestSize() const +{ + // something is better than nothing... + return wxSize (200,200); // but it should be specified values! FIXME +} + +wxString wxTreeListCtrl::OnGetItemText( wxTreeItemData* WXUNUSED(item), long WXUNUSED(column)) const +{ + return wxEmptyString; +} + +void wxTreeListCtrl::SetToolTip(const wxString& tip) { + m_header_win->SetToolTip(tip); + m_main_win->SetToolTip(tip); +} +void wxTreeListCtrl::SetToolTip(wxToolTip *tip) { + m_header_win->SetToolTip(tip); + m_main_win->SetToolTip(tip); +} + +void wxTreeListCtrl::SetItemToolTip(const wxTreeItemId& item, const wxString &tip) { + m_main_win->SetItemToolTip(item, tip); +} diff --git a/xvaga/treelistctrl.h b/xvaga/treelistctrl.h new file mode 100755 index 000000000..026913226 --- /dev/null +++ b/xvaga/treelistctrl.h @@ -0,0 +1,552 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: treelistctrl.h +// Purpose: wxTreeListCtrl class +// Author: Robert Roebling +// Maintainer: Otto Wyss +// Created: 01/02/97 +// RCS-ID: $Id: treelistctrl.h,v 1.1 2010-08-11 12:53:55 guy Exp $ +// Copyright: (c) 2004 Robert Roebling, Julian Smart, Alberto Griggio, +// Vadim Zeitlin, Otto Wyss +// Licence: wxWindows +///////////////////////////////////////////////////////////////////////////// + + +#ifndef TREELISTCTRL_H +#define TREELISTCTRL_H + +#if defined(__GNUG__) && !defined(__APPLE__) + #pragma interface "treelistctrl.h" +#endif +#include +#include // for wxListEvent + +class WXDLLEXPORT wxTreeListItem; +class WXDLLEXPORT wxTreeListHeaderWindow; +class WXDLLEXPORT wxTreeListMainWindow; + +#define wxTR_COLUMN_LINES 0x1000 // put border around items +#define wxTR_VIRTUAL 0x4000 // The application provides items text on demand. + +// Using this typedef removes an ambiguity when calling Remove() +#ifdef __WXMSW__ +#if !wxCHECK_VERSION(2, 5, 0) +typedef long wxTreeItemIdValue; +#else +typedef void *wxTreeItemIdValue; +#endif +#endif + +//----------------------------------------------------------------------------- +// wxTreeListColumnAttrs +//----------------------------------------------------------------------------- + +enum { + DEFAULT_COL_WIDTH = 100 +}; + +class /*WXDLLEXPORT*/ wxTreeListColumnInfo: public wxObject { + +public: + wxTreeListColumnInfo (const wxString &text = wxEmptyString, + int width = DEFAULT_COL_WIDTH, + int flag = wxALIGN_LEFT, + int image = -1, + bool shown = true, + bool edit = false) { + m_text = text; + m_width = width; + m_flag = flag; + m_image = image; + m_selected_image = -1; + m_shown = shown; + m_edit = edit; + } + + wxTreeListColumnInfo (const wxTreeListColumnInfo& other) { + m_text = other.m_text; + m_width = other.m_width; + m_flag = other.m_flag; + m_image = other.m_image; + m_selected_image = other.m_selected_image; + m_shown = other.m_shown; + m_edit = other.m_edit; + } + + ~wxTreeListColumnInfo() {} + + // get/set + wxString GetText() const { return m_text; } + wxTreeListColumnInfo& SetText (const wxString& text) { m_text = text; return *this; } + + int GetWidth() const { return m_width; } + wxTreeListColumnInfo& SetWidth (int width) { m_width = width; return *this; } + + int GetAlignment() const { return m_flag; } + wxTreeListColumnInfo& SetAlignment (int flag) { m_flag = flag; return *this; } + + int GetImage() const { return m_image; } + wxTreeListColumnInfo& SetImage (int image) { m_image = image; return *this; } + + int GetSelectedImage() const { return m_selected_image; } + wxTreeListColumnInfo& SetSelectedImage (int image) { m_selected_image = image; return *this; } + + bool IsEditable() const { return m_edit; } + wxTreeListColumnInfo& SetEditable (bool edit) + { m_edit = edit; return *this; } + + bool IsShown() const { return m_shown; } + wxTreeListColumnInfo& SetShown(bool shown) { m_shown = shown; return *this; } + +private: + wxString m_text; + int m_width; + int m_flag; + int m_image; + int m_selected_image; + bool m_shown; + bool m_edit; +}; + +//---------------------------------------------------------------------------- +// wxTreeListCtrl - the multicolumn tree control +//---------------------------------------------------------------------------- + +// modes for navigation +const int wxTL_MODE_NAV_FULLTREE = 0x0000; // default +const int wxTL_MODE_NAV_EXPANDED = 0x0001; +const int wxTL_MODE_NAV_VISIBLE = 0x0002; +const int wxTL_MODE_NAV_LEVEL = 0x0004; + +// modes for FindItem +const int wxTL_MODE_FIND_EXACT = 0x0000; // default +const int wxTL_MODE_FIND_PARTIAL = 0x0010; +const int wxTL_MODE_FIND_NOCASE = 0x0020; + +// additional flag for HitTest +const int wxTREE_HITTEST_ONITEMCOLUMN = 0x2000; +extern /*WXDLLEXPORT*/ const wxChar* wxTreeListCtrlNameStr; + + +class /*WXDLLEXPORT*/ wxTreeListCtrl : public wxControl +{ +friend class wxTreeListHeaderWindow; +friend class wxTreeListMainWindow; +friend class wxTreeListItem; +public: + // creation + // -------- + wxTreeListCtrl() + : m_header_win(0), m_main_win(0), m_headerHeight(0) + {} + + wxTreeListCtrl(wxWindow *parent, wxWindowID id = -1, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxTR_DEFAULT_STYLE, + const wxValidator &validator = wxDefaultValidator, + const wxString& name = wxTreeListCtrlNameStr ) + : m_header_win(0), m_main_win(0), m_headerHeight(0) + { + Create(parent, id, pos, size, style, validator, name); + } + + virtual ~wxTreeListCtrl() {} + + bool Create(wxWindow *parent, wxWindowID id = -1, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxTR_DEFAULT_STYLE, + const wxValidator &validator = wxDefaultValidator, + const wxString& name = wxTreeListCtrlNameStr ); + + void Refresh(bool erase=TRUE, const wxRect* rect=NULL); + void SetFocus(); + // accessors + // --------- + + // get the total number of items in the control + size_t GetCount() const; + + // indent is the number of pixels the children are indented relative to + // the parents position. SetIndent() also redraws the control + // immediately. + unsigned int GetIndent() const; + void SetIndent(unsigned int indent); + + // line spacing is the space above and below the text on each line + unsigned int GetLineSpacing() const; + void SetLineSpacing(unsigned int spacing); + + // image list: these functions allow to associate an image list with + // the control and retrieve it. Note that when assigned with + // SetImageList, the control does _not_ delete + // the associated image list when it's deleted in order to allow image + // lists to be shared between different controls. If you use + // AssignImageList, the control _does_ delete the image list. + // + // The normal image list is for the icons which correspond to the + // normal tree item state (whether it is selected or not). + // Additionally, the application might choose to show a state icon + // which corresponds to an app-defined item state (for example, + // checked/unchecked) which are taken from the state image list. + wxImageList *GetImageList() const; + wxImageList *GetStateImageList() const; + wxImageList *GetButtonsImageList() const; + + void SetImageList(wxImageList *imageList); + void SetStateImageList(wxImageList *imageList); + void SetButtonsImageList(wxImageList *imageList); + void AssignImageList(wxImageList *imageList); + void AssignStateImageList(wxImageList *imageList); + void AssignButtonsImageList(wxImageList *imageList); + + void SetToolTip(const wxString& tip); + void SetToolTip (wxToolTip *tip); + void SetItemToolTip(const wxTreeItemId& item, const wxString &tip); + + // Functions to work with columns + + // adds a column + void AddColumn (const wxString& text, + int width = DEFAULT_COL_WIDTH, + int flag = wxALIGN_LEFT, + int image = -1, + bool shown = true, + bool edit = false) { + AddColumn (wxTreeListColumnInfo (text, width, flag, image, shown, edit)); + } + void AddColumn (const wxTreeListColumnInfo& colInfo); + + // inserts a column before the given one + void InsertColumn (int before, + const wxString& text, + int width = DEFAULT_COL_WIDTH, + int flag = wxALIGN_LEFT, + int image = -1, + bool shown = true, + bool edit = false) { + InsertColumn (before, + wxTreeListColumnInfo (text, width, flag, image, shown, edit)); + } + void InsertColumn (int before, const wxTreeListColumnInfo& colInfo); + + // deletes the given column - does not delete the corresponding column + void RemoveColumn (int column); + + // returns the number of columns in the ctrl + int GetColumnCount() const; + + // tells which column is the "main" one, i.e. the "threaded" one + void SetMainColumn (int column); + int GetMainColumn() const; + + void SetColumn (int column, const wxTreeListColumnInfo& colInfo); + wxTreeListColumnInfo& GetColumn (int column); + const wxTreeListColumnInfo& GetColumn (int column) const; + + void SetColumnText (int column, const wxString& text); + wxString GetColumnText (int column) const; + + void SetColumnWidth (int column, int width); + int GetColumnWidth (int column) const; + + void SetColumnAlignment (int column, int flag); + int GetColumnAlignment (int column) const; + + void SetColumnImage (int column, int image); + int GetColumnImage (int column) const; + + void SetColumnShown (int column, bool shown = true); + bool IsColumnShown (int column) const; + + void SetColumnEditable (int column, bool edit = true); + bool IsColumnEditable (int column) const; + + // Functions to work with items. + + // accessors + // --------- + + // retrieve item's label (of the main column) + wxString GetItemText (const wxTreeItemId& item) const + { return GetItemText (item, GetMainColumn()); } + // retrieves item's label of the given column + wxString GetItemText (const wxTreeItemId& item, int column) const; + + // get one of the images associated with the item (normal by default) + int GetItemImage (const wxTreeItemId& item, + wxTreeItemIcon which = wxTreeItemIcon_Normal) const + { return GetItemImage (item, GetMainColumn(), which); } + int GetItemImage (const wxTreeItemId& item, int column, + wxTreeItemIcon which = wxTreeItemIcon_Normal) const; + + // get the data associated with the item + wxTreeItemData *GetItemData (const wxTreeItemId& item) const; + + bool GetItemBold (const wxTreeItemId& item) const; + wxColour GetItemTextColour (const wxTreeItemId& item) const; + wxColour GetItemBackgroundColour (const wxTreeItemId& item) const; + wxFont GetItemFont (const wxTreeItemId& item) const; + + // modifiers + + // set item's label + void SetItemText (const wxTreeItemId& item, const wxString& text) + { SetItemText (item, GetMainColumn(), text); } + void SetItemText (const wxTreeItemId& item, int column, const wxString& text); + + // get one of the images associated with the item (normal by default) + void SetItemImage (const wxTreeItemId& item, int image, + wxTreeItemIcon which = wxTreeItemIcon_Normal) + { SetItemImage (item, GetMainColumn(), image, which); } + // the which parameter is ignored for all columns but the main one + void SetItemImage (const wxTreeItemId& item, int column, int image, + wxTreeItemIcon which = wxTreeItemIcon_Normal); + + // associate some data with the item + void SetItemData (const wxTreeItemId& item, wxTreeItemData *data); + + // force appearance of [+] button near the item. This is useful to + // allow the user to expand the items which don't have any children now + // - but instead add them only when needed, thus minimizing memory + // usage and loading time. + void SetItemHasChildren(const wxTreeItemId& item, bool has = true); + + // the item will be shown in bold + void SetItemBold (const wxTreeItemId& item, bool bold = true); + + // set the item's text colour + void SetItemTextColour (const wxTreeItemId& item, const wxColour& colour); + + // set the item's background colour + void SetItemBackgroundColour (const wxTreeItemId& item, const wxColour& colour); + + // set the item's font (should be of the same height for all items) + void SetItemFont (const wxTreeItemId& item, const wxFont& font); + + // set the window font + virtual bool SetFont ( const wxFont &font ); + + // set the styles. + void SetWindowStyle (const long styles); + long GetWindowStyle() const; + long GetWindowStyleFlag () const { return GetWindowStyle(); } + + // item status inquiries + // --------------------- + + // is the item visible (it might be outside the view or not expanded)? + bool IsVisible (const wxTreeItemId& item, bool fullRow = false, bool within = true) const; + // does the item has any children? + bool HasChildren (const wxTreeItemId& item) const; + // is the item expanded (only makes sense if HasChildren())? + bool IsExpanded (const wxTreeItemId& item) const; + // is this item currently selected (the same as has focus)? + bool IsSelected (const wxTreeItemId& item) const; + // is item text in bold font? + bool IsBold (const wxTreeItemId& item) const; + // does the layout include space for a button? + + // number of children + // ------------------ + + // if 'recursively' is FALSE, only immediate children count, otherwise + // the returned number is the number of all items in this branch + size_t GetChildrenCount (const wxTreeItemId& item, bool recursively = true); + + // navigation + // ---------- + + // wxTreeItemId.IsOk() will return FALSE if there is no such item + + // get the root tree item + wxTreeItemId GetRootItem() const; + + // get the item currently selected (may return NULL if no selection) + wxTreeItemId GetSelection() const; + + // get the items currently selected, return the number of such item + size_t GetSelections (wxArrayTreeItemIds&) const; + + // get the parent of this item (may return NULL if root) + wxTreeItemId GetItemParent (const wxTreeItemId& item) const; + + // for this enumeration function you must pass in a "cookie" parameter + // which is opaque for the application but is necessary for the library + // to make these functions reentrant (i.e. allow more than one + // enumeration on one and the same object simultaneously). Of course, + // the "cookie" passed to GetFirstChild() and GetNextChild() should be + // the same! + + // get child of this item +#if !wxCHECK_VERSION(2, 5, 0) + wxTreeItemId GetFirstChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetNextChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetPrevChild(const wxTreeItemId& item, long& cookie) const; + wxTreeItemId GetLastChild(const wxTreeItemId& item, long& cookie) const; +#else + wxTreeItemId GetFirstChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetNextChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetPrevChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; + wxTreeItemId GetLastChild(const wxTreeItemId& item, wxTreeItemIdValue& cookie) const; +#endif + + // get sibling of this item + wxTreeItemId GetNextSibling(const wxTreeItemId& item) const; + wxTreeItemId GetPrevSibling(const wxTreeItemId& item) const; + + // get item in the full tree (currently only for internal use) + wxTreeItemId GetNext(const wxTreeItemId& item) const; + wxTreeItemId GetPrev(const wxTreeItemId& item) const; + + // get expanded item, see IsExpanded() + wxTreeItemId GetFirstExpandedItem() const; + wxTreeItemId GetNextExpanded(const wxTreeItemId& item) const; + wxTreeItemId GetPrevExpanded(const wxTreeItemId& item) const; + + // get visible item, see IsVisible() + wxTreeItemId GetFirstVisibleItem( bool fullRow = false) const; + wxTreeItemId GetFirstVisible( bool fullRow = false, bool within = true) const; + wxTreeItemId GetNextVisible (const wxTreeItemId& item, bool fullRow = false, bool within = true) const; + wxTreeItemId GetPrevVisible (const wxTreeItemId& item, bool fullRow = false, bool within = true) const; + wxTreeItemId GetLastVisible ( bool fullRow = false, bool within = true) const; + + // operations + // ---------- + + // add the root node to the tree + wxTreeItemId AddRoot (const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item in as the first child of the parent + wxTreeItemId PrependItem (const wxTreeItemId& parent, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item after a given one + wxTreeItemId InsertItem (const wxTreeItemId& parent, + const wxTreeItemId& idPrevious, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item before the one with the given index + wxTreeItemId InsertItem (const wxTreeItemId& parent, + size_t index, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // insert a new item in as the last child of the parent + wxTreeItemId AppendItem (const wxTreeItemId& parent, + const wxString& text, + int image = -1, int selectedImage = -1, + wxTreeItemData *data = NULL); + + // delete this item (except root) + children and associated data if any + void Delete (const wxTreeItemId& item); + // delete all children (but don't delete the item itself) + void DeleteChildren (const wxTreeItemId& item); + // delete the root and all its children from the tree + void DeleteRoot(); + + // expand this item + void Expand (const wxTreeItemId& item); + // expand this item and all subitems recursively + void ExpandAll (const wxTreeItemId& item); + // collapse the item without removing its children + void Collapse (const wxTreeItemId& item); + // collapse the item and remove all children + void CollapseAndReset(const wxTreeItemId& item); //? TODO ??? + // toggles the current state + void Toggle (const wxTreeItemId& item); + + // remove the selection from currently selected item (if any) + void Unselect(); + void UnselectAll(); + // select this item - return true if selection was allowed (no veto) + bool SelectItem (const wxTreeItemId& item, + const wxTreeItemId& last = (wxTreeItemId*)NULL, + bool unselect_others = true); + // select all items in the expanded tree + void SelectAll(); + // make sure this item is visible (expanding the parent item and/or + // scrolling to this item if necessary) + void EnsureVisible (const wxTreeItemId& item); + // scroll to this item (but don't expand its parent) + void ScrollTo (const wxTreeItemId& item); + + // The first function is more portable (because easier to implement + // on other platforms), but the second one returns some extra info. + wxTreeItemId HitTest (const wxPoint& point) + { int flags; int column; return HitTest (point, flags, column); } + wxTreeItemId HitTest (const wxPoint& point, int& flags) + { int column; return HitTest (point, flags, column); } + wxTreeItemId HitTest (const wxPoint& point, int& flags, int& column); + + // get the bounding rectangle of the item (or of its label only) + bool GetBoundingRect (const wxTreeItemId& item, wxRect& rect, + bool textOnly = false) const; + + // Start editing the item label: this (temporarily) replaces the item + // with a one line edit control. The item will be selected if it hadn't + // been before. + void EditLabel (const wxTreeItemId& item) + { EditLabel (item, GetMainColumn()); } + // edit item's label of the given column + void EditLabel (const wxTreeItemId& item, int column); + + // virtual mode + virtual wxString OnGetItemText( wxTreeItemData* item, long column ) const; + + // sorting + // this function is called to compare 2 items and should return -1, 0 + // or +1 if the first item is less than, equal to or greater than the + // second one. The base class version performs alphabetic comparaison + // of item labels (GetText) + virtual int OnCompareItems (const wxTreeItemId& item1, const wxTreeItemId& item2); + // sort the children of this item using OnCompareItems + // NB: this function is not reentrant and not MT-safe (FIXME)! + void SortChildren(const wxTreeItemId& item); + + // searching + wxTreeItemId FindItem (const wxTreeItemId& item, const wxString& str, int mode = 0); + + // overridden base class virtuals + virtual bool SetBackgroundColour (const wxColour& colour); + virtual bool SetForegroundColour (const wxColour& colour); + + // drop over item + void SetDragItem (const wxTreeItemId& item = (wxTreeItemId*)NULL); + + + virtual wxSize DoGetBestSize() const; + +protected: + // header window, responsible for column visualization and manipulation + wxTreeListHeaderWindow* GetHeaderWindow() const + { return m_header_win; } + wxTreeListHeaderWindow* m_header_win; // future cleanup: make private or remove GetHeaderWindow() + + // main window, the "true" tree ctrl + wxTreeListMainWindow* GetMainWindow() const + { return m_main_win; } + wxTreeListMainWindow* m_main_win; // future cleanup: make private or remove GetMainWindow() + + int GetHeaderHeight() const { return m_headerHeight; } + + void CalculateAndSetHeaderHeight(); + void DoHeaderLayout(); + void OnSize(wxSizeEvent& event); + +private: + int m_headerHeight; + + DECLARE_EVENT_TABLE() + DECLARE_DYNAMIC_CLASS(wxTreeListCtrl) +}; + +#endif // TREELISTCTRL_H +