Patch level : 10.0 274
Files correlati : ca2.exe Ricompilazione Demo : [ ] Commento : Corretto comportamento dei sorted_cursor per le maschere. Ora la ricerca viene fatta sulla chiave di sort. Aggiunta modalità batch delle transazioni. Viene generato un log e l'esecuzione non viene interrotta da segnalazioni ( Da collaudare meglio alla prima occasione). Resa possibile la visualizzazione di record bloccati da un altro utente. Queste funzionalità sono presenti nelle relapp dall patch 274 e successive. git-svn-id: svn://10.65.10.50/branches/R_10_00@23210 c028cbd2-c16b-5b4b-a496-9718f37d4682
This commit is contained in:
parent
3469b56f48
commit
24ed90d54e
@ -1,6 +1,8 @@
|
||||
#include <dongle.h>
|
||||
#include <execp.h>
|
||||
#include <expr.h>
|
||||
#include <msksheet.h>
|
||||
#include <postman.h>
|
||||
#include <recarray.h>
|
||||
#include <recset.h>
|
||||
#include <relapp.h>
|
||||
@ -385,6 +387,67 @@ bool TBrowse::parse_copy(const TString& what, const TBrowse& b)
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBrowse::update_filter(TEdit_field * f)
|
||||
{
|
||||
if (!f->in_key(0))
|
||||
{
|
||||
TString rf = get_user_read_filter(); rf.trim();
|
||||
|
||||
if (rf.not_empty())
|
||||
{
|
||||
TExpression e(rf);
|
||||
bool ok = true;
|
||||
|
||||
for (int i = e.numvar()-1; ok && i >= 0; i--)
|
||||
{
|
||||
TString s = e.varname(i);
|
||||
TString id;
|
||||
int fileid = 0;
|
||||
int pos = s.find("->");
|
||||
|
||||
if (pos > 0)
|
||||
{
|
||||
id = s.left(pos); id.strip(" ");
|
||||
fileid = name2log(id);
|
||||
pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = s.find('.');
|
||||
if (pos > 0)
|
||||
{
|
||||
id = s.left(pos); id.strip(" ");
|
||||
fileid = name2log(id);
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
pos = 0;
|
||||
}
|
||||
if (fileid < CNF_GENERAL)
|
||||
{
|
||||
s.ltrim(pos);
|
||||
int par = s.find('[', pos);
|
||||
|
||||
if (par >= 0)
|
||||
s.cut(par);
|
||||
TRelation * r = cursor()->relation();
|
||||
ok = (fileid == 0 && r->curr().exist(s)) || (r->log2ind(fileid) >= 0 && r->curr(fileid).exist(s));
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
{
|
||||
if (_filter.blank())
|
||||
_filter = rf;
|
||||
else
|
||||
{
|
||||
_filter.insert("(", 0);
|
||||
_filter << ")&&(" << rf << ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TBrowse::replace_cursor(TCursor* c)
|
||||
{
|
||||
if (_relation != NULL)
|
||||
|
@ -213,6 +213,9 @@ public:
|
||||
// @cmember Aggiorna la lista completa degli identificatori dei campi di output da un campo
|
||||
void copy_output(const TBrowse * b);
|
||||
|
||||
// @cmember Aggiorna il filtro con l'espressione per l'utente
|
||||
void update_filter(TEdit_field * f);
|
||||
|
||||
// @cmember Cambia il cursore senza darne il possesso (come nella COPY USE)
|
||||
void replace_cursor(TCursor* c);
|
||||
|
||||
|
@ -3,8 +3,44 @@
|
||||
#include <diction.h>
|
||||
#include <dongle.h>
|
||||
#include <keys.h>
|
||||
#include <strings.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define buildmsg() char msg[1024];va_list argptr;va_start(argptr,fmt);_vsnprintf_s(msg,sizeof(msg),_TRUNCATE,fmt,argptr);va_end(argptr);msg[1023] = '\0';
|
||||
#else
|
||||
#define buildmsg() char msg[1024];va_list argptr;va_start(argptr,fmt);vsprintf(msg,fmt,argptr);va_end(argptr)
|
||||
#endif
|
||||
|
||||
static bool __batch = false;
|
||||
TString_array __errors;
|
||||
TString_array __warnings;
|
||||
|
||||
bool is_batch()
|
||||
{
|
||||
return __batch;
|
||||
}
|
||||
|
||||
void batch(bool on)
|
||||
{
|
||||
__batch = on;
|
||||
if (on)
|
||||
{
|
||||
__errors.destroy();
|
||||
__warnings.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
TString_array & errors()
|
||||
{
|
||||
return __errors;
|
||||
}
|
||||
|
||||
TString_array & warnings()
|
||||
{
|
||||
return __warnings;
|
||||
}
|
||||
|
||||
|
||||
#define buildmsg() char msg[1024];va_list argptr;va_start(argptr,fmt);vsnprintf_s(msg,sizeof(msg),_TRUNCATE,fmt,argptr);va_end(argptr);
|
||||
|
||||
// @doc EXTERNAL
|
||||
|
||||
@ -15,6 +51,9 @@ bool fatal_box(
|
||||
// @comm Il programma viene interrotto al momento in cui si e' verificato l'errore.
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
__errors.add(msg);
|
||||
else
|
||||
xvt_dm_post_fatal_exit(msg);
|
||||
return false;
|
||||
}
|
||||
@ -29,6 +68,9 @@ bool error_box(
|
||||
// e l'icona punto esclamativo.
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
__errors.add(msg);
|
||||
else
|
||||
xvt_dm_post_error(msg);
|
||||
return false;
|
||||
}
|
||||
@ -42,6 +84,9 @@ bool warning_box(
|
||||
// e l'icona punto di domanda.
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
__warnings.add(msg);
|
||||
else
|
||||
xvt_dm_post_warning(msg);
|
||||
return 0;
|
||||
}
|
||||
@ -55,6 +100,9 @@ bool message_box(
|
||||
// e l'icona informazioni.
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
__warnings.add(msg);
|
||||
else
|
||||
xvt_dm_post_message(msg);
|
||||
return false;
|
||||
}
|
||||
@ -68,6 +116,9 @@ bool sorry_box(
|
||||
// e l'icona informazioni.
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
__warnings.add(msg);
|
||||
else
|
||||
xvt_dm_post_note(msg);
|
||||
return false;
|
||||
}
|
||||
@ -78,6 +129,11 @@ bool noyes_box(
|
||||
...) // @parmvar Uno o piu' parametri corrispondenti ai codici in <p fmt>
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
{
|
||||
__errors.add(msg);
|
||||
return true;
|
||||
}
|
||||
ASK_RESPONSE r = xvt_dm_post_ask("No", "Si", NULL, msg);
|
||||
return r == RESP_DEFAULT;
|
||||
}
|
||||
@ -88,6 +144,11 @@ int noyesall_box(
|
||||
...) // @parmvar Uno o piu' parametri corrispondenti ai codici in <p fmt>
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
{
|
||||
__errors.add(msg);
|
||||
return K_NO;
|
||||
}
|
||||
ASK_RESPONSE r = xvt_dm_post_ask("No", "Si", "Si Tutti", msg);
|
||||
return r == RESP_DEFAULT ? K_YES : (r == RESP_2 ? K_NO : K_SPACE);
|
||||
}
|
||||
@ -106,6 +167,11 @@ bool yesno_box(
|
||||
// @flag 0 | Se viene premuto il taso NO
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
{
|
||||
__errors.add(msg);
|
||||
return false;
|
||||
}
|
||||
ASK_RESPONSE r = xvt_dm_post_ask("Si", "No", NULL, msg);
|
||||
return r == RESP_DEFAULT;
|
||||
}
|
||||
@ -116,6 +182,11 @@ int yesnoall_box(
|
||||
...) // @parmvar Uno o piu' parametri corrispondenti ai codici in <p fmt>
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
{
|
||||
__errors.add(msg);
|
||||
return K_NO;
|
||||
}
|
||||
ASK_RESPONSE r = xvt_dm_post_ask("No", "Si", "No Tutti", msg);
|
||||
return r == RESP_DEFAULT ? K_NO : (r == RESP_2 ? K_YES : K_SPACE);
|
||||
}
|
||||
@ -170,6 +241,11 @@ int yesnocancel_box(
|
||||
// @xref <m yesno_box> <m yesnofatal_box>
|
||||
{
|
||||
buildmsg();
|
||||
if (__batch)
|
||||
{
|
||||
__errors.add(msg);
|
||||
return K_NO;
|
||||
}
|
||||
ASK_RESPONSE r = xvt_dm_post_ask("Si", "No", "Annulla", msg);
|
||||
return r == RESP_DEFAULT ? K_YES : (r == RESP_2 ? K_NO : K_ESC);
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
#ifndef __CHECKS_H
|
||||
#define __CHECKS_H
|
||||
|
||||
#ifndef __STDTYPES_H
|
||||
#include <stdtypes.h>
|
||||
#endif
|
||||
|
||||
class TString_array;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -21,6 +27,11 @@ extern "C" {
|
||||
bool cantaccess_box(const char* filename);
|
||||
bool __trace(const char* fmt, ...);
|
||||
bool __tracemem(const char* fmt);
|
||||
|
||||
void batch(bool on = true);
|
||||
bool is_batch();
|
||||
TString_array & errors();
|
||||
TString_array & warnings();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -90,7 +90,7 @@ WINDOW TMask::curr_win() const
|
||||
return page_win(_page > 0 && _page < _pages ? _page : 0);
|
||||
}
|
||||
|
||||
TMask::TMask() : _mask_num(0)
|
||||
TMask::TMask() : _mask_num(0), _sheet_first_selection_row(-1)
|
||||
{ init_mask(); }
|
||||
|
||||
TMask::TMask(const char* title, int pages, int cols, int rows,
|
||||
@ -870,6 +870,13 @@ long TMask::handler(WINDOW w, EVENT* ep)
|
||||
case 2: _last_sheet->reset_columns_order(); break;
|
||||
case 3: _last_sheet->on_key(K_F11); break;
|
||||
case 4: _last_sheet->esporta(); break;
|
||||
case 5: _last_sheet->clear_range_selection(); break;
|
||||
case 6:
|
||||
_last_sheet->select_range(_sheet_first_selection_row);
|
||||
_sheet_first_selection_row = -1;
|
||||
break;
|
||||
case 7: _last_sheet->copy_rows(); break;
|
||||
case 8: _last_sheet->paste_rows(); break;
|
||||
default: break;
|
||||
}
|
||||
return 0L;
|
||||
@ -2688,8 +2695,26 @@ void TMask::post_error_message(const char* msg, int sev)
|
||||
return; // Ignora messaggio duplicato;
|
||||
on_idle();
|
||||
}
|
||||
if (is_batch())
|
||||
{
|
||||
if (sev > 2)
|
||||
errors().add(msg);
|
||||
else
|
||||
warnings().add(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
_error_message = msg;
|
||||
_error_severity = sev;
|
||||
}
|
||||
}
|
||||
|
||||
KEY TMask::check_mask()
|
||||
|
||||
// @comm Se la finestra non era aperta la apre in modo modale
|
||||
{
|
||||
start_run();
|
||||
return stop_run(K_ENTER) ? K_ENTER : K_ESC;
|
||||
}
|
||||
|
||||
// @doc INTERNAL
|
||||
|
@ -98,6 +98,9 @@ class TMask : public TWindow
|
||||
// @cmember:(INTERNAL) Puntatore allo sheet che contiene la maschera (puo' essere NULL)
|
||||
TSheet_field* _sheet;
|
||||
|
||||
// @cmember:(INTERNAL) Numero della prima riga di selezione nello sheet
|
||||
long _sheet_first_selection_row;
|
||||
|
||||
// @cmember:(INTERNAL) Handler per gestire i tasti speciali nelle maschere
|
||||
MASK_HANDLER _handler;
|
||||
|
||||
@ -475,6 +478,8 @@ public:
|
||||
{ return _toolwin; }
|
||||
WINDOW toolbar() const
|
||||
{ return _toolbar; }
|
||||
// @cmember verifica la mascher senza eseguirla
|
||||
KEY check_mask();
|
||||
};
|
||||
|
||||
// @doc EXTERNAL
|
||||
|
@ -805,6 +805,15 @@ bool TMask_field::yesno_box(const char* fmt, ...) const
|
||||
return yes;
|
||||
}
|
||||
|
||||
bool TMask_field::noyes_box(const char* fmt, ...) const
|
||||
{
|
||||
set_focus();
|
||||
build_msg();
|
||||
const bool yes = ::noyes_box("%s", _msg);
|
||||
set_focus();
|
||||
return yes;
|
||||
}
|
||||
|
||||
KEY TMask_field::yesnocancel_box(const char* fmt, ...) const
|
||||
{
|
||||
set_focus();
|
||||
@ -2035,7 +2044,6 @@ word TEdit_field::class_id() const
|
||||
|
||||
void TEdit_field::set_len(short w)
|
||||
{
|
||||
CHECKD(w > 0 && w <= 50, "Invalid field length ", w);
|
||||
_size = w;
|
||||
}
|
||||
|
||||
@ -2256,7 +2264,7 @@ bool TEdit_field::parse_item(TScanner& scanner)
|
||||
scanner.line();
|
||||
}
|
||||
scanner.push();
|
||||
|
||||
browse()->update_filter(this);
|
||||
if (tablename.full())
|
||||
{
|
||||
tablename.insert("MTB", 0);
|
||||
|
@ -256,6 +256,9 @@ public:
|
||||
// @cmember Ritorna true se si tratta di campo fantasma
|
||||
bool ghost() const
|
||||
{ return _flags.ghost; }
|
||||
// @cmember Ritorna true se il campo e' un numero romano
|
||||
bool persistent() const
|
||||
{ return _flags.persistent; }
|
||||
|
||||
// @cmember Controlla se il campo appartiene ad una chiave di ricerca
|
||||
virtual bool in_key(word) const
|
||||
@ -403,6 +406,8 @@ public:
|
||||
virtual bool error_box(const char* fmt, ...) const;
|
||||
// @cmember Crea una yesno-box relativamente al campo (chiama <f yesno_box>)
|
||||
virtual bool yesno_box(const char* fmt, ...) const;
|
||||
// @cmember Crea una noyes-box relativamente al campo (chiama <f yesno_box>)
|
||||
virtual bool noyes_box(const char* fmt, ...) const;
|
||||
// @cmember Crea una yesnocancel-box relativamente al campo (chiama <f yesnocancel_box>)
|
||||
virtual KEY yesnocancel_box(const char* fmt, ...) const;
|
||||
|
||||
|
@ -25,6 +25,11 @@ class TCell_property : public TObject
|
||||
public:
|
||||
void set(COLOR back, COLOR fore) { _back = back; _fore = fore; }
|
||||
bool get(COLOR& back, COLOR& fore) const;
|
||||
|
||||
virtual TCell_property & copy(const TCell_property & p);
|
||||
virtual TObject* dup() const { return new TCell_property(*this); }
|
||||
|
||||
TCell_property(const TCell_property & p);
|
||||
TCell_property();
|
||||
};
|
||||
|
||||
@ -39,6 +44,19 @@ bool TCell_property::get(COLOR& back, COLOR& fore) const
|
||||
return false;
|
||||
}
|
||||
|
||||
TCell_property & TCell_property::copy(const TCell_property & p)
|
||||
{
|
||||
_back = p._back;
|
||||
_fore = p._fore;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCell_property::TCell_property(const TCell_property & p)
|
||||
{
|
||||
copy
|
||||
(p);
|
||||
}
|
||||
|
||||
TCell_property::TCell_property() : _back(COLOR_INVALID), _fore(COLOR_INVALID)
|
||||
{ }
|
||||
|
||||
@ -58,10 +76,33 @@ public:
|
||||
|
||||
TBit_array & disabled() { return _disabled;}
|
||||
const TBit_array & disabled() const { return _disabled;}
|
||||
|
||||
virtual TRow_property & copy(const TRow_property & r);
|
||||
virtual TObject* dup() const { return new TRow_property(*this); }
|
||||
|
||||
TRow_property(const TRow_property & p);
|
||||
TRow_property();
|
||||
virtual ~TRow_property() { }
|
||||
};
|
||||
|
||||
TRow_property & TRow_property::copy(const TRow_property & p)
|
||||
{
|
||||
_disabled = p._disabled;
|
||||
_back = p._back;
|
||||
_fore = p._fore;
|
||||
_height = p._height;
|
||||
if (p._cell_prop != NULL)
|
||||
_cell_prop = new TArray(*p._cell_prop);
|
||||
else
|
||||
_cell_prop = NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TRow_property::TRow_property(const TRow_property & p)
|
||||
{
|
||||
copy(p);
|
||||
}
|
||||
|
||||
TRow_property::TRow_property()
|
||||
: _back(COLOR_INVALID), _fore(COLOR_INVALID), _height(-1), _cell_prop(NULL)
|
||||
{ }
|
||||
@ -146,13 +187,18 @@ class TSpreadsheet : public TControl
|
||||
|
||||
// @cmember:(INTERNAL) Array di TToken_strings contenenti le righe
|
||||
TString_array _str;
|
||||
// @cmember:(INTERNAL) Array di TToken_strings contenenti le righe copiate
|
||||
TString_array _copied_rows;
|
||||
// @cmember:(INTERNAL) Array delle colonne disattivate (solo visualizzazione)
|
||||
TBit_array _column_disabled;
|
||||
|
||||
// @cmember:(INTERNAL) righe selezionate
|
||||
int _from;
|
||||
int _to;
|
||||
|
||||
// @cmember:(INTERNAL) Array delle proprieta' delle righe
|
||||
TArray _properties;
|
||||
// @cmember:(INTERNAL) Array delle proprieta' standard di tutte le righe
|
||||
TRow_property* _row_properties;
|
||||
TArray * _saved_properties;
|
||||
|
||||
// @cmember:(INTERNAL) Maschera in cui e' contenuto lo spreadsheet
|
||||
TMask _mask;
|
||||
@ -403,6 +449,12 @@ public:
|
||||
|
||||
bool point2cell(const PNT& pnt, short& id, long& row) const;
|
||||
|
||||
bool get_range_selection(int & from, int & to) const ;
|
||||
void clear_range_selection();
|
||||
void select_range(int row);
|
||||
void copy_rows();
|
||||
void paste_rows();
|
||||
|
||||
// @cmember Costruttore
|
||||
TSpreadsheet(WINDOW parent, short dlg, short x, short y, short dx, short dy, const char* maskname, int maskno, const char* head, TSheet_field* owner);
|
||||
// @cmember Distruttore
|
||||
@ -436,8 +488,8 @@ TSpreadsheet::TSpreadsheet(
|
||||
_edit_field(NULL), _cur_row(0), _cur_rec(0), _cur_col(1),
|
||||
_row_dirty(false), _cell_dirty(false), _check_enabled(true),
|
||||
_needs_update(-1), _selection_posted(-1), _ignore_button(0), _save_columns_order(false),
|
||||
_f9_target(NULL), _auto_append(false), _first_nav_column_id(-1),_last_nav_column_id(-1),
|
||||
_row_properties(NULL)
|
||||
_f9_target(NULL), _auto_append(false), _first_nav_column_id(-1), _last_nav_column_id(-1),
|
||||
_from(-1), _to(-1), _saved_properties(NULL)
|
||||
{
|
||||
int m_width[MAX_COL], v_width[MAX_COL];
|
||||
int fixed_cols = 0; // Number of fixed columns
|
||||
@ -512,6 +564,15 @@ TSpreadsheet::TSpreadsheet(
|
||||
|
||||
XI_OBJ* itf = get_interface(parent);
|
||||
XI_RCT rct = coord2rct(itf, x, y, dx, dy);
|
||||
|
||||
if (x > 0 && dx < 0)
|
||||
{
|
||||
RCT max_rct; xvt_vobj_get_client_rect((WINDOW)xi_get_window(itf), &max_rct);
|
||||
const short MAXX = max_rct.right;
|
||||
|
||||
if (MAXX > 80 * CHARX)
|
||||
rct.left += (MAXX - 80 * CHARX) / 2;
|
||||
}
|
||||
rct.right -= 2*XI_FU_MULTIPLE; // toglie scroll-bar
|
||||
|
||||
// Controlla se posso bloccare anche questa colonna
|
||||
@ -547,6 +608,9 @@ TSpreadsheet::TSpreadsheet(
|
||||
l->active_back_color = FOCUS_BACK_COLOR;
|
||||
l->white_space_color = MASK_DARK_COLOR;
|
||||
l->rule_color = MASK_DARK_COLOR;
|
||||
#ifdef LINUX
|
||||
l->scroll_on_thumb_track = true;
|
||||
#endif
|
||||
|
||||
// Definizione della prima colonna (numero di riga)
|
||||
word attr = XI_ATR_RJUST;
|
||||
@ -640,7 +704,8 @@ TSpreadsheet::TSpreadsheet(
|
||||
|
||||
TSpreadsheet::~TSpreadsheet()
|
||||
{
|
||||
delete _row_properties;
|
||||
if (_saved_properties != NULL)
|
||||
delete _saved_properties;
|
||||
}
|
||||
|
||||
TMask& TSpreadsheet::sheet_mask() const
|
||||
@ -2346,21 +2411,15 @@ void TSpreadsheet::set_row_height(const int row, const int height)
|
||||
TRow_property* TSpreadsheet::get_property(int row, bool create)
|
||||
{
|
||||
if (row < 0)
|
||||
{
|
||||
if (_row_properties == NULL && create)
|
||||
_row_properties = new TRow_property;
|
||||
return _row_properties;
|
||||
}
|
||||
row = 0;
|
||||
|
||||
TRow_property* p = (TRow_property*)_properties.objptr(row);
|
||||
if (p == NULL)
|
||||
{
|
||||
if (create)
|
||||
|
||||
if (p == NULL && create)
|
||||
{
|
||||
p = new TRow_property;
|
||||
_properties.add(p, row);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -2639,6 +2698,11 @@ bool TSpreadsheet::error_box(const char* msg)
|
||||
const int r = _cur_row;
|
||||
const int c = _cur_col;
|
||||
|
||||
if (is_batch())
|
||||
{
|
||||
errors().add(msg);
|
||||
return false;
|
||||
}
|
||||
if (ADVANCED_GRAPHICS && ANIMATED_BOXES)
|
||||
xvt_dm_popup_error(msg);
|
||||
else
|
||||
@ -2691,6 +2755,101 @@ bool TSpreadsheet::point2cell(const PNT& pnt, short& id, long& row) const
|
||||
return inside;
|
||||
}
|
||||
|
||||
bool TSpreadsheet::get_range_selection(int & from, int & to) const
|
||||
{
|
||||
from = _from;
|
||||
to = _to;
|
||||
return _from >= 0;
|
||||
}
|
||||
|
||||
void TSpreadsheet::clear_range_selection()
|
||||
{
|
||||
_from = _to = -1;
|
||||
if (_saved_properties)
|
||||
{
|
||||
const int it = items();
|
||||
for (int r = 0; r < it; r++)
|
||||
{
|
||||
TRow_property* prop = (TRow_property*)_saved_properties->objptr(r);
|
||||
COLOR back = NORMAL_BACK_COLOR;
|
||||
COLOR fore = NORMAL_COLOR;
|
||||
|
||||
if (prop != NULL)
|
||||
prop->get(-1, back, fore);
|
||||
set_back_and_fore_color(back, fore, r, -1);
|
||||
}
|
||||
delete _saved_properties;
|
||||
_saved_properties = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void TSpreadsheet::select_range(int row)
|
||||
{
|
||||
int from = _from;
|
||||
int to = _to;
|
||||
|
||||
if (row < 0)
|
||||
row = selected();
|
||||
if (_saved_properties == NULL)
|
||||
_saved_properties = new TArray(_properties);
|
||||
|
||||
if (_from < 0)
|
||||
_from = _to = row;
|
||||
else
|
||||
{
|
||||
if (row < _from)
|
||||
from = _from = row;
|
||||
else
|
||||
from = _to = row;
|
||||
}
|
||||
if (from < 0)
|
||||
from = 0;
|
||||
if (from >_from)
|
||||
from = _from;
|
||||
if (to < 0)
|
||||
to = items();
|
||||
if (to < _to)
|
||||
to = _to;
|
||||
for (int r = from; r <= to; r++)
|
||||
{
|
||||
TRow_property* prop = (TRow_property*)_saved_properties->objptr(r);
|
||||
COLOR back = NORMAL_BACK_COLOR;
|
||||
COLOR fore = NORMAL_COLOR;
|
||||
|
||||
if (prop != NULL)
|
||||
prop->get(-1, back, fore);
|
||||
if (r >= _from && r <= _to)
|
||||
back = blend_colors(back, FOCUS_BACK_COLOR, 0.75);
|
||||
set_back_and_fore_color(back, fore, r, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void TSpreadsheet::copy_rows()
|
||||
{
|
||||
int from = _from > 0 ? _from : 0;
|
||||
int to = _to < items() - 1 ? _to : items() - 1;
|
||||
|
||||
_copied_rows.destroy();
|
||||
for (int i = from; i <= to; i++)
|
||||
_copied_rows.add(_str.row(i));
|
||||
}
|
||||
|
||||
void TSpreadsheet::paste_rows()
|
||||
{
|
||||
int row = selected();
|
||||
int nrows = _copied_rows.items();
|
||||
|
||||
if (row >= _str.items() - 1)
|
||||
{
|
||||
row = _str.items() - 1;
|
||||
_str.add(_copied_rows.row(--nrows));
|
||||
}
|
||||
|
||||
for (int i = nrows - 1; i >= 0; i--)
|
||||
_str.insert(_copied_rows.row(i), row + 1);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// TSheet_field
|
||||
///////////////////////////////////////////////////////////
|
||||
@ -3692,6 +3851,47 @@ static TString& clean_white_space(TString& str)
|
||||
return str;
|
||||
}
|
||||
|
||||
bool TSheet_field::get_range_selection(int &from, int & to) const
|
||||
{
|
||||
TSpreadsheet* s = (TSpreadsheet*)_ctl;
|
||||
|
||||
return s->get_range_selection(from, to);
|
||||
}
|
||||
|
||||
void TSheet_field::clear_range_selection()
|
||||
{
|
||||
TSpreadsheet* s = (TSpreadsheet*)_ctl;
|
||||
|
||||
s->clear_range_selection();
|
||||
force_update();
|
||||
s->set_focus_cell(s->_cur_row, s->_cur_col);
|
||||
}
|
||||
|
||||
void TSheet_field::select_range(int row)
|
||||
{
|
||||
TSpreadsheet* s = (TSpreadsheet*)_ctl;
|
||||
|
||||
s->select_range(row);
|
||||
force_update();
|
||||
s->set_focus_cell(s->_cur_row, s->_cur_col);
|
||||
}
|
||||
|
||||
void TSheet_field::copy_rows()
|
||||
{
|
||||
TSpreadsheet* s = (TSpreadsheet*)_ctl;
|
||||
|
||||
s->copy_rows();
|
||||
}
|
||||
|
||||
void TSheet_field::paste_rows()
|
||||
{
|
||||
TSpreadsheet* s = (TSpreadsheet*)_ctl;
|
||||
|
||||
s->paste_rows();
|
||||
on_key(K_CTRL + 'V');
|
||||
force_update();
|
||||
s->set_focus_cell(s->_cur_row, s->_cur_col);
|
||||
}
|
||||
///////////////////////////////////////////////////////////
|
||||
// TSheet_recordset
|
||||
///////////////////////////////////////////////////////////
|
||||
|
@ -285,6 +285,12 @@ public:
|
||||
// @cmember Trasferisce i valori dalla riga alla maschera <p n>
|
||||
void update_mask(int n) { row2mask(n, row(n)); }
|
||||
|
||||
bool get_range_selection(int & from, int & to) const ;
|
||||
void clear_range_selection();
|
||||
void select_range(int row = -1);
|
||||
void copy_rows();
|
||||
void paste_rows();
|
||||
|
||||
bool esporta() const;
|
||||
|
||||
// @cmember Restituisce il numero della colonna corrente
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
TRelation_application::TRelation_application()
|
||||
: _mask(NULL), _search_id(-1), _lnflag(0),
|
||||
_autodelete(0), _navigating(false)
|
||||
_autodelete(0), _navigating(false),
|
||||
_locked(false)
|
||||
{ }
|
||||
|
||||
TRelation_application::~TRelation_application()
|
||||
@ -34,16 +35,22 @@ bool TRelation_application::has_filtered_cursor() const
|
||||
}
|
||||
|
||||
|
||||
TCursor& TRelation_application::get_filtered_cursor() const
|
||||
TCursor * TRelation_application::get_filtered_cursor() const
|
||||
{
|
||||
if (has_filtered_cursor())
|
||||
{
|
||||
const TEdit_field& f = get_search_field();
|
||||
return *f.browse()->cursor();
|
||||
|
||||
return f.browse()->cursor();
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TRelation_application::setkey()
|
||||
{
|
||||
if (has_filtered_cursor())
|
||||
get_filtered_cursor().setkey();
|
||||
get_filtered_cursor()->setkey();
|
||||
else
|
||||
file().setkey(1);
|
||||
}
|
||||
@ -99,19 +106,19 @@ void TRelation_application::set_limits(
|
||||
if (f.browse() != NULL)
|
||||
f.browse()->do_input(true);
|
||||
|
||||
TCursor& cur = get_filtered_cursor();
|
||||
cur.setkey();
|
||||
if (cur.items() > 0)
|
||||
TCursor* cur = get_filtered_cursor();
|
||||
cur->setkey();
|
||||
if (cur->items() > 0)
|
||||
{
|
||||
TBaseisamfile& f = cur.file();
|
||||
TBaseisamfile& f = cur->file();
|
||||
if (what & 0x1)
|
||||
{
|
||||
cur = 0;
|
||||
*cur = 0;
|
||||
_first = f.recno();
|
||||
}
|
||||
if (what & 0x2)
|
||||
{
|
||||
cur = cur.items() - 1;
|
||||
*cur = cur->items() - 1;
|
||||
_last = f.recno();
|
||||
}
|
||||
}
|
||||
@ -270,7 +277,7 @@ void TRelation_application::enable_query()
|
||||
}
|
||||
|
||||
bool TRelation_application::can_I_write(const TRelation* rel) const
|
||||
{ return user_can_write(rel); }
|
||||
{ return !_locked && user_can_write(rel); }
|
||||
|
||||
bool TRelation_application::can_I_read(const TRelation* rel) const
|
||||
{ return user_can_read(rel); }
|
||||
@ -542,24 +549,30 @@ void TRelation_application::insert_mode()
|
||||
bool TRelation_application::modify_mode()
|
||||
{
|
||||
TRelation* rel = get_relation();
|
||||
const TReclock block = can_I_write(rel) ? _testandlock : _nolock;
|
||||
int err = rel->read(_isequal, block);
|
||||
|
||||
if (!can_I_read(rel))
|
||||
{
|
||||
warning_box(TR("I dati non sono accessibili per l'utente %s"), (const char*)user());
|
||||
query_mode();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const TReclock block = can_I_write(rel) ? _testandlock : _nolock;
|
||||
int err = rel->read(_isequal, block);
|
||||
_locked = false;
|
||||
if (err != NOERR)
|
||||
{
|
||||
if (err == _islocked)
|
||||
message_box(TR("I dati sono giŕ usati da un altro utente"));
|
||||
{
|
||||
_locked = true;
|
||||
message_box(TR("I dati sono già usati da un altro programma, scrittura disabilitata"));
|
||||
}
|
||||
else
|
||||
{
|
||||
error_box(FR("Impossibile leggere i dati: errore %d"), err);
|
||||
if (!is_transaction())
|
||||
query_mode();
|
||||
return FALSE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool changing = changing_mask(MODE_MOD);
|
||||
@ -890,7 +903,9 @@ int TRelation_application::delete_mode()
|
||||
r.restore_status();
|
||||
can_delete = remove();
|
||||
}
|
||||
cur.freeze(false);
|
||||
query_mode();
|
||||
cur.freeze(true);
|
||||
}
|
||||
_autodelete = FALSE;
|
||||
}
|
||||
@ -1395,6 +1410,13 @@ void TRelation_application::main_loop()
|
||||
}
|
||||
}
|
||||
|
||||
if (_curr_trans_mode == TM_BATCH)
|
||||
{
|
||||
batch();
|
||||
k = _mask->check_mask();
|
||||
batch(false);
|
||||
}
|
||||
else
|
||||
k = _mask->run();
|
||||
|
||||
switch (k)
|
||||
@ -1445,7 +1467,7 @@ void TRelation_application::main_loop()
|
||||
else
|
||||
insert_mode();
|
||||
}
|
||||
if (_curr_trans_mode == TM_AUTOMATIC)
|
||||
if (_curr_trans_mode == TM_AUTOMATIC || _curr_trans_mode == TM_BATCH)
|
||||
_mask->send_key(K_CTRL+'R', 0);
|
||||
break;
|
||||
case K_SAVE:
|
||||
@ -1489,7 +1511,7 @@ void TRelation_application::main_loop()
|
||||
else
|
||||
insert_mode();
|
||||
}
|
||||
if (_curr_trans_mode == TM_AUTOMATIC)
|
||||
if (_curr_trans_mode == TM_AUTOMATIC || _curr_trans_mode == TM_BATCH)
|
||||
_mask->send_key(K_CTRL+'R', 0);
|
||||
break;
|
||||
case K_DEL:
|
||||
@ -1525,32 +1547,82 @@ void TRelation_application::main_loop()
|
||||
err = file().readat(_first, _testandlock);
|
||||
break;
|
||||
case K_NEXT:
|
||||
err = file().reread();
|
||||
if (has_filtered_cursor())
|
||||
{
|
||||
TCursor& cur = get_filtered_cursor();
|
||||
cur.curr() = file().curr();
|
||||
cur.read();
|
||||
++cur;
|
||||
file().curr() = cur.curr();
|
||||
err = get_relation()->read(_isequal, _testandlock);
|
||||
TCursor* c = get_filtered_cursor();
|
||||
|
||||
if (!has_filtered_cursor() || c->curr().num() != file().curr().num())
|
||||
{
|
||||
for (TEdit_field* e = (TEdit_field *) _mask->get_key_field(1, TRUE); e; e = (TEdit_field *) _mask->get_key_field(1, FALSE))
|
||||
{
|
||||
if (e->shown() && e->browse() != NULL) // Ignora campi invisibili o senza check
|
||||
{
|
||||
TCursor* b = e->browse()->cursor();
|
||||
|
||||
if (b && b->curr().num() == file().curr().num())
|
||||
{
|
||||
c = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TCursor* cur = c ;
|
||||
|
||||
if (c == NULL)
|
||||
cur = new TCursor(get_relation());
|
||||
|
||||
err = file().reread();
|
||||
cur->curr() = file().curr();
|
||||
cur->read();
|
||||
++(*cur);
|
||||
while (cur->pos() < cur->items() && !can_I_read(cur->relation()))
|
||||
++(*cur);
|
||||
file().curr() = cur->curr();
|
||||
if (can_I_read(cur->relation()))
|
||||
err = get_relation()->read(_isequal, _testandlock);
|
||||
if (c == NULL)
|
||||
delete cur;
|
||||
}
|
||||
else
|
||||
err = file().next(_testandlock);
|
||||
break;
|
||||
case K_PREV:
|
||||
file().reread();
|
||||
if (has_filtered_cursor())
|
||||
{
|
||||
TCursor& cur = get_filtered_cursor();
|
||||
cur.curr() = file().curr();
|
||||
cur.read();
|
||||
--cur;
|
||||
file().curr() = cur.curr();
|
||||
err = get_relation()->read(_isequal, _testandlock);
|
||||
TCursor* c = get_filtered_cursor();
|
||||
|
||||
if (!has_filtered_cursor() || c->curr().num() != file().curr().num())
|
||||
{
|
||||
for (TEdit_field* e = (TEdit_field *) _mask->get_key_field(1, TRUE); e; e = (TEdit_field *) _mask->get_key_field(1, FALSE))
|
||||
{
|
||||
if (e->shown() && e->browse() != NULL) // Ignora campi invisibili o senza check
|
||||
{
|
||||
TCursor* b = e->browse()->cursor();
|
||||
|
||||
if (b && b->curr().num() == file().curr().num())
|
||||
{
|
||||
c = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TCursor* cur = c ;
|
||||
|
||||
if (c == NULL)
|
||||
cur = new TCursor(get_relation());
|
||||
|
||||
file().reread();
|
||||
cur->curr() = file().curr();
|
||||
cur->read();
|
||||
--(*cur);
|
||||
while (cur->pos() > 0 && !can_I_read(cur->relation()))
|
||||
--(*cur);
|
||||
file().curr() = cur->curr();
|
||||
if (can_I_read(cur->relation()))
|
||||
err = get_relation()->read(_isequal, _testandlock);
|
||||
if (c == NULL)
|
||||
delete cur;
|
||||
}
|
||||
else
|
||||
err = file().prev(_testandlock);
|
||||
break;
|
||||
case K_END:
|
||||
err = file().readat(_last, _testandlock);
|
||||
@ -1601,6 +1673,18 @@ void TRelation_application::main_loop()
|
||||
ini.set("Result", err == NOERR ? "CANCEL" : "ERROR");
|
||||
ini.set("Error", err);
|
||||
}
|
||||
if (_curr_trans_mode == TM_BATCH)
|
||||
{
|
||||
TString_array & errs = errors();
|
||||
|
||||
FOR_EACH_ARRAY_ROW(errs, r, s)
|
||||
ini.set("ErrMsg", *s, "Main", false, r);
|
||||
|
||||
TString_array & warns = warnings();
|
||||
|
||||
FOR_EACH_ARRAY_ROW(warns, r1, s1)
|
||||
ini.set("WarningMsg", *s1, "Main", false, r1);
|
||||
}
|
||||
|
||||
}
|
||||
_trans_counter++;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#define TM_INTERACTIVE 'I'
|
||||
#define TM_AUTOMATIC 'A'
|
||||
#define TM_REMAIN 'R'
|
||||
#define TM_BATCH 'B'
|
||||
// @doc EXTERNAL
|
||||
|
||||
// @class TRelation_application | Classe per la gestione di una applicazione di manutenzione di uno
|
||||
@ -74,6 +75,7 @@ class TRelation_application : public TSkeleton_application
|
||||
int _autodelete;
|
||||
// @cmember:(INTERNAL) Flag di navigazione tramite toolbar
|
||||
bool _navigating;
|
||||
bool _locked;
|
||||
|
||||
private:
|
||||
// @cmember:(INTERNAL) Carica la transazione corrente (protocollo via .ini)
|
||||
@ -106,7 +108,7 @@ private:
|
||||
bool autonum(TMask* m, bool rec);
|
||||
// @cmember:(INTERNAL) Controlla se il <c TCursor> ha un filtro
|
||||
virtual bool has_filtered_cursor() const;
|
||||
virtual TCursor& get_filtered_cursor() const;
|
||||
virtual TCursor* get_filtered_cursor() const;
|
||||
|
||||
// @cmember:(INTERNAL) Sistema il bottone ricerca se necessario
|
||||
void set_find_button();
|
||||
@ -195,7 +197,7 @@ protected:
|
||||
|
||||
// @cmember Richiede se il record corrente e' protetto (non cancellabile)
|
||||
virtual bool protected_record(TRectype&)
|
||||
{ return false; }
|
||||
{ return _locked; }
|
||||
|
||||
// @cmember Richiede se il record corrente e' protetto (non cancellabile)
|
||||
virtual bool protected_record(TRelation &);
|
||||
@ -205,7 +207,7 @@ protected:
|
||||
|
||||
// @cmember Inizializza la maschera per il modo ricerca
|
||||
virtual void init_query_mode(TMask&)
|
||||
{ }
|
||||
{ _locked = false; }
|
||||
// @cmember Inizializza la maschera per il modo ricerca ed inserimento (chiama <mf TRelation_application::init_query_mode>)
|
||||
virtual void init_query_insert_mode(TMask& m)
|
||||
{ init_query_mode(m); }
|
||||
|
@ -1533,7 +1533,7 @@ void TCursor::setkey(int nkey)
|
||||
}
|
||||
}
|
||||
|
||||
int TCursor::test(TIsamop op, TReclock lockop) const
|
||||
int TCursor::test(TIsamop op, TReclock lockop)
|
||||
{
|
||||
TLocalisamfile& curfile = file();
|
||||
const TRectype& currec = curfile.curr();
|
||||
@ -2049,7 +2049,9 @@ const char* TSorted_cursor::fill_sort_key(TString& k)
|
||||
{
|
||||
const bool is_up = is_upper(s);
|
||||
const char last = s.right(1)[0];
|
||||
if (last == '-' || last == '+')
|
||||
const bool align = last == '*';
|
||||
|
||||
if (last == '-' || last == '+' || align)
|
||||
s.rtrim(1);
|
||||
|
||||
const TFieldref f(s,0);
|
||||
@ -2072,7 +2074,7 @@ const char* TSorted_cursor::fill_sort_key(TString& k)
|
||||
}
|
||||
else
|
||||
{
|
||||
// if (is_up) // Test inutile: tutte le chiavi sono maiuscole 08-02-2016
|
||||
if (is_up) // Test inutile: tutte le chiavi sono maiuscole 08-02-2016
|
||||
sf.upper();
|
||||
}
|
||||
switch (fld_type)
|
||||
@ -2080,9 +2082,24 @@ const char* TSorted_cursor::fill_sort_key(TString& k)
|
||||
case _boolfld:
|
||||
case _charfld:
|
||||
case _memofld:
|
||||
case _alfafld: sf.left_just(fld_len); break;
|
||||
case _alfafld: sf.left_just(fld_len > 0 ? fld_len : 50); break;
|
||||
case _intfld:
|
||||
case _longfld:
|
||||
case _intzerofld:
|
||||
case _longzerofld: sf.right_just(fld_len > 0 ? fld_len : 50); break;
|
||||
case _datefld: break; // Gia' lungo 8!
|
||||
default : sf.right_just(fld_len); break;
|
||||
case _realfld:
|
||||
if (align)
|
||||
{
|
||||
real r(sf);
|
||||
sf = r.string(fld_len > 0 ? fld_len : 50, fld_len > 0 ? (fld_len-4)/2 : 10);
|
||||
}
|
||||
else
|
||||
sf.right_just(fld_len > 0 ? fld_len : 50);
|
||||
break;
|
||||
default :
|
||||
sf.left_just(fld_len > 0 ? fld_len : 50);
|
||||
break;
|
||||
}
|
||||
k << sf;
|
||||
}
|
||||
@ -2103,6 +2120,96 @@ bool TSorted_cursor::changed()
|
||||
return rt;
|
||||
}
|
||||
|
||||
int TSorted_cursor::test(TIsamop op, TReclock lockop)
|
||||
|
||||
{
|
||||
int err = NOERR;
|
||||
|
||||
if (items() == 0L)
|
||||
{
|
||||
err = _isemptyfile;
|
||||
file().setstatus(err);
|
||||
return err;
|
||||
}
|
||||
|
||||
TString256 searching; fill_sort_key(searching);
|
||||
searching.rtrim();
|
||||
const int cmplen = searching.len();
|
||||
|
||||
TRecnotype first = 0L;
|
||||
TRecnotype last = items()-1;
|
||||
TRecnotype found = -1L;
|
||||
|
||||
FOR_EACH_ARRAY_ROW(fpkey(), i, s)
|
||||
{
|
||||
const int cmp = searching.compare(*s, cmplen);
|
||||
if (cmp <= 0)
|
||||
last = (i + 1) * pagesize() - 1;
|
||||
else
|
||||
first = i * pagesize();
|
||||
}
|
||||
|
||||
const bool ghiacciato = !frozen();
|
||||
if (ghiacciato) freeze(true);
|
||||
|
||||
TString256 testing;
|
||||
while (first <= last)
|
||||
{
|
||||
const TRecnotype test = (first+last)/2;
|
||||
TCursor::operator=(test); // verif
|
||||
fill_sort_key(testing);
|
||||
const int cmp = searching.compare(testing, cmplen);
|
||||
if (cmp == 0)
|
||||
{
|
||||
if (op != _isgreat)
|
||||
{
|
||||
if (found < 0l || test < found)
|
||||
found = test;
|
||||
last = test-1;
|
||||
}
|
||||
else
|
||||
first = test+1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmp < 0)
|
||||
{
|
||||
last = test-1;
|
||||
if (op != _isequal)
|
||||
{
|
||||
if (found < 0l || test < found)
|
||||
found = test;
|
||||
}
|
||||
}
|
||||
else
|
||||
first = test+1;
|
||||
}
|
||||
}
|
||||
|
||||
if (found >= 0L)
|
||||
{
|
||||
TCursor::operator=(found); // verif
|
||||
file().setstatus(NOERR);
|
||||
if (lockop != _nolock)
|
||||
lock(lockop);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
found = items()-1;
|
||||
if (found >= 0)
|
||||
TCursor::operator=(found);
|
||||
err = op == _isequal ? _iskeynotfound : _iseof;
|
||||
file().setstatus(err);
|
||||
}
|
||||
|
||||
if (ghiacciato)
|
||||
freeze(false);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
TRecnotype TSorted_cursor::read(TIsamop op, TReclock lockop)
|
||||
{
|
||||
TString256 searching; fill_sort_key(searching);
|
||||
|
@ -367,8 +367,8 @@ public:
|
||||
// @cmember Ritorna il descrittore della tabella
|
||||
TRectype& curr(const char * tab) const
|
||||
{ return _if->lfile(tab).curr(); }
|
||||
//@cmember Testa la presenza di un record senza spostare il cursore
|
||||
int test(TIsamop op = _isequal, TReclock lockop = _nolock) const;
|
||||
//@cmember Testa la presenza di un record
|
||||
virtual int test(TIsamop op = _isequal, TReclock lockop = _nolock);
|
||||
// @cmember Legge il record
|
||||
virtual TRecnotype read(TIsamop op = _isgteq, TReclock lockop = _nolock);
|
||||
// @cmember Mette un lock sul record
|
||||
@ -508,6 +508,10 @@ public:
|
||||
// @cmember Si sposta alla posizione <p nr>
|
||||
TRecnotype operator =(const TRecnotype nr)
|
||||
{ return TCursor::operator =(nr); }
|
||||
|
||||
//@cmember Testa la presenza di un record
|
||||
virtual int test(TIsamop op = _isequal, TReclock lockop = _nolock);
|
||||
|
||||
// @cmember Trova l'indice del record corrente
|
||||
virtual TRecnotype read(TIsamop op = _isgteq, TReclock lockop = _nolock);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user